面向对象的开发中,设计一个功能,设计一个系统,应该遵循的一些原则。一个理念,六个原则。
一个理念:万事万物都是对象。
开闭原则
“开一闭”原则讲的是:一个软件实体应当对扩展开放,对修改关闭。
这个规则说的是,在设计一个模块的时候,应当使这个模块可以在不被修改的前提下被扩展。
从另外一个角度讲,就是所谓的“对可变性封装原则”。“对可变性封装原则”意味着两点:
1.一种可变性不应当散落在代码的很多角落里,而应当被封装到一个对象里面。同一种可变性的不同表象意味着同一个继承等级结构中的具体子类。
2.一种可变性不应当与另一种可变性混合在一起。即类图的继承结构一般不应超过两层。
做到“开—闭”原则不是一件容易的事,但是也有很多规律可循,这些规律同样也是设计原则,它们是实现开—闭原则的工具。
里氏代换原则
里氏代换原则:就是子类代替父类,程序或者代码的行为不变。例如如果对每一个类型为T1的对象o1,都有类型为T2的对象o2,使得以T1定义的所有程序P在所有对象o1都换成o2时,程序P的行为没有变化,那么类型T2是T1的子类型。
即如果一个软件实体使用的是基类的话那么也一定适用于子类。但反过来的代换不成立。
如果有两个具体类A和B之间的关系违反了里氏代换原则,可以在以下两种重构方案中选择一种:
1创建一个新的抽象类C,作为两个具体类的超类,将A和B共同的行为移动到C中,从而解决A和B行为不完全一致的问题。
2从B到A的继承关系改写为委派关系。
依赖倒转原则
依赖倒转原则讲的是:要依赖于抽象,不要依赖于具体。即针对接口编程,不要针对实现编程。针对接口编程的意思是,应当使用接口和抽象类进行变量的类型声明、参量的类型声明,方法的返还类型声明,以及数据类型的转换等。不要针对实现编程的意思就是说,不应当使用具体类进行变量的类型声明、参量的类型声明,方法的返还类型声明,以及数据类型的转换等。
依赖倒转原则虽然强大,但却不易实现,因为依赖倒转的缘故,对象的创建很可能要使用对象工厂,以避免对具体类的直接引用,此原则的使用还会导致大量的类。维护这样的系统需要较好的面向对象的设计知识。
此外,依赖倒转原则假定所有的具体类都是变化的,这也不总是正确的。有一些具体类可能是相当稳定、不会发生变化的,消费这个具体类实例的客户端完全可以依赖于这个具体类。
接口隔离原则
接口隔离原则讲的是:使用多个专门的接口比使用单一的接口要好。从客户的角度来说:一个类对另外一个类的依赖性应当是建立在最小的接口上的。如果客户端只需要某一些方法的话,那么就应当向客户端提供这些需要的方法,而不要提供不需要的方法。提供接口意味着向客户端作出承诺,过多的承诺会给系统的维护造成不必要的负担。
合成、聚合复用原则
合成、聚合复用原则就是在一个新的对象里面使用一些已有的对象,使之成为新对象的一部份,新的对象通过向这些对象的委派达到复用已有功能的目的。这个原则有一个简短的描述:要尽量使用合成、聚合,尽量不要使用继承。
合成、聚合有如下好处:
新对象存取成分对象的唯一方法是通过成分对象的接口。
这种复用是黑箱复用,因为成分对象的内部细节是新对象所看不到的。
这种复用可以在运行时间内动态进行,新对象可以动态的引用与成分对象类型相同的对象。
合成、聚合可以应用到任何环境中去,而继承只能应用到一些有限环境中去。
导致错误的使用合成、聚合与继承的一个常见原因是错误的把“Has-a”关系当作“Is-a”关系。如果两个类是“Has-a”关系那么应使用合成、聚合,如果是“Is-a”关系那么可使用继承。
迪米特法则
迪米特法则说的是一个对象应该对其它对象有尽可能少的了解。即只与你直接的朋友通信,不要跟陌生人说话。如果需要和陌生人通话,而你的朋友与陌生人是朋友,那么可以将你对陌生人的调用由你的朋友转发,使得某人只知道朋友,不知道陌生人。换言之,某人会认为他所调用的是朋友的方法。
以下条件称为朋友的条件:
当前对象本身。
以参量的形式传入到当前对象方法中的对象。
当前对象的实例变量直接引用的对象。
当前对象的实例变量如果是一个聚集,那么聚集中的元素也都是朋友。
当前对象所创建的对象。
任何一个对象,如果满足上面的条件之一,就是当前对象的朋友,否则就是陌生人。
迪米特法则的主要用意是控制信息的过载,在将其运用到系统设计中应注意以下几点:
在类的划分上,应当创建有弱耦合的类。类之间的耦合越弱,就越有利于复用。
在类的结构设计上,每一个类都应当尽量降低成员的访问权限。一个类不应当public自己的属性,而应当提供取值和赋值的方法让外界间接访问自己的属性。
在类的设计上,只要有可能,一个类应当设计成不变类。
在对其它对象的引用上,一个类对其它对象的引用应该降到最低。
12. 谈谈Unicode编码,简要解释UCS、UTF、BMP、BOM等名词
这是一篇程序员写给程序员的趣味读物。所谓趣味是指可以比较轻松地了解一些原来不清楚的概念,增进知识,类似于打RPG游戏的升级。整理这篇文章的动机是两个问题:
问题一:
使用Windows记事本的“另存为”,可以在GBK、Unicode、Unicodebigendian和UTF-8这几种编码方式间相互转换。同样是txt文件,Windows是怎样识别编码方式的呢?
我很早前就发现Unicode、Unicodebigendian和UTF-8编码的txt文件的开头会多出几个字节,分别是FF、FE(Unicode),FE、FF(Unicodebigendian),EF、BB、BF(UTF-8)。但这些标记是基于什么标准呢?
问题二:
最近在网上看到一个ConvertUTF.c,实现了UTF-32、UTF-16和UTF-8这三种编码方式的相互转换。对于Unicode(UCS2)、GBK、UTF-8这些编码方式,我原来就了解。但这个程序让我有些糊涂,想不起来UTF-16和UCS2有什么关系。
查了查相关资料,总算将这些问题弄清楚了,顺带也了解了一些Unicode的细节。写成一篇文章,送给有过类似疑问的朋友。本文在写作时尽量做到通俗易懂,但要求读者知道什么是字节,什么是十六进制。
0、bigendian和littleendian
bigendian和littleendian是CPU处理多字节数的不同方式。例如“汉”字的Unicode编码是6C49。那么写到文件里时,究竟是将6C写在前面,还是将49写在前面?如果将6C写在前面,就是bigendian。还是将49写在前面,就是littleendian。
“endian”这个词出自《格列佛游记》。小人国的内战就源于吃鸡蛋时是究竟从大头(Big-Endian)敲开还是从小头(Little-Endian)敲开,由此曾发生过六次叛乱,其中一个皇帝送了命,另一个丢了王位。
我们一般将endian翻译成“字节序”,将bigendian和littleendian称作“大尾”和“小尾”。
1、字符编码、内码,顺带介绍汉字编码
字符必须编码后才能被计算机处理。计算机使用的缺省编码方式就是计算机的内码。早期的计算机使用7位的ASCII编码,为了处理汉字,程序员设计了用于简体中文的GB2312和用于繁体中文的big5。
GB2312(1980年)一共收录了7445个字符,包括6763个汉字和682个其它符号。汉字区的内码范围高字节从B0-F7,低字节从A1-FE,占用的码位是72*94=6768。其中有5个空位是D7FA-D7FE。
GB2312支持的汉字太少。1995年的汉字扩展规范GBK1.0收录了21886个符号,它分为汉字区和图形符号区。汉字区包括21003个字符。2000年的GB18030是取代GBK1.0的正式国家标准。该标准收录了27484个汉字,同时还收录了藏文、蒙文、维吾尔文等主要的少数民族文字。现在的PC平台必须支持GB18030,对嵌入式产品暂不作要求。所以手机、MP3一般只支持GB2312。
从ASCII、GB2312、GBK到GB18030,这些编码方法是向下兼容的,即同一个字符在这些方案中总是有相同的编码,后面的标准支持更多的字符。在这些编码中,英文和中文可以统一地处理。区分中文编码的方法是高字节的最高位不为0。按照程序员的称呼,GB2312、GBK到GB18030都属于双字节字符集(DBCS)。
有的中文Windows的缺省内码还是GBK,可以通过GB18030升级包升级到GB18030。不过GB18030相对GBK增加的字符,普通人是很难用到的,通常我们还是用GBK指代中文Windows内码。
这里还有一些细节:
GB2312的原文还是区位码,从区位码到内码,需要在高字节和低字节上分别加上A0。
在DBCS中,GB内码的存储格式始终是bigendian,即高位在前。
GB2312的两个字节的最高位都是1。但符合这个条件的码位只有128*128=16384个。所以GBK和GB18030的低字节最高位都可能不是1。不过这不影响DBCS字符流的解析:在读取DBCS字符流时,只要遇到高位为1的字节,就可以将下两个字节作为一个双字节编码,而不用管低字节的高位是什么。
2、Unicode、UCS和UTF
前面提到从ASCII、GB2312、GBK到GB18030的编码方法是向下兼容的。而Unicode只与ASCII兼容(更准确地说,是与ISO-8859-1兼容),与GB码不兼容。例如“汉”字的Unicode编码是6C49,而GB码是BABA。
Unicode也是一种字符编码方法,不过它是由国际组织设计,可以容纳全世界所有语言文字的编码方案。Unicode的学名是“UniversalMultiple-OctetCodedCharacterSet“,简称为UCS。UCS可以看作是“UnicodeCharacterSet“的缩写。
根据维基百科全书(http://zh.wikipedia.org/wiki/)的记载:历史上存在两个试图独立设计Unicode的组织,即国际标准化组织(ISO)和一个软件制造商的协会(unicode.org)。ISO开发了ISO10646项目,Unicode协会开发了Unicode项目。
在1991年前后,双方都认识到世界不需要两个不兼容的字符集。于是它们开始合并双方的工作成果,并为创立一个单一编码表而协同工作。从Unicode2.0开始,Unicode项目采用了与ISO10646-1相同的字库和字码。
目前两个项目仍都存在,并独立地公布各自的标准。Unicode协会现在的最新版本是2005年的Unicode4.1.0。ISO的最新标准是10646-3:2003。
UCS规定了怎么用多个字节表示各种文字。怎样传输这些编码,是由UTF(UCSTransformationFormat)规范规定的,常见的UTF规范包括UTF-8、UTF-7、UTF-16。
IETF的RFC2781和RFC3629以RFC的一贯风格,清晰、明快又不失严谨地描述了UTF-16和UTF-8的编码方法。我总是记不得IETF是InternetEngineeringTaskForce的缩写。但IETF负责维护的RFC是Internet上一切规范的基础。
3、UCS-2、UCS-4、BMP
UCS有两种格式:UCS-2和UCS-4。顾名思义,UCS-2就是用两个字节编码,UCS-4就是用4个字节(实际上只用了31位,最高位必须为0)编码。下面让我们做一些简单的数学游戏:
UCS-2有2^16=65536个码位,UCS-4有2^31=2147483648个码位。
UCS-4根据最高位为0的最高字节分成2^7=128个group。每个group再根据次高字节分为256个plane。每个plane根据第3个字节分为256行(rows),每行包含256个cells。当然同一行的cells只是最后一个字节不同,其余都相同。
group0的plane0被称作BasicMultilingualPlane,即BMP。或者说UCS-4中,高两个字节为0的码位被称作BMP。
将UCS-4的BMP去掉前面的两个零字节就得到了UCS-2。在UCS-2的两个字节前加上两个零字节,就得到了UCS-4的BMP。而目前的UCS-4规范中还没有任何字符被分配在BMP之外。
4、UTF编码
UTF-8就是以8位为单元对UCS进行编码。从UCS-2到UTF-8的编码方式如下:
UCS-2编码(16进制)UTF-8字节流(二进制)
0000-007F0xxxxxxx
0080-07FF110xxxxx10xxxxxx
0800-FFFF1110xxxx10xxxxxx10xxxxxx
例如“汉”字的Unicode编码是6C49。6C49在0800-FFFF之间,所以肯定要用3字节模板了:1110xxxx10xxxxxx10xxxxxx。将6C49写成二进制是:0110110001001001,用这个比特流依次代替模板中的x,得到:111001101011000110001001,即E6B189。
读者可以用记事本测试一下我们的编码是否正确。
UTF-16以16位为单元对UCS进行编码。对于小于0x10000的UCS码,UTF-16编码就等于UCS码对应的16位无符号整数。对于不小于0x10000的UCS码,定义了一个算法。不过由于实际使用的UCS2,或者UCS4的BMP必然小于0x10000,所以就目前而言,可以认为UTF-16和UCS-2基本相同。但UCS-2只是一个编码方案,UTF-16却要用于实际的传输,所以就不得不考虑字节序的问题。
5、UTF的字节序和BOM
UTF-8以字节为编码单元,没有字节序的问题。UTF-16以两个字节为编码单元,在解释一个UTF-16文本前,首先要弄清楚每个编码单元的字节序。例如收到一个“奎”的Unicode编码是594E,“乙”的Unicode编码是4E59。如果我们收到UTF-16字节流“594E”,那么这是“奎”还是“乙”?
Unicode规范中推荐的标记字节顺序的方法是BOM。BOM不是“BillOfMaterial”的BOM表,而是ByteOrderMark。BOM是一个有点小聪明的想法:
在UCS编码中有一个叫做“ZEROWIDTHNO-BREAKSPACE“的字符,它的编码是FEFF。而FFFE在UCS中是不存在的字符,所以不应该出现在实际传输中。UCS规范建议我们在传输字节流前,先传输字符“ZEROWIDTHNO-BREAKSPACE“。
这样如果接收者收到FEFF,就表明这个字节流是Big-Endian的;如果收到FFFE,就表明这个字节流是Little-Endian的。因此字符“ZEROWIDTHNO-BREAKSPACE“又被称作BOM。
UTF-8不需要BOM来表明字节顺序,但可以用BOM来表明编码方式。字符“ZEROWIDTHNO-BREAKSPACE“的UTF-8编码是EFBBBF(读者可以用我们前面介绍的编码方法验证一下)。所以如果接收者收到以EFBBBF开头的字节流,就知道这是UTF-8编码了。
Windows就是使用BOM来标记文本文件的编码方式的。
6、进一步的参考资料
本文主要参考的资料是“ShortoverviewofISO-IEC10646andUnicode“(http://www.nada.kth.se/i18n/ucs/unicode-iso10646-oview.html)。
我还找了两篇看上去不错的资料,不过因为我开始的疑问都找到了答案,所以就没有看:
“UnderstandingUnicodeAgeneralintroductiontotheUnicodeStandard“(http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&item_id=IWS-Chapter04a)
“CharactersetencodingbasicsUnderstandingcharactersetencodingsandlegacyencodings“(http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&item_id=IWS-Chapter03)
我写过UTF-8、UCS-2、GBK相互转换的软件包,包括使用WindowsAPI和不使用WindowsAPI的版本。以后有时间的话,我会整理一下放到我的个人主页上(http://fmddlmyy.home4u.china.com)。
我是想清楚所有问题后才开始写这篇文章的,原以为一会儿就能写好。没想到考虑措辞和查证细节花费了很长时间,竟然从下午1:30写到9:00。希望有读者能从中受益。
13. VisualFoxPro成长之路
VisualFoxPro(以下简称VFP)是个不断成长的小伙,承蒙VisualStudio(以下简称VS)的关照,VFP在开发者心目中一直是和VB、VC地位相同的工具语言,只不过它并不是通用开发工具,而只是专注于数据库应用的开发。
然而,吊足大家胃口的VS.NET终于出现在开发者面前时,竟不见了VFP的身影,着实引起开发社区的一阵骚动。微软不要VFP了?不是,恰恰相反,VFP忍受不了VS缓慢的升级速度,“单干”了。现在的VFP已经升级到版本9,并且完全支持.NET技术。
以下让我们来一起关注一下VFP的成长之路,看看每次升级VFP都为我们带来了那些诱人的新特性。当然,我们只整理了VFP6.0以后的版本,也就是VFP7、8、9。这些内容来自MSDNLibraryOnline,我们仅参考每个版本VFP产品文档中的“What’sNew”部分进行整理。
VFP7
WebServices支持VFP7支持注册和发布WebServices,而无需使用MicrosoftSOAPToolkit和VFP扩展来从底层完成这些任务;
服务器增强VFP7对于COM服务器作了很大程度的增强,可以与核心平台如COM+服务进行互操作;
XML支持为了适应以XML形式在Web上传送数据的潮流,VFP7提供了一些函数用于在XML数据和FoxPro游标(Cursor)或表格(Table)之间的转换。
多样的XBase特性VFP添加了很多新的或改进的XBase特性,并且这些特性都是用VFP语言编写的;
OLEDBProvider通过实现OLEDBProvider接口,开发者可以在任何支持OLEDB的程序和语言中调用VFP数据。
VFP8
数据特性增强VFP8对其数据特性进行了改进,并增加了很多新的数据特性,包括:远程数据连接、创建DataEnvironment类、自动增长域值、支持对照序列、与SQL语句Select…Union之间的隐式数据转换、使用SQLSelect命令插入行等;
其他增强VFP8对一些工具、示例数据库和解决方案案例都进行了改进。
VFP9
数据和XML增强这一时期的VFP对其数据特性进行了巨大的加强,改进项目之多真是令人眼花缭乱,详情可以参考这里,此处不再赘述;
实现SQL语言VFP9已经能够充分地支持SQL查询语言;
设计器的增强VFP9增强了这些设计器:报表和标签设计器、菜单设计器、表格设计器、查询和视图设计器、数据环境设计器以及类和窗体设计器等;
其他方面的增强和VFP8一样,VFP9在其他微小的细节上进行了不少的改进,使得开发者的体验更加舒适。
当然,上面提到的只是每次版本更新时所带来的新特性的冰山一角,另外没有提到的是IDE(在VFP里是InteractiveDevelopmentEnvironment)和语言的增强,这是每次版本更新都会有所改进而且是大幅改进的,相信个中感受只有铁杆Foxer能够体会得到了。尽管这里所列的改进看上去甚少,但在MSDNLibraryOnline上却占去了巨大的篇幅,有兴趣的朋友不妨到MSDN上浏览一下,就能深刻体会到为什么VFP不会死,而且还能茁壮成长了。
当然,上面提到的只是每次版本更新时所带来的新特性的冰山一角,另外没有提到的是IDE(在VFP里是InteractiveDevelopmentEnvironment)和语言的增强,这是每次版本更新都会有所改进而且是大幅改进的,相信个中感受只有铁杆Foxer能够体会得到了。尽管这里所列的改进看上去甚少,但在MSDNLibraryOnline上却占去了巨大的篇幅,有兴趣的朋友不妨到MSDN上浏览一下,就能深刻体会到为什么VFP不会死,而且还能茁壮成长了。
14. googlepagerankchecksum算法
原来网上早就有了checksum的相关破解,下面试checksum的汇编代码和vb版的破解。
目前我所用的就是vb版的checksum代码。
checksum的汇编代码:
GOOGLECHECKprocnear
var_8=dwordptr-8
var_4=dwordptr-4
url_offset=dwordptr8
url_length=dwordptr0Ch
magic_dword=dwordptr10h
pushebp
movebp,esp
pushecx
pushecx
moveax,[ebp+url_length]
cmpeax,0Ch
pushebx
pushesi
movesi,[ebp+magic_dword];=0xE6359A60
pushedi
movedi,9E3779B9h;derivedfromthegoldennumber,hiTEA;)
movebx,edi
mov[ebp+var_4],eax
jbjump_1
push0Ch
popecx
xoredx,edx
divecx
movecx,[ebp+url_offset]
mov[ebp+var_8],eax
loop_1:
movzxeax,byteptr[ecx+7]
movzxedx,byteptr[ecx+6]
shleax,8
addeax,edx
movzxedx,byteptr[ecx+5]
shleax,8
addeax,edx
movzxedx,byteptr[ecx+4]
addedx,edi
shleax,8
leaedi,[edx+eax]
movzxeax,byteptr[ecx+0Bh]
movzxedx,byteptr[ecx+0Ah]
shleax,8
addeax,edx
movzxedx,byteptr[ecx+9]
shleax,8
addeax,edx
movzxedx,byteptr[ecx+8]
addedx,esi
shleax,8
leaesi,[edx+eax]
movzxedx,byteptr[ecx+3]
movzxeax,byteptr[ecx+2]
shledx,8
addedx,eax
movzxeax,byteptr[ecx+1]
shledx,8
addedx,eax
movzxeax,byteptr[ecx]
shledx,8
addedx,eax
subedx,edi
subedx,esi
moveax,esi
shreax,0Dh
addedx,ebx
xoredx,eax
subedi,edx
subedi,esi
moveax,edx
shleax,8
xoredi,eax
subesi,edi
subesi,edx
moveax,edi
shreax,0Dh
xoresi,eax
subedx,edi
subedx,esi
moveax,esi
shreax,0Ch
xoredx,eax
subedi,edx
subedi,esi
moveax,edx
shleax,10h
xoredi,eax
subesi,edi
sub[ebp+var_4],0Ch
subesi,edx
moveax,edi
shreax,5
xoresi,eax
subedx,edi
moveax,esi
shreax,3
subedx,esi
xoredx,eax
movebx,edx
subedi,ebx
subedi,esi
moveax,ebx
shleax,0Ah
xoredi,eax
subesi,edi
moveax,edi
subesi,ebx
shreax,0Fh
xoresi,eax
addecx,0Ch
dec[ebp+var_8]
jnzloop_1
jmpshortjump_2
jump_1:
movecx,[ebp+url_offset]
jump_2:
addesi,[ebp+url_length]
moveax,[ebp+var_4]
deceax
cmpeax,0Ah;switch11cases
jadefaultswitch;default
jmpds:off_100307EA[eax*4];switchjump
switch_10:
movzxeax,byteptr[ecx+0Ah];case0xA
shleax,18h
addesi,eax
switch_9:
movzxeax,byteptr[ecx+9];case0x9
shleax,10h
addesi,eax
switch_8:
movzxeax,byteptr[ecx+8];case0x8
shleax,8
addesi,eax
switch_7:
movzxeax,byteptr[ecx+7];case0x7
movzxedx,byteptr[ecx+6]
shleax,8
addeax,edx
movzxedx,byteptr[ecx+5]
shleax,8
addeax,edx
movzxedx,byteptr[ecx+4]
shleax,8
addedx,edi
leaedi,[edx+eax]
jmpshortswitch_3;case0x3
switch_6:
movzxeax,byteptr[ecx+6];case0x6
shleax,10h
addedi,eax
switch_5:
movzxeax,byteptr[ecx+5];case0x5
shleax,8
addedi,eax
switch_4:
movzxeax,byteptr[ecx+4];case0x4
addedi,eax
switch_3:
movzxeax,byteptr[ecx+3];case0x3
movzxedx,byteptr[ecx+2]
shleax,8
addeax,edx
movzxedx,byteptr[ecx+1]
movzxecx,byteptr[ecx]
shleax,8
addeax,edx
shleax,8
addecx,ebx
leaebx,[ecx+eax]
jmpshortdefaultswitch;default
switch_2:
movzxeax,byteptr[ecx+2];case0x2
shleax,10h
addebx,eax
switch_1:
movzxeax,byteptr[ecx+1];case0x1
shleax,8
addebx,eax
switch_0:
movzxeax,byteptr[ecx];case0x0
addebx,eax
defaultswitch:
subebx,edi;default
subebx,esi
moveax,esi
shreax,0Dh
xorebx,eax
subedi,ebx
subedi,esi
moveax,ebx
shleax,8
xoredi,eax
subesi,edi
subesi,ebx
moveax,edi
shreax,0Dh
xoresi,eax
subebx,edi
subebx,esi
moveax,esi
shreax,0Ch
xorebx,eax
subedi,ebx
subedi,esi
moveax,ebx
shleax,10h
xoredi,eax
subesi,edi
moveax,edi
subesi,ebx
shreax,5
xoresi,eax
subebx,edi
moveax,esi
movecx,eax
subebx,eax
shrecx,3
xorebx,ecx
subedi,ebx
subedi,eax
movecx,ebx
shlecx,0Ah
xoredi,ecx
subeax,edi
subeax,ebx
shredi,0Fh
xoreax,edi
popedi
popesi
popebx
leave
retn
GOOGLECHECKendp
;Switchtable
off_100307EA
ddoffsetswitch_0
ddoffsetswitch_1
ddoffsetswitch_2
ddoffsetswitch_3
ddoffsetswitch_4
ddoffsetswitch_5
ddoffsetswitch_6
ddoffsetswitch_7
ddoffsetswitch_8
ddoffsetswitch_9
ddoffsetswitch_10
checksum的vb代码:
’=========================================================
’functionsforthechecksum:
’
’Functionsl(ByValx,ByValn)
’Functionsr(ByValx,ByValn)
’FunctionzeroFill(ByVala,ByValb)
’PrivateFunctionuadd(ByValL1,ByValL2)
’PrivateFunctionusub(ByValL1,ByValL2)
’Functionmix(ByValia,ByValib,ByValic)
’Functiongc(ByVals,ByVali)
’functionGoogleCH(ByValsURL)
’FunctionCalculateChecksum(sURL)
’=========================================================
Functionsl(ByValx,ByValn)
Ifn=0Then
sl=x
Else
Dimk
k=CLng(2^(32-n-1))
Dimd
d=xAnd(k-1)
Dimc
c=d*CLng(2^n)
IfxAndkThen
c=cOr&H80000000
EndIf
sl=c
EndIf
EndFunction
Functionsr(ByValx,ByValn)
Ifn=0Then
sr=x
Else
Dimy
y=xAnd&H7FFFFFFF
Dimz
Ifn=32-1Then
z=0
Else
z=y\CLng(2^n)
EndIf
Ify〈〉xThen
z=zOrCLng(2^(32-n-1))
EndIf
sr=z
EndIf
EndFunction
FunctionzeroFill(ByVala,ByValb)
Dimx
if(&H80000000ANDa)then
x=sr(a,1)
x=xAND(NOT&H80000000)
x=xOR&H40000000
x=sr(x,b-1)
else
x=sr(a,b)
endif
zeroFill=x
EndFunction
PrivateFunctionuadd(ByValL1,ByValL2)
DimL11,L12,L21,L22,L31,L32
L11=L1And&HFFFFFF
L12=(L1And&H7F000000)\&H1000000
IfL1〈0ThenL12=L12Or&H80
L21=L2And&HFFFFFF
L22=(L2And&H7F000000)\&H1000000
IfL2〈0ThenL22=L22Or&H80
L32=L12+L22
L31=L11+L21
If(L31And&H1000000)ThenL32=L32+1
uadd=(L31And&HFFFFFF)+(L32And&H7F)*&H1000000
IfL32And&H80Thenuadd=uaddOr&H80000000
EndFunction
PrivateFunctionusub(ByValL1,ByValL2)
DimL11,L12,L21,L22,L31,L32
L11=L1And&HFFFFFF
L12=(L1And&H7F000000)\&H1000000
IfL1〈0ThenL12=L12Or&H80
L21=L2And&HFFFFFF
L22=(L2And&H7F000000)\&H1000000
IfL2〈0ThenL22=L22Or&H80
L32=L12-L22
L31=L11-L21
IfL31〈0Then
L32=L32-1
L31=L31+&H1000000
EndIf
usub=L31+(L32And&H7F)*&H1000000
IfL32And&H80Thenusub=usubOr&H80000000
EndFunction
Functionmix(ByValia,ByValib,ByValic)
Dima,b,c
a=ia
b=ib
c=ic
a=usub(a,b)
a=usub(a,c)
a=aXORzeroFill(c,13)
b=usub(b,c)
b=usub(b,a)
b=bXORsl(a,8)
c=usub(c,a)
c=usub(c,b)
c=cXORzeroFill(b,13)
a=usub(a,b)
a=usub(a,c)
a=aXORzeroFill(c,12)
b=usub(b,c)
b=usub(b,a)
b=bXORsl(a,16)
c=usub(c,a)
c=usub(c,b)
c=cXORzeroFill(b,5)
a=usub(a,b)
a=usub(a,c)
a=aXORzeroFill(c,3)
b=usub(b,c)
b=usub(b,a)
b=bXORsl(a,10)
c=usub(c,a)
c=usub(c,b)
c=cXORzeroFill(b,15)
Dimret(3)
ret(0)=a
ret(1)=b
ret(2)=c
mix=ret
EndFunction
Functiongc(ByVals,ByVali)
gc=Asc(Mid(s,i+1,1))
EndFunction
functionGoogleCH(ByValsURL)
DimiLength,a,b,c,k,iLen,m
iLength=Len(sURL)
a=&H9E3779B9
b=&H9E3779B9
c=GOOGLE_MAGIC
k=0
iLen=iLength
dowhileiLen〉=12
a=uadd(a,(uadd(gc(sURL,k+0),uadd(sl(gc(sURL,k+1),8),uadd(sl(gc(sURL,k+2),16),sl(gc(sURL,k+3),24))))))
b=uadd(b,(uadd(gc(sURL,k+4),uadd(sl(gc(sURL,k+5),8),uadd(sl(gc(sURL,k+6),16),sl(gc(sURL,k+7),24))))))
c=uadd(c,(uadd(gc(sURL,k+8),uadd(sl(gc(sURL,k+9),8),uadd(sl(gc(sURL,k+10),16),sl(gc(sURL,k+11),24))))))
m=mix(a,b,c)
a=m(0)
b=m(1)
c=m(2)
k=k+12
iLen=iLen-12
loop
c=uadd(c,iLength)
selectcaseiLen’allthecasestatementsfallthrough
case11
c=uadd(c,sl(gc(sURL,k+10),24))
c=uadd(c,sl(gc(sURL,k+9),16))
c=uadd(c,sl(gc(sURL,k+8),8))
b=uadd(b,sl(gc(sURL,k+7),24))
b=uadd(b,sl(gc(sURL,k+6),16))
b=uadd(b,sl(gc(sURL,k+5),8))
b=uadd(b,gc(sURL,k+4))
a=uadd(a,sl(gc(sURL,k+3),24))
a=uadd(a,sl(gc(sURL,k+2),16))
a=uadd(a,sl(gc(sURL,k+1),8))
a=uadd(a,gc(sURL,k+0))
case10
c=uadd(c,sl(gc(sURL,k+9),16))
c=uadd(c,sl(gc(sURL,k+8),8))
b=uadd(b,sl(gc(sURL,k+7),24))
b=uadd(b,sl(gc(sURL,k+6),16))
b=uadd(b,sl(gc(sURL,k+5),8))
b=uadd(b,gc(sURL,k+4))
a=uadd(a,sl(gc(sURL,k+3),24))
a=uadd(a,sl(gc(sURL,k+2),16))
a=uadd(a,sl(gc(sURL,k+1),8))
a=uadd(a,gc(sURL,k+0))
case9
c=uadd(c,sl(gc(sURL,k+8),8))
b=uadd(b,sl(gc(sURL,k+7),24))
b=uadd(b,sl(gc(sURL,k+6),16))
b=uadd(b,sl(gc(sURL,k+5),8))
b=uadd(b,gc(sURL,k+4))
a=uadd(a,sl(gc(sURL,k+3),24))
a=uadd(a,sl(gc(sURL,k+2),16))
a=uadd(a,sl(gc(sURL,k+1),8))
a=uadd(a,gc(sURL,k+0))
case8
b=uadd(b,sl(gc(sURL,k+7),24))
b=uadd(b,sl(gc(sURL,k+6),16))
b=uadd(b,sl(gc(sURL,k+5),8))
b=uadd(b,gc(sURL,k+4))
a=uadd(a,sl(gc(sURL,k+3),24))
a=uadd(a,sl(gc(sURL,k+2),16))
a=uadd(a,sl(gc(sURL,k+1),8))
a=uadd(a,gc(sURL,k+0))
case7
b=uadd(b,sl(gc(sURL,k+6),16))
b=uadd(b,sl(gc(sURL,k+5),8))
b=uadd(b,gc(sURL,k+4))
a=uadd(a,sl(gc(sURL,k+3),24))
a=uadd(a,sl(gc(sURL,k+2),16))
a=uadd(a,sl(gc(sURL,k+1),8))
a=uadd(a,gc(sURL,k+0))
case6
b=uadd(b,sl(gc(sURL,k+5),8))
b=uadd(b,gc(sURL,k+4))
a=uadd(a,sl(gc(sURL,k+3),24))
a=uadd(a,sl(gc(sURL,k+2),16))
a=uadd(a,sl(gc(sURL,k+1),8))
a=uadd(a,gc(sURL,k+0))
case5
b=uadd(b,gc(sURL,k+4))
a=uadd(a,sl(gc(sURL,k+3),24))
a=uadd(a,sl(gc(sURL,k+2),16))
a=uadd(a,sl(gc(sURL,k+1),8))
a=uadd(a,gc(sURL,k+0))
case4
a=uadd(a,sl(gc(sURL,k+3),24))
a=uadd(a,sl(gc(sURL,k+2),16))
a=uadd(a,sl(gc(sURL,k+1),8))
a=uadd(a,gc(sURL,k+0))
case3
a=uadd(a,sl(gc(sURL,k+2),16))
a=uadd(a,sl(gc(sURL,k+1),8))
a=uadd(a,gc(sURL,k+0))
case2
a=uadd(a,sl(gc(sURL,k+1),8))
a=uadd(a,gc(sURL,k+0))
case1
a=uadd(a,gc(sURL,k+0))
EndSelect
m=mix(a,b,c)
GoogleCH=m(2)
EndFunction
FunctionCalculateChecksum(sURL)
CalculateChecksum=“6“&CStr(GoogleCH(“info:“&sURL)AND&H7FFFFFFF)
EndFunction
15. WindowsSocketAPI使用经验
本文是我在进行MS-Windows、HP-Unix网络编程的实践过程中总结出来的一些经验,仅供大家参考。本文所谈到的Socket函数如果没有特别说明,都是指的WindowsSocketAPI。
一、WSAStartup函数
intWSAStartup(
WORDwVersionRequested,
LPWSADATAlpWSAData
);
使用Socket的程序在使用Socket之前必须调用WSAStartup函数。该函数的第一个参数指明程序请求使用的Socket版本,其中高位字节指明副版本、低位字节指明主版本;操作系统利用第二个参数返回请求的Socket的版本信息。当一个应用程序调用WSAStartup函数时,操作系统根据请求的Socket版本来搜索相应的Socket库,然后绑定找到的Socket库到该应用程序中。以后应用程序就可以调用所请求的Socket库中的其它Socket函数了。该函数执行成功后返回0。
例:假如一个程序要使用2.1版本的Socket,那么程序代码如下
wVersionRequested=MAKEWORD(2,1);
err=WSAStartup(wVersionRequested,&wsaData);
二、WSACleanup函数
intWSACleanup(void);
应用程序在完成对请求的Socket库的使用后,要调用WSACleanup函数来解除与Socket库的绑定并且释放Socket库所占用的系统资源。
三、socket函数
SOCKETsocket(
intaf,
inttype,
intprotocol
);
应用程序调用socket函数来创建一个能够进行网络通信的套接字。第一个参数指定应用程序使用的通信协议的协议族,对于TCP/IP协议族,该参数置PF_INET;第二个参数指定要创建的套接字类型,流套接字类型为SOCK_STREAM、数据报套接字类型为SOCK_DGRAM;第三个参数指定应用程序所使用的通信协议。该函数如果调用成功就返回新创建的套接字的描述符,如果失败就返回INVALID_SOCKET。套接字描述符是一个整数类型的值。每个进程的进程空间里都有一个套接字描述符表,该表中存放着套接字描述符和套接字数据结构的对应关系。该表中有一个字段存放新创建的套接字的描述符,另一个字段存放套接字数据结构的地址,因此根据套接字描述符就可以找到其对应的套接字数据结构。每个进程在自己的进程空间里都有一个套接字描述符表但是套接字数据结构都是在操作系统的内核缓冲里。下面是一个创建流套接字的例子:
structprotoent*ppe;
ppe=getprotobyname(“tcp“);
SOCKETListenSocket=socket(PF_INET,SOCK_STREAM,ppe-〉p_proto);
四、closesocket函数
intclosesocket(
SOCKETs
);
closesocket函数用来关闭一个描述符为s套接字。由于每个进程中都有一个套接字描述符表,表中的每个套接字描述符都对应了一个位于操作系统缓冲区中的套接字数据结构,因此有可能有几个套接字描述符指向同一个套接字数据结构。套接字数据结构中专门有一个字段存放该结构的被引用次数,即有多少个套接字描述符指向该结构。当调用closesocket函数时,操作系统先检查套接字数据结构中的该字段的值,如果为1,就表明只有一个套接字描述符指向它,因此操作系统就先把s在套接字描述符表中对应的那条表项清除,并且释放s对应的套接字数据结构;如果该字段大于1,那么操作系统仅仅清除s在套接字描述符表中的对应表项,并且把s对应的套接字数据结构的引用次数减1。
closesocket函数如果执行成功就返回0,否则返回SOCKET_ERROR。
五、send函数
intsend(
SOCKETs,
constcharFAR*buf,
intlen,
intflags
);
不论是客户还是服务器应用程序都用send函数来向TCP连接的另一端发送数据。客户程序一般用send函数向服务器发送请求,而服务器则通常用send函数来向客户程序发送应答。该函数的第一个参数指定发送端套接字描述符;第二个参数指明一个存放应用程序要发送数据的缓冲区;第三个参数指明实际要发送的数据的字节数;第四个参数一般置0。这里只描述同步Socket的send函数的执行流程。当调用该函数时,send先比较待发送数据的长度len和套接字s的发送缓冲区的长度,如果len大于s的发送缓冲区的长度,该函数返回SOCKET_ERROR;如果len小于或者等于s的发送缓冲区的长度,那么send先检查协议是否正在发送s的发送缓冲中的数据,如果是就等待协议把数据发送完,如果协议还没有开始发送s的发送缓冲中的数据或者s的发送缓冲中没有数据,那么send就比较s的发送缓冲区的剩余空间和len,如果len大于剩余空间大小send就一直等待协议把s的发送缓冲中的数据发送完,如果len小于剩余空间大小send就仅仅把buf中的数据copy到剩余空间里(注意并不是send把s的发送缓冲中的数据传到连接的另一端的,而是协议传的,send仅仅是把buf中的数据copy到s的发送缓冲区的剩余空间里)。如果send函数copy数据成功,就返回实际copy的字节数,如果send在copy数据时出现错误,那么send就返回SOCKET_ERROR;如果send在等待协议传送数据时网络断开的话,那么send函数也返回SOCKET_ERROR。要注意send函数把buf中的数据成功copy到s的发送缓冲的剩余空间里后它就返回了,但是此时这些数据并不一定马上被传到连接的另一端。如果协议在后续的传送过程中出现网络错误的话,那么下一个Socket函数就会返回SOCKET_ERROR。(每一个除send外的Socket函数在执行的最开始总要先等待套接字的发送缓冲中的数据被协议传送完毕才能继续,如果在等待时出现网络错误,那么该Socket函数就返回SOCKET_ERROR)
注意:在Unix系统下,如果send在等待协议传送数据时网络断开的话,调用send的进程会接收到一个SIGPIPE信号,进程对该信号的默认处理是进程终止。
六、recv函数
intrecv(
SOCKETs,
charFAR*buf,
intlen,
intflags
);
不论是客户还是服务器应用程序都用recv函数从TCP连接的另一端接收数据。该函数的第一个参数指定接收端套接字描述符;第二个参数指明一个缓冲区,该缓冲区用来存放recv函数接收到的数据;第三个参数指明buf的长度;第四个参数一般置0。这里只描述同步Socket的recv函数的执行流程。当应用程序调用recv函数时,recv先等待s的发送缓冲中的数据被协议传送完毕,如果协议在传送s的发送缓冲中的数据时出现网络错误,那么recv函数返回SOCKET_ERROR,如果s的发送缓冲中没有数据或者数据被协议成功发送完毕后,recv先检查套接字s的接收缓冲区,如果s接收缓冲区中没有数据或者协议正在接收数据,那么recv就一直等待,只到协议把数据接收完毕。当协议把数据接收完毕,recv函数就把s的接收缓冲中的数据copy到buf中(注意协议接收到的数据可能大于buf的长度,所以在这种情况下要调用几次recv函数才能把s的接收缓冲中的数据copy完。recv函数仅仅是copy数据,真正的接收数据是协议来完成的),recv函数返回其实际copy的字节数。如果recv在copy时出错,那么它返回SOCKET_ERROR;如果recv函数在等待协议接收数据时网络中断了,那么它返回0。
注意:在Unix系统下,如果recv函数在等待协议接收数据时网络断开了,那么调用recv的进程会接收到一个SIGPIPE信号,进程对该信号的默认处理是进程终止。
七、bind函数
intbind(
SOCKETs,
conststructsockaddrFAR*name,
intnamelen
);
当创建了一个Socket以后,套接字数据结构中有一个默认的IP地址和默认的端口号。一个服务程序必须调用bind函数来给其绑定一个IP地址和一个特定的端口号。客户程序一般不必调用bind函数来为其Socket绑定IP地址和断口号。该函数的第一个参数指定待绑定的Socket描述符;第二个参数指定一个sockaddr结构,该结构是这样定义的:
structsockaddr{
u_shortsa_family;
charsa_data[14];
};
sa_family指定地址族,对于TCP/IP协议族的套接字,给其置AF_INET。当对TCP/IP协议族的套接字进行绑定时,我们通常使用另一个地址结构:
structsockaddr_in{
shortsin_family;
u_shortsin_port;
structin_addrsin_addr;
charsin_zero[8];
};
其中sin_family置AF_INET;sin_port指明端口号;sin_addr结构体中只有一个唯一的字段s_addr,表示IP地址,该字段是一个整数,一般用函数inet_addr()把字符串形式的IP地址转换成unsignedlong型的整数值后再置给s_addr。有的服务器是多宿主机,至少有两个网卡,那么运行在这样的服务器上的服务程序在为其Socket绑定IP地址时可以把htonl(INADDR_ANY)置给s_addr,这样做的好处是不论哪个网段上的客户程序都能与该服务程序通信;如果只给运行在多宿主机上的服务程序的Socket绑定一个固定的IP地址,那么就只有与该IP地址处于同一个网段上的客户程序才能与该服务程序通信。我们用0来填充sin_zero数组,目的是让sockaddr_in结构的大小与sockaddr结构的大小一致。下面是一个bind函数调用的例子:
structsockaddr_insaddr;
saddr.sin_family=AF_INET;
saddr.sin_port=htons(8888);
saddr.sin_addr.s_addr=htonl(INADDR_ANY);
bind(ListenSocket,(structsockaddr*)&saddr,sizeof(saddr));
八、listen函数
intlisten(SOCKETs,intbacklog);
服务程序可以调用listen函数使其流套接字s处于监听状态。处于监听状态的流套接字s将维护一个客户连接请求队列,该队列最多容纳backlog个客户连接请求。假如该函数执行成功,则返回0;如果执行失败,则返回SOCKET_ERROR。
九、accept函数
SOCKETaccept(
SOCKETs,
structsockaddrFAR*addr,
intFAR*addrlen
);
服务程序调用accept函数从处于监听状态的流套接字s的客户连接请求队列中取出排在最前的一个客户请求,并且创建一个新的套接字来与客户套接字创建连接通道,如果连接成功,就返回新创建的套接字的描述符,以后与客户套接字交换数据的是新创建的套接字;如果失败就返回INVALID_SOCKET。该函数的第一个参数指定处于监听状态的流套接字;操作系统利用第二个参数来返回新创建的套接字的地址结构;操作系统利用第三个参数来返回新创建的套接字的地址结构的长度。下面是一个调用accept的例子:
structsockaddr_inServerSocketAddr;
intaddrlen;
addrlen=sizeof(ServerSocketAddr);
ServerSocket=accept(ListenSocket,(structsockaddr*)&ServerSocketAddr,&addrlen);
十、connect函数
intconnect(
SOCKETs,
conststructsockaddrFAR*name,
intnamelen
);
客户程序调用connect函数来使客户Sockets与监听于name所指定的计算机的特定端口上的服务Socket进行连接。如果连接成功,connect返回0;如果失败则返回SOCKET_ERROR。下面是一个例子:
structsockaddr_indaddr;
memset((void*)&daddr,0,sizeof(daddr));
daddr.sin_family=AF_INET;
daddr.sin_port=htons(8888);
daddr.sin_addr.s_addr=inet_addr(“133.197.22.4“);
connect(ClientSocket,(structsockaddr*)&daddr,sizeof(daddr));
16. 代理服务器工作原理的研究(1)代理服务原理
代理服务器有很多种,大体来说有http,ftp,socks代理三种,其中又分透明代理和不透明代理。其中透明代理一般是网关,是硬件。所以这里讨论不透明代理。
当机器通过代理服务器上网时。通讯是分两次的,先是机器和代理服务器通讯,再是代理服务器和目的地址通讯。
机器和代理服务器通讯时,目的IP是代理服务器的IP。代理服务器和目的地址通讯时,源IP是代理服务器的IP,当外部的数据也是一样的,在内网中,出现的IP数据,全是内网和代理服务器的IP。因此,从IP包头是看不出任何与外面通讯的信息的。只有从数据中才能看到。
例如,用http代理上网。过程是
机器和代理服务器建立TCP连接。
机器发出GET命令。这时GET命令中包含URL或IP地址,明文。
代理服务器将其中的URL转换为IP地址,可能会有DNS。将源数据包中的数据拷贝下来。去掉URL,重新组包,再发出去。
我们需要解析第一个GET包。
现在来看几种代理方式。
http(get)
http(connect)
ftp(useruser@host:port)
ftp(useruser@hostport)
ftp(openhost)
ftp(sitehost)
ftp(siteuser@host)
socks5
socks4
这些代理方式都有一个特点。就是在连接时,都会先和代理服务器连接,发出请求,一般为commandurl,command就是get,connect,user等。http和ftp都一样,可以通过关键字来识别。而且url是明文。Socks有些特别。它不是明文的,而是十六进制数据。要获得IP地址,还要经过转换。
(2)目前的代理服务技术
代理服务技术是在一台PC机上安装一套代理软件,主要用于用户对Internet资源的访问。
ICS即Internet连接共享(InternetConnectionSharing)的英文简称,是Windows系统针对家庭网络或小型的Intranet网络提供的一种Internet连接共享服务。它实际上相当于一种网络地址转换器,所谓网络地址转换器就是当数据包向前传递的过程中,可以转换数据包中的IP地址和TCP/UCP端口等地址信息。有了网络地址转换器,家庭网络或小型的办公网络中的电脑就可以使用私有地址,并且通过网络地址转换器将私有地址转换成ISP分配的单一的公用IP地址从而实现对Internet的连接。ICS方式也称之为Internet转换连接。
软件:Wingate,、Winproxy
NAT即网络地址转换(NetworkAddressTranslator),从广义上讲,ICS也是使用了一种NAT技术,不过我们这里讨论的NAT是指将运行Windows2000Server的计算机作为IP路由器,通过它在局域网和Internet主机间转发数据包从而实现Internet的共享。NAT方式也称之为Internet的路由连接。网络地址转换NAT通过将专用内部地址转换为公共外部地址,对外隐藏了内部管理的IP地址。这样,通过在内部使用非注册的IP地址,并将它们转换为一小部分外部注册的IP地址,从而减少了IP地址注册的费用。同时,这也隐藏了内部网络结构,从而降低了内部网络受到攻击的风险。
软件:WinRoute、Sygate
(3)代理数据变代
非透明代理:
上网主机向代理提交TCP连接请求:源ip(上网主机)目标ip(代理服务器)
00000000:4500019E64CE4000800617C0C0A8FD91E...d.@.........
00000010:C0A8FDE812470438FB328B567E5557EA.....G.8.2.V~UW.
00000020:501844701BEC00004745542068747470P.Dp....GEThttp
00000030:3A2F2F7777772E636374762E636F6D2E://www.cctv.com.
00000040:636E2F20485454502F312E300D0A4163cn/HTTP/1.0..Ac
00000050:636570743A20696D6167652F6769662Ccept:image/gif,
00000060:20696D6167652F782D786269746D6170image/x-xbitmap
00000070:2C20696D6167652F6A7065672C20696D,image/jpeg,im
00000080:6167652F706A7065672C206170706C69age/pjpeg,appli
00000090:636174696F6E2F766E642E6D732D6578cation/vnd.ms-ex
000000a0:63656C2C206170706C69636174696F6Ecel,application
000000b0:2F766E642E6D732D706F776572706F69/vnd.ms-powerpoi
000000c0:6E742C206170706C69636174696F6E2Fnt,application/
000000d0:6D73776F72642C206170706C69636174msword,applicat
000000e0:696F6E2F782D73686F636B776176652Dion/x-shockwave-
000000f0:666C6173682C202A2F2A0D0A41636365flash,*/*..Acce
00000100:70742D4C616E67756167653A207A682Dpt-Language:zh-
00000110:636E0D0A557365722D4167656E743A20cn..User-Agent:
00000120:4D6F7A696C6C612F342E302028636F6DMozilla/4.0(com
00000130:70617469626C653B204D53494520362Epatible;MSIE6.
00000140:303B2057696E646F7773204E5420352E0;WindowsNT5.
00000150:323B202E4E455420434C5220312E312E2;.NETCLR1.1.
00000160:34333232290D0A486F73743A207777774322)..Host:www
00000170:2E636374762E636F6D2E636E0D0A5072.cctv.com.cn..Pr
00000180:6F78792D436F6E6E656374696F6E3A20oxy-Connection:
00000190:4B6565702D416C6976650D0A0D0AFD00Keep-Alive......
代理服务器转换后的数据:源IP(代理服务器)目IP(网站IP)
00000000:450001885D4E400080061955C0A8FDE8E...][email protected]....
00000010:CA6CF9CE06AC00507E57F3C7E1412F21.l.....P~W...A/!
00000020:50184470F9DF0000474554202F204854P.Dp....GET/HT
00000030:54502F312E300D0A4163636570743A20TP/1.0..Accept:
00000040:696D6167652F6769662C20696D616765image/gif,image
00000050:2F782D786269746D61702C20696D6167/x-xbitmap,imag
00000060:652F6A7065672C20696D6167652F706Ae/jpeg,image/pj
00000070:7065672C206170706C69636174696F6Epeg,application
00000080:2F766E642E6D732D657863656C2C2061/vnd.ms-excel,a
00000090:70706C69636174696F6E2F766E642E6Dpplication/vnd.m
000000a0:732D706F776572706F696E742C206170s-powerpoint,ap
000000b0:706C69636174696F6E2F6D73776F7264plication/msword
000000c0:2C206170706C69636174696F6E2F782D,application/x-
000000d0:73686F636B776176652D666C6173682Cshockwave-flash,
000000e0:202A2F2A0D0A4163636570742D4C616E*/*..Accept-Lan
000000f0:67756167653A207A682D636E0D0A5573guage:zh-cn..Us
00000100:65722D4167656E743A204D6F7A696C6Cer-Agent:Mozill
00000110:612F342E302028636F6D70617469626Ca/4.0(compatibl
00000120:653B204D53494520362E303B2057696Ee;MSIE6.0;Win
00000130:646F7773204E5420352E323B202E4E45dowsNT5.2;.NE
00000140:5420434C5220312E312E34333232290DTCLR1.1.4322).
00000150:0A486F73743A207777772E636374762E.Host:www.cctv.
00000160:636F6D2E636E0D0A50726F78792D436Fcom.cn..Proxy-Co
00000170:6E6E656374696F6E3A204B6565702D41nnection:Keep-A
00000180:6C6976650D0A0D0AFD00000000000000live............
通过非透明代理上网,上网主机向代理提交连接请求。在请求包里,包含了真正目标的URL,通过代理转交。
ftp代理
代理服务器是明文
..]ZY...][email protected][email protected]@...yr.........i.I6u[[email protected]...
http代理
http代理是明文
..]ZY...][email protected][email protected].*[email protected]://mp3.yzu.edu.cn/HTTP/1.0..Accept:*/*..Accept-Language:zh-cn..Accept-Encoding:gzip,deflate..User-Agent:Mozilla/4.0(compatible;MSIE5.01;WindowsNT5.0)..Host:mp3.yzu.edu.cn..Proxy-Connection:Keep-Alive..Pragma:no-cache..Cookie:ASPSESSIONIDQQCDACCS=CLCLPECCDCEKIGEOKFAFNDAG......-.....0......
socks5代理
socks5代理都不是明文,是数据。
202.204.8.10:21
CACC080A0015
如果使用的是域名,就是明文
..]ZY...][email protected]..〉[email protected]%p:.P...;........vod.sjtu.edu.cn.....
qqhttp代理
当qq上线的时候会和腾迅的服务器联系
..]ZY...][email protected]@...m*...........8]...3.j.P....6..CONNECT218.18.95.165:443HTTP/1.1..Accept:*/*..Content-Type:text/html..Proxy-Connection:Keep-Alive..Content-length:0................
qqsocks5代理
当qq使用socks5代理的时候,不是明文,是数据
CA60AAA5
202.96.170.165:8000
(4)VLAN工作原理的研究
VLAN(VirtualLocalAreaNetwork)就是虚拟局域网的意思。VLAN可以不考虑用户的物理位置,而根据功能、应用等因素将用户从逻辑上划分为一个个功能相对独立的工作组,每个用户主机都连接在一个支持VLAN的交换机端口上并属于一个VLAN。同一个VLAN中的成员都共享广播,形成一个广播域,而不同VLAN之间广播信息是相互隔离的。这样,将整个网络分割成多个不同的广播域(VLAN)。一般来说,如果一个VLAN里面的工作站发送一个广播,那么这个VLAN里面所有的工作站都接收到这个广播,但是交换机不会将广播发送至其他VLAN上的任何一个端口。如果要将广播发送到其它的VLAN端口,就要用到三层交换机。
17. VxD技术及其在实时反病毒中的应用
Windows9x平台反病毒产品大多属静态反病毒软件,指导思想是“以杀为主“,这一方式的缺点是病毒在被清除之前可能早已造成了严重危害一个好的反病毒软件应该是“以防为主,以杀为辅“,在病毒入侵时就把它清除掉,这就是实时反病毒技术。
Windows9x使用IntelCPU的Ring0和Ring3两个保护级。系统进程运行于Ring0,因而具有对系统全部资源的访问权和管理权;而普通用户进程运行于Ring3,只能访问自己的程序空间,不允许对系统资源进行直接访问许多操作受到限制。显然这种普通用户进程是无法胜任实时反病毒工作的,必须使后台监视进程运行在Ring0优先级,实现这一目的基础就是VxD技术。
一、VxD技术的特点
VxD即虚拟设备驱动程序,用作Windows9x系统和物理设备之间的接口。但它不仅适用于硬件设备,也适用于按VxD规范所编制的各种软件“设备“
VxD技术的实质是:通过加载具有Ring0最高优先级的VxD,运行于Ring3上的应用程序能够以一定的接口控制VxD的动作,从而达到控制系统的目的。实时反病毒软件之所以要使用VxD技术,关键有二:(1)VxD拥有系统最高运
行权限(2)许多Windows9x系统底层功能只能在VxD中调用,应用程序如果要用必须编个VxD作为中介。VxD作为应用程序在系统中的一个代理,应用程序通过它来完成任何自己本身做不到的事情,通过这一手段,Windows9x系统为普通应用程序留下了扩充接口。很不幸,这一技术同样为病毒所利用,CIH病毒正是利用了VxD技术才得以驻留内存、传染执行文件、毁坏硬盘和FlashBIOS。
Windows9x系统下有众多的VxD,每个VxD可提供4种服务,即PM(保护模式)API、V86(虚拟86)API、Win32服务和VxD服务,前3种分别供应用程序在16位保护模式、V86模式以及32位保护模式下调用,VxD服务则只供其他VxD使用用户开发的VxD可提供任意上述服务。除此之外,应用程序还可通过调用API函数DeviceIoControl与支持IOCTL接口的VxD进行通信,执行Win32API不支持的系统低级操作。
二、VxD技术的实现
VxD的操作基于寄存器,所以一般用汇编语言编写,它的关键部分是一个和普通窗口的消息处理过程WndProc相类似的控制过程,不同之处在于它的处理对象是系统发来的控制消息。这些消息共51种,在VxD自加载至卸出整个生命周期内操作系统不断向它发送各种控制消息,VxD根据自己的需要选择处理,其余的忽略。系统向VxD发送控制消息时将?代号放在EAX寄存器中并在EBX寄存器中放系统虚拟机(VM)句柄。
对动态VxD来说,最重要的消息有三个:SYS_DYNAMIC_DEVICE_INIT、SYS_DYNAMIC_DEVICE_EXIT以及W32_DEVICEIOCONTROL,消息代号分别是1Bh、1Ch、23h。当VxD被动态加载至内存时。
系统向其发送SYS_DYNAMIC_DEVICE_INIT消息,VxD应在此时完成初始化设置并建立必要的数据结构;当VxD将被卸出内存时,系统向其发送SYS_DYNAMIC_DEVICE_EXIT消息VxD在收到后应清除所作设置并释放相关数据结构;当应用程序调用API函数DeviceIoControl与VxD进行通信时,系统向VxD发送W32_DEVICEIOCONTROL消息,它是应用程序和VxD联系的重要手段,此时ESI寄存器指向一个DIOCParams结构,VxD从输入缓冲区获取应用程序传来数据,相应处理后将结果放在输出缓冲区回送应用程序,达到相互传递数据的目的。
应用程序向VxD发出DeviceIoControl调用时,第2个参数用于指定进行何种控制,控制过程从DIOCParams结构+0Ch处取得此控制码再进行相应处理控制码的代号和含义由应用程序和VxD自行约定,系统预定义了DIOCGETVERSION(0)和DIOC_CLOSEHANDLE(-1)两个控制码,当应用程序调用API函数CreateFile(“\.\VxDName“,...)动态加载一VxD时,系统首先向该VxD的控制过程发送SYS_DYNAMIC_DEVICE_INIT控制消息,若VxD返回成功,系统将再次向VxD发送带有控制码DIOC_OPEN(即DIOC_GETVERSION,值为0)的W32_DEVICEIOCONTROL消息以决定此VxD是否能够支持设备IOCTL接口,VxD必须清零EAX寄存器以表明支持IOCTL接口,这时CreateFile将返回一个设备句柄hDevice,通过它应用程序才能使用DeviceIoControl函数对VxD进行控制。
同一个VxD可用CreateFile打开多次,每次打开时都会返回此VxD的一个唯一句柄,但是系统内存中只保留一份VxD,系统为每个VxD维护一个引用计数,每打开一次计数值加1。当应用程序调用API函数CloseHandle(hDevice)关闭VxD句柄时,VxD将收到系统发来的带控制码DIOC_CLOSEHANDLEW32_DEVICEIOCONTROL消息,同时该VxD的引用计数减1,当最终引用计数为0时,系统向VxD发送控制消息SYS_DYNAMIC_DEVICE_EXIT,然后将其从内存中清除。在极少数情况下应用程序也可调用API函数DeleteFile(“\.\VxDName“)忽略引用计数的值直接将VxD卸出内存,这将给使用同一VxD的其他应用程序造成毁灭性影响,应避免使用。
--一个典型的VxD控制过程代码如下:
BeginProcVXD_Control
cmpeax,1Bh
;SYS_DYNAMIC_DEVICE_INIT消息
jzvxd_dynamic_init_handle
cmpeax,1Ch
;SYS_DYNAMIC_DEVICE_EXIT消息
jzvxd_dynamic_exit_handle
cmpeax,23h
;W32_DEVICEIOCONTROL消息
jnzexit_control_proc
movecx,[esi+0Ch]
;从DIOCParams+0Ch处取控制码
....
;处理控制码
EndProcVXD_Control
三、实时反病毒的关键技术-FileHooking
应用程序通过使用动态加载的VxD,间接获得了对Windows9x系统的控制权,但要实现对系统中所有文件I/O操作的实时监视,还要用到另一种关键技术-FileHooking,通过挂接一个处理函数,截获所有与文件I/O操作有关的系统调用。Windows9x使用32位保护模式可安装文件系统(IFS),由可安装文件系统管理器(IFSManager)协调对文件系统和设备的访问,它接收以Win32API函数调用形式向系统发出的文件I/O请求,再将请求转给文件系统驱动程序FSD,由它调用低级别的IOS系统实现最终访问。每个文件I/OAPI调用都有一个特定的FSD函数与之对应,IFSManager负责完成由API到FSD的参数装配工作,在完成文件I/OAPI函数参数的装配之后转相应FSD执行之前,它会调用一个称为FileSystemApiHookFunction的Hooker函数。通过安装自己的Hooker函数,就可以截获系统内所有对文件I/O的API调用,并适时对相关文件进行病毒检查,从而实现实时监控。
上述过程由用户VxD调用系统VxDIFSMgr提供的服务完成,该VxD提供了丰富的底层文件操作功能:IFSMgr_InstallSyatemApiHook函数用来安装FileSystemApiHookFunction,IFSMgr_RemoveSystemApiHook用来卸除Hooker,IFSMgr_Ring0_FileIO用来对文件和磁盘扇区进行读写访问等等。当由IFSManager转入SystemApiHookFunction时,带有6个参数:
FileSystemApiHookFunction(
pIFSFuncFSDFnAddr,
//对应FSD服务函数地址
intFunctionNum,
//与API对应的FSD服务功能号(详见下面)
intDrive,
//驱动器代号(1=A,2=B,3=C...)
intResourceFlags,
//资源标志(详见下面)
intCodePage,
//代码页(0=ANSI,1=OEM)
pioreqpir
//指向IOREQ结构的指针
)
参数中比较重要的是FSD功能号、驱动器号和IOREQ结构指针3项。如需截获某个文件I/OAPI调用,只需在Hooker中对相应FSD功能号进行处理
系统中可挂接多个Hooker,形成一条链。IFSMgr_InstallFileSystemApiHook安装Hooker成功时返回前一个Hooker地址,每个Hooker在做特定处理后总应调用前一个Hooker,最后安装的Hooker最先被调用。在VxD中调用其他VxD服务采用INT20h指令后跟一个双字的特殊格式,其中高字为被调用VxD的ID号(系统VxD的ID固定),低字为该VxD之服务号,这一形式称为VxDcall,如:
int20h
dd00400043h
;VxDCallIFSMgr_InstallSystemApiHook
int20h
dd00400044h
;VxDCallIFSMgr_RemoveSystemApiHookr〉
int20h
dd00400043h
;VxDCallIFSMgr_InstallSystemApiHook
int20h
dd00400044h
;VxDCallIFSMgr_RemoveSystemApiHookemApiHookok
18. 语言越低级,运行效率就越高吗?
本来我是要继续写我的foreach系列文章的另外两篇的。但是看了评C#事件处理,有一些感触。
我不同意大家关于语言越低级,运行效率就越高的说法。
语言越低级,编译器能够决定的东西就越少,就越不能很好的优化执行代码。大家想想为什么c语言编译器,一般都忽略register关键字,C++一般都忽略inline关键字?很多时候,编译器只要掌握了足够的信息,就能做出明智的决定。
以前我测过delphi和C++生成的代码,delphi的代码比C++高效得多。因为C/C++里广泛使用了指针,对于变量的位置,struct中各个成员的排列方法,调用约定,对于编译器都有非常严格的限制。编译器只能忠实的翻译,错失了很多优化代码的机会。为了能够享受各种代码优化技术,C++编译器都提供了非常多的编译优化选项,但是绝大部分都不是100%安全的。因为C/C++程序员总是可以越权完成很多低级操作。比如:用const修饰的变量是不会被修改的,编译器可以利用这个知识将这个变量的值缓存在寄存器中。但是在C++中,就不能做这项优化,因为C++程序员可以强制cast掉const限制。也许大家认为编译器能够监测到这种cast,自动关掉这项优化,但是谁能保证其它.obj文件里的代码有没有修改这个const变量呢?
同样类的私有成员,在C++中也很难真正实现访问跟踪。编译器没有100%确信的知识,怎么能去优化呢?
世界上最好的汽车是手工打造的,但是不是说手工打造的汽车就一定比工业批量造出的汽车好。
我想写程序最重要的是如何最清晰的表达我们要解决的问题,而不要过多的体现解决问题的次要细节。我们要的是报表、订单;而不是内存,指针,函数。如果我们能从更高、更抽象的层次描述问题,就会留给底层软件更多发挥的空间。比如底层软件可以选择低运行效率,极高开发效率的方法完成功能,而快速的制作原形,展示设计;或者底层软件选择高速运行的策略,面向运行时间要求高的场合;或者选择低内存占用作为目标,等等。
规则不是约束创造性地力量,而是激发创造性地基础。没有稳定的规则,哪里会有什么创造?我认为.net规则,是激发创造的开始,是一个新的世界的开始。
有人质疑C#完成事件模型太隐晦,臃肿,和il不直接对应,太复杂。event关键字完成了多播事件、异步事件。想想java为了实现多播事件,明确的使用了列表。虽然C#也使用列表实现多播,但是使用列表这个事实并没有明确的在语法上体现出来。这有什么不同呢?这当然不同!在delegate中只有一个函数指针时,这个列表是不生成的,而且调用也是直接的,并没有一个调用循环。另外,C#通过delegate还可以实现异步事件,统一了异步I/O的编程模型,最近Ado.net也执行异步执行sql语句了。Java事件的interface实现,要完成异步事件不知道要使用多少代码。而且只要代码一旦写成,它就死了,运行环境就不能再做进一步的优化了。
比如做出租车,如果告诉司机你的目标地点,司机就可以综合道路的长度、拥堵情况、路况情况选择一个优化解。相反,如果告诉司机你的路线,那么一切都固定了,僵化了,司机拥有再多的知识,都无法用上了。高级语言和低级语言之间的差别就和这个现实生活的例子是一个道理。也许很多人不同意,认为自己既是乘客也是司机。但是你认为自己真的对所有的计算机知识都了解吗?CPU的不同,Cache效率的不同,总线效率的不同。硬件影响软件的要素已经非常多,软件的影响就更多了。现代的软件会依赖大量的操作系统的调用、库函数调用、编译器等等,谁能知道在数以万计的函数中,那一个效率高,那一个效率低。我们优化了数周代码获得的性能提升,会被一个设计糟糕的库函数轻易吞没。就算优化好了一个系统,换一个环境还是效率高的吗?比如用户机器更换了显卡驱动程序?谁都不会有能力做一个好司机。
让真正拥有实际运行知识的C#语言、.net运行环境来替我们完成95%的性能优化吧,我们能做一个好乘客,说清楚我们的目的地,就是极大的成功了。
19. Windows程序开发基础
1、大致说来windows编程有两种方法:
a.windwosc方式(SDK),SDK编程就是直接调用windows的API进行编程;
b.c++方式:即对SDK函数进行包装,如VC的MFC,BCB的OWL等。MFC把这些API封闭起来,共有一百多个类组成.
2、API,全称applicationprograminterface,意思是应用程序编程接口(说起API并不仅仅指windows而言,windows支持的API叫winapi)。winapi就是应用程序和windows之间通讯的一个编程界面。windows提供了上千个API函数,以方便程序员来编写应用程序。
3、WinSDK程序设计就是API方式的windows程序设计。SDK,全称SoftwareDevelopersKit,意思是软件开发工具箱。
4、MFC,全称MicrosoftFoundationClasses,伪软把WinAPI进行封装的类库。它是一个类的集合,通过覆盖WinAPI,为编程提供了一个面向对象的界面。它使windows程序员能够利用C++面象对象的特性进行编程,类似BCB的OWL,Delphi的VCL组件。它把那些进行SDK编程时最繁琐的部分提供给程序员,使之专注于功能的实现。你不妨把它想象成类似TC提供的函数库吧。
5、dos下的C编程的main()一样,windows下的入口是WinMain()函数。
6、WinMain()所起的作用:初始化,展示,销毁应用程序等。
第一个参数:应用程序的当前实例句柄。
第二个参数:应用程序的前一个实例句柄,别管它,对于Win32位而言,它一般是NULL.
第三个参数:指向任何传给程序的命令行参数。PSTR代表“指向字符串的指针“。
第四个参数:它告诉应用程序如何初始化窗口,如最大化,最小化等状态。
7、句柄(handle):
在标准C库中句柄用来对文件输入输出。
在Windows环境中,句柄是用来标识项目的,这些项目包括:
*.模块(module)
*.任务(task)
*.实例(instance)
*.文件(file)
*.内存块(blockofmemory)
*.菜单(menu)
*.控制(control)
*.字体(font)
*.资源(resource),包括图标(icon),光标(cursor),字符串(string)等
*.GDI对象(GDIobject),包括位图(bitmap),画刷(brush),元文件(metafile),调色板(palette),画笔(pen),区域(region),以及设备描述表(devicecontext)。
WINDOWS程序中并不是用物理地址来标识一个内存块,文件,任务或动态装入模块的,相反的,WINDOWSAPI给这些项目分配确定的句柄,并将句柄返回给应用程序,然后通过句柄来进行操作。
窗口句柄:
系统通过窗口句柄来在整个系统中唯一标识一个窗口,发送一个消息时必须指定一个窗口句柄表明该消息由那个窗口接收。而每个窗口都会有自己的窗口过程,所以用户的输入就会被正确的处理。
8、所有的命名采用了匈牙利表示法。如消息的前缀使用msg.句柄使用h.函数使用fn等。
9、MainFrm.cpp、MainFrm.h:
这两个文件将从CFrameWnd(SDI应用程序)或CMDIFrameWnd(MDI应用程序)派生CMainFrame类。如果在AppWizard的ApplicationOptions页(6步中的第4步)中选择了对应的可选项的话,CMainFrame类将处理工具条按钮和状态条的创建。MAINFRM.CPP文件还含有MFC应用程序提供的默认工具条按钮的对象ID——叫做buttons数组。
10、DOS程序主要使用顺序的,过程驱动的程序设计方法。顺序的,过程驱动的程序有一个明显的开始,明显的过程及一个明显的结束,因此程序能直接控制程序事件或过程的顺序。虽然在顺序的过程驱动的程序中也有很多处理异常的方法,但这样的异常处理也仍然是顺序的,过程驱动的结构。
11、Windows的驱动方式是事件驱动,就是不由事件的顺序来控制,而是由事件的发生来控制,所有的事件是无序的。做为一个程序员,在你编写程序时,你并不知道用户先按哪个按纽,也不知道程序先触发哪个消息。你的任务就是对正在开发的应用程序要发出或要接收的消息进行排序和管理。事件驱动程序设计是密切围绕消息的产生与处理而展开的,一条消息是关于发生的事件的消息。
12、Windows程序则至少两个主程序,
一个是WinMain(),
intWINAPIWinMain(
HINSTANCEhInstance,//handletocurrentinstance
HINSTANCEhPrevInstance,//handletopreviousinstance
LPSTRlpCmdLine,//commandline
intnCmdShow//showstate
);
另一个是窗口过程函数WndProc,它的函数原型为:
longFARPASCALWndProc(HWNDhWnd,WORDmessage,WORDwParam,LONGlParam);
13、窗口函数与回调函数:
在Windows中,应用程序通过要求Windows完成指定操作,而承担这项通信任务的API函数就是Windows的相应窗口函数WndProc。应用程序不直接调用任何窗口函数,而是等待Windows调用窗口函数,请求完成任务或返回信息。为保证Windows调用这个窗口函数,这个函数必须先向Windows登记,然后在Windows实施相应操作时回调,所以窗口函数又称为回调函数。WndProc是一个主回调函数,Windows至少有一个回调函数。典型的回调函数有窗口过程、对话框过程和钩子函数。实际上,也许有不止一个的窗口过程。例如,每一个不同的窗口类都有一个与之相对应的窗口过程。
15、实例:在Windows中,能多次同时运行同一个应用程序,即运行多个副本,每个副本叫做一个“实例”。
16、Cruntime函数库:
就跟它的名字一样,运行类型信息
主要有COject类和CRuntimeClass类来实现,用来存贮COject类和派生类的运行类型信息,
1.类的基本情况:如类的名字,存贮空间大小,用于运行类的类型确定.
2.ms在C++的标准上,添加动态创建的类对象功能,也就是时时提到的动态创建
3.串行化处理.
TheOSdoesnotknowmain(),soC-runtimeisfirstcalledandtransfercontroltomain
italsoprovidelibraryforcommonusage,suchasmathfunctions
17、WinMain()函数的调用约定是PASCAL。
在这里PASCAL是一个调用约定,由于这种方式最早由PASCAL采用,所以这么叫。
在MSDN中的C++LanguageReference中,CallingConventions这一章都是讲调用约定的。
约定:微软重定义了许多约定类型,为的是可以让代码更容易跨平台或者跨编译器。
其实,调用约定要解决两个问题,都是针对堆栈操作:
1。参数传递的顺序(本质是压栈的顺序)
2。谁负责平栈(调用者还是调用对象)
一个函数的声明、定义和实现中的调用方式一般都一致。
WINAPI标识符的定义是:#defineWINAPI__stdcall,__stdcall指Window调用函数的一种方式,也就是如何在堆中存取函数参数的方式。许多WindowsApi函数调用声明为__stdcall方式。
18、用位的“或”操作(操作符“|”)把若干个常数组合起来控制消息窗口显示的按钮和图标等。
19、在Windows应用程序中,每一个窗口都必须从属于一个窗口类,窗口类定义了窗口所具有的属性,如它的样式、图标、鼠标指针、菜单名称及窗口过程名等。
窗口种类是定义窗口属性的模板,这些属性包括窗口式样,鼠标形状,菜单等等,窗口种类也指定处理该类中所有窗口消息的窗口函数.只有先建立窗口种类,才能根据窗口种类来创建Windows应用程序的一个或多个窗口.创建窗口时,还可以指定窗口独有的附加特性.窗口种类简称窗口类,窗口类不能重名.在建立窗口类后,必须向Windows登记.建立窗口类就是用WNDCLASS结构定义一个结构变量.
20、VisualC++提供Alt+F8功能键,进行文件规格化,根据周围的代码行,正确缩进选定的代码行。常使用该按键可使得文件保持规格化(注,Alt+F8功能键对连续两个CASE语句则会发生处理错误,不能规格化)。
21、消息:
一个消息由一个消息名称(UINT),和两个参数(WPARAM,LPARAM)。当用户进行了输入或是窗口的状态发生改变时系统都会发送消息到某一个窗口。
22、一个典型的应用程序应该活动在称为“框架窗口”中。一个框架窗口是一个全功能的主窗口,用户可以改变尺寸、最小化、最大化等。
23、消息机制:
系统将会维护一个或多个消息队列,所有产生的消息都回被放入或是插入队列中。系统会在队列中取出每一条消息,根据消息的接收句柄而将该消息发送给拥有该窗口的程序的消息循环。每一个运行的程序都有自己的消息循环,在循环中得到属于自己的消息并根据接收窗口的句柄调用相应的窗口过程。而在没有消息时消息循环就将控制权交给系统所以Windows可以同时进行多个任务。
24、预编译头文件:
VC++程序一般包含的头文件都比较复杂,如果每次都逐行分析可能会花很多时间,所以VC++默认设置是第一次编译时分析所有头文件,生成.pch文件,这个文件很大,但以后每次编译时就可以节省很多时间。如果删除了这个文件,下次编译时VC++会自动生成它。
StdAfx.h是每个MFC程序的类中必须包括的文件,它一般由AppWizard自动生成,包括编译MFC类所必须的定义。
25、Windows支持两种类型的对话框:模式和无模式对话框。
模式对话框一旦出现在屏幕上,只有当它退出时,屏幕上该应用程序的其余部分才能响应。
无模式对话框出现在屏幕上时,程序的其余部分也可以作出响应,它就象浮动在上面一样。
26、单文档界面(SDI),只有一个框架窗口。
MDI系统允许用户在同一应用程序中同时可以查看多个文档。
20. 谈谈Windows2000的服务
I、摘要
II、关于WIN2K的服务
III、服务的启动和关闭基本过程
IV、服务的编程
V、关于服务的安全
VI、服务的管理
VII、结尾
I、摘要
WINNT下的服务就类似*NIX下面的守护进程一样,而且现在越来越多的软件开始设计成服务的形式,从XP推出之后,
通过服务来实现多用户切换等就显得很有作用了。
从安全角度来看待WIN的服务的话,也就因此有很多的话题,比如运行的权限、运行的时间等等。
本文就从一些方面来介绍并谈谈WIN服务的一些东西,受水平限制,内容不精致。
II、关于WIN2K的服务
WIN32服务由三部分组成:服务应用程序、服务控制程序(SCP)和服务控制管理器(SCM)。
一、服务控制管理器
服务控制管理器(ServiceControlManager):在系统启动的时候开始,是WIN系统的一部分,它是一个远程过
程调用(RPC)服务器。这也是WIN服务系统的核心。
SCM主要负责下面的东西:
·维护安装的服务数据库
·在系统启动或者有命令的时候开始服务和驱动服务
·枚举安装的服务和驱动
·维护运行着的服务和驱动的状态
·传输控制请求去运行服务
·锁定和解锁服务数据库
SCM维护着注册表中的服务数据库,位于:HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services。其下的子键就
是安装的服务和驱动服务。每个子键的名称就是服务名,当安装的时候由服务安全程序的CreateService函数指定。
当系统安装的时候,最初的数据库就被创建。这个数据库包含系统启动时候的设备驱动。数据库中的每个服务和驱动
的信息包括:
·服务类型。服务执行时候是自己的进行还是同其他服务共享进行,是否是核心驱动还是文件系统驱动。
·启动类型。服务或者驱动服务是否是在系统启动的时候自动启动还是,是否是由SCM来接受控制请求来启动。启动类
型也表明服务是否被禁止。
·错误控制等级。指明如果服务或者驱动服务启动失败的错误处理。
·执行文件的全路径。
·附加依赖信息决定启动的正确顺序。对于服务,这个信息包括在服务启动之前SCM需要先启动的指定服务,服务所属
加载顺序组的名称,服务在组中启动顺序的标志符。对于驱动服务,这个信息包括驱动启动前需要启动的指定驱动。
·对于服务,还有附加的帐号名称和密码。如果没有指定帐号,服务就使用LocalSystem帐号。
·对于驱动,附加驱动对象名称,用于I/0系统加载设备驱动。如果没有指明对象名,I/O系统在驱动服务名称基础上创
建一个默认的名称。
二、服务控制程序
服务控制程序(SCP)则是控制服务应用程序的功能块,也是服务应用程序同服务管理器(SCM)之间的桥梁。服务控制
程序可以完成这些动作:
·如果服务启动类型为SERVICE_DEMAND_START,那么服务控制程序来启动服务
·发送控制请求给运行着的服务
·查询运行着的服务的当前状态
这些动作要求打开一个服务对象的句柄。
·服务启动
要启动一个服务,服务控制程序使用StartService函数。如果数据库被锁定,那么StartService函数会失败。如果遇
到这种情况,那么服务控制程序需要等待,并重新调用StartService。可以通过QueryServiceLockStatus来查询服务数据
库的状态。
当服务控制程序开始一个服务的时候,可以通过StartService函数来指定传递给服务ServiceMain函数的参数。当创建
一个新的线程去执行ServiceMain后,StartService就返回了。服务控制程序可以通过QueryServiceStatus函数来查询被启
动的服务的状态。在SERVICE_STATUS结构初始化中dwCurrentState应该是SERVICE_START_PENDING,而dwWaitHint则是一个
毫秒的时间间隔,表示服务控制程序在调用QueryServiceStatus应该等待的时间。当初始化完成,服务就会改变服务的状态
dwCurrentState为SERVICE_RUNNING。
如果服务在80秒,再加上最后的等待时间内没有改变它的状态,服务控制管理器确定服务已经停止响应,会记录事件并
停止服务。
如果程序在启动驱动服务,StartService会在设备驱动初始化完成后返回。
·服务控制请求
服务控制程序通过ControlService来发送一个控制请求给运行着的服务。这个函数指定控制值传递给指定服务的
HandlerEx函数。这个控制值可以是用户自定义码,也可以是下面这些基本控制码:
·停止服务:SERVICE_CONTROL_STOP
·暂停服务:SERVICE_CONTROL_PAUSE
·恢复被暂停的服务:SERVICE_CONTROL_CONTINUE
·返回服务的更新状态信息:SERVICE_CONTROL_INTERROGATE
每个服务可以指定它接收和处理的控制值。要确定哪个基本控制值被服务接收,可以使用QueryServiceStatus函数或
者指定SERVICE_CONTROL_INTERROGATE来调用ControlService函数。SERVICE_STATUS结构中的dwControlsAccepted返回的
是是否服务能被停止、暂停和恢复。所有的服务都能接收SERVICE_CONTROL_INTERROGATE。
QueryServiceStatus函数返回指定服务的最近状态,而不会获得服务本身更新的状态。使用
SERVICE_CONTROL_INTERROGATE控制来调用ControlService函数可以确定状态是否是当前的信息。
三、服务应用程序
服务应用程序是一个服务的主体程序,它是一个或者多个服务的可执行代码。这将在服务的编程中详细解释。
III、服务的启动和关闭的基本过程
当系统启动的时候,SCM会启动所有自动启动的服务以及这些服务依赖的服务。如果一个自动启动的服务所依赖的服务是
“手动”(需要命令才启动)的服务,那么这个服务也会被自动启动。服务的加载顺序由下面这些方面来决定:
1.组的顺序
2.一个组中服务的加载顺序
3.每个服务所依赖的服务
当启动完成的时候,系统执行启动确认程序(由注册表的HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control
中的BootVerificationProgram值指定,默认情况下,这个值是没有的。)。当第一个用户登录后,系统会简单地报告启
动成功。可以单独提供一个启动确认程序来检查系统问题和报告启动状态给SCM,使用NotifyBootConfigStatus函数。
当系统成功启动后,系统就克隆保存一份数据库备份,作为last-known-good(LKG)配置。如果当前使用的数据库
导致系统启动失败,那么可以用备份来恢复。备份的数据库就保存在:
HKEY_LOCAL_MACHINE\SYSTEM\ControlSetXXX\Services中。
其中XXX值也被保存在:
HKEY_LOCAL_MACHINE\System\Select\LastKnownGood中。
如果自动启动的服务自动的时候得出SERVICE_ERROR_CRITICAL错误,SCM就会重新启动机器,并使用LKG的配置,如果
LKG的配置已经被使用了,启动就会失败。
注册表中服务的ErrorControl值表示SCM如何处理服务错误。如果值为SERVICE_ERROR_IGNORE(0)或者没有指定,SCM
只忽略错误并继续服务的启动,如果为SERIVCE_ERROR_NORMAL(1),就在事件日志中记录下错误原因。如果错误控制为
SERIVCE_ERROR_SEVERE(2)或者SERIVCE_ERROR_CRITICAL(3),服务就报告启动错误。SCM记录事件日志,并调用函数
ScreverToLastKnownGood,将系统注册配置切换到LKG的版本,然后调用NtShutDownSystem重新启动系统。如果系统已经
使用LKG版本,就直接重新启动。
LKG版本的产生:SCM在系统启动阶段启动了所有自起服务之后,需要来决定这个LKG配置。缺省情况下,一次成功的
启动包括所有服务的成功启动和一个用户的登录。如果在启动服务阶段存在服务的SERIVCE_ERROR_SEVERE(2)或者
SERIVCE_ERROR_CRITICAL(3)错误,那么这就是失败的启动。如果SCM成功完成服务的启动,当有用户登录的时候,
Winlogon调用NotifyBootConfigStatus函数发送消息给SCM。在成功启动所有服务,并且收到NotifyBootConfigStatus的
登录信息,SCM就调用NtInitializeRegistry保存当前的启动配置信息。
第三方可以用自己的定义取代Winlogon的确认,这可以由注册表中:
KHLM\SYSTEM\CurrentControlSet\Control\BootVerificationProgam中的程序确定,可通过此加入对系统成功启动的定
义。启动验证程序则通过设定HKLM\SOFTWARE\Microsoft\WindowsNT\CurrentVersion\Winlogon\ReportBootOK为0禁止
Winlogon对NotifyBootConfigStatus的调用。这样,SCM启动完服务后,等待这个验证程序调用NotifyBootConfigStatus
函数通知登录成功,然后才保存LKG配置。
SCM的执行文件是:WINN\System32\Service.exe,以控制台模式运行,Winlogon进程在系统启动早期启动SCM。
SvcCtrlMain在紧接着屏幕变为空白时刻运行,在Winlogon加载图形化身份鉴定并显示登录界面GINA之前运行。
SvcCtrlMain首先创建一个nonsignaled初始化的名为SvcCtrlEvent_A3752DX的同步事件,在完成准备接受SCP的命令
的各项步骤之后,SCM才设定此事件为signaled状态。SCP通过OpenSCManager函数来确认SCM,这个函数通过等待
SvcCtrlEvent_A3752DX为signaled来防止SCP在SCM初始化完成之前接触SCM。
SvcCtrlMain然后调用ScCreateServiceDB函数,建立SCM的服务数据库。它先读取注册表中:
HKLM\system\CurrentControlSet\Control\ServicegroupOrder\list内容,列出服务组名称和它们的启动顺序,然后再搜
索HKLM\SYSTEM\currentControlSet\Services的内容,为每一条主键在服务的数据库中创建一个条目。SCM本身属于自起
服务和设备驱动,并且标记为引导启动和系统启动驱动的启动错误,也就是,所有标记为引导驱动和系统启动驱动将在
SCM启动前被加载,在用户模式进程执行前,I/O管理器就会加载这些启动。ScCreateServiceDB读取服务的组键值来确定
服务所属组,并同先前建立的组列表联系起来。该函数还通过DependOnGroup和DependOnService函数来查询服务和组的
依赖关系。
在服务启动的时候,SCM可能需要调用Lsass,SCM会等待Lsass在其初始化结束时的LSA_RPC_SERVICE_ACTIVE同步事件
通知,Winlogon也会启动Lsass进程,Lsass和SCM的初始化是同步的,但初始化结束顺序不确定。SvcCtrlMain会调用
ScGetBootAndSystemDriverState来遍历服务数据库查询引导启动和系统启动的设备驱动,该函数通过查询对象管理器中的
名字域目录\Driver中的名字来确定驱动是否成功启动。当设备驱动被成功加载,I/O管理器就把驱动器对象插入名字域。
如果驱动没有被加载,SCM在PnP_DeviceList函数返回的驱动列表中查询其名字,SvcCtrlMain记录没有启动的驱动名称,
并作为ScFailedDrivers列表中当前配置文件的一部分。
在启动自启服务之前,SCM创建一个管道作远程过程调用Pipe\Ntsvcs,并且创建一个线程来监听SCP的消息,然后通知
它的初始化结束事件SvcCtrlEvent_A3752DX。SCM通过由RegesterServiceProcess注册一个控制台应用程序关闭事件处理并
向WIN32子系统注册来为系统关闭作准备。
SvcCtrlMain调用SCM的ScAutoStartService来启动自启动的服务,算法是阶段性执行的,每个阶段有一个服务组构成。
当一个阶段开始时,ScAutoStartService把所有属于该组的服务作标记,然后循环所有标志的服务,检测是否每个服务能
够启动。检测内容包括服务组的依赖关系,如果存在依赖关系,服务所依赖的组必须先完成初始化,并且这个组中至少有
一个服务已经成功启动了。如果组启动顺序中服务的依赖组比服务所在组靠后,SCM标记circulardependency错误给服务。
对于一个服务(非驱动),还回检测服务的依赖关系,并且所依赖的服务是否已经启动。同样,也会标记
circulardependency错误,这时就不启动服务,如果服务依赖的是本组的服务,并且还没有被启动,那么这个服务的执行
就被跳过。
依赖关系检测通过后,在服务启动前,ScAutoStartService还要检测此服务是否是当前引导配置的一部分。比如安全
模式启动,在HKLM\SYSTEM\CurrentControlSet\Control\SafeBoot中列举,分为最小配置和网络支持配置需要启动的服务
和驱动。
当确定要启动服务,SCM就调用ScStartService函数。当ScStartService启动一个WIN32服务的时候,会读取服务注册
主键的ImagePath来确定运行服务进程的文件,然后检查服务类型是否为SERVICE_WIN32_SHARE_PROCESS,SCM确保服务进程
与已启动服务使用相同的帐号登录,服务的注册键值中包含有帐号信息。这时,SCM调用ScLogonAndStartImage启动服务进
程,如果是其他帐号,就使用Lsass函数登录帐号。调用LsaLogonUser指派服务的登录类型,Lsass在注册表Security下
Secrets下的_SC_中找到密码,登录成功后,LsaLogonUser返回调用者访问句柄,代表用户的安全权限。
登录成功后,如果没有装载帐户信息,SCM调用LoadUserProfile函数装载帐号信息。对于交互服务必须打开WinSta0。
然后服务还没有被启动,ScLogonAndStartImage函数会继续启动服务进程。SCM用CreateProcessAsUser函数以挂起状
态启动进程,然后创建一个命名管道来同服务进程通信:\Pipe\Net\NetControlPipeX(X是每一个新管道叠加的),SCM
通过ResumeThread来恢复服务进程和等待服务同SCM管道连接。注册表中:
HTLM\SYSTEM\CurrentControlSet\Control\ServicePipeTimeout的值决定了这个等待时间,默认为30秒。
如果服务顺利同SCM连接上,SCM就发送启动命令给服务,如果在超时内,如果没有对响应启动命令,SCM就放弃并开始
启动下一个服务,并在系统日志中记录下错误。
ScAutoStartServices循环组内的所有服务直到服务被启动或者发生依赖关系错误为止。循环是按照服务的依赖关系自
动排列服务的次序。SCM会先循环所依赖的服务。SCM结束所有服务组后,执行那些列表中没有列出的组的服务,最后执行
不属于任何组的服务。
当系统开始关闭的时候,Win32ExitWindowsEx函数发送消息给系统进程Csrss,调用Csrss的关闭例程。Csrss遍历所有
进程同志它们系统正在关闭。在通报下一个进程前,Csrss等待除SCM以外的每个系统进程退出,等待时间为:
HKLM\.DEFAULT\ControlPanel\Desktop\WaitToKillAppTimeout,缺省为20秒。当Csrss遇到SCM进程时,也通知SCM系统正
在关闭,并等待为SCM指定的超时。在系统初始化的时候,SCM通过RegisterServicesProcess函数向Csrss注册它的进程ID,
Csrss通过使用SCM的进程ID来识别SCM。SCM的超时值为:
HKLM\SYSTEM\CurrentControlSet\Control\WaitToKillServiceTimeout,缺省为20秒。
SCM的关闭处理程序发送关闭通知给所有SCM初始化时申请需要关闭通知的服务。SCM的ScShutdownAllServices遍历
SCM数据库寻找那些请求关闭通知的服务,并发送关闭通知,同时记录等待延时。发送关闭通知后,SCM等待通知的服务退
出或者等待超时为止。如果服务超时没有退出,SCM测定一个或者多个等待退出的服务是否发送一个消息给SCM,这个消息
是来告诉SCM服务在关闭过程中取得的进展。如果至少一个服务有进展,SCM就在延时等待范围内再等待一次。SCM持续该
等待循环,直到所有服务退出,或者在等待延时范围内没有收到服务的进展信息为止。
当SCM通知服务关系并且等待服务退出的时候,Csrss等待SCM退出。如果Csrss等待超时,而SCM还没有退出,Csrss就
继续关闭过程,所以,在系统关闭时,没有在规定时间内成功关闭的服务只是简单地同SCM一起执行。
IV、服务的编程
服务程序是一个或者多个服务的可执行代码。SERVICE_WIN32_OWN_PROCESS类型创建的服务只能是一个服务的执行程
序。服务可以配置使用本地、主域或者信任域的帐号关系执行。SERVICE_WIN32_SHARE_PROCESS类型的服务代码中可以包
含多个服务。
一个服务必须包含main、ServiceMain和控制处理函数
·服务的main函数
服务通常是控制台程序,入口点就是main函数,main函数从注册表中服务的ImagePath值中获得参数。当SCM开始服
务程序的时候,等待调用StartServiceCtrlDispatcher函数。规则为:
·SERVICE_WIN32_OWN_PROCESS类型的服务会从主线程立刻调用StartServiceCtrlDispatcher函数。可以在服务
启动后完全初始化。
·SERVICE_WIN32_SHARE_PROCESS类型的服务,在程序中进行公共初始化,可以在StartServiceCtrlDispatcher函
数调用前在主线程中完成初始化,只要花费的时间少于30秒。否则,当主线程调用StartServiceCtrlDispatcher的时候
必须创建另外一个线程去完成公共初始化。可以在ServiceMain函数中去完成每个服务单独的初始化。
StartServiceCtrlDispatcher函数为在进程中的每个服务获得一个SERVICE_TABLE_ENTRY结构。每个结构指定服务
名和服务的入口点。如果StartServiceCtrlDispatcher函数调用成功,调用线程不会返回,直到所有运行服务的进程都
终止。SCM通过命名管道控制这个线程的请求。这个线程就象发报机(调度器),完成下面任务:
·当新的服务开始时,创建一个新的线程去调用适当的入口
·调用适当的句柄函数去操作服务控制请求
当SCM启动一个服务进程的时候,就会调用StartServiceCtrlDispatcher函数,它接收一个服务入口列表或者单个
服务进程的单个入口,每个入口点通过于入口通讯的服务名来鉴别。在建议一个命名管道同SCM通讯后,此函数陷入循
环等待来自管道的SCM命令。SCM在每一次启动服务时发一个服务启动命令。而StartServiceCtrlDispatcher函数每接
收一次命令就创建一个服务线程来调用服务的如后和执行服务的循环命令。StartServiceCtrlDispatcher函数等待来
自SCM的命令,在所有进程的服务线程都停止并允许进程在离开时清除资源后,才将控制权交还给进程的主函数。
·服务的ServiceMain函数
ServiceMain函数是服务的入口点。
当服务控制程序要求运行新的服务,SCM启动该服务,并且发送一个开始请求到调度器。调度器创建一个新线程执
行服务的ServiceMain函数。ServiceMain函数完成下面的任务:
·立刻调用RegisterServiceCtrlHandlerEx函数去注册服务的句柄控制请求,返回值就是服务的状态句柄,可以
用来通知SCM服务的状态。
·完成初始化。如果初始化代码执行的时间很短(少于1秒),初始化可以在ServiceMain函数中直接完成;如果
初始化的时间长于1秒,那么调用SetServiceStatus函数,在SERVICE_STATUS结构中指定SERVICE_START_PENDING服
务状态和等待时间。当初始化继续,服务应当另外调用SetServiceStatus去报告进展。
·当初始化完成,调用SetServiceStatus,在SERVICE_STATUS结构中指定服务状态为SERVICE_RUNNING。
·完成服务任务,或者,如果没有未决(pending)任务,返回。所有状态变化,都调用SetServiceStatus去报告。
·如果在服务初始化或者运行中发生了错误,服务应该调用SetServiceStatus,指定SERVICE_STOP_PENDING状态,
如果清除过程比较长。一旦清除完成,从最后终止的线程调用SetServiceStatus,指定SERVICE_STOPPED状态。确定要
指定SERVICE_STATUS结构中dwServiceSpecificExitCode和dwWin32ExitCode来确定这个错误。
·服务的控制处理函数
每个服务都有控制处理函数:HandlerEx函数,它被控制发出者调用,当服务进程接受一个控制请求的时候,因
此,这个函数以控制发出者的安全关系执行。无论什么时候HandlerEx被调用,服务都必须调用SetServiceStatus函
数去向SCM报告服务状态,而不管是否服务的状态被改变。
服务控制程序可以使用ControlService函数发出控制请求。所有的服务都必须接受和处理
SERVICE_CONTROL_INTERROGATE控制码。可以通过SetServiceStatus来同意或者禁止接受其他控制码。要接收
SERVICE_CONTROL_DEVICEEVENT控制码,必须调用RegisterDeviceNotification函数。服务可以处理用户自定义的控
制码。控制处理必须在30秒以内返回,否则SCM就会返回一个错误。如果服务需要完成一个很长的任务,应该创
建一个新的线程去完成这个长任务,然后返回。这可以防止服务阻碍控制发出者。
当用户关闭系统,所有的控制处理要调用SetServiceStatus设置SERVICE_ACCEPT_SHUTDOWN控制码去接收
SERVICE_CONTROL_SHUTDOWN控制码,它们都会按照服务数据库中的顺序依次被通知。默认情况下,在系统关闭前,
一个服务通常有大约20秒去完成清除任务。当时间过了之后,系统会关闭进程,而不管服务是否完成了关闭。请注意,
如果系统停留在shutdown状态(不是restarted和powereddown),服务仍然是在运行的。
如果服务需要时间去清除,它可以发送STOP_PENDING状态消息,连同一个等待时间,这样,服务控制器在报
告系统服务关闭之前才知道应该待多长时间,无论如何,都有一个服务控制器需要等待的时间,防止服务停留在
shutdown状态。要改变这个时间限制,可以修改HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control中的
WaitToKillServiceTimeout值。
V、关于服务的安全
服务的很多特性导致它受到安全的特别关照:多数服务的运行安全等级是比管理员权力还高的LocalSystem;
一个服务存在的安全问题通常导致系统崩溃,权利提升等,比如DDE服务问题;服务能够在帐户登录之前由系统运行,
这也是木马喜欢的运行方式。而且很多本身提供的服务也特别让人喜爱:Telnet服务、Task服务、远程注册表操作、
SNMP服务等。
服务的安全问题来自下面这些方面:
1、服务应用程序本身的问题
这个安全问题是服务程序本身编写时造成的,由于大多数服务的运行帐号是LocalSystem,因此,这些问题
通常能够造成权限提升。比如:NetDDE服务的权限提升漏洞;Telnet服务的权限提升漏洞;处理在SNMP服务中的缓
冲区溢出,可以让攻击者远程用SYSTEM帐号权限执行命令。
一些服务属于网络服务,监听某个TCP端口,比如Telnet服务,可以进行远程漏洞利用,但是一些服务只是
本地的服务,不能远程利用,需要拥有本地帐号,然后把权限提升。
2、服务的启动问题
在服务管理中,服务的启动有三种方式:自动、手动和禁止。手动和禁止的启动方式不会直接将服务启动,
在需要的时候则需要手工将他们启动起来。这是一个很普遍的认识。
但是这里有一些问题。手动和禁止方式并不能完全禁止一个服务的运行。如果按照这两种方式启动的服务
被某自动运行的服务所依赖,那么,这些服务将被自动先运行。这可以从上面SCM的启动过程可以得到。而且禁止方
式的服务也可以手动运行起来。
SC工具提供删除服务的功能,这可以完全禁止一个服务的存在,但是需要谨慎运用。
3、服务的帐号问题
因为服务的LocalSystem帐号,使得服务受到了很多关照,因此,一些安全配置上介绍将服务的运行权限
降低,使用其他帐号来运行服务。当服务安装的时候,通过CreateService函数指定用户名和密码。可以通过
ChangeServiceConfig改变帐号内容。也可以通过QueryServiceConfig获得服务对象的用户名。
当启动服务的时候,SCM用服务的帐号登录。如果登录成功,系统就产生一个访问令牌赋予服务进程。该令牌会同
后来所获得对象交互。比如,一个服务试图打开管道句柄,系统就比较服务的令牌和管道的安全描述符。
在注册表中一个服务的项就含有一个键ObjectName,这个键指定的就是服务的运行帐号。如果运用其他帐号来运行
服务,那么在注册表中:HKEY_LOCAL_MACHINE\SECURITY\Policy\Secrets\_SC_(服务名)下的SecDesc键中就会保
存该帐号的密码散列,这同该帐号本身的密码散列是完全一样的。这里可以做的事情就比较多了。SCM不会维护服
务用户帐号的密码,系统改变密码的时候是不会通知SCM去更改密码。如果密码过期,或者密码更改,登录就失败,
服务启动也就失败。
4、服务的管理问题
由于服务在应用程序中显得很重要,而且一些服务彼此依赖,很多管理员也因此很难确定哪些服务是需要
的,哪些服务可以去关闭和禁止,从服务的简单介绍中,很难判别一个服务实际做的什么工作。而且现在也没有一
个现成的服务管理介绍,如果错误关闭某些服务,可能就造成系统某项功能的停止。
因为服务管理起来不是那么容易,因此,方便一些木马做成服务的形式来迷惑受害者。
VI、服务的管理
从服务的描述很难确定它们的真实用途。比如StorageGroveler服务,描述为:“ScansSingleInstance
Storage(SIS)volumesforduplicatefiles,andpointsduplicatesfilestoonedatastoragepoint,
conservingdiskspace“它做什么并没有提及,然而只被用语远程安装服务(RIS),如果你不需要远程安装,你
就不需要这个服务。
要决定这个服务真实做什么的,试试这样来:
*检查更多的描述细节,在MicrosoftTechNet’sWindows2000Services
(http://www.microsoft.com/technet/prodtechnol/windows2000serv/deploy/prodspecs/win2ksvc.asp)
*检查文件描述,通过右击文件并选择版本查看
*检查服务的依存关系
*检查服务打开的端口
·怎样知道一个服务使用的文件?
了解一个服务,需要了解这个服务所用的文件。通常,你能通过查看它运行的文件来决定是否需要这个服务。
这个批处理文件可以来查看文件属性,其中使用了WindowsResourceKit工具的:reg.exe和depends.exe:
@setimagepath=
@FOR/F“tokens=3“%%ain(’regqueryHKLM\system\currentcontrolset\services\%1
/vimagepath2^〉nul^|find“imagepath“’)DO@setimagepath=%%a
@ifdefinedimagepath(@echoDependenciesfor%imagepath%:
@calldepends/a0f1c/oc:~svcdep.tmp“%imagepath%“
@FOR/F“tokens=1delims=,skip=1“%%bin(’type~svcdep.tmp^|findstr/B/c:“,“^|
findstr/V/c:“?“^|sort’)do@echo%%b
@del~svcdep.tmp2〉nul
)else(@Echo’%1’isnotinstalledorisnotavalidservice
)
把上面内容保存为svcdep.bat,然后用服务名(短名称)做为参数启动,比如查看Replication服务,输入
svcdep.batntfrs:
可以看到:
Dependenciesfor%SystemRoot%\system32\ntfrs.exe:
“c:\winnt\system32\DBGHELP.DLL“
“c:\winnt\system32\DNSAPI.DLL“
“c:\winnt\system32\ESENT.DLL“
“c:\winnt\system32\GDI32.DLL“
“c:\winnt\system32\KERNEL32.DLL“
“c:\winnt\system32\MSVCRT.DLL“
“c:\winnt\system32\NETAPI32.DLL“
“c:\winnt\system32\NETRAP.DLL“
“c:\winnt\system32\NTDLL.DLL“
“c:\winnt\system32\NTDSAPI.DLL“
“c:\winnt\system32\NTFRS.EXE“
“c:\winnt\system32\RPCRT4.DLL“
“c:\winnt\system32\SAMLIB.DLL“
“c:\winnt\system32\SECUR32.DLL“
“c:\winnt\system32\USER32.DLL“
“c:\winnt\system32\WLDAP32.DLL“
“c:\winnt\system32\WS2_32.DLL“
“c:\winnt\system32\WS2HELP.DLL“
“c:\winnt\system32\WSOCK32.DLL“
从中可以看出它使用了DNS(DNSAPI.DLL)、Winsock(WS*.DLL)和RemoteProcedureCall(RPCRT4.DLL)库,这
不仅让你能了解服务会做什么,也能让你知道它需要什么。
现在的很多软件特别是安全软件都开始偏向于注册成服务,比如Norton就会写进一大堆的服务,除了向
上面那样去了解一个服务实际做的工作,维护一个服务列表也非常重要。经常性地检查服务列表,对照有什么变
化,是个不错的主意。SC是个好工具,在我的主页www.opengram.com上也有一个类似的程序(包括源代码)。要
知道服务的具体管理,可以去微软网站看看,也可以去这里:
http://www.plasma-online.de/index.html?content=http%3A//www.plasma-online.de/english/help/
solutions/nt4_services.html
禁止一个服务并不意味着不能使用该服务。比如对于Telnet服务,我以前写的工具OpenTelnet即便是在
禁止该服务的时候一样去打开它。要完全禁止该服务,最好还是直接删除它,或者将服务应用程序更换地方。
VII、结尾
虽然说了这么多,其实控制服务基本是需要管理员权限的。因此,管好自己的管理员是维护服务安全的
重要方面。
Reference:
1.MSDN
2.“NTServiceChecks“
http://www.nextgenss.com/typhon/reports/10.1.1.2/ntsvc.html
3.“InsideWin32Services“
http://www.winnetmag.com/Articles/Index.cfm?ArticleID=8943&pg=1
4.“HowtowriteaWindowsNTservice“
http://www.muukka.net/programming/service.html
5.“WindowsNTServices“
http://www.mailbag.com/users/pengel/index.html
6.“SecuringMicrosoftServices“
http://online.securityfocus.com/infocus/1581