• 1、做你真正感兴趣的事——你会花很多时间在上面,因此你一定要感兴趣才行,如果不是这样的话,你不合愿意把时间花在上面,就得不到成功。

  • 2、自己当老板,为别人打工,你绝不会变成巨富,老板一心一意地缩减开支,他的目标不是使他的职员变成有钱人。   

  • 3、提供一种有实效的服务,或一种实际的产品,你要以写作、绘画或作曲变成百万富翁的机会可以说是无限小。而你要在营造业、房地产、制造业发大财的机会比较大。记住,出版商赚的钱比作家多得多。

  • 4、如果你坚持要又自己的灵感来创业。最好选择娱乐业,在这方面,发财的速度相当快,流行歌曲和电视最理想。  

  • 5、不论你是演员或商人。尽量增加你的观众,在小咖啡馆唱歌的人,所赚的钱一定比不上替大唱片公司灌唱片的人,地方性的商人,不会比全国性的商人赚得的钱多。

  • 6、找出一种需要,然后满足它,社会越变越复杂,人们所需要的产品和服务越来越多,最先发现这些需求而且满足他们的人,是改进现有产品和服务的人,也是最先成为富翁的人。

  • 7、不要不敢采用不同的方式——新的方法和新产品,会造成新的财富。但必须确定你的新方法比旧方法更理想,你的新方法必须增进产品外观、效率、品质、方便或者减低成本。

  • 8、如果你受过专业教育。或者特殊才能,充分利用它,如果你烧得一手好菜,而要去当泥水匠,那就太笨了。

  •  9、在你着手任何事情之前,仔细地对周围的情形研究一番,政府机关和公共图书馆,可以提供不少资料,先做研究,可以节省你不少时间和金钱。

  • 10、不要一直都想着发大财,不如你想想如何改进你的事业,您应该常常问自己的是:“我如何改良我的事业?”如何使事业进行顺利,财富就会跟着而来。

  • 11、可能的话,进行一种家庭事业,这种方法可以减低费用,增进士气,利润的分配很简单,利润能够得到充分的利用,整个事业控制也较容易。 

  • 12、尽可能减低你的费用,但不能牺牲你的品质,否则的话,你等于是在慢性自杀,赚钱的机会不会大。

  • 13、跟同行的分维持友谊——他们可能对你很有帮助。

  • 14、尽量把时间花在你的事业上,一天十二小时,一星期六天。是最低的要求,一天十四小时到十八小时是很平常,一星期工作七天最好了。你必须先生家庭和社会上的娱乐,直到你事业站稳为止,也只有到那时候,你才能把责任分给别人。

  • 15、不要不敢自己下决心,听听别人的赞美和批评,但你自己要下决心。

  • 16、不要不敢说实话,拐弯抹角,只会浪费时间,心理想什么,就说什么,而且要尽可能地直截了当地、明确地说出来。 

  • 17、不要不敢承认自己的错误,犯了错误并不是一种罪行,犯错不改才是罪过。

  • 18、不要因为失败就裹足不前,失败是难免的,也是有价值的,从失败中,你会学到正确的方法论。

  • 19、不要在不可行的观念上打转,一发现其某种方法行不通,立即把它放弃,世界上有无数的方法,把时间浪费在那些不可行的方法上是无可弥补的损失。

  • 20、不要冒你承担不起的风险,如果你损失十万元,若损失的起的话,就可以继续下去,但如果你赔不起五万元,而一旦失败的话,你就完蛋了。

  • 21、一再投资,不要认你的利润空闲着,你的利润要继续投资下去,最好投资别的事业或你控制的事业上,那样,钱才能钱滚钱,替你增加好几倍的财富。

  • 22、请一位高明的律师——他会替你节约更多的金钱和时间,比起你所给以的将要多得多。

  • 23、请一位精明的会计师,最初的时候,你自己记账,但除非你本身是个会计师,你还是请一位精明的会计师,可能决定你的成功和失败——他是值得你花钱的。

  • 24、请专家替你报税,一位机灵的税务专家,可又替你免很多的税。

  • 25、好好维持你的健康和你的平静心灵——否则的话,拥有再多的钱也没有什么意思。

在中国留学的外国人,几乎都与中国大学生结为一帮一的互助组,你教我汉语一小时,我帮你纠正英语一小时。在中国留学的老外圈子里的“官方语言”,是一套自己独特的洋汉语。比如,老外汉语里把互相学习的伙伴称其为我的“互相”。留学生间的对话常常是中英合璧,比如“How’s it going?””Oh, it’s just‘tai mafan!’”(“你今天怎样?我今 天太麻烦了。”)或”You’re really‘shuaidai le!’”“你真是帅呆了。”留学生与中国“互相”物物交换的同时,西方人也抱怨与“互相”大学生聊天没劲。比如我每次与我的“互相”互相完后,都会跑出校外与我的那帮布衣哥们、姐们扎堆解解闷,与陋巷里的摊贩、个体画家、餐厅服务员、出租车司机什么的侃大山,那才叫过瘾。这些普通草民都是实实在在地生活着。你有机会与他们掏心窝里的话,与他们唠家常,倾诉过日子的甜酸苦辣,一起笑、一起骂、一起哭,有滋有味。而中国的大学生们常常太一本正经,个个都踌躇满志,期盼成为干番大事的成功者,都觉得自己是介人物。对于我们西方青年来说,如今对这种人生观早已失去了兴趣,我们更在意“平凡着、生活着。”

在中国大学生眼中我是典型的胸无大志者,不想挣大钱、不想出巨著、不想干什么大事业、甚至也不想读高学位。我最在意每天是否过得快活有趣,没想过设计自己的 “锦绣前程”。因人生观、价值观、生活观的差异,使我们留学生往往没情绪与中国大学生聊那些如何功成名就的人生话题,对我们来说特Boring(枯燥)。对比之下,西方青年更热衷去亚洲、非洲、南美等不发达的地区当一名志愿工作者,比如义务医生、教师。有机会帮助所需要的人,这种人生特别带劲和有意义,这才体现自己的价值,充满成就感。

一次有位中国朋友考我。他问:“长江里有几条船?”我茫然起来。朋友笑起来说:“只有两条船,一条为‘名 ’,一条为‘利’。”中国人对会见权贵能抛头露脸荣耀感的重视,留给我颇深的印象。在电视上看到采访一位华人女士,她说老爸见到克林顿夫妇邀请女儿到白宫参加国庆午宴的请柬时,爸爸激动得老泪纵横。当然去白宫做客的确是难得的机会,不过与“人民公仆”同进一次午餐,怎至于激动得落泪,西方人多会以平常心对待。这也令我产生了另一个感叹,不管是大陆、港台的官员、名人,他们出街时个个派头十足前呼后拥,要不就墨镜遮面。而欧美的大人物则非常注重平民化,欧洲老百姓常会在街头碰见王室成员随意逛街、购物。如戴安娜王妃生前的一个圣诞节前夕,她去拜访为其看病的中医师。戴妃亲自上伦敦哈罗斯百货公司选来礼物,然后肩上扛着这一大箱礼物穿街走巷。医生回忆说,当戴妃汗岑岑地进门后,她掂了掂那箱子觉得满重的。没料到戴妃居然既无保镖也无随从帮忙,一介王妃也不计较在大街上独个扛着箱子是什么样的形象。

读过中国杂志上一则故事,介绍旅美音乐家谭盾,初赴美时曾为生计所迫在纽约的一间银行门外拉琴卖艺,与他做伴的还有一位黑人乐手。十年后的一天,谭盾路过那间银行时瞧见昔日同伴仍在老地方卖艺。黑人问谭盾如今在哪里卖艺,谭盾说在卡内基(纽约著名音乐厅)拉呢。那黑人便打趣道:“在那能挣着钱吗?”谭盾也幽默地回应:“还行。”

这本来是个很有趣的段子,可惜段子随后的说教才煞风景呢,板着面孔告诫读者,谭盾靠自我奋斗在洋人的地界上功成名就,而那黑人因庸庸碌碌十年落得终日在街头卖艺。这种评论令我觉得好笑,且过于“中国特色”。在西方,街头艺术是当地的一道亮丽风景,街头艺术家同样是艺术家,不论你在街头表演还是在音乐厅表演,无贵贱之分,都是受人尊敬的职业艺术家。有些街头乐队水准相当不错并演出了名气,每天他们到固定的广场上班演奏时,会有乐迷拥在周围,并风风光光地出售自己乐队的CD。一些成功的街头乐手、画家的收入,与一介办公室职员的薪水相若,其中有些街头艺术家就靠着在街头、集市上为行人提供娱乐置了房产。我有位朋友原在大乐团拉小提琴,可他嫌不自由,便放弃了整天着礼服在音乐厅表演的职位,宁愿下到酒吧、及街头狂欢节上去表演,他认为这种与观众近距离的交流,更令他放松惬意。周围亲友也理解他的个人志趣,无人认为他胸无大志,不思进取。

成功不应只是狭隘的成名成家。而鄙视平凡的普罗大众是低俗的心态。

<!DOCTYPE HTML PUBLIC “-//W3C//DTD HTML 4.0 Transitional//EN”>
  <HTML>
  <HEAD>
  <TITLE> New Document </TITLE>
  <META NAME=”Generator” CONTENT=”Microsoft FrontPage 5.0”>
  <META NAME=”Author” CONTENT=”zhaoweiwei”>
  <META NAME=”Keywords” CONTENT=”birthday_input”>
  <META NAME=”Description” CONTENT=”日起输入控件”>
  <script>
  function showdb()  //显示控件/隐藏空间
  {
    var CurrentDate=new Date();
    var CYear=CurrentDate.getFullYear();
    var CMonth=CurrentDate.getMonth();
    var CDay=CurrentDate.getDay();
 
    var a=document.getElementById(“input_birth”);
 
    var obj_year=document.getElementById(“year”);  //当展开控件时自动取到当前时间(客户端)
    obj_year.innerText=CYear;
    var obj_month=document.getElementById(“month”);
    obj_month.innerText=CMonth+1;
 
    var s=new Date(CYear,CMonth,1);
    var startDay=s.getDay();   //日期起始数字
    var numDays=numberOfDays(CMonth,CYear);  //本月天数
    var day=document.getElementsByName(“day”);
    for(var j=1;j<=numDays;j++)  //插入日期
  {
    day[startDay+j-1].innerText=j;
  }

    if(a.style.visibility=='visible')
    {
     a.style.visibility='hidden';
    }
  else
  {
    a.style.visibility='visible';
  }
 
  }
  function addyear()
   {
   var obj_year=document.getElementById("year");
   var obj_month=document.getElementById("month");

   var num_year=obj_year.innerText;

   obj_year.innerText=parseInt(num_year)+1;  //年数增加
  
   displayCalendar(obj_month.innerText-1,obj_year.innerText);
  }
  function plusyear()
   {
  
   var obj_year=document.getElementById("year");
   var obj_month=document.getElementById("month");
  
   var num_year=obj_year.innerText;
   obj_year.innerText=parseInt(num_year)-1;  //年数减少
  
   displayCalendar(obj_month.innerText-1,obj_year.innerText);
  }
  function addmonth()
  {
  
     var obj_month=document.getElementById("month");
     var obj_year=document.getElementById("year");
    
     if(parseInt(obj_month.innerText)>=12)        //月数增加到12时,年数相应增加1,月数从1开始
     {
      obj_month.innerText="0";
      obj_year=document.getElementById("year");
      var num_year=obj_year.innerText;
      obj_year.innerText=parseInt(num_year)+1;
     }
  
       var num_month=obj_month.innerText;
      obj_month.innerText=parseInt(num_month)+1;  //月数增加
  
     displayCalendar(obj_month.innerText-1,obj_year.innerText);
    }
    function plusmonth()
    {
  
     var obj_month=document.getElementById("month");
     var obj_year=document.getElementById("year");
  
    if(parseInt(obj_month.innerText)<=1)    //月数减少到1时,年数相应减少1,月数从12开始
   {
      obj_month.innerText="13";
      obj_year=document.getElementById("year");
      var num_year=obj_year.innerText;
      obj_year.innerText=parseInt(num_year)-1;
     }
  
     var num_month=obj_month.innerText;
   obj_month.innerText=parseInt(num_month)-1;  //月数减少

     displayCalendar(obj_month.innerText-1,obj_year.innerText);
  }

  function numberOfDays(month,year)  //取得每月天数,判断平年闰年
  {
    var numDays=new Array(31,28,31,30,31,30,31,31,30,31,30,31);
    n=numDays[month];
    if(month==1&&year%4==0) ++n;
    return n;
  }

  function displayCalendar(month,year)
  {

    var d=new Date(year,month,1);
    var startDay=d.getDay();   //日期起始数字
    var numDays=numberOfDays(month,year);  //本月天数
 
    var day=document.getElementsByName("day");
    for(var i=0;i<day.length;i++)
    {
     day[i].innerText="";
    }
    for(var j=1;j<=numDays;j++)  //插入日期
    {
      day[startDay+j-1].innerText=j;
    }
 
  }
  function writeDate(n)
  {
     document.writeln("<H3 ALIGN='CENTER'>"+n+"</H3>");
   }

  function add_day(year,month,day)
  {
     bd.birthdate.value=year+"-"+month+"-"+day;
  }
  </script>
  </HEAD>
   <BODY>
  <form name="bd">
  <input type="text" name="birthdate" size="20" readOnly><input type="button" value="..." onclick="showdb()">
  </form>
  <div id="input_birth" name="input_birth" style="visibility:hidden; width:239; height:128">
  <table border="0" cellpadding="0" cellspacing="0" style="border-collapse: collapse" style="cursor:hand" bordercolor="#111111" width="100%"  height="26">
    <tr>
    <  td width="100%" colspan="3" height="26">&nbsp;&nbsp; <input type="button" value="&lt;&lt;" onclick="plusyear()" name="plusyear">
      <input type="button" value="&lt;" onclick="plusmonth()" name="plusmonth">&nbsp; <font id="year">
      </font>年<font id="month"></font>月
      <input type="button" value="&gt;" onclick="addmonth()" name="addmonth">
      <input type="button" value="&gt;&gt;" name="addyear" onclick="addyear()"></td>
    </tr>
     <tr>
      <td width="100%" colspan="3" height="119" valign="top">
     <table border="0" cellpadding="0" cellspacing="0" style="border-collapse: collapse" bordercolor="#111111" width="100%">
        <tr>
        <td width="14%" align="center"><b><font color="#FF0000">日</font></b></td>
        <td width="14%" align="center"><b>一</b></td>
        <td width="14%" align="center"><b>二</b></td>
        <td width="14%" align="center"><b>三</b></td>
        <td width="14%" align="center"><b>四</b></td>
        <td width="15%" align="center"><b>五</b></td>
        <td width="15%" align="center"><b><font color="#FF0000">六</font></b></td>
      </tr>
      <tr>
        <td width="14%" align="center" id="day" onclick="add_day(year.innerText,month.innerText,this.innerText)"></td>
        <td width="14%" align="center" id="day" onclick="add_day(year.innerText,month.innerText,this.innerText)"> </td>
        <td width="14%" align="center" id="day" onclick="add_day(year.innerText,month.innerText,this.innerText)"> </td>
        <td width="14%" align="center" id="day" onclick="add_day(year.innerText,month.innerText,this.innerText)"> </td>
        <td width="14%" align="center" id="day" onclick="add_day(year.innerText,month.innerText,this.innerText)"> </td>
        <td width="15%" align="center" id="day" onclick="add_day(year.innerText,month.innerText,this.innerText)"> </td>
        <td width="15%" align="center" id="day" onclick="add_day(year.innerText,month.innerText,this.innerText)"> </td>
        </tr>
        <tr>
          <td width="14%" align="center" id="day" onclick="add_day(year.innerText,month.innerText,this.innerText)"> </td>
        <td width="14%" align="center" id="day" onclick="add_day(year.innerText,month.innerText,this.innerText)"> </td>
        <td width="14%" align="center" id="day" onclick="add_day(year.innerText,month.innerText,this.innerText)"> </td>
        <td width="14%" align="center" id="day" onclick="add_day(year.innerText,month.innerText,this.innerText)"> </td>
        <td width="14%" align="center" id="day" onclick="add_day(year.innerText,month.innerText,this.innerText)"> </td>
        <td width="15%" align="center" id="day" onclick="add_day(year.innerText,month.innerText,this.innerText)"> </td>
        <td width="15%" align="center" id="day" onclick="add_day(year.innerText,month.innerText,this.innerText)"> </td>
      </tr>
      <tr>
        <td width="14%" align="center" id="day" onclick="add_day(year.innerText,month.innerText,this.innerText)"> </td>
        <td width="14%" align="center" id="day" onclick="add_day(year.innerText,month.innerText,this.innerText)"> </td>
        <td width="14%" align="center" id="day" onclick="add_day(year.innerText,month.innerText,this.innerText)"> </td>
        <td width="14%" align="center" id="day" onclick="add_day(year.innerText,month.innerText,this.innerText)"> </td>
        <td width="14%" align="center" id="day" onclick="add_day(year.innerText,month.innerText,this.innerText)"> </td>
        <td width="15%" align="center" id="day" onclick="add_day(year.innerText,month.innerText,this.innerText)"> </td>
        <td width="15%" align="center" id="day" onclick="add_day(year.innerText,month.innerText,this.innerText)"> </td>
      </tr>
      <tr>
        <td width="14%" align="center" id="day" onclick="add_day(year.innerText,month.innerText,this.innerText)"> </td>
        <td width="14%" align="center" id="day" onclick="add_day(year.innerText,month.innerText,this.innerText)"> </td>
        <td width="14%" align="center" id="day" onclick="add_day(year.innerText,month.innerText,this.innerText)"> </td>
        <td width="14%" align="center" id="day" onclick="add_day(year.innerText,month.innerText,this.innerText)"> </td>
        <td width="14%" align="center" id="day" onclick="add_day(year.innerText,month.innerText,this.innerText)"> </td>
        <td width="15%" align="center" id="day" onclick="add_day(year.innerText,month.innerText,this.innerText)"> </td>
        <td width="15%" align="center" id="day" onclick="add_day(year.innerText,month.innerText,this.innerText)"> </td>
      </tr>
      <tr>
        <td width="14%" align="center" id="day" onclick="add_day(year.innerText,month.innerText,this.innerText)"> </td>
        <td width="14%" align="center" id="day" onclick="add_day(year.innerText,month.innerText,this.innerText)"> </td>
        <td width="14%" align="center" id="day" onclick="add_day(year.innerText,month.innerText,this.innerText)"> </td>
        <td width="14%" align="center" id="day" onclick="add_day(year.innerText,month.innerText,this.innerText)"> </td>
        <td width="14%" align="center" id="day" onclick="add_day(year.innerText,month.innerText,this.innerText)"> </td>
        <td width="15%" align="center" id="day" onclick="add_day(year.innerText,month.innerText,this.innerText)"> </td>
        <td width="15%" align="center" id="day" onclick="add_day(year.innerText,month.innerText,this.innerText)"> </td>
      </tr>
      <tr>
        <td width="14%" align="center" id="day" onclick="add_day(year.innerText,month.innerText,this.innerText)"> </td>
        <td width="14%" align="center" id="day" onclick="add_day(year.innerText,month.innerText,this.innerText)"> </td>
        <td width="14%" align="center" id="day" onclick="add_day(year.innerText,month.innerText,this.innerText)"> </td>
        <td width="14%" align="center" id="day" onclick="add_day(year.innerText,month.innerText,this.innerText)"> </td>
        <td width="14%" align="center" id="day" onclick="add_day(year.innerText,month.innerText,this.innerText)"> </td>
        <td width="15%" align="center" id="day" onclick="add_day(year.innerText,month.innerText,this.innerText)"> </td>
        <td width="15%" align="center" id="day" onclick="add_day(year.innerText,month.innerText,this.innerText)"> </td>
      </tr>
    </table>
    </td>
  </tr>
  <tr>
    <td width="33%" height="1"></td>
    <td width="33%" height="1"></td>
    <td width="34%" height="1"></td>
    </tr>
  </table>
   </div>
  </BODY>
  </HTML>

在普通的WINDOWS 2000下实现实现包过滤的方法主要是书写NDIS过滤驱动程序,需要的技巧比较高,而且烦琐,需要考虑很多细节。但是对于很多应用而言,只需要能更方便的对ip包进行过滤处理,其实NDIS对于ip包的过滤提供一种书写过滤钩子驱动的方式,主要方法是:
驱动中建立一个普通的设备,然后通过IOCTL_PF_SET_EXTENSION_POINTER操作将你的内核模式的过滤钩子挂接到系统默认的ip过滤驱动上,这样你就可以在自己的过滤钩子里面实现完整的基于包的各种分析和过滤的处理了。
下面就是一个完整的NDIS过滤钩子驱动的代码拒绝所有外来的TCP带S的建立连接的请求。
注意事项:
1。需要在DDK环境中编译
2。需要修改注册表中LMHK\System\\CurrentControlSet\\Services\\IPFILTERDRIVER的START类型为3,让他随系统启动而启动
3。编译生成了sys文件后需要拷贝到winnt\system32\drivers目录下
4。需要运行一个程序后手动生成注册表项
5。使用时用net start f *** ilthook启动驱动,用net stop f *** ilthook停止驱动
6。此方法只能对ip包进行过滤,其他的协议不会经过这个过滤钩子进行处理。
//驱动程序的头文件
#include "ntddk.h"
#include "ntddndis.h"
#include "pfhook.h"
#ifndef __NTHANDLE_H
#define __NTHANDLE_H
#define NT_DEVICE_NAME L"\\Device\\F *** ilthook"
#define DOS_DEVICE_NAME L"\\DosDevices\\F *** ilthook"

#define PROT_TCP 6

#include "ntddk.h"
#include " *** ilthook.h"

typedef struct IPHeader {
UCHAR iph_verlen; // Version and length
UCHAR iph_tos; // Type of service
USHORT iph_length; // Total datagram length
USHORT iph_id; // Identification
USHORT iph_offset; // Flags, fragment offset
UCHAR iph_ttl; // Time to live
UCHAR iph_protocol; // Protocol
USHORT iph_xsum; // Header checksum
ULONG iph_src; // Source address
ULONG iph_dest; // Destination address
} IPHeader;

NTSTATUS
DriverEntry(
IN PDRIVER_OBJECT DriverObject,
IN PUNICODE_STRING RegistryPath);

NTSTATUS
CreateFilterHook
(IN PDRIVER_OBJECT DriverObject);

VOID
DriverUnload
(IN PDRIVER_OBJECT DriverObject);

PF_FORWARD_ACTION
IpFilterHook(
IN unsigned char *PacketHeader,
IN unsigned char *Packet,
IN unsigned int PacketLength,
IN unsigned int RecvInterfaceIndex,
IN unsigned int SendInterfaceIndex,
IN IPAddr RecvLinkNextHop,
IN IPAddr SendLinkNextHop);
#endif

//驱动程序的c文件
#define PROT_TCP 6
#include "ntddk.h"
#include "ntddndis.h"
#include "pfhook.h"
#include "f *** ilthook.h"

PDEVICE_OBJECT deviceObject;
UNICODE_STRING win32DeviceName;

//住驱动入口点
NTSTATUS
DriverEntry(
IN PDRIVER_OBJECT DriverObject,
IN PUNICODE_STRING RegistryPath
)
{
NTSTATUS status = STATUS_SUCCESS;
UNICODE_STRING ntDeviceName;

RtlInitUnicodeString(&ntDeviceName,NT_DEVICE_NAME);
//建立一个过滤钩子驱动设备
status = IoCreateDevice (DriverObject,0,&ntDeviceName,FILE_DEVICE_UNKNOWN,0,TRUE,&deviceObject);
if (!NT_SUCCESS (status)) {
goto ERROR;
}
RtlInitUnicodeString(&win32DeviceName, DOS_DEVICE_NAME);
//建立一个过滤钩子驱动设备符号连接
status = IoCreateSymbolicLink( &win32DeviceName, &ntDeviceName );
if (!NT_SUCCESS(status)) // If we couldn't create the link then
{ // abort installation.
goto ERROR;
}
//申明卸载例程
DriverObject->DriverUnload = DriverUnload;
//建立钩子挂接
status = CreateFilterHook(DriverObject);
if (!NT_SUCCESS(status)) // If we couldn't create the link then
{ // abort installation.
IoDeleteSymbolicLink(&win32DeviceName);
goto ERROR;
}
return(STATUS_SUCCESS);
ERROR:
if(deviceObject)
IoDeleteDevice(deviceObject);
//DbgPrint( "Leave DriverEntry failed\n" );
return status;
}

NTSTATUS
CreateFilterHook(IN PDRIVER_OBJECT DriverObject)
{
PIRP nirp;
NTSTATUS status = STATUS_SUCCESS;
PFILE_OBJECT filtfileob;
UNICODE_STRING ntDeviceName;
PDEVICE_OBJECT filtdeviceob;
PF_SET_EXTENSION_HOOK_INFO filthook;
IO_STATUS_BLOCK filtstatus;

RtlInitUnicodeString(&ntDeviceName,L"\\Device\\IPFILTERDRIVER");
//将钩子挂接函数放入结构中
filthook.ExtensionPointer = IpFilterHook;
//获得系统ipfilterdriver驱动的设备指针
status = IoGetDeviceObjectPointer(&ntDeviceName,FILE_GENERIC_READ|FILE_GENERIC_WRITE,&filtfileob,&filtdeviceob);
if(status!=STATUS_SUCCESS)
return status;
//绑定过滤钩子到系统ipfilterdriver驱动的设备指针
nirp = IoBuildDeviceIoControlRequest(
IOCTL_PF_SET_EXTENSION_POINTER,
filtdeviceob,
&filthook,
sizeof(PF_SET_EXTENSION_HOOK_INFO),
NULL,
0,
FALSE,
NULL,
&filtstatus);
if(nirp==NULL)
return filtstatus.Status;
//调度系统ipfilterdriver设备重新操作irp
return (IoCallDriver(filtdeviceob,nirp));
}

VOID
DriverUnload(IN PDRIVER_OBJECT DriverObject)
{
//与加载一样,只是钩子函数结构中放NULL,让系统ipfilterdriver卸载加载的钩子函数

PIRP nirp;
NTSTATUS status = STATUS_SUCCESS;
PDEVICE_OBJECT filtdeviceob;
PFILE_OBJECT filtfileob;
PF_SET_EXTENSION_HOOK_INFO filthook;
IO_STATUS_BLOCK filtstatus;
UNICODE_STRING ntDeviceName;

RtlInitUnicodeString(&ntDeviceName,L"\\Device\\IPFILTERDRIVER");
filthook.ExtensionPointer = NULL;
status = IoGetDeviceObjectPointer(&ntDeviceName,FILE_GENERIC_READ|FILE_GENERIC_WRITE,&filtfileob,&filtdeviceob);
if(status==STATUS_SUCCESS)
{
nirp = IoBuildDeviceIoControlRequest(
IOCTL_PF_SET_EXTENSION_POINTER,
filtdeviceob,
&filthook,
sizeof(PF_SET_EXTENSION_HOOK_INFO),
NULL,
0,
FALSE,
NULL,
&filtstatus);
if(nirp!=NULL)
IoCallDriver(filtdeviceob,nirp);
}
IoDeleteSymbolicLink(&win32DeviceName);
IoDeleteDevice(deviceObject);
return;
}

PF_FORWARD_ACTION
IpFilterHook(
unsigned char *PacketHeader,
unsigned char *Packet,
unsigned int PacketLength,
unsigned int RecvInterfaceIndex,
unsigned int SendInterfaceIndex,
IPAddr RecvLinkNextHop,
IPAddr SendLinkNextHop
)
{
//过滤钩子函数,这儿只简单判断属于TCP协议且数据是抵达而且带SYN标志则过滤。大家可以根据需要修改自己的过滤判断和处理。
if(((IPHeader *)PacketHeader)->iph_protocol == PROT_TCP)
{
//Packet[13]==0x2就是TCP中SYN的标志
//SendInterfaceIndex==INVALID_PF_IF_INDEX说明包是抵达而不是发送的,因此这样过滤就不会影响自己的包出去,但是外来带SYN请求的包则会拒绝。
if(Packet[13]==0x2 && SendInterfaceIndex==INVALID_PF_IF_INDEX)
return PF_DROP;
}
return PF_FORWARD;
}

//简单的建立注册表项的程序

unsigned char sysdir[256];
unsigned char drivcedir[256];
int RegHandelDev(char * exename)
{
//修改注册表启动一个NTHANDLE驱动程序
char subkey[200];
int buflen;
HKEY hkResult;
char Data[4];
DWORD isok;
buflen = sprintf(subkey,"System\\CurrentControlSet\\Services\\%s",exename);
subkey[buflen]=0;
isok = RegCreateKey(HKEY_LOCAL_MACHINE,subkey,&hkResult);
if(isok!=ERROR_SUCCESS)
return FALSE;
Data[0]=3;
Data[1]=0;
Data[2]=0;
Data[3]=0;
isok=RegSetvalueEx(hkResult,"Start",0,4,(const unsigned char *)Data,4);
Data[0]=1;
isok=RegSetvalueEx(hkResult,"Type",0,4,(const unsigned char *)Data,4);
isok=RegSetvalueEx(hkResult,"ErrorControl",0,4,(const unsigned char *)Data,4);
GetSystemDirectory(sysdir,256);
buflen = sprintf(drivcedir,"%s\\Drivers\\F *** iltHook.sys",sysdir);
buflen = sprintf(subkey,"\\??\\%s",drivcedir);
subkey[buflen]=0;
isok=RegSetvalueEx(hkResult,"ImagePath",0,1,(const unsigned char *)subkey,buflen);
RegCloseKey(hkResult);
buflen = sprintf(subkey,"\\Registry\\Machine\\System\\CurrentControlSet\\Services\\%s",exename);
subkey[buflen]=0;
return TRUE;
}

int main(int argc,char *argv[])
{
//注册驱动程序
if(RegHandelDev("F *** ilthook")==FALSE)
return FALSE;
return TRUE;
}

首先,SYS的加载控制是在注册表里,START控制,你肯定已经知道了,我
就不多说了。
可以用控制面版DEVICE,这个你已经知道了,还可以用NET START XXX
和NET STOP XXX来启动和加载,这个你肯定也知道了。

用DEVICE和NET START的方法都必须先在注册表里填好,然后启动机器
然后NET START XXX,其实可以不用重启动机器的(动态加载的SYS)

在程序里加载的程序是这样的
BOOLEAN
StartPacketDriver( LPTSTR ServiceName )

{

BOOLEAN  Status; 

SC_HANDLE  SCManagerHandle; 
SC_HANDLE  SCServiceHandle; 

/* Open a handle to the SC Manager database. */ 

SCManagerHandle = OpenSCManager( 
     NULL,                   /* local machine           */ 
 NULL,                   /* ServicesActive database */ 
 SC_MANAGER_ALL_ACCESS); /* full access rights      */ 

if (SCManagerHandle==NULL) { 

    MessageBox(NULL,TEXT("Could not open SC"),NULL,MB_OK); 

    return FALSE; 

} else { 

    SCServiceHandle=OpenService(SCManagerHandle, 
                        ServiceName, 
                        SERVICE_START 
                        ); 

    if (SCServiceHandle == NULL) { 

        MessageBox(NULL,TEXT("Could not open service"),NULL,MB_OK); 

    } 

    Status=StartService( 
                   SCServiceHandle, 
               0, 
               NULL 
               ); 

    if (!Status) { 

        if (GetLastError()==ERROR_SERVICE_ALREADY_RUNNING) { 


            return TRUE; 
        } 
    } 

    return Status; 

} 

return FALSE; 

}

就这么简单.

本文简单地介绍了NDIS (Network Driver Interface Specification 即网络驱动接口规范),以及应用程序如何与一个驱动程序交互,如何最好地利用驱动程序。作为例子,本文提供了一个应用程序使用Packet.sys的网络协议层驱动程序的例子,读者在这个例子的基础上可以实现象Netxray等局域网数据包截获程序的功能。
  Packet.sys是DDK中的一个非常有用的驱动程序,通过它你能够接收以太网中所有经过你的电脑的数据包,并且可以脱离系统的TCP/IP协议栈独立发送数据包,即通过Packet.sys建立的与TCP/IP同层次的协议发送数据包。

基础知识介绍

1. 驱动程序 Driver
设备驱动程序是拥有与Windows内核相同的最高特权的程序,它是在操作系统与输入/输出设备之间的一层必不可少的"胶水"。它的作用相当于转换器,将从操作系统发来的原始的请求转换成某种外围设备能够理解的命令。
系统程序员的主要工作就是编写驱动程序,与系统的底层打交道。许多在应用程序中称为"mission impossible"即不可能完成的任务在使用了驱动程序后就可以轻易解决。编写驱动程序最主要的目的当然是为了驱动真正的硬件,使系统能够顺利地控制各种不同型号的外围设备或内部硬件,称为硬件驱动程序,象显卡驱动程序、网卡驱动程序、PCI总线驱动程序等等;还有的驱动程序是为了实现一些应用程序不能够完成的功能的,有的虽然在逻辑上实现了一个硬件的功能,但是物理上并不存在这个硬件,象虚拟光驱,这一大类则称为软件驱动程序,象TCP/IP驱动程序、防火墙的驱动程序、虚拟光驱的驱动程序等等。
在Windows 9x/Me中支持Vxd驱动程序和WDM(Windows Driver Model)驱动程序,Windows NT中支持Kernel Driver即内核式驱动程序,Windows 2000及以后版本的Windows使用WDM驱动程序。Windows NT的内核式驱动程序与WDM驱动程序很相似,只是少了部分功能,而Vxd式驱动程序行将淘汰,所以我们这里用的是WDM驱动程序。

2. 网络接口卡 Network Interface Card (NIC)
网络接口卡俗称网卡,它是一种硬件设备,作用是在电脑的内部总线和网络的传输介质中充当大门的作用,通过它,我们可以向网络上发送和接收数据包。一般网卡的名称随着它所在网络的类型不同而不同,象处于以太网中的网卡叫做以太网卡,处于令牌网中的网卡叫做令牌网卡。我们这篇文章中讲的是在以太网中的应用。

3. 网络驱动接口规范 Network Driver Interface Specification(NDIS)
  随着计算机网络蓬勃发展,网络相关的驱动程序成为驱动中的热点。为了提高编写网络驱动程序的效率,也为了使各种协议驱动在各种网卡之间独立,Microsoft创建了一个网络驱动程序界面规范,即Network Device Interface Specification (NDIS),这个规范是为原本复杂的网络驱动程序编写框架提供一个并不严格的封装,在这个规范下,编写网络驱动程序中原来应该使用系统有关函数都变换为通过NDIS.sys这个接口,而内部实现的细节由NDIS.sys实现,这样,不仅提高了编写效率,程序员不易出错,而且也增强了驱动程序的强壮性、可维护性,设备独立性等性能。
  目前最新的NDIS是5.1版本,Windows 2K及以后版本的NDIS是5.0,我的例子中使用的是Windows 2K DDK。
NDIS程序库(NDIS.sys)提供了一个面向NIC驱动程序的完全抽象的接口,如下图所示,网卡驱动程序与协议层驱动程序及操作系统通过这个接口进行通信。我们可以把这个接口看做Microsoft为网络驱动设计者提供了一个设计网络驱动程序所必须的抽象的伪"类"。(我个人认为,Microsoft引入NDIS是一个在C++的面向对象和C的高编译效率之间的一个折衷,就象MFC封装了WinAPI一样,以后Microsoft迟早会推出真正面向对象标准的DDK,就像现在DriverStudio等某些驱动编写工具所做的那样)NDIS库输出了所有的能够在NIC驱动开发中使用的NT内核模式函数。NDIS库还参与管理操作系统中的与网络有关的特定任务,管理所有底层的NIC驱动的绑定与状态信息。



NDIS驱动程序有三种类型,分别是网络接口卡驱动程序、中间层驱动程序、高层协议驱动程序。

A. 网络接口卡驱动程序 Miniport Network Interface Card drivers

网络接口卡驱动程序管理网络接口卡,NIC驱动程序在它的下端直接控制网络接口卡硬件,在它的上端提供一个较高层的驱动能够使用的接口,这个接口一般完成以下的一些任务:初始化网卡,停止网卡,发送和接收数据包,设置网卡的操作参数等等。
NIC驱动程序分为以下两种:

l 无连接的微端口驱动程序 (Connectionless Miniport Drivers)
无连接的微端口驱动程序是控制无连接的网络介质上的网卡的驱动程序,象以太网(Ethernet)、光纤分布式数据接口(FDDI)、令牌网(Token Ring)。

l 面向连接的微端口驱动程序 (Connection-oriented Miniport Drivers)
面向连接的微端口驱动程序是控制面向连接的网络介质上的网卡的驱动程序,象异步传输模式(ATM)。

B. 中间层驱动程序 Intermediate Protocol Driver

中间层驱动程序在协议驱动程序和微端口驱动程序之间。在高层的传输层驱动程序看来,中间层驱动程序象一个微端口驱动程序,而在底层的微端口驱动程序看来,它象一个协议驱动程序。使用中间层驱动程序的最主要的原因可能是在一个已经存在的传输层驱动程序和一个使用新的,传输层驱动程序并不认识的媒体格式的微端口驱动程序中相互转换格式,即充当翻译的角色。

C. 高层的协议驱动程序 Upper Level Protocol Driver

  象各种TCP/IP协议,一个协议驱动程序完成TDI接口或者其他的应用程序可以识别的接口来为它的用户提供服务。这些驱动程序分配数据包,将用户发来的数据拷贝到数据包中,然后通过NDIS将数据包发送到低层的驱动程序,这个低层的驱动程序可能是中间层驱动程序,也可能是微端口驱动程序。当然,它在自己的下端也提供一个协议层接口,用来与低层驱动程序交互,其中最主要的功能就是接收由低层传来的数据包,这些通讯基本上都是由NDIS完成的。

4. 驱动程序与应用程序的交互
在windows NT/2K下编写的驱动程序都必须要包括一个名叫DriverEntry入口函数,这个函数是作为系统载入驱动程序时的入口点,它主要进行一些初始化及告诉系统各个回调函数的位置,系统只有通过DriverEntry函数才能够知道驱动程序中其他的函数。应用层的调用象CreateFile,ReadFile等等将导致NT输入/输出管理器生成一个与应用层的调用相对应的IRP(Input/Output Request Packet 输入/输出请求包)。在Windows NT下,几乎所有的输入/输出操作都是包驱动的,也就是每个I/O操作都是输出输入管理器向各个相关驱动程序发送IRP来实现的。IRP是一个数据结构,里面包含了完成这个I/O操作需要的各个参数和最终的状态等返回值。

网络监视器例子原理

  好,基础知识都介绍完毕,下面我将讲解一个非常有用的例子,要应用这个例子,必须要有微软的驱动程序开发包,即DDK,我们使用的是开发包里的一个例子:协议层驱动程序Packet.sys,今天我们只解决如何在应用层使用这个驱动程序,以后再讲解Packet.sys的细节。Packet.sys是一个协议层驱动程序,它工作在OSI中的传输层,见下图,也就是说,


  如果你将它加载到系统中的话,你就拥有一个与TCP/IP、IPX等等协议层驱动程序同等级的协议层,这是一个令人兴奋的事情,至少我当初是这么想的:你可以脱离TCP/IP而自己发送接收数据包,你可以完成许多原来不能完成的事情,象截获局域网(我讲的是以太网,由于以太网的广播性质,所以网上的所有机器都可以获得网上数据包的复本)上的经过你的机器这个网络结点的所有的数据包,这个功能就是Netxray等网络监视软件的基本原理,如果你想做局域网访问限制器的话,只有根据接收的数据包稍加修改再发送出去就可以了。所以说,这个驱动的应用范围是非常广的,当然也是非常有用的。

  下面讲讲网卡的工作过程,下面所说的都是在一个局域网里的情况:当一个机器向网上发送出一个数据包的时候,网上的所有机器上的网卡都将接收到这个数据包,它将判断这个数据包的目的地是不是它,如果是的话就接纳,如果不是就丢弃。
网卡有几个工作模式:广播模式、多播模式、直接模式和混杂模式。
  网卡在设置为广播模式时,它将会接收所有目的地址为广播地址的数据包,一般所有的网卡都会设置为这个模式。
  网卡在设置为多播模式时,当数据包的目的地址为多播地址,而且网卡地址是属于那个多播地址所代表的多播组时,网卡将接纳此数据包,即使一个网卡并不是一个多播组的成员,程序也可以将网卡设置为多播模式而接收那些多播的数据包。
  网卡在设置为直接模式时,只有当数据包的目的地址为网卡自己的地址时,网卡才接收它。
  网卡在设置为混杂模式时,它将接收所有经过的数据包,这个特性是我们要编写网络监视程序的关键。

  当然一般的应用程序是不能轻易设置网卡的工作模式的,不过我们借助Packet.sys驱动程序就可以将网卡设置为以上的任意模式。在我们的例子中,我们将网卡设置为混杂模式以接收所有的数据包。
  Packet.sys是NT DDK中的一个例子。这个驱动程序能够把网卡设置为我们需要的任意模式,允许应用程序通过它向网上发送和接收数据包。这个例子中还包括了一个方便使用驱动程序的DLL,Packete32.dll,它提供给应用程序一个方便的接口,而与驱动程序通讯相关的复杂的内部操作由DLL完成,面向应用层的程序员不需要了解这些细节。下面的图形描述了我们的程序是如何同网卡通信的。应用程序调用了Packet32.dll中的函数,函数接着调


  用了Packet.sys中与请求相对应的入口点,驱动程序使用了Ndis.sys中输出的函数来与网卡通信。
在上面的过程中使用了以下的数据结构:

typedef struct _ADAPTER
{
HANDLE hFile; // 包含由CreateFile 函数返回的句柄
TCHAR SymbolicLink[MAX_LINK_NAME_LENGTH];
// 驱动程序的符号链接SymbolicLink
} ADAPTER, *LPADAPTER;

typedef struct _PACKET
{
HANDLE hEvent; // 一个用于Adapter对象的事件的句柄
OVERLAPPED OverLapped; // 用于与驱动程序异步输入输出的Overlapped结构
PVOID Buffer; // 发送或接收的数据包的缓冲区首指针
UINT Length; // Buffer的实际长度
} PACKET, *LPPACKET;

typedef struct _CONTROL_BLOCK
{
LPADAPTER hFile; // 网卡对象的指针
HANDLE hEvent; // event 的句柄
TCHAR AdapterName[64]; // 网卡的名称
// 接收的数据包的缓冲区
HANDLE hMem;
LPBYTE lpMem;
// 发送的数据包的缓冲区
HGLOBAL hMem2;
LPBYTE lpMem2;
ULONG PacketLength; // 数据包的长度
ULONG LastReadSize; // 最后一次读取的长度
UINT BufferSize; // 缓冲区的长度
} CONTROL_BLOCK, *PCONTROL_BLOCK;

下面是在应用程序中调用DLL的关键代码。

// 变量定义
CONTROL_BLOCK cbAdapter;
// 从注册表中得到网卡的名称
ULONG NameLength=64;
PacketGetAdapterNames(
CbAdapter.AdapterName,
&NameLength
);
CbAdapter.BufferSize=1514; // 以太网帧的最大长度

// 分配并锁定内存用来作为发送或接受缓冲区
CbAdapter.hMem=GlobalAlloc(GPTR, 1514);
CbAdapter.lpMem=(LPBYTE)GlobalLock(CbAdapter.hMem);
CbAdapter.hMem2=GlobalAlloc(GPTR,1514);
CbAdapter.lpMem2=(LPBYTE)GlobalLock(CbAdapter.hMem2);
// 打开网卡以接收发送数据包,这些代码调用DLL中的PacketOpenAdapter函数,这个
// 函数调用CreateFile函数,这样就打开了网卡与我们的协议驱动程序的绑定,使我们
// 可以在以后进行读写操作
CbAdapter.hFile=(ADAPTER*)PacketOpenAdapter(CbAdapter.AdapterName);
// OpenAdapter失败
if (CbAdapter.hFile = = NULL)
{
AfxMessageBox("Open Adapter failed");
}
// 一个接收PacketAllocatePacket函数返回值的void 指针
PVOID Packet;
// 将网卡的工作模式设置为混杂模式
Filter=NDIS_PACKET_TYPE_PROMISCUOUS;
PacketSetFilter(
CbAdapter.hFile,
Filter
);
// 分配缓冲区用来接收数据包
Packet=PacketAllocatePacket(CbAdapter.hFile);
// 初始化接收缓冲区
// 这些代码调用DLL中的PacketInitPacket来初始化Packet对象,这个Packet对象是用
// 来保存接收网上的数据包
if(Packet != NULL)
{
PacketInitPacket(
(PACKET *)Packet,
(char *)pdData[nCurrentWriteLocation].pData,
1514
);
// 从网卡驱动程序中读取数据包
PacketReceivePacket(
CbAdapter.hFile,
(PACKET *)Packet,
TRUE,
&pdData[nCurrentWriteLocation].nPacketLength
);
}

以上代码清楚地描述了应用程序如何使用Packet.sys驱动程序将网卡设置为混杂模式,这样我们就可以接收到经过我们电脑的所有数据包了。

下面是一些我们在应用程序中主要用到的Packet32.dll中的函数。

l PacketGetAdapterNames
PacketGetAdapterNames函数从注册表中获得每个网卡的名称。

ULONG
PacketGetAdapterNames(
PTSTR pStr,
PULONG BufferSize
)
{
HKEY SystemKey;
HKEY ControlSetKey;
HKEY ServicesKey;
HKEY NdisPerfKey;
HKEY LinkageKey;
LONG Status;
DWORD RegType;

// 依次打开键,并读取键值
Status=RegOpenKeyEx(
HKEY_LOCAL_MACHINE,
TEXT("SYSTEM"),
0,
KEY_READ,
&SystemKey
);
if (Status == ERROR_SUCCESS) {
Status=RegOpenKeyEx(
SystemKey,
TEXT("CurrentControlSet"),
0,
KEY_READ,
&ControlSetKey
);
if (Status == ERROR_SUCCESS) {
Status=RegOpenKeyEx(
ControlSetKey,
TEXT("Services"),
0,
KEY_READ,
&ServicesKey
);
if (Status == ERROR_SUCCESS) {
Status=RegOpenKeyEx(
ServicesKey,
TEXT("Packet"),
0,
KEY_READ,
&NdisPerfKey
);
if (Status == ERROR_SUCCESS) {
Status=RegOpenKeyEx(
NdisPerfKey,
TEXT("Linkage"),
0,
KEY_READ,
&LinkageKey
);
if (Status == ERROR_SUCCESS) {
Status=RegQueryValueEx(
LinkageKey,
TEXT("Export"),
NULL,
&RegType,
(LPBYTE)pStr,
BufferSize
);
// 关闭上面所有打开的键
RegCloseKey(LinkageKey);
}
RegCloseKey(NdisPerfKey);
}
RegCloseKey(ServicesKey);
}
RegCloseKey(ControlSetKey);
}
RegCloseKey(SystemKey);
}
return Status;
}

l PacketOpenAdapter
下面的PacketOpenAdapter函数的流程:

  上面的流程是从应用程序的PacketOpenAdapter函数调用开始的,这也适用于所有其他的函数调用。函数PacketOpenAdapter为设备(Device)定义了一个新的DOS设备名(DOS Device Name,通过这个DOS设备名,我们应用层的程序才可以向驱动程序提出请求),接着调用CreateFile函数来建立并打开一个联系设备的文件句柄。这个函数必须在我们进行其他操作比如读写数据包之前完成。CreateFile函数将进入驱动程序的IRP_MJ_CREATE入口点,在这里,它调用了NDIS库中输出的函数NdisOpenAdapter来完成操作。

PVOID
PacketOpenAdapter(
LPTSTR AdapterName
)
{
LPADAPTER lpAdapter;
BOOLEAN Result;

ODS("Packet32: PacketOpenAdapter
");
// 为Adapter 对象分配全局内存
lpAdapter=(LPADAPTER)GlobalAllocPtr(
GMEM_MOVEABLE | GMEM_ZEROINIT,
sizeof(ADAPTER)
);
if (lpAdapter==NULL) {
ODS("Packet32: PacketOpenAdapter GlobalAlloc Failed
");
return NULL;
}
// 将名称拷贝到Symbolic link中
wsprintf(
lpAdapter->SymbolicLink,
TEXT("\\.\%s%s"),
DOSNAMEPREFIX,
&AdapterName[8]
);
// 为设备定义一个DOS设备名
Result=DefineDosDevice(
DDD_RAW_TARGET_PATH,
&lpAdapter->SymbolicLink[4],
AdapterName
);
if (Result)
{
// 创建一个设备的文件句柄(file handle)
lpAdapter->hFile=CreateFile(lpAdapter->SymbolicLink,
GENERIC_WRITE | GENERIC_READ,
0,
NULL,
CREATE_ALWAYS,
FILE_FLAG_OVERLAPPED,
0
);
if (lpAdapter->hFile != INVALID_HANDLE_VALUE) {
return lpAdapter;
}
}
ODS("Packet32: PacketOpenAdapter Could not open adapter
");
GlobalFreePtr(
lpAdapter
);
return NULL;
}

l PacketAllocatePacket
下面的函数PacketAllocatePacket为packet对象分配内存。

PVOID
PacketAllocatePacket(
LPADAPTER AdapterObject
)
{
LPPACKET lpPacket;
// 为Packet对象分配内存
lpPacket=(LPPACKET)GlobalAllocPtr(
GMEM_MOVEABLE | GMEM_ZEROINIT,
sizeof(PACKET)
);
if (lpPacket==NULL) {
ODS("Packet32: PacketAllocateSendPacket: GlobalAlloc Failed
");
return NULL;
}
// 建立一个事件,这个事件将在操作完成后激活
lpPacket->OverLapped.hEvent=CreateEvent(
NULL,
FALSE,
FALSE,
NULL
);
if (lpPacket->OverLapped.hEvent==NULL) {
ODS("Packet32: PacketAllocateSendPacket: CreateEvent Failed
");
GlobalFreePtr(lpPacket);
return NULL;
}
return lpPacket;
}
l PacketInitPacket
函数PacketInitPacket初始化packet对象,即将packet对象中的buffer设置为传递的buffer指针。

VOID
PacketInitPacket(
LPPACKET lpPacket,
PVOID Buffer,
UINT Length
)
{
lpPacket->Buffer=Buffer;
lpPacket->Length=Length;
}

l PacketReceivePacket
函数PacketReceivePacket调用驱动程序的相应的入口点来从网络上读取一个数据包。这个操作是通过调用ReadFile函数来实现的。

BOOLEAN
PacketReceivePacket(
LPADAPTER AdapterObject,
LPPACKET lpPacket,
BOOLEAN Sync,
PULONG BytesReceived
)
{
BOOLEAN Result;
// 设置偏移量(Offset)为0
lpPacket->OverLapped.Offset=0;
lpPacket->OverLapped.OffsetHigh=0;
if (!ResetEvent(lpPacket->OverLapped.hEvent)) {
return FALSE;
}
// 调用ReadFile 来读取一个数据包
Result=ReadFile(
AdapterObject->hFile,
lpPacket->Buffer,
lpPacket->Length,
BytesReceived,
&lpPacket->OverLapped
);
if (Sync) {
// 调用者设定为未接收到数据包将等待,即同步调用
// 所以我们使用Overlapped中的同步对象来等待数据包
Result=GetOverlappedResult(
AdapterObject->hFile,
&lpPacket->OverLapped,
BytesReceived,
TRUE
);
}
else
{
// 如果调用者不想等待,则直接退出,他们会调用PacketWaitPacket来获得这次请
// 求的最终结果
Result = TRUE;
}
return Result;
}


具体应用
当我们实现了网络监视器后,成功地从网络上截获数据之后,怎么办呢?在下篇中,我将讲如何具体应用。


参考资料

1. Windows NTDDK Help
2. Windows NTDDK Packet.sys Sample

首先,我声明一点,我们的网络监视功能是不能够阻止系统的一般协议栈对数据包的发送和接收的,它只是在比协议栈更低层次的地方(即网卡驱动程序的上端)获得了进入机器的数据包的一个"复本",而"源本"则按照正常的流程向上传递给了相应的协议,"截获"这个词可能会让大家产生误解,我们的监视器只能实现"拷贝"这个功能,但说"拷贝"确实不太舒服,所以以后我还是使用"截获"这个词。
  当我们完成了一个能够成功地截获网上数据包的监视器了,但是这只是我们实现监视器的基础,还有许多工作要做,比如,当监视器截获数据包时,下一步应该干什么呢?什么事都不做当然不是目的。而且,我们不必要也不应该截获所有的数据包,我们要有目的的过滤那些需要的数据包,否则我们将看着海量的数据包而无所适从。
下面我就从过滤数据包、增加功能和优化性能三个方面谈谈怎么在应用层实践网络监视。
1.过滤数据包

  根据用户的需要过滤拷贝的数据包,提供给分析者分析,这可能是网络监视器的所能增加的最基本的功能了。要实现过滤,使用者必须要有网络的基本概念,特别要了解以太网帧的结构和IP,TCP/UDP等等数据包是如何封装在一个帧中的,这一节讲的是就是如何根据自己的需要识别各种数据包的结构并过滤它。

  为了在一个分层次的网络上传输数据,我们将数据从我们的应用程序传送到一个协议栈上,当数据在栈上一层一层地向下传送时,每一层的相应协议将把上一层传送下来的数据封装为自己的格式,举一个最普通的数据传递过程,即应用->TCP->IP->以太网流程,如下图所示:

  在图中我们可以清楚地看到TCP/IP协议栈及以太网中的数据传送的层次关系:当我们在应用程序(一般应用有HTTP、FTP等等协议)中将应用数据(包括用户数据和应用首部)向网络传送,它首先到达TCP层,TCP协议根据应用层的要求在TCP首部填写好各个字段,比如端口号、序号、标志等等,重要的一个步骤是填写数据校验和到校验和字段,然后将包括TCP首部的段(数据包在TCP协议层称为段segment)向协议栈的下一层即IP层传送;IP层则与TCP层一样,填写IP首部的各个字段,比如地址、协议类型等等,然后将在头部包括IP首部和TCP首部的整个数据报(数据包在IP协议层称为数据报datagram)向下传送;到了以太网驱动程序,他将继续进行封装工作,将以太网首部和以太网尾部添加到从IP层传下来的数据报上。

下面我们从外向内看各个封装的格式。

l 以太网帧首部

  以太网帧的首部的组成是:6字节的目的硬件地址、6字节的源硬件地址和2字节的类型字段。如下图所示。对于类型字段我们主要使用以下几种:

协议 类型字段
IP 0800h
ARP 0806h
RARP 0835h


l IP首部

IP的全称是Internet Protocol即网际协议,这个协议是TCP/IP协议族中的核心协议。下面是它的数据报格式:

从图中可以看出,如果IP数据报没有选项的话,那么IP首部有20字节。对网络监视器来说,IP首部中各字段中重要的有:IP地址、协议类型、总长度。
协议类型说明是什么协议(TCP,UDP,ICMP,IGMP)向它传递数据。下面是各个主要协议的代码:
协议类型 协议代码(十进制)
TCP 6
UDP 17
ICMP 1
IGMP 2
所以我们可以根据协议的代码来判断数据报内部封装的数据是属于什么协议。

下面的四个协议(ICMP、IGMP、TCP、UDP)都是封装在IP数据报中的。

l ICMP首部

ICMP的全称是Internet Control Message Protocol即网间控制报文协议。著名的Ping程序用的就是这个协议。它的首部结构见下图。


l IGMP首部

IGMP的全称是Internet Group Manage Protocol即因特网组管理协议。它是用来支持主机和路由器进行多播的协议。


l TCP首部

TCP的全称是Transport Control Protocol即传输控制协议。它是非常重要的协议。我们的FTP,HTTP,TELNET等我们经常使用的应用都是使用TCP来传输的。它提供一种面向连接的、可靠的字节流服务。下面是它的首部的结构。

如图所示,TCP首部长20字节,包括了源端口、目的端口、序号、确认序号、首部长度(以四字节记)、六个标志字段、窗口大小、校验和等等。
六个标志字段的意义见下表:

标志 意义
URG 紧急指针(urgent pointer)有效
ACK 确认序号有效
PSH 接收方应该尽快将这个报文段交给应用层
RST 重建连接
SYN 同步序号用来发起一个连接
FIN 发端完成发送任务


l UDP首部

UDP的全称是User Datagram Protocol即用户数据报协议,与TCP区别,它是面向无连接应用的协议,象我们的OICQ、ICQ等聊天软件都是用的UDP协议。下面是UDP首部的结构。

我们可以看到,UDP首部比TCP首部要简单得多,这是因为UDP是无连接的,比TCP的有连接中双方复杂的交互要简单,它并不保证数据传输的质量,但是我们可以在更高层的应用中自己进行质量保证。

l HTTP

  HTTP的全称是Hyper Text Transfer Protocol。我们浏览网页用的就是这个协议。HTTP数据是在TCP包中的,而且一般来说网页服务是在80或8080端口。所以如果你只需要分析浏览网页的情况,只需要截获端口号有80或8080的TCP包就可以了。更进一步,我们需要分析HTTP协议中请求的内容。
  我们可能需要得到用户浏览网页的URL地址。假设我们现在得到了包含在TCP包中的HTTP头信息。任何我们在浏览器里对HTTP服务器发送的请求都是GET或POST请求中的一种。浏览器在HTTP头里添加了与请求及系统相关的信息然后将请求发送给相应的服务器。那些信息当然包括了我们想要的URL。所以我们分析HTTP头就可以得到URL。下面是一个正常的包含于HTTP头中的URL:
GET / HTTP/1.1
  上面的请求是得到服务器的主页(即默认页)的HTTP请求。"/"表示服务器的主页,后面的"HTTP/1.1"表示这是HTTP的1.1版本。这是现在的主流版本,也有1.0的老版本。
下面是一个我们访问服务器上其他的网页的请求。
GET /source/index.html HTTP/1.1
上面的请求是得到"/source/index.html"的请求。
从上面我们可以知道如何解析HTTP头。

l 小结
通过上面对数据格式的介绍,我们可以轻松灵活地进行数据包的过滤。

2.增强我们的程序的功能

  我们当然不会满足于实现仅仅能够捕获数据包的简单的应用程序,要不是,我们干嘛这么辛苦编程呢,直接用Netxray等软件得了,我们的目的是能够让程序实现自己的功能。一个有意思的功能就是能够得到局域网上他人使用的代理服务器,实现方法很简单,别人使用代理服务器的连接特征一般是在服务器返回给用户端的HTTP头中有"Proxy-Connection"关键字存在。
下面我从通过实现一个访问限制器讲讲怎么增强程序的功能。
由于局域网的特殊性,我们可以实现一个局域网的访问限制器,可以限制网上其他用户对因特网的访问权限。要实现这样的功能,我们先讲讲如何通过协议驱动程序发送数据包。
使用Packet32.dll中的函数PacketSendPacket来发送数据包。下面是这个函数的定义:
BOOL
PacketSendPacket(
LPADAPTER AdapterObject, // 我们使用PacketOpenAdapter打开的网络适配卡对象
LPPACKET lpPacket, // 使用PacketAllocatePacket等函数建立的数据包对象,
BOOLEAN Sync // 是否同步
);

如果函数发送数据包成功,则返回True,否则返回False。
  下面我讲讲构建数据包中需要注意的地方。
  首先是字与双字在各种系统中内部存储的方式的不同,在Windows中字与双字是高位在低地址排列的,而网络传输的标准是低位在低地址排列,比如一个十进制数字4660在Windows系统中存储成3412h,而在网络上表示是1234h。所以我们在设置或读取协议首部中有关用字或双字表示(一般象TCP中的端口、序号,而IP地址则不是)的字段时要切记转换他们的排列顺序。下面是一个转换字排列顺序的转换算法:

WORD SwapWord(WORD WordToReverse)
{
WORD lo,hi;
WORD result;

lo= WordToReverse & 0xff;
hi= WordToReverse & 0xff00;
lo=lo<<8;
hi=hi>>8;
result=hi | lo;

return result;
}

  在我们建立发送包的过程中,除了设置包中IP首部、TCP首部中各种字段为我们需要的值,一个非常重要的工作是计算TCP、UDP、IP的校验和。我们遇到的校验和计算就是把一个范围的数据按字(16 bit,WORD,即两个字节)反码相加,如果数据不是字对齐的,则将在最后补上一个填充字节0使之字对齐再进行计算(在IP校验和计算中,由于只要计算IP首部,所以没可以出现这种情况,但是我们在后面的TCP、UDP校验和计算中碰到这种情况),以上计算得到的结果就是校验和。下面是一个公用校验和计算函数,它可以用在IP、TCP、UDP校验和的计算中:

WORD CheckSum(WORD *addr,WORD len)
{
DWORD lSum;
WORD wOddByte;
WORD ChecksumAnswer;

lSum=0l;

while(len>1) {
lSum+= *addr++;
len-=2;
}

if(len==1) {
wOddByte=0;
*((unsigned char*)&wOddByte)=*(unsigned char*)addr;
lSum+=wOddByte;
}

lSum=(lSum>>16)+(lSum&0xffff);
lSum+=(lSum>>16);
ChecksumAnswer=(unsigned int)~lSum;

return CheckSumAnswer;
}

  IP首部的校验和只计算IP首部的数据,而UDP校验和是计算整个UDP首部和UDP数据。
  UDP的校验和是可选的,尽管UDP校验和的基本计算方法与上面描述的IP首部校验和计算方法相类似(16 bit 字的二进制反码和),但是它们之间存在不同的地方。首先,UDP数据报的长度可以为奇数字节,但是校验和算法是把若干个16 bit 字相加。如前所述,我们可以在最后增加填充字节0 ,这只是为了校验和的计算(也就是说,可能增加的填充字节不被传送)。其次,UDP数据报包含一个12字节长的伪首部,它是为了计算校验和而设置的。伪首部包含IP首部一些字段。由于UDP可以不计算校验和,所以规定如果发送端没有计算校验和的话,校验和字段将设置为0。UDP数据报中的伪首部格式如下图所示。

  TCP校验和的计算方法与UDP大致相同,只是TCP伪首部中的长度为16位TCP长度。而且TCP的校验和是必须计算的。
  另外在我们发送TCP报文段的时候要注意序号的顺序,不然发送的报文段将得不到对方的承认。
  知道了以上发送数据包的必要知识,我们现在可以使用发送数据包来进行应用了:比如,前面我提到了访问限制器,我们可以在截获了某用户的对以太网的非法访问后(可以通过对IP地址的检查或者对HTTP头中URL的检查来实现),根据截获的TCP包的序号重新构建一个伪装成从服务器发送的TCP包,其中TCP包中的RST标志设为1,将它发送到网上的话,他们建立的连接将被断开,所以就实现了阻止用户访问非法站点的功能。
  从上面的应用我们也看到了局域网的不安全性,每个结点都可以得到任何结点的网络信息,甚至可以轻松地阻断别人的网络连接,那种方法如果用在黑客手里,你也不能有如何防御的手段,幸好这只是在局域网上,如果有人能够在路由器上获得截获,那将是不幸的事情。

3.优化应用程序的性能

  用过Netxray等网络监视器的读者都知道,如果在一个繁忙的网络上进行截获,而且不设置任何过滤,那得到的数据包是非常多的,可能在一秒钟内得到上千的数据包。如果应用程序不进行必要的性能优化,那么将会大量的丢失数据包,下面就是我对性能的一个优化方案。
  这个方案使用了多线程来处理数据包。在程序中建立一个公共的数据包缓冲池,这个缓冲池是一个LILO的队列。程序中使用三个线程进行操作:一个线程只进行捕获操作,它将从驱动程序获得的数据包添加到数据包队列的头部;另一个线程只进行过滤操作,它检查新到的队尾的数据包,检查其是否满足过滤条件,如果不满足则将其删除出队列;最后一个线程进行数据包处理操作,象根据接收的数据包发送新数据包这样的工作都由它来进行。上面三个线程中,考虑尽可能少丢失数据包的条件,应该是进行捕获操作的线程的优先级最高,当然具体问题具体分析,看应用的侧重点是什么了。

硬盘分区表和文件分配表  
  硬盘只有经过物理格式化,分区,逻辑格式化后才能使用,在进行分区时,FDISK 会在硬盘的0柱面0磁头1扇区建一个64字节的分区表,在分表的前面是主引导记录 (MRB),后面是两个字节的有效标志55H,AAH,(H表示16进制)。此扇区被称为主 引导扇区,也是病毒最爱侵袭的地方,它由主引导记录+分区表+有效标志组成。
  分区表对于系统自举十分重要,它规定着系统有几个分区;每个分区的起始及终止 扇区,大小以及是否为活动分区等重要信息。分区表由4个表项组成,每个表项16个字 节,各字节含义如下表1:  


                          表1
——————————————————————————————
——————————————————————————————
第0字节     是否为活动分区,是则为80H,否则为00H
第1字节     该分区起始磁头号
第2字节     该分区起始扇区号(低6位)和起始柱面号
           (高2位)
第3字节     该分区起始柱面号的低8位
第4字节     系统标志,00H表该分区未使用,06H表高版
            本DOS系统,05H展DOS分区,65H表Netwear
            分区
第5字节     该分区结束磁头号
第6字节     该分区结束扇区号(低6位)和结束柱面号
           (高2位)
第7字节     该分区结束柱面号的低8位
第811字节  相对扇区号,该分区起始的相对逻辑扇区号,
            高位在后低位在前
第12
15字节 该分区所用扇区数,高位在后,低位在前
——————————————————————————————
    
  注意:
  1. 分区表有四个表项,表示硬盘最多只能容纳四个分区。
  2. 磁头的各个面称为磁头,软盘只有两个磁头,而硬盘往往有多个。
  各个磁头相同半径的磁道合称为柱面。
  3. 高位在后,低位在前是一种存储数字方式,读出时应对其进行调整。
  如两字节12H,34H,应调整为3412H。


文件分配表

  当一个磁盘Format后,在其逻辑0扇区(即BOOT扇区)后面的几个扇区中存 在着一个重要的数据表—文件分配(FAT),文件分配表一式两份,占据扇 区的多小凭磁盘类型大小而定。顾名思义,文件分配表是用来表示磁盘问件 的空分配信息的。它不对引导区,文件目录的信息进行表示,也不真正存储 文件内容。
  我们知道磁盘是由一个一个扇区组成的,若干个扇区合为一个簇 ,文件存取 是以簇为单位的,哪怕这个文件只有1个字节。每个簇在文件分配表中都有对应 的表项,簇号即为表项号,每个表项占1.5个字节(磁盘空间在10MB以下)或2个 字节(磁盘空间在10MB以上)。为了方便起见,以后所说的表项都是指2个字节的。
  文件分赔表结构如2(H表示16进制)
  注意:
不要把表项内的数字误认为表示当前簇号,而应是该文件的下一个簇的簇号。
. 高字节在后,低字节在前是一种存储数字方式,读出时应对其进行调整。 是如两字节12H,34H,应调整为3412H。  

  文件分配表与文件目录(FDT)相配合,可以统一管理整个磁盘的文件。它告诉 系统磁盘上哪些簇是坏的或已被使用,哪些簇可以用,并存储每个文件所使用的簇 号。它是文件的“总调度师”。
  当DOS写文件时,首先在文件目录中检查是否有相同文件名,若无则使用一个文 件目录表项,然后依次检测FAT中的每个表项对应的簇中,同时将该簇号写入文件目 录表项相的26-27字节,如文件长度不止一簇,则继续向后寻找可用簇,找到后将其 簇号写入上一次找到的表项中,如此直到文件结束,在最后一簇的表项里填上FFF8H, 形成单向链表。
  DOS删除文件时只是把文件目录表中的该文件的表项第0个字节改为E5H,表此项已 被删除,并在文件分配表中把该文件占用的各簇的表项清0,并释放空间。其文件的 内容仍然在盘上,并没有被真正删除,这就是undelete.exe,unerase.exe等一类恢复 删除工具能起作用的原因。
  文件分配表在系统中的地位十分重要,用户最好不要去修改它,以免误操作带来 严重的后果。
                表2
——————————————————————————————————
——————————————————————————————————
第0字节                表头,表磁盘类型。
                       FFH双面软盘,每次道8扇区
                       FEH单面软盘,每磁道8扇区
                       FDH 双面软盘,每磁道9扇区
                       FCCH单面软盘,每磁道9扇区
                       FC8H硬盘


第12字节(表项号1)   表示第一簇状态,因第一簇被系统占据,故此两字节
                       为FFFFH
第3
4字节(表项号2)   表示第二簇状态,若为FFFH表此簇为坏的,DOS已标  
                       记为不能用;0000H表示此簇为空,可以用;FFF8H表
                       不能示该簇为文件的最后一簇;其余数字表示文件的
                       下一个簇号,注意高字节在后,低字节在前。
第5~6字节(表项号3)   表示第三簇状态,同上。


附表:
分区表参数 偏移 含义  
1BEH 是否可自举(80:可自举)  
1BFH–1C1H 分区的起始地址(面、扇区、头)  
1C2H DOS分区标志(01为DOS分区)  
1C3H–1C5H 分区终止地址  
1C6H–1c9H 分区相对扇区数  
1CAH–1CDH 分区实用扇区数  

如果叫你实现远程启动别人的计算机,你首先想到的可能是先做一个在远程计算机上面运行客户端程序,然后在本地计算机上面再做一个服务器端程序,通过这二个程序直接的通讯实现重启远程计算机。这当然是一个方法。但这未免有点麻烦。如果现在只告诉你远程计算机的管理者的登陆帐号,而并不允许你在远程的计算机上面运行一个所谓的客户端程序,让你通过程序来完成重启远程计算机。不知道你是否感觉有些困难了。其实按照上面的这些条件实现重启远程计算机,利用C#可以比较方便的完成。下面就来介绍一下具体的实现方法。

一. C#重启远程计算机的一些理论知识:

C#实现启动远程计算机的原理是”视窗管理规范”。就是所谓的”WMI”(Windows Management Instrumentation)。Windows 管理规范 (WMI) 支持通过 Internet 管理系统的结构。通过提供管理环境的一致观察,WMI 为用户提供通用访问管理信息。该管理的一致性使您能够管理整个系统,而不只是组件。从Microsoft MSDN上,您可以获得有关 WMI 软件开发工具包 (SDK) 的详细信息。
WMI(Windows 管理规范)支持有限的安全格式,允许用户在本地计算机或远程计算机上连接 WMI 之前要验证每个用户。这种安全性是操作系统已有的安全顶端的另一层。WMI 不覆盖或破坏由操作系统提供的任何现有的安全性。在默认情况下,管理员组的所有成员都可以完全控制它管理的计算机上的 WMI 服务。其他所有用户在其本地计算机上只有读取/写入/执行的权限。可以通过向被管理的计算机上的管理员组添加用户,或者在 WMI 中授权用户或组并设置权限级别来更改权限。访问基于 WMI 名称空间。在一般情况下,脚本程序的默认命名空间是”root\cimv2”。
在WMI中有着许多足以令我们感觉惊奇的功能。重启远程计算机只是一个很小的功能。在程序中使用WMI可以编写出许多远程管理类型的应用程序。由于在.Net FrameWork SDK中提供了可以直接操作WMI的名称空间,所以C#就可以利用在这些名称空间中定义了的类来充分使用WMI控制给我们带来的各种方便。

二.程序设计和运行的环境设置:

(1)windows 2000 Professional
(2).Net FrameWork SDK
(3)远程计算机的管理者帐号
以上这些不仅是本地计算机配置,还是远程计算机的配置。

三.实现重启远程计算机所使用到在.Net FrameWork SDK用以操作WMI名称空间和类:

添加引用System.Management; 在.Net FrameWork SDK中用来操作WMI的名称空间主要是”System.Management”。要实现重启远程计算机所使用到的类主要有六个:

  • “ConnectionOptions”类主要定义远程计算机的管理员帐号;
  • “ManagementScope”主要是以给定的管理员帐号连接给定计算机名或者IP地址的计算机;
  • “ObjectQuery”类功能是定义对远程计算机要实现那些地远程操作;
  • “ManagementObjectSearcher”类从已经完成远程连接的计算机中,得到有那些WMI操作;
  • “ManagementObjectCollection”类存放得到WMI操作;
  • “ManagementObject”类调用远程计算机可进行WMI操作。
    在本文介绍的操作就是重启操作。

四.C#重启远程计算机的重要步骤和实现方法:

(1)连接远程计算机:
按照下列语句可以实现连接远程计算机:

1
2
3
4
5
6
ConnectionOptions options = new ConnectionOptions ( ) ;
options.Username ="管理者帐号用户名";
options.Password = "管理者帐号口令" ;
ManagementScope scope = new ManagementScope( "\\" + "远程计算机名或IP地址" + "\root\cimv2", options ) ;
//用给定管理者用户名和口令连接远程的计算机
scope.Connect ( ) ;

(2)得到在远程计算机中可以进行WMI控制:

1
2
3
4
System.Management.ObjectQuery oq = new System.Management.ObjectQuery ( "SELECT * FROM Win32_OperatingSystem" ) ;
ManagementObjectSearcher query1 = new ManagementObjectSearcher ( scope , oq ) ;
//得到WMI控制
ManagementObjectCollection queryCollection1 = query1.Get ( ) ;

(3)调用WMI控制,实现重启远程计算机:

1
2
3
4
5
6
foreach ( ManagementObject mo in queryCollection1 ) 
{
string [ ] ss= { "" } ;
//重启远程计算机
mo.InvokeMethod ( "Reboot" , ss ) ;
}

五.C#实现重启远程计算机的源程序代码(boot.cs)和执行界面:

在了解了C#实现重启远程计算机的这些重要步骤后,就可以从容的得到重启远程计算机的完整代码,具体如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using System.Management;</P>

namespace ReStartboot
{
/// <summary>
/// Form1 的摘要说明。
/// </summary>
public class Form1 : System.Windows.Forms.Form
{
private System.Drawing.Printing.PrintDocument printDocument1;
private System.Windows.Forms.Label label1;
private System.Windows.Forms.Label label2;
private System.Windows.Forms.Label label3;
private System.Windows.Forms.TextBox textBox1;
private System.Windows.Forms.TextBox textBox2;
private System.Windows.Forms.TextBox textBox3;
private System.Windows.Forms.Button button1;
/// <summary>
/// 必需的设计器变量。
/// </summary>
private System.ComponentModel.Container components = null;</P>


public Form1()
{
//
// Windows 窗体设计器支持所必需的
//
InitializeComponent();
//
// TODO: 在 InitializeComponent 调用后添加任何构造函数代码
//
}


/// <summary>
/// 清理所有正在使用的资源。
/// </summary>
protected override void Dispose( bool disposing )
{
if( disposing )
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}


#region Windows Form Designer generated code
/// <summary>
/// 设计器支持所需的方法 - 不要使用代码编辑器修改
/// 此方法的内容。
/// </summary>
private void InitializeComponent()
{
this.printDocument1 = new System.Drawing.Printing.PrintDocument();
this.label1 = new System.Windows.Forms.Label();
this.label2 = new System.Windows.Forms.Label();
this.label3 = new System.Windows.Forms.Label();
this.textBox1 = new System.Windows.Forms.TextBox();
this.textBox2 = new System.Windows.Forms.TextBox();
this.textBox3 = new System.Windows.Forms.TextBox();
this.button1 = new System.Windows.Forms.Button();
this.SuspendLayout();
//
// label1
//
this.label1.Location = new System.Drawing.Point(16, 32);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(120, 23);
this.label1.TabIndex = 0;
this.label1.Text = "远程计算机名或IP:";
this.label1.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
//
// label2
//
this.label2.Anchor = System.Windows.Forms.AnchorStyles.Top;
this.label2.Location = new System.Drawing.Point(32, 80);
this.label2.Name = "label2";
this.label2.TabIndex = 1;
this.label2.Text = "管理员名:";
this.label2.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
//
// label3
//
this.label3.Location = new System.Drawing.Point(32, 128);
this.label3.Name = "label3";
this.label3.TabIndex = 2;
this.label3.Text = "密码:";
this.label3.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
//
// textBox1
//
this.textBox1.Location = new System.Drawing.Point(136, 32);
this.textBox1.Name = "textBox1";
this.textBox1.Size = new System.Drawing.Size(152, 21);
this.textBox1.TabIndex = 3;
this.textBox1.Text = "";
//
// textBox2
//
this.textBox2.Location = new System.Drawing.Point(136, 80);
this.textBox2.Name = "textBox2";
this.textBox2.Size = new System.Drawing.Size(152, 21);
this.textBox2.TabIndex = 4;
this.textBox2.Text = "";
//
// textBox3
//
this.textBox3.Location = new System.Drawing.Point(136, 128);
this.textBox3.Name = "textBox3";
this.textBox3.Size = new System.Drawing.Size(152, 21);
this.textBox3.TabIndex = 5;
this.textBox3.Text = "";
//
// button1
//
this.button1.Location = new System.Drawing.Point(104, 168);
this.button1.Name = "button1";
this.button1.Size = new System.Drawing.Size(120, 23);
this.button1.TabIndex = 6;
this.button1.Text = "重启远程计算机";
this.button1.Click += new System.EventHandler(this.button1_Click);
//
// Form1
//
this.AutoScaleBaseSize = new System.Drawing.Size(6, 14);
this.ClientSize = new System.Drawing.Size(320, 213);
this.Controls.AddRange(new System.Windows.Forms.Control[] { this.label1});
this.Name = "Form1";
this.Text = "重启远程计算机";
this.ResumeLayout(false);
}
#endregion</P>


/// <summary>
/// 应用程序的主入口点。
/// </summary>
[STAThread]
static void Main()
{
Application.Run(new Form1());
}


private void button1_Click(object sender, System.EventArgs e)
{
//定义连接远程计算机的一些选项
ConnectionOptions options=new ConnectionOptions();
options.Username=textBox2.Text;
options.Password=textBox3.Text;
ManagementScope scope=new ManagementScope("<A>\\\\"+textBox1.Text+"\\root\\cimv2",options</A>);
try
{
//用给定管理者用户名和口令连接远程的计算机
scope.Connect();
ObjectQuery oq=new ObjectQuery("select * from win32_OperatingSystem");
ManagementObjectSearcher query1=new ManagementObjectSearcher(scope,oq);
ManagementObjectCollection queryCollection1=query1.Get();
foreach(ManagementObject mo in queryCollection1)
{
string[] ss={""};
mo.InvokeMethod("Reboot",ss);
}
}
catch(Exception er)
{
MessageBox.Show("连接" + textBox1.Text + "出错,出错信息为:" +er.Message);
}
}
}
}

下图是编译上面代码后得到的程序运行界面:
reboot

六.总结:

其实WMI控制可以实现很多以前让我们很头痛的操作。并且使用WMI编写的管理程序也比不用WMI来实现同样功能的程序在设计难度上大大减轻。WMI内容十分丰富,重新启动远程计算机只是其中的一个最为基本的操作。在使用WMI控制之前有一点必须记住,就是你必须知道你所要进行操作的远程计算机的超级管理者的帐号,这是使用WMI的一个前提。