2005年07月20日

有那么一类应用程序,是能够为各种用户(包括本地用户和远程用户)所用的,

拥有用户授权级进行管理的能力,并且不论用户是否物理的与正在运行该应用程

序的计算机相连都能正常执行,这就是所谓的服务了。

(一)服务的基础知识

Question 1. 什么是服务?它的特征是什么?

  在NT/2000中,服务是一类受到操作系统优待的程序。一个服务首先是一个

Win32可执行程序,如果要写一个功能完备且强大的服务,需要熟悉动态连接库

(Dlls)、结构异常处理、内存映射文件、虚拟内存、设备I/O、线程及其同步、

Unicode以及其他的由WinAPI函数提供的应用接口。当然本文讨论的只是建立一

个可以安装、运行、启动、停止的没有任何其他功能的服务,所以无需上述知识

仍可以继续看下去,我会在过程中将理解本文所需要的知识逐一讲解。

  第二要知道的是一个服务决不需要用户界面。大多数的服务将运行在那些被

锁在某些黑暗的,冬暖夏凉的小屋子里的强大的服务器上面,即使有用户界面一

般也没有人可以看到。如果服务提供任何用户界面如消息框,那么用户错过这些

消息的可能性就极高了,所以服务程序通常以控制台程序的形式被编写,进入点

函数是main()而不是WinMain()。

  也许有人有疑问:没有用户界面的话,要怎样设置、管理一个服务?怎样开

始、停止它?服务如何发出警告或错误信息、如何报告关于它的执行情况的统计

数据?这些问题的答案就是服务能够被远程管理,Windows NT/2000提供了大量

的管理工具,这些工具允许通过网络上的其它计算机对某台机器上面的服务进行

管理。比如Windows 2000里面的“控制台”程序(mmc.exe),用它添加“管理单

元”就可以管理本机或其他机器上的服务。

 

Question 2. 服务的安全性…

  想要写一个服务,就必须熟悉Win NT/2000的安全机制,在上述操作系统之

中,所有安全都是基于用户的。换句话说——进程、线程、文件、注册表键、信

号、事件等等等等都属于一个用户。当一个进程被产生的时候,它都是执行在一

个用户的上下文(context),这个用户帐号可能在本机,也可能在网络中的其他

机器上,或者是在一个特殊的账号:System Account——即系统帐号的上下文

  如果一个进程正在一个用户帐号下执行,那么这个进程就同时拥有这个用户

所能拥有的一切访问权限,不论是在本机还是网络。系统帐号则是一个特殊的账

号,它用来标识系统本身,而且运行在这个帐号下的任何进程都拥有系统上的所

有访问权限,但是系统帐号不能在域上使用,无法访问网络资源…

  服务也是Win32可执行程序,它也需要执行在一个context,通常服务都是在

系统账号下运行,但是也可以根据情况选择让它运行在一个用户账号下,也就会

因此获得相应的访问资源的权限。

 

Question 3. 服务的三个组成部分

  一个服务由三部分组成,第一部分是Service Control Manager(SCM)。每个

Windows NT/2000系统都有一个SCM,SCM存在于Service.exe中,在Windows启动

的时候会自动运行,伴随着操作系统的启动和关闭而产生和终止。这个进程以系

统特权运行,并且提供一个统一的、安全的手段去控制服务。它其实是一个RPC

Server,因此我们可以远程安装和管理服务,不过这不在本文讨论的范围之内。

SCM包含一个储存着已安装的服务和驱动程序的信息的数据库,通过SCM可以统一

的、安全的管理这些信息,因此一个服务程序的安装过程就是将自身的信息写入

这个数据库。

  第二部分就是服务本身。一个服务拥有能从SCM收到信号和命令所必需的的

特殊代码,并且能够在处理后将它的状态回传给SCM。

  第三部分也就是最后一部分,是一个Service Control Dispatcher(SCP)。

它是一个拥有用户界面,允许用户开始、停止、暂停、继续,并且控制一个或多

个安装在计算机上服务的Win32应用程序。SCP的作用是与SCM通讯,Windows

2000管理工具中的“服务”就是一个典型的SCP。

 

  在这三个组成部分中,用户最可能去写服务本身,同时也可能不得不写一个

与其伴随的客户端程序作为一个SCP去和SCM通讯,本文只讨论去设计和实现一个

服务,关于如何去实现一个SCP则在以后的其它文章中介绍。

Question 4. 怎样开始设计服务

  还记得前面我提到服务程序的入口点函数一般都是main()吗?一个服务拥有

很重要的三个函数,第一个就是入口点函数,其实用WinMain()作为入口点函数

也不是不可以,虽然说服务不应该有用户界面,但是其实存在很少的几个例外,

这就是下面图中的选项存在的原因。

 

  由于要和用户桌面进行信息交互,服务程序有时会以WinMain()作为入口点

函数。

  入口函数负责初始化整个进程,由这个进程中的主线程来执行。这意味着它

应用于这个可执行文件中的所有服务。要知道,一个可执行文件中能够包含多个

服务以使得执行更加有效。主进程通知SCM在可执行文件中含有几个服务,并且

给出每一个服务的ServiceMain回调(Call Back)函数的地址。一旦在可执行文件

内的所有服务都已经停止运行,主线程就在进程终止前对整个进程进行清除。

  第二个很重要的函数就是ServiceMain,我看过一些例子程序里面对自己的

服务的进入点函数都固定命名为ServiceMain,其实并没有规定过一定要那样命

名,任何的函数只要符合下列的形式都可以作为服务的进入点函数。

VOID WINAPI ServiceMain(
DWORD dwArgc, // 参数个数
LPTSTR *lpszArgv // 参数串
);

  这个函数由操作系统调用,并执行能完成服务的代码。一个专用的线程执行

每一个服务的ServiceMain函数,注意是服务而不是服务程序,这是因为每个服

务也都拥有与自己唯一对应的ServiceMain函数,关于这一点可以用“管理工具

”里的“服务”去察看Win2000里面自带的服务,就会发现其实很多服务都是由

service.exe单独提供的。当主线程调用Win32函数StartServiceCtrlDispatcher

的时候,SCM为这个进程中的每一个服务产生一个线程。这些线程中的每一个都

和它的相应的服务的ServiceMain函数一起执行,这就是服务总是多线程的原因

——一个仅有一个服务的可执行文件将有一个主线程,其它的线程执行服务本身

  第三个也就是最后的一个重要函数是CtrlHandler,它必须拥有下面的原型

VOID WINAPI CtrlHandler(
DWORD fdwControl //控制命令
)

  像ServiceMain一样,CtrlHandler也是一个回调函数,用户必须为它的服务

程序中每一个服务写一个单独的CtrlHandler函数,因此如果有一个程序含有两

个服务,那么它至少要拥有5个不同的函数:作为入口点的main()或WinMain(),

用于第一个服务的ServiceMain函数和CtrlHandler函数,以及用于第二个服务的

ServiceMain函数和CtrlHandler函数。

  SCM调用一个服务的CtrlHandler函数去改变这个服务的状态。例如,当某个

管理员用管理工具里的“服务”尝试停止你的服务的时候,你的服务的

CtrlHandler函数将收到一个SERVICE_CONTROL_STOP通知。CtrlHandler函数负责

执行停止服务所需的一切代码。由于是进程的主线程执行所有的CtrlHandler函

数,因而必须尽量优化你的CtrlHandler函数的代码,使它运行起来足够快,以

便相同进程中的其它服务的CtrlHandler函数能在适当的时间内收到属于它们的

通知。而且基于上述原因,你的CtrlHandler函数必须要能够将想要传达的状态

送到服务线程,这个传递过程没有固定的方法,完全取决于你的服务的用途。

(二)对服务的深入讨论之上

  上一章其实只是概括性的介绍,下面开始才是真正的细节所在。在进入点函

数里面要完成ServiceMain的初始化,准确点说是初始化一个

SERVICE_TABLE_ENTRY结构数组,这个结构记录了这个服务程序里面所包含的所

有服务的名称和服务的进入点函数,下面是一个SERVICE_TABLE_ENTRY的例子:

SERVICE_TABLE_ENTRY service_table_entry[] =
{
{ "MyFTPd" , FtpdMain },
{ "MyHttpd", Httpserv},
{ NULL, NULL },
};

  第一个成员代表服务的名字,第二个成员是ServiceMain回调函数的地址,

上面的服务程序因为拥有两个服务,所以有三个SERVICE_TABLE_ENTRY元素,前

两个用于服务,最后的NULL指明数组的结束。

  接下来这个数组的地址被传递到StartServiceCtrlDispatcher函数:

BOOL StartServiceCtrlDispatcher(
LPSERVICE_TABLE_ENTRY lpServiceStartTable
)

  这个Win32函数表明可执行文件的进程怎样通知SCM包含在这个进程中的服务

。就像上一章中讲的那样,StartServiceCtrlDispatcher为每一个传递到它的数

组中的非空元素产生一个新的线程,每一个进程开始执行由数组元素中的

lpServiceStartTable指明的ServiceMain函数。

  SCM启动一个服务程序之后,它会等待该程序的主线程去调

StartServiceCtrlDispatcher。如果那个函数在两分钟内没有被调用,SCM将会

认为这个服务有问题,并调用TerminateProcess去杀死这个进程。这就要求你的

主线程要尽可能快的调用StartServiceCtrlDispatcher。

  StartServiceCtrlDispatcher函数则并不立即返回,相反它会驻留在一个循

环内。当在该循环内时,StartServiceCtrlDispatcher悬挂起自己,等待下面两

个事件中的一个发生。第一,如果SCM要去送一个控制通知给运行在这个进程内

一个服务的时候,这个线程就会激活。当控制通知到达后,线程激活并调用相应

服务的CtrlHandler函数。CtrlHandler函数处理这个服务控制通知,并返回到

StartServiceCtrlDispatcher。StartServiceCtrlDispatcher循环回去后再一次

悬挂自己。

  第二,如果服务线程中的一个服务中止,这个线程也将激活。在这种情况下

,该进程将运行在它里面的服务数减一。如果服务数为零,

StartServiceCtrlDispatcher就会返回到入口点函数,以便能够执行任何与进程

有关的清除工作并结束进程。如果还有服务在运行,哪怕只是一个服务,

StartServiceCtrlDispatcher也会继续循环下去,继续等待其它的控制通知或者

剩下的服务线程中止。

  上面的内容是关于入口点函数的,下面的内容则是关于ServiceMain函数的

。还记得以前讲过的ServiceMain函数的的原型吗?但实际上一个ServiceMain函

数通常忽略传递给它的两个参数,因为服务一般不怎么传递参数。设置一个服务

最好的方法就是设置注册表,一般服务在
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Service\ServiceName\Parame

ters
子键下存放自己的设置,这里的ServiceName是服务的名字。事实上,可能要写

一个客户应用程序去进行服务的背景设置,这个客户应用程序将这些信息存在注

册表中,以便服务读取。当一个外部应用程序已经改变了某个正在运行中的服务

的设置数据的时候,这个服务能够用RegNotifyChangeKeyValue函数去接受一个

通知,这样就允许服务快速的重新设置自己。

  前面讲到StartServiceCtrlDispatcher为每一个传递到它的数组中的非空元

素产生一个新的线程。接下来,一个ServiceMain要做些什么呢?MSDN里面的原

文是这样说的:The ServiceMain function should immediately call the

RegisterServiceCtrlHandler function to specify a Handler function to

handle control requests. Next, it should call the SetServiceStatus

function to send status information to the service control manager. 为

什么呢?因为发出启动服务请求之后,如果在一定时间之内无法完成服务的初始

化,SCM会认为服务的启动已经失败了,这个时间的长度在Win NT 4.0中是80秒

,Win2000中不详…

  基于上面的理由,ServiceMain要迅速完成自身工作,首先是必不可少的两

项工作,第一项是调用RegisterServiceCtrlHandler函数去通知SCM它的

CtrlHandler回调函数的地址:

SERVICE_STATUS_HANDLE RegisterServiceCtrlHandler(
LPCTSTR lpServiceName, //服务的名字
LPHANDLER_FUNCTION lpHandlerProc //CtrlHandler函数地址
)


  第一个参数指明你正在建立的CtrlHandler是为哪一个服务所用,第二个参

数是CtrlHandler函数的地址。lpServiceName必须和在SERVICE_TABLE_ENTRY里

面被初始化的服务的名字相匹配。RegisterServiceCtrlHandler返回一个

SERVICE_STATUS_HANDLE,这是一个32位的句柄。SCM用它来唯一确定这个服务。

当这个服务需要把它当时的状态报告给SCM的时候,就必须把这个句柄传给需要

它的Win32函数。注意:这个句柄和其他大多数的句柄不同,你无需关闭它。

  SCM要求ServiceMain函数的线程在一秒钟内调用

RegisterServiceCtrlHandler函数,否则SCM会认为服务已经失败。但在这种情

况下,SCM不会终止服务,不过在NT 4中将无法启动这个服务,同时会返回一个

不正确的错误信息,这一点在Windows 2000中得到了修正。

  在RegisterServiceCtrlHandler函数返回后,ServiceMain线程要立即告诉

SCM服务正在继续初始化。具体的方法是通过调用SetServiceStatus函数传递

SERVICE_STATUS数据结构。

BOOL SetServiceStatus(
SERVICE_STATUS_HANDLE hService, //服务的句柄
SERVICE_STATUS lpServiceStatus //SERVICE_STATUS结构的地址
)

  这个函数要求传递给它指明服务的句柄(刚刚通过调用

RegisterServiceCtrlHandler得到),和一个初始化的SERVICE_STATUS结构的地

址:

typedef struct _SERVICE_STATUS
{
DWORD dwServiceType;
DWORD dwCurrentState;
DWORD dwControlsAccepted;
DWORD dwWin32ExitCode;
DWORD dwServiceSpecificExitCode;
DWORD dwCheckPoint;
DWORD dwWaitHint;
} SERVICE_STATUS, *LPSERVICE_STATUS;

  SERVICE_STATUS结构含有七个成员,它们反映服务的现行状态。所有这些成

员必须在这个结构被传递到SetServiceStatus之前正确的设置。

  成员dwServiceType指明服务可执行文件的类型。如果你的可执行文件中只

有一个单独的服务,就把这个成员设置成SERVICE_WIN32_OWN_PROCESS;如果拥

有多个服务的话,就设置成SERVICE_WIN32_SHARE_PROCESS。除了这两个标志之

外,如果你的服务需要和桌面发生交互(当然不推荐这样做),就要用“OR”运算

符附加上SERVICE_INTERACTIVE_PROCESS。这个成员的值在你的服务的生存期内

绝对不应该改变。

  成员dwCurrentState是这个结构中最重要的成员,它将告诉SCM你的服务的

现行状态。为了报告服务仍在初始化,应该把这个成员设置成

SERVICE_START_PENDING。在以后具体讲述CtrlHandler函数的时候具体解释其它

可能的值。

  成员dwControlsAccepted指明服务愿意接受什么样的控制通知。如果你允许

一个SCP去暂停/继续服务,就把它设成SERVICE_ACCEPT_PAUSE_CONTINUE。很多

服务不支持暂停或继续,就必须自己决定在服务中它是否可用。如果你允许一个

SCP去停止服务,就要设置它为SERVICE_ACCEPT_STOP。如果服务要在操作系统关

闭的时候得到通知,设置它为SERVICE_ACCEPT_SHUTDOWN可以收到预期的结果。

这些标志可以用“OR”运算符组合。

  成员dwWin32ExitCode和dwServiceSpecificExitCode是允许服务报告错误的

关键,如果希望服务去报告一个Win32错误代码(预定义在WinError.h中),它就

设置dwWin32ExitCode为需要的代码。一个服务也可以报告它本身特有的、没有

映射到一个预定义的Win32错误代码中的错误。为了这一点,要把

dwWin32ExitCode设置为ERROR_SERVICE_SPECIFIC_ERROR,然后还要设置成员

dwServiceSpecificExitCode为服务特有的错误代码。当服务运行正常,没有错

误可以报告的时候,就设置成员dwWin32ExitCode为NO_ERROR。

  最后的两个成员dwCheckPoint和dwWaitHint是一个服务用来报告它当前的事

件进展情况的。当成员dwCurrentState被设置成SERVICE_START_PENDING的时候

,应该把dwCheckPoint设成0,dwWaitHint设成一个经过多次尝试后确定比较合

适的数,这样服务才能高效运行。一旦服务被完全初始化,就应该重新初始化

SERVICE_STATUS结构的成员,更改dwCurrentState为SERVICE_RUNNING,然后把

dwCheckPoint和dwWaitHint都改为0。

  dwCheckPoint成员的存在对用户是有益的,它允许一个服务报告它处于进程

的哪一步。每一次调用SetServiceStatus时,可以增加它到一个能指明服务已经

执行到哪一步的数字,它可以帮助用户决定多长时间报告一次服务的进展情况。

如果决定要报告服务的初始化进程的每一步,就应该设置dwWaitHint为你认为到

达下一步所需的毫秒数,而不是服务完成它的进程所需的毫秒数。

  在服务的所有初始化都完成之后,服务调用SetServiceStatus指明

SERVICE_RUNNING,在那一刻服务已经开始运行。通常一个服务是把自己放在一

个循环之中来运行的。在循环的内部这个服务进程悬挂自己,等待指明它下一步

是应该暂停、继续或停止之类的网络请求或通知。当一个请求到达的时候,服务

线程激活并处理这个请求,然后再循环回去等待下一个请求/通知。

  如果一个服务由于一个通知而激活,它会先处理这个通知,除非这个服务得

到的是停止或关闭的通知。如果真的是停止或关闭的通知,服务线程将退出循环

,执行必要的清除操作,然后从这个线程返回。当ServiceMain线程返回并中止

时,引起在StartServiceCtrlDispatcher内睡眠的线程激活,并像在前面解释过

的那样,减少它运行的服务的计数。

(三)对服务的深入讨论之下
 

  现在我们还剩下一个函数可以在细节上讨论,那就是服务的CtrlHandler函

数。

  当调用RegisterServiceCtrlHandler函数时,SCM得到并保存这个回调函数

的地址。一个SCP调一个告诉SCM如何去控制服务的Win32函数,现在已经有10个

预定义的控制请求:

Control code  Meaning 
SERVICE_CONTROL_STOP Requests the service to stop. The hService handle

must have SERVICE_STOP access.
SERVICE_CONTROL_PAUSE Requests the service to pause. The hService

handle must have SERVICE_PAUSE_CONTINUE access.
SERVICE_CONTROL_CONTINUE Requests the paused service to resume. The

hService handle must have SERVICE_PAUSE_CONTINUE access.
SERVICE_CONTROL_INTERROGATE Requests the service to update immediately

its current status information to the service control manager. The

hService handle must have SERVICE_INTERROGATE access.
SERVICE_CONTROL_SHUTDOWN Requests the service to perform cleanup

tasks, because the system is shutting down. For more information, see

Remarks.
SERVICE_CONTROL_PARAMCHANGE Windows 2000: Requests the service to

reread its startup parameters. The hService handle must have

SERVICE_PAUSE_CONTINUE access.
SERVICE_CONTROL_NETBINDCHANGE Windows 2000: Requests the service to

update its network binding. The hService handle must have

SERVICE_PAUSE_CONTINUE access.
SERVICE_CONTROL_NETBINDREMOVE Windows 2000: Notifies a network service

that a component for binding has been removed. The service should

reread its binding information and unbind from the removed component.
SERVICE_CONTROL_NETBINDENABLE Windows 2000: Notifies a network service

that a disabled binding has been enabled. The service should reread

its binding information and add the new binding.
SERVICE_CONTROL_NETBINDDISABLE Windows 2000: Notifies a network

service that one of its bindings has been disabled. The service should

reread its binding information and remove the binding.

  上表中标有Windows 2000字样的就是2000中新添加的控制代码。除了这些代

码之外,服务也可以接受用户定义的,范围在128-255之间的代码。

  当CtrlHandler函数收到一个SERVICE_CONTROL_STOP、

SERVICE_CONTROL_PAUSE、 SERVICE_CONTROL_CONTINUE控制代码的时候,

SetServiceStatus必须被调用去确认这个代码,并指定你认为服务处理这个状态

变化所需要的时间。

  例如:你的服务收到了停止请求,首先要把SERVICE_STATUS结构的

dwCurrentState成员设置成SERVICE_STOP_PENDING,这样可以使SCM确定你已经

收到了控制代码。当一个服务的暂停或停止操作正在执行的时候,必须指定你认

为这种操作所需要的时间:这是因为一个服务也许不能立即改变它的状态,它可

能必须等待一个网络请求被完成或者数据被刷新到一个驱动器上。指定时间的方

法就像我上一章说的那样,用成员dwCheckPoint和dwWaitHint来指明它完成状态

改变所需要的时间。如果需要,可以用增加dwCheckPoint成员的值和设置

dwWaitHint成员的值去指明你期待的服务到达下一步的时间的方式周期性的报告

进展情况。

  当整个启动的过程完成之后,要再一次调用SetServiceStatus。这时就要把

SERVICE_STATUS结构的dwCurrentState成员设置成SERVICE_STOPPED,当报告状

态代码的同时,一定要把成员dwCheckPoint和dwWaitHint设置为0,因为服务已

经完成了它的状态变化。暂停或继续服务的时候方法也一样。

  当CtrlHandler函数收到一个SERVICE_CONTROL_INTERROGATE控制代码的时候

,服务将简单的将dwCurrentState成员设置成服务当前的状态,同时,把成员

dwCheckPoint和dwWaitHint设置为0,然后再调用SetServiceStatus就可以了。

  在操作系统关闭的时候,CtrlHandler函数收到一个

SERVICE_CONTROL_SHUTDOWN控制代码。服务根本无须回应这个代码,因为系统即

将关闭。它将执行保存数据所需要的最小行动集,这是为了确定机器能及时关闭

。缺省时系统只给很少的时间去关闭所有的服务,MSDN里面说大概是20秒的时间

,不过那可能是Windows NT 4的设置,在我的Windows 2000 Server里这个时间

是10秒,你可以手动的修改这个数值,它被记录在

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control子键里面的

WaitToKillServiceTimeout,单位是毫秒。

 

  当CtrlHandler函数收到任何用户定义的代码时,它应该执行期望的用户自

定义行动。除非用户自定义的行动要强制服务去暂停、继续或停止,否则不调

SetServiceStatus函数。如果用户定义的行动强迫服务的状态发生变化,

SetServiceStatus将被调用去设置dwCurrentState、dwCheckPoint和dwWaitHint

,具体控制代码和前面说的一样。

  如果你的CtrlHandler函数需要很长的时间执行操作的话,千万要注意:假

如CtrlHandler函数在30秒内没有返回的话,SCM将返回一个错误,这不是我们所

期望的。所以如果出现上述情况,最好的办法是再建立一个线程,让它去继续执

行操作,以便使得CtrlHandler函数能够迅速的返回。例如,当收到一个

SERVICE_CONTROL_STOP请求的时候,就像上面说的一样,服务可能正在等待一个

网络请求被完成或者数据被刷新到一个驱动器上,而这些操作所需要的时间是你

不能估计的,那么就要建立一个新的线程等待操作完成后执行停止命令,

CtrlHandler函数在返回之前仍然要报告SERVICE_STOP_PENDING状态,当新的线

程执行完操作之后,再由它将服务的状态设置成SERVICE_STOPPED。如果当前操

作的时间可以估计的到就不要这样做,仍然使用前面交待的方法处理。

  CtrlHandler函数我就先讲这些,下面说说服务怎么安装。一个服务程序可

以使用CreateService函数将服务的信息添加到SCM的数据库。

SC_HANDLE CreateService( SC_HANDLE hSCManager, // handle to SCM

database LPCTSTR lpServiceName, // name of service to start LPCTSTR

lpDisplayName, // display name DWORD dwDesiredAccess, // type of

access to service DWORD dwServiceType, // type of service DWORD

dwStartType, // when to start service DWORD dwErrorControl, //

severity of service failure LPCTSTR lpBinaryPathName, // name of

binary file LPCTSTR lpLoadOrderGroup, // name of load ordering group

LPDWORD lpdwTagId, // tag identifier LPCTSTR lpDependencies, // array

of dependency names LPCTSTR lpServiceStartName, // account name

LPCTSTR lpPassword // account password );

  hSCManager是一个标示SCM数据库的句柄,可以简单的通过调用

OpenSCManager得到。

SC_HANDLE OpenSCManager( LPCTSTR lpMachineName, // computer name

LPCTSTR lpDatabaseName, // SCM database name DWORD dwDesiredAccess //

access type );

  lpMachineName是目标机器的名字,还记得我在第一章里说过可以在其它的

机器上面安装服务吗?这就是实现的方法。对方机器名字必须以“\\”开始。如

果传递NULL或者一个空的字符串的话就默认是本机。

  lpDatabaseName是目标机器上面SCM数据库的名字,但MSDN里面说这个参数

要默认的设置成SERVICES_ACTIVE_DATABASE,如果传递NULL,就默认的打开

SERVICES_ACTIVE_DATABASE。所以我还没有真的搞明白这个参数的存在意义,总

之使用的时候传递NULL就行了。

  dwDesiredAccess是SCM数据库的访问权限,具体值见下表:

Object access  Description 
SC_MANAGER_ALL_ACCESS Includes STANDARD_RIGHTS_REQUIRED, in addition

to all of the access types listed in this table.
SC_MANAGER_CONNECT Enables connecting to the service control manager.
SC_MANAGER_CREATE_SERVICE Enables calling of the CreateService

function to create a service object and add it to the database.
SC_MANAGER_ENUMERATE_SERVICE Enables calling of the EnumServicesStatus

function to list the services that are in the database.
SC_MANAGER_LOCK Enables calling of the LockServiceDatabase function to

acquire a lock on the database.
SC_MANAGER_QUERY_LOCK_STATUS Enables calling of the

QueryServiceLockStatus function to retrieve the lock status

information for the database.

  想要获得访问权限的话,似乎没那么复杂。MSDN里面说所有进程都被允许获

得对所有SCM数据库的SC_MANAGER_CONNECT, SC_MANAGER_ENUMERATE_SERVICE,

and SC_MANAGER_QUERY_LOCK_STATUS权限,这些权限使得你可以连接SCM数据库

,枚举目标机器上安装的服务和查询目标数据库是否已被锁住。但如果要创建服

务,首先你需要拥有目标机器的管理员权限,一般的传递

SC_MANAGER_ALL_ACCESS就可以了。这个函数返回的句柄可以被

CloseServiceHandle函数关闭。

  lpServiceName是服务的名字,lpDisplayName是服务在“服务”管理工具里

显示的名字。

  dwDesiredAccess也是访问的权限,有一个比上面的还长的多的一个表,各

位自己查MSDN吧。我们要安装服务,仍然简单的传递SC_MANAGER_ALL_ACCESS。

  dwServiceType是指你的服务是否和其它的进程相关联,一般是

SERVICE_WIN32_OWN_PROCESS,表示不和任何进程相关联。如果你确认你的服务

需要和某些进程相关联,就设置成SERVICE_WIN32_SHARE_PROCESS。当你的服务

要和桌面相关联的时候,需要设置成SERVICE_INTERACTIVE_PROCESS。

  dwStartType是服务的启动方式。服务有三种启动方式,分别是“自动

(SERVICE_AUTO_START)”“手动(SERVICE_DEMAND_START)”和“禁用

(SERVICE_DISABLED)”。在MSDN里还有另外的两种方式,不过是专为驱动程序设

置的。

  dwErrorControl决定服务如果在系统启动的时候启动失败的话要怎么办。

值  意义 
SERVICE_ERROR_IGNORE 启动程序记录错误发生,但继续启动。
SERVICE_ERROR_NORMAL 启动程序记录错误发生,并弹出一个消息框,但仍继续

启动
SERVICE_ERROR_SEVERE 启动程序记录错误发生,如果是以last-known-good

configuration启动的话,启动会继续。否则会以last-known-good

configuration重新启动计算机。
SERVICE_ERROR_CRITICAL 启动程序记录错误发生,如果可能的话。如果是以

last-known-good configuration启动的话,启动会失败。否则会以last-known

-good configuration重新启动计算机。好严重的错误啊。

  lpBinaryPathName是服务程序的路径。MSDN里面特别提到如果服务路径里面

有空格的话一定要将路径用引号引起来。例如"d:\\my share\\myservice.exe"

就一定要指定为"\"d:\\my share\\myservice.exe\""。

  lpLoadOrderGroup的意义在于,如果有一组服务要按照一定的顺序启动的话

,这个参数用于指定一个组名用于标志这个启动顺序组,不过我还没有用过这个

参数。你的服务如果不属于任何启动顺序组,只要传递NULL或者一个空的字符串

就行了。

  lpdwTagId是应用了上面的参数之后要指定的值,专用于驱动程序,与本文

内容无关。传递NULL。

  lpDependencies标示一个字符串数组,用于指明一串服务的名字或者一个启

动顺序组。当与一个启动顺序组建立关联的时候,这个参数的含义就是只有你指

定的启动顺序组里有至少一个经过对整个组里所有的成员已经全部尝试过启动后

,有至少一个成员成功启动,你的服务才能启动。不需要建立依存关系的话,仍

是传递NULL或者一个空的字符串。但如果你要指定启动顺序组的话,必须为组名

加上SC_GROUP_IDENTIFIER前缀,因为组名和服务名是共享一个命名空间的。

  lpServiceStartName是服务的启动账号,如果你设置你的服务的关联类型是

SERVICE_WIN32_OWN_PROCESS的话,你需要以DomainName\UserName的格式指定用

户名,如果这个账户在你本机的话,用.\UserName就可以指定。如果传递NULL的

话,会以本地的系统账户登陆。如果是Win NT 4.0或更早的版本的话,如果你指

定了SERVICE_WIN32_SHARE_PROCESS,就必须传递.\System指定服务使用本地的

系统账户。最后,如果你指定了SERVICE_INTERACTIVE_PROCESS,你必须使服务

运行在本机系统账户。

  看名字就知道了,lpPassword是账户的密码。如果指定系统账户的话,传递

NULL。如果账户没有密码的话,传递空字符串。

  总之服务的基本原理就是这样子了,到了这里这篇文章似乎可以告一段落了

,但实际上还有很多内容必须要讨论,所以我还不能草草收笔,敬请关注下一章


2005年04月19日

WinPcap 教程
原文出处:http://winpcap.polito.it/docs/man/html/index.html

作者:
Loris Degioanni (degioanni@polito.it), NetGroup, Politecnico di Torino
http://winpcap.polito.it

译者:

记忆碎片 (val_cong@htomail.com)
http://www.s8s8.net

概述:
这篇教程将会指引读者逐步了解WinPcap编程, 从简单的基础函数(获取网络接口列表, 捕捉数据包)到更高级的内容(处理发送队列, 网络流量统计). 教程中包括一些代码片断, 以及一些简单但完整的例子, 读者可以参考这些例子更好的理解教程的内容. 这些例子全部用C语言写成, 所以基本的C语言编程知识是必要. 同时, 因为这篇教程的内容是与底层网络紧密相连的, 所以笔者假设读者已经具备有关网络和协议的相关知识.

译者的话:
WinPcap是一套免费的, 基于Windows的网络接口API, 它在底层网络操作方面对程序员很有帮助. 这篇文档翻译自 "WinPcap Documentation 3.0" 中的 "WinPcap tutorial: a step by step guide to program WinPcap" 一部分. 这篇教程对初学者的帮助很大, 尤其是简短清晰的例子, 但这篇教程只是整个文档的一小部分, 我认为你仍然需要参考文档的其它部分来了解各种结构等信息. 教程中注有前缀 "Y-" 的部分是译者为了让读者更明白作者的意思添加的, 原文中没有.

1. 获取网络接口列表

通常, 一个基于WinPcap的应用程序所要做的第一件事, 就是获得适合的网络接口的列表. Libpcap中的pcap_findalldevs()函数就是干这活的: 这个函数然回一个pcap_if结构的列表, 每个元素都记录了一个接口的信息. 其中, name和description以人类可以阅读的形式, 记录了设备的信息.
下面的源代码输出可用的网络接口的列表, 并且在没有找到任何借口的情况下输出错误信息:



代码

#include "pcap.h"
main()
{
pcap_if_t *alldevs;
pcap_if_t *d;
int i=0;
char errbuf[PCAP_ERRBUF_SIZE];

/* 取得列表 */
if (pcap_findalldevs(&alldevs, errbuf) == -1)
{
fprintf(stderr,"Error in pcap_findalldevs: %s\n", errbuf);
exit(1);
}

/* 输出列表 */
for(d=alldevs;d;d=d->next)
{
printf("%d. %s", ++i, d->name);
if (d->description)
printf(" (%s)\n", d->description);
else
/* Y- 没有有效的描述 */
printf(" (No description available)\n");
}

if(i==0)
{
/* Y- 没有有效的接口, 可能是因为没有安装WinPcap */
printf("\nNo interfaces found! Make sure WinPcap is installed.\n");
return;
}

/* 我们不再需要列表了, 释放 */
pcap_freealldevs(alldevs);
}



我们来看看这段代码.

首先, 和其他的libpcap函数一样, pcap_findalldevs(), 有一个错误缓冲区(errbuf)参数. 这个参数是一个字符串指针, 一旦发生错误,libpcap将会在这里填入错误描述. 然后, 请注意, pcap_findalldev系统下的s()函数同时也被UNIX下的libpcap所支持, 但是并不是所有的操作系统都支持“网络接口描述”(description)这一项. 所以, 如果我们想写一个可以移植的的应用程序,那么我们必须要为描述为“空”(null)的情况做好准备:遇到这种情况我们就输出一个“没有有效的描述”的消息.

最后我们通过pcap_freealldevs()函数来释放接口列表.

现在让我们编译并运行我们的第一个WinPcap程序. 如果你使用UNIX或者Cgywin的话, 你只需要以下命令:

gcc -o testaprog testprog.c -lpcap

在Windows环境中(Y – 如果你使用Microsoft Visual C++), 你需要建立一个工程, 按照"Using WinPcap in your programs " 一节中说明来做.
不过, 我仍然建议你参照Winpcap开发者包(WinPcap developer’s pack)中的例子, 那些例子包括了所以配置完善的工程, 以及全部你所需要的库和包含文件.
(Y – 你可以在本章最后找到Microsoft Visual C++ 的配置方法)

假设现在你已经成功编译了程序, 我们就来运行它. 在我的WinXP工作站上, 输出结果是:
1. {4E273621-5161-46C8-895A-48D0E52A0B83} (Realtek RTL8029(AS) Ethernet Adapter)
2. {5D24AE04-C486-4A96-83FB-8B5EC6C7F430} (3Com EtherLink PCI)

就如你所看到的, 网络接口的名称(当打开这个接口时, 需要传递这个名称给libpcap库)在windows环境下几乎是没有办法读懂的(Y-严重同意), 所以输出一个描述对于你的用户来说是非常有帮助的.


附注: Microsoft Visual C++ 工程的设置
1. 下载并安装 WinPcap, 推荐的版本是3.0
2. 从 http://winpcap.polito.it 下载 WinPcap Developer’s Pack 并解压缩
3. 用 Microsoft Visual C++ 建立一个空工程 (empty project)
4. 复制源代码
5. 把 Winpcap Developer’s Pack 中的 Includes 目录添加为新的包含文件目录
6. 添加库 wpcap.lib 和 wsock32.lib
原文出处:http://winpcap.polito.it/docs/man/html/index.html

作者:
Loris Degioanni (degioanni@polito.it), NetGroup, Politecnico di Torino
http://winpcap.polito.it

译者:

记忆碎片 (val_cong@htomail.com)
http://www.s8s8.net


2. 获取设备的高级信息

上一课我们介绍了如何获取一个设备的基本信息(比如设备名称和设备描述). 实际上, WinPcap 也可以为我们提供关于接口的更多信息. 由 pcap_findalldevs() 函数返回的 pcap_if 结构也包含了一个 pcap_addr 结构的列表, 它记录了以下信息:
1. 接口的地址列表
2. 接口的掩码列表 (与地址列表一一对应)
3. 接口的广播地址列表 (与地址列表一一对应)
4. 目标地址列表 (与地址列表一一对应)

下面例子中的 ifprint() 函数将会输出 pcap_if 结构的全部内容. 它包括了 pcap_findalldevs() 函数所返回的所有元素. ( Y- 全部有效接口)

代码


/*
* Copyright (c) 1999 – 2002
* Politecnico di Torino. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that: (1) source code distributions
* retain the above copyright notice and this paragraph in its entirety, (2)
* distributions including binary code include the above copyright notice and
* this paragraph in its entirety in the documentation or other materials
* provided with the distribution, and (3) all advertising materials mentioning
* features or use of this software display the following acknowledgement:
* “This product includes software developed by the Politecnico
* di Torino, and its contributors.” Neither the name of
* the University nor the names of its contributors may be used to endorse
* or promote products derived from this software without specific prior
* written permission.
* THIS SOFTWARE IS PROVIDED “AS IS” AND WITHOUT ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
*/

#include "pcap.h"
#ifndef WIN32
#include
#include
#else
#include
#endif

void ifprint(pcap_if_t *d);
char *iptos(u_long in);

int main()
{
pcap_if_t *alldevs;
pcap_if_t *d;
char errbuf[PCAP_ERRBUF_SIZE+1];

/* 获得设备列表 */
if (pcap_findalldevs(&alldevs, errbuf) == -1)
{
fprintf(stderr,"Error in pcap_findalldevs: %s\n",errbuf);
exit(1);
}

/* 遍历所有元素 */
for(d=alldevs;d;d=d->next)
{
ifprint(d);
}

return 1;
}

/* Print all the available information on the given interface */
void ifprint(pcap_if_t *d)
{
pcap_addr_t *a;

/* 名称 */
printf("%s\n",d->name);

/* 描述 */
if (d->description)
printf("\tDescription: %s\n",d->description);

/* 回环地址 */
printf("\tLoopback: %s\n",(d->flags & PCAP_IF_LOOPBACK)?"yes":"no");

/* IP 地址 */
for(a=d->addresses;a;a=a->next) {
printf("\tAddress Family: #%d\n",a->addr->sa_family);

switch(a->addr->sa_family)
{
case AF_INET:
printf("\tAddress Family Name: AF_INET\n");
if (a->addr)
/* Y- IP 地址 */
printf("\tAddress: %s\n",iptos(((struct sockaddr_in *)a->addr)->sin_addr.s_addr));
if (a->netmask)
/* Y- 掩码 */
printf("\tNetmask: %s\n",iptos(((struct sockaddr_in *)a->netmask)->sin_addr.s_addr));
if (a->broadaddr)
/* Y- 广播地址 */
printf("\tBroadcast Address: %s\n",iptos(((struct sockaddr_in *)a->broadaddr)->sin_addr.s_addr));
if (a->dstaddr)
/* Y – 目标地址 */
printf("\tDestination Address: %s\n",iptos(((struct sockaddr_in *)a->dstaddr)->sin_addr.s_addr));
break;
default:
/* 未知 */
printf("\tAddress Family Name: Unknown\n");
break;
}
}
printf("\n");
}

/* 来自 tcptracert, 把数字IP地址转换为点格式 */
#define IPTOSBUFFERS 12
char *iptos(u_long in)
{
static char output[IPTOSBUFFERS][3*4+3+1];
static short which;
u_char *p;

p = (u_char *)∈
which = (which + 1 == IPTOSBUFFERS ? 0 : which + 1);
sprintf(output[which], "%d.%d.%d.%d", p[0], p[1], p[2], p[3]);
return output[which];
}
原文出处: http://winpcap.polito.it/docs/man/html/index.html

作者:
Loris Degioanni (degioanni@polito.it), NetGroup, Politecnico di Torino
http://winpcap.polito.it

译者:
记忆碎片 (val_cong@htomail.com)
http://www.s8s8.net

3. 打开一个接口并捕捉流量

现在我们已经知道如何获取一个接口的有关信息了, 我们可以来点真家伙了 — 打开一个接口并捕捉流量. 在这一课里, 我们会编译一个程序, 它将捕捉网络中所有的数据包并输出他们的一些相关信息。我们使用函数 pcap_open_live() 来打开一个捕捉设备. 这里, 我们需要解释一下 snaplen, promisc 和 to_ms 参数.

( Y- 函数原型: pcap_t * pcap_open_live (char *device, int snaplen, int promisc, int to_ms, char *ebuf) )

"snaplen" 参数指定了要捕捉的数据包的部分. 在某些操作系统中 (如 xBSD 和 Win32), 驱动程序提供了只捕捉每个数据包其中一部分的可能性: 这样就降低了要处理的数据的量, 从而提高了捕捉程序的效率. 在例子中, 我们使用一个高出 MTU 最大值的值 (65536) 以确保可以捕捉到成个数据包.

"promisc" 表明接口将会被设置为混杂模式. 一般情况下, 接口只处理目标地址为自己的数据; 到其他主机的数据包将会被忽略. 然而当一个接口处于混杂模式时, 它将会处理全部的流量: 也就是说, 在共享媒介 ( Y- 才疏学浅, 不知道怎么翻译好 ), 例如非交换型以太网 ( Y- 比如基于集线器的网络 )中, WinPcap 可以捕捉到所有主机的数据包. 混在模式是多数捕捉程序的默认模式, 所以我们在例子中也采用这种模式.

"to_ms" 用以设置超时, 单位是毫秒. 一个从接口读取 ( Y- 捕捉) 的操作, (例如 pcap_dispatch() 或者 pcap_next_ex()), 如果没有捕捉到数据包, 那么在超过指定的时间以后就会返回. 进一步说, 如果接口处在静态模式中, to_ms 也定义了静态报告的间隔时间 (参阅 "Gathering Statistics on the network traffic " 以获取更多信息). 设置 to_ms 为 0, 则说明永远不会超时, 如果没有数据包到达, 那么捕捉操作将会永远不会返回, 而将其值设置为 -1 则会立刻返回.

代码

#include "pcap.h"

/* 数据包处理函数声明 */
void packet_handler(u_char *param, const struct pcap_pkthdr *header, const u_char *pkt_data);

main()
{
pcap_if_t *alldevs;
pcap_if_t *d;
int inum;
int i=0;
pcap_t *adhandle;
char errbuf[PCAP_ERRBUF_SIZE];

/* 获取设备列表 */
if (pcap_findalldevs(&alldevs, errbuf) == -1)
{
fprintf(stderr,"Error in pcap_findalldevs: %s\n", errbuf);
exit(1);
}

/* 数据列表 */
for(d=alldevs; d; d=d->next)
{
printf("%d. %s", ++i, d->name);
if (d->description)
printf(" (%s)\n", d->description);
else
printf(" (No description available)\n");
}

if(i==0)
{
printf("\nNo interfaces found! Make sure WinPcap is installed.\n");
return -1;
}

printf("Enter the interface number (1-%d):",i);
scanf("%d", &inum);

if(inum < 1 || inum > i)
{
printf("\nInterface number out of range.\n");
/* 释放设备列表 */
pcap_freealldevs(alldevs);
return -1;
}

/* 转到选择的设备 */
for(d=alldevs, i=0; i< inum-1;d=d->next, i++);

/* 打开设备 */
if ( (adhandle= pcap_open_live(d->name, //设备名
65536, // 捕捉完整的数据包
1, // 混在模式
1000, // 读入超时
errbuf // 错误缓冲
) ) == NULL)
{
/* Y- 打开失败*/
fprintf(stderr,"\nUnable to open the adapter. %s is not supported by WinPcap\n");
/* 释放列表 */
pcap_freealldevs(alldevs);
return -1;
}

printf("\nlistening on %s…\n", d->description);

/* 我们已经不需要设备列表了, 释放它 */
pcap_freealldevs(alldevs);

/* 开始捕捉 */
pcap_loop(adhandle, 0, packet_handler, NULL);

return 0;
}


/* 处理数据包的回调函数*/
void packet_handler(u_char *param, const struct pcap_pkthdr *header, const u_char *pkt_data)
{
struct tm *ltime;
char timestr[16];

/* 转换时间戳为可以阅读的格式 */
ltime=localtime(&header->ts.tv_sec);
strftime( timestr, sizeof timestr, "%H:%M:%S", ltime);

printf("%s,%.6d len:%d\n", timestr, header->ts.tv_usec, header->len);

}


一旦接口被打开, pcap_dispatch() 或者 pcap_loop() 函数将会开始捕捉. 这两个函数非常相似, pcap_dispatch() 将会在超时后直接返回, 而 pcap_loop() 则一定要等到一定数量的数据包被处理了以后才会返回 (Y- 第二个参数指定了要处理的数据包的数量, 0 为无限, 在这里, 我们设置的超时对 pcap_loop() 不起作用.) 在本例中, pcap_loop() 已经足够我们使用了, 而 pcap_dispatch() 一般应用在更复杂的程序里.

这两个函数都有一个回调参数, 只想一个处理数据包的函数, 如本例中的 packet_handler. 每当有新的数据包到来的时候, libpcap将会调用这个函数来处理数据包, libpcap也会提供这个数据包的一些信息: 一个首部, 包含了时间戳和长度信息 (Y-header 参数); 真实数据包 (Y- pkt_data参数), 包括各种协议首部. 请注意, MAC CRC一般不会出现, 因为当设备(网卡)进行帧确认操作时, 它就已经被移除了. 同时, 大部分网卡将会丢弃错误的 CRC, 所以 WinPcap 基本上也不能捕捉他们.

上面的例子只输出每个数据包时间戳以及长度 (来自 pcap_pkthdr header).

2005年04月16日

RT

2004年10月28日
学习PHP2个月了,收获挺多.但是与别人不同的是,我更喜欢SOCKET.PHP在SOCKET这方面的文章太少了.所以决定写一系列PHP-SOCKET读书笔记.一直从最基本写到SOCKET_RAW.
实例+心得.实例将会有端口转发(突破防火墙),动网类型EXP,端口扫描,PHP后门,发包型EXP框架.由于学习缘故,每周只能写一篇.现给出卷一.希望大家一起投入到PHP SHELL编程中来.

前言:

PHP是世界上最流行的脚本语言之一。一直以来它在WEB编程中得到极广泛的应用。我想说的是PHP不仅在WEB方面出色,在SHELL方面同样出色。只是人们更习惯用PERL来编写SHELL脚本.这里申明一下,本人不是PHP高手,接触PHP不过几个星期,这只是一篇读书笔记。有错误的地方请提出来。也可以给我MAIL,共同探讨PHP。

前置知识:

PHP最吸引我的地方就是SOCKETS 扩展,事实上我会简单的VB WINSOCK,完全能用VB写一个常用的WINSOCK程序出来。但是我还是选择了PHP。因为它是跨平台的。

PHP默认是不支持高级SOCKET的,只支持被“封装”的fsockopen等几个函数。SOCKET作为PHP的扩展,需要设置一下才能使其支持。在WINDOWS需要设置PHP。INI,在PHP。INI找;Windows Extensions这一行,去掉;extension=php_sockets.dll 前面的分号。THAT‘S OK。*NIX下则需要在编译的时候加入-enable—sockets命令。在没有使用DL()函数的时候,你的PHP必须和在同一目录php_sockets.dll。好了,完成PHP SOCKET配置了。

下面就是运行的问题了

在终端下运行PHP脚本很简单。WINDOWS下C:\php\php.exe –q test.php,*NIX下要在PHP文件事先申明由PHP来执行,就像PERL一样。像#!/usr/local/bin/php –q .,然后再来个./test.php。参数q的意思就是不输出PHP标头信息。

输入参数问题:

有的人说,PHP SHELL如何输入参数。在WEB的时候可以这样输入参数http://xxx.com/aa.php?参数1=XXXX&参数2=ssssss。没关系PHP同PERL一样,具有相似的参数功能。来看官方的描述

“argv”

传递给该脚本的参数。当脚本运行在命令行方式时,argv 变量传递给程序 C 语言样式的命令行参数。当调用 GET 方法时,该变量包含请求的数据。

“argc”

包含传递给程序的命令行参数的个数(如果运行在命令行模式)。

呵呵,简单的说。看我举个例子

<?

if ($argc != 4 || in_array($argc[1] , array(‘–help’,'-h’,'?’)))

{

echo “By Darkness[BST].We will come back soon!\r\n”;

echo “————————————————\r\n”;

echo “C:/PHP/PHP.exe -q uploadexp.php http://www.bugkidz.org/upload.php filepath\r\n”;

echo “————————————————\r\n”;

}

$host = $argv[1];

$url = $argv[2];

$path = $argv[3];

?>

我想你应该看懂了哦,这里ARGC[0]是指的程序本身。也可以这样来.

Printf(%s,$argv[1]);

2004年09月28日

vbscript错误代码及对应解释大全
VBScript 语法错误
如果 VBScript 语句结构违反了一个或多个 VBScript 脚本语言语法规则,就会产生
VBScript 语法错误。
错误通常在执行程序前,编译程序时产生。 以下是53个语法错误:
错误编号 描述
十进制 十六进制 说明
1001 800A03E9 内存不足
1002 800A03EA 语法错误
1003 800A03EB 缺少“:”
1005 800A03ED 需要 ‘(‘
1006 800A03EE 需要 ‘)’
1007 800A03EF 缺少“]”
1010 800A03F2 需要标识符
1011 800A03F3 需要 ‘=’
1012 800A03F4 需要 ‘If’
1013 800A03F5 需要 ‘To’
1014 800A03F6 需要 ‘End’
1015 800A03F7 需要 ‘Function’
1016 800A03F8 需要 ‘Sub’
1017 800A03F9 需要 ‘Then’
1018 800A03FA 需要 ‘Wend’
1019 800A03FB 需要 ‘Loop’
1020 800A03FC 需要 ‘Next’
1021 800A03FD 需要 ‘Case’
1022 800A03FE 需要 ‘Select’
1023 800A03FF 需要表达式
1024 800A0400 需要语句
1025 800A0401 需要语句的结束
1026 800A0402 需要整数常数
1027 800A0403 需要 ‘While’ 或 ‘Until’
1028 800A0404 需要 ‘While,’、 ‘Until,’ 或语句未结束
1029 800A0405 需要 ‘With’
1030 800A0406 标识符太长
1031 800A0407 无效的数
1032 800A0408 无效的字符
1033 800A0409 未结束的串常量
1034 800A040A 未结束的注释
1037 800A040D 无效使用关键字 ‘Me’
1038 800A040E ‘loop’ 没有 ‘do’
1039 800A040F 无效 ‘exit’ 语句
1040 800A0410 无效 ‘for’ 循环控制变量
1041 800A0411 名称重定义
1042 800A0412 必须为行的第一个语句
1043 800A0413 不能赋给非Byval参数
1044 800A0414 调用 Sub 时不能使用圆括号
1045 800A0415 需要文字常数
1046 800A0416 需要 ‘In’
1047 800A0417 需要 ‘Class’
1048 800A0418 必须在一个类的内部定义
1049 800A0419 在属性声明中需要 Let , Set 或 Get
1050 800A041A 需要 ‘Property’
1051 800A041B 参数数目必须与属性说明一致
1052 800A041C 在类中不能有多个缺省的属性/方法
1053 800A041D 类初始化或终止不能带参数
1054 800A041E Property Let 或 Set 至少应该有一个参数
1055 800A041F 不需要的 ‘Next’
1056 800A0420 只能在 ‘Property’ 或 ’Function’ 或 ’Sub’ 上指定 ’Default

1057 800A0421 说明 ‘Default’ 必须同时说明 ‘Public’ ”
1058 800A0422 只能在 Property Get 中指定 ‘Default’
 
VBScript 运行时错误
如果 VBScript 脚本执行系统无法实施的操作,则会产生 VBScript 运行时错误。只有
在运行脚本、为变量表达式赋值或
分配内存时,才会产生 VBScript 运行时错误。 以下是65个运行时错误:
错误编号 描述
十进制 十六进制 说明
5 800A0005 无效过程调用或参数
6 800A0006 溢出
7 800A0007 内存不足
9 800A0009 下标越界
10 800A000A 该数组为定长的或临时被锁定
11 800A000B 被零除
13 800A000D 类型不匹配
14 800A000E 字符串空间溢出
17 800A0011 无法执行请求的操作
28 800A001C 堆栈溢出
35 800A0023 未定义 Sub 或 Function
48 800A0030 加载 DLL 错误
51 800A0033 内部错误
52 800A0034 坏文件名或数
53 800A0035 文件未找到
54 800A0036 坏文件模式
55 800A0037 文件已经打开
57 800A0039 设备I/O错误
58 800A003A 文件已经存在
61 800A003D 磁盘空间已满
62 800A003E 输入超出文件尾
67 800A0043 文件太多
68 800A0044 设备不可用
70 800A0046 权限禁用
71 800A0047 磁盘未准备好
74 800A004A 不能用不同的驱动器重新命名
75 800A004B 路径/文件访问错误
76 800A004C 路径未找到
91 800A005B 未设置对象变量
92 800A005C For 循环未初始化
94 800A005E 非法使用 Null
322 800A0142 不能建立所需临时文件
424 800A01A8 需要对象
429 800A01AD ActiveX 部件无法创建对象
430 800A01AE 类不支持自动化
432 800A01B0 在自动化操作中未找到文件名或类名
438 800A01B6 对象不支持该属性或方法
440 800A01B8 Automation错误
445 800A01BD 对象不支持此操作
446 800A01BE 对象不支持指定的参数
447 800A01BF 对象不支持当前的区域设置
448 800A01C0 未找到命名参数
449 800A01C1 参数不可选
450 800A01C2 错误的参数个数或无效的参数属性值
451 800A01C3 对象不是一个集合
453 800A01C5 指定的dll函数未找到
455 800A01C7 代码源锁错误
457 800A01C9 这个键已经是本集合的一个元素关联
458 800A01CA 变量使用了一个 VBScript 中不支持的自动化(Automation)类型
462 800A01CE 远程服务器不存在或不能访问
481 800A01E1 无效图片
500 800A01F4 变量未定义
501 800A01F5 违法的分配
502 800A01F6 脚本对象不安全
503 800A01F7 对象不能安全初始化
504 800A01F8 对象不能安全创建
505 800A01F9 无效的或不合格的引用
506 800A01FA 类未被定义
507 800A01FB 发生异常
5016 800A1398 需要正则表达式对象
5017 800A1399 正则表达式中的语法错误
5018 800A139A 错误的数量词
5019 800A139B 在正则表达式中需要 ‘]’
5020 800A139C 在正则表达式中需要 ‘)’
5021 800A139D 字符集越界
32811 800A802B 元素未找到
 
 
 
回复人: liuqinyi(宝贝) ( ) 信誉:105 2003-02-24 09:51:00 得分:0
 
 
—————————————————————————–

 
 
 
 
jscript错误代码及相应解释大全
JScript 语法错误
JScript 语法错误是指当 JScript 语句违反了 JScript 脚本语言的一条或多条语法规
则时导致的错误。JScript 语法错误发生在程序编译阶段,在开始运行该程序之前。(
错误发生在开发过程中),以下是32个语法错误
 
错误号 描述
十进制 十六进制 说明
1001 800A03E9 内存不足
1002 800A03EA 语法错误
1003 800A03EB 需要“:”
1004 800A03EC 需要“;”
1005 800A03ED 需要“(”
1006 800A03EE 需要“)”
1007 800A03EF 需要“]”
1008 800A03F0 需要“{”
1009 800A03F1 需要“}”
1010 800A03F2 需要标识符
1011 800A03F3 需要“=”
1012 800A03F4 需要“/”
1013 800A03F5 无效数
1014 800A03F6 非法字符
1015 800A03F7 字符串常数未结束
1016 800A03F8 注释未结束
1018 800A03FA 函数外有 ’return’ 语句
1019 800A03FB 在循环外不能有“break”
1020 800A03FC 在循环外不能有“continue”
1023 800A03FF 需要十六进制数
1024 800A0400 需要“while”
1025 800A0401 标签定义重复
1026 800A0402 未找到标签
1027 800A0403 一条 “switch” 语句中只能有一个 “default”
1028 800A0404 需要标识符、字符串或者数字
1029 800A0405 需要“@end
1030 800A0406 条件编译已关闭
1031 800A0407 需要常数
1032 800A0408 需要“@”
1033 800A0409 需要“catch”
1034 800A040A 需要“var”
1035 800A040B “Throw”的后面必须跟有一个表达式,且在同一源代码行上
 
 
JScript 运行时错误
JScript 运行时错误是指当 JScript 脚本试图执行一个系统不能运行的动作时导致的错
误。当正在运行脚本、计算变量表达式、或者正在动态分配内存时出现JScript 运行时
错误时。
以下是76个运行时错误
 
错误号 描述
十进制 十六进制 说明
5 800A0005 非法过程调用或参数
6 800A0006 溢出
7 800A0007 内存不足
9 800A0009 下标超界
10 800A000A 此数组被固定或临时锁定
11 800A000B 零除错误
13 800A000D 类型失配
14 800A000E 串空间不足
17 800A0011 不能执行所请求的操作
28 800A001C 栈空间不足
35 800A0023 子过程或函数未找到
48 800A0030 装载DLL出错
51 800A0033 内部出错
52 800A0034 坏文件名或数
53 800A0035 文件未找到
54 800A0036 坏文件模式
55 800A0037 文件已经打开
57 800A0039 设备I/O错误
58 800A003A 文件已经存在
61 800A003D 磁盘空间已满
62 800A003E 输入超出文件尾
67 800A0043 文件太多
68 800A0044 设备不可用
70 800A0046 权限禁用
71 800A0047 磁盘未准备好
74 800A004A 不能用不同的驱动重命名
75 800A004B 路径/文件访问错误
76 800A004C 路径未找到
91 800A005B 对象变量或With块变量未设置
92 800A005C For循环未初始化
94 800A005E Null使用无效
322 800A0042 不能建立所需的临时文件
424 800A01A8 需要对象
429 800A01A9 Automation服务器不能建立对象
430 800A01AE 类不支持Automation
432 800A01B0 在Automation操作中找不到文件名或类名
438 800A01B6 对象不支持这个属性或方法
440 800A01B8 Automation错误
445 800A01BD 对象不支持这个动作
446 800A01BE 对象不支持指定的参数
447 800A01BF 对象不支持当前区域设置
448 800A01C0 指定的参数未找到
449 800A01C1 参数不可选
450 800A01C2 错误的参数数目或非法属性分配
451 800A01C3 对象不是一个集合
453 800A01C5 指定的dll函数未找到
458 800A01CA 变量使用了一个Jscript不支持的Automation类型
462 800A01CE 远程服务器机器不存在或不可用
501 800A01F5 不能分配给变量
502 800A01F6 对象对于脚本不安全
503 800A01F7 对象对于初始化不安全
504 800A01F8 对象对建立不安全
5000 800A1388 不能分配给“this”
5001 800A1389 需要 Number 类型
5002 800A138A 需要 Function 对象
5003 800A138B 不能给函数返回值赋值
5004 800A138C 不能索引对象
5005 800A138D 需要 String
5006 800A138E 需要 Date 对象
5007 800A138F 需要 Object 类型
5008 800A1390 非法赋值
5009 800A1391 未定义标识符
5010 800A1392 需要 Boolean
5011 800A1393 不能执行来自一个自由脚本的代码
5012 800A1394 需要对象的成员
5013 800A1395 需要 VBArray
5014 800A1396 需要 JScript 对象
5015 800A1397 需要 Enumerator 对象
5016 800A1398 需要正则表达式对象
5017 800A1399 正则表达式语法错误
5018 800A139A 未预期的限定符
5019 800A139B 正则表达式中缺少“]”
5020 800A139C 正则表达式中缺少“)”
5021 800A139D 字符集范围无效
5022 800A139E 异常抛出,但无法抓住
5023 800A139F 函数没有合法的 Prototype (原型)对象
5024 800A13A0 待解码的 URI 包含有非法字符
5025 800A13A1 待解码的 URI 编码非法
5026 800A13A2 小数部分的位数越界
5027 800A13A3 精度越界
5028 800A13A4 需要 Array 或 arguments 对象
5029 800A13A5 数组长度必须为一有限正整数
5030 800A13A6 必须赋给数组长度一个有限正数
 
 
 
Top
 
回复人: liuqinyi(宝贝) ( ) 信誉:105 2003-02-24 09:54:00 得分:0
 
 
 
 
—————————————————————————–

 
 
大部分的ADO的错误码对应的含义
除了在 Error 对象和 Errors 集合中说明的提供者错误之外,ADO 本身也将错误返回到
运行时环境的异常处理机制之中。使用编程语言的错误捕获机制(如 Microsoft&reg;
Visual Basic&reg; 中的 On Error 语句)可捕获及处理下列错误。下表将同时显示十
进制和十六进制错误代码值。
 
 
 
常量名称 编号 说明
adErrInvalidArgument 3001 0×800A0BB9 应用程序使用的参数其类型错误、超出可接受
的范围或者与其他参数冲突。
adErrNoCurrentRecord 3021 0×800A0BCD BOF 或 EOF 为 True,或者当前记录已经删除
。应用程序请求的操作需要当前记录。
adErrIllegalOperation 3219 0×800A0C93 应用程序请求的操作不允许出现在该上下文
中 adErrInTransaction 3246 0×800A0CAE 在事务中应用程序无法显式关闭
Connection 对象。
adErrFeatureNotAvailable 3251 0×800A0CB3 提供者不支持应用程序请求的操作。
adErrItemNotFound 3265 0×800A0CC1 ADO 无法在对应于应用程序请求的名称或顺序引
用的集合中找到对象。
adErrObjectInCollection 3367 0×800A0D27 无法追加,对象已经在集合中。
adErrObjectNotSet 3420 0×800A0D5C 应用程序引用的对象不再指向有效的对象。
adErrDataConversion 3421 0×800A0D5D 应用程序使用了不符合对当前操作的值类型。
adErrObjectClosed 3704 0×800A0E78 如果对象关闭,则不允许应用程序请求的操作。
adErrObjectOpen 3705 0×800A0E79 如果对象打开,则不允许应用程序请求的操作。
adErrProviderNotFound 3706 0×800A0E7A ADO 找不到指定的提供者。
adErrBoundToCommand 3707 0×800A0E7B 应用程序无法用 Command 对象将 Recordset
对象的 ActiveConnection 属性更改为它的来源数据。
adErrInvalidParamInfo 3708 0×800A0E7C 应用程序错误地定义了 Parameter 对象。
adErrInvalidConnection 3709 0×800A0E7D 应用程序通过引用关闭或无效的
Connection 对象来请求对对象的操作。
 
 
 
Top
 
回复人: liuqinyi(宝贝) ( ) 信誉:105 2003-02-24 09:55:00 得分:0
 
 
ASP错误代码说明
错误代码 错误消息 说明
ASP0100 Out of memory 内存不足(不能分配要求的内存
ASP0101 Unexpected error 意外错误
ASP0102 Expecting string input 缺少字符串输入
ASP0103 Expecting numeric input 缺少数字输入
ASP0104 Opration not allowed 操作不允许
ASP0105 Index out of ange 索引超出范围(一个数组索引超届)
ASP0106 Type Mismatch 类型不匹配(遇到的数据类型不能被处理)
ASP0107 Stack Overflow 栈溢出(正在处理的数据超出了允许的范围)
ASP0115 Unexpected error 意外错误(外部对象出现可捕获的exception_name错误,脚
本不能继续运行)
ASP0177 Server.CreateObject Failed 服务器创建对象失败(无效的progid)
ASP0190 Unexpected error 意外错误(当释放外部对象,产生可捕获的错误)
ASP0191 Unexpected error 意外错误(在外部对象的OnStartPage方法中产生可捕获的错
误)
ASP0192 Unexpected error 意外错误(在外部对象的OnEndPage方法中产生可捕获的错误
发信人: longsi——现代速龙(时速200公里),信区:X-COM基地
ASP0177 Server.CreateObject Failed 服务器创建对象失败(无效的progid)
发信站: 侏罗纪公园(2050年2月31日18:30:00 星期六),站内信件 获的错误)
ASP0191 Unexpected error 意外错误(在外部对象的OnStartPage方法中产生可捕获的错
误)
ASP0192 Unexpected error 意外错误(在外部对象的OnEndPage方法中产生可捕获的错误
)
ASP0193 OnStartPage Failed 在外部对象的OnStartPage方法中产生错误
ASP0194 OnEndPage Failed 在外部对象的OnEndPage方法中产生错误
ASP0240 Script Engine Exception 脚本引擎从object_name对象中抛出exception_anme
异常
ASP0241 CreateObject Exception object_name 对象的CreatObject方法引起了excepti
on_name异常
ASP0242 Query OnStartPage nterface 查询对象Object_name的OnsException

2004年09月22日

出处:天极  作者:刘涛


Windows的应用程序都是基于消息驱动的,应用程序的操作都依赖于它所得到的消息的类型及内容。钩子与Dos中断截获处理机制有类似之处。钩子(Hook)是Windows消息处理机制的一个平台,通过安装各种钩子,应用程序可以在上面设置子程序以监视指定窗口的某种消息,并且当消息到达目标窗口之前处理它。

  在Windows中,钩子有两种,一种是系统钩子(RemoteHook),它对消息的监视是整个系统范围,另一种是线程钩子(LocalHook),它的拦截范围只有进程内部的消息。对于系统钩子,其钩子函数(HookFunction)应在Windows系统的动态链接库(DLL)中实现,而对于线程钩子来说,钩子函数可以在DLL之中实现,也可以在相应的应用程序之中实现。这是因为当开发人员创建一个钩子时,Windows先在系统内存中创建一个数据结构,该数据结构包含了钩子的相关信息,然后把该结构体加到已经存在的钩子链表中去,并且新的钩子将排在老的钩子的前面。当一个事件发生时,如果安装的是一个局部钩子,当前进程中的钩子函数将被调用。如果是一个远程钩子,系统就必须把钩子函数插入到其它进程的地址空间,要做到这一点就要求钩子函数必须在一个动态链接库中,所以如果想要使用远程钩子,就必须把该钩子函数放到动态链接库中去。对于钩子所监视的消息类型来说,Windws一共提供了如下几种类型:如表1所示:

表一、Windows消息类型


消息类型常量标识 消息类型 适用范围
WH_CALLWNDPROC 4 发给窗口的消息 线程或系统
WH_CALLWNDPROCRET 12 窗口返回的消息 线程或系统
WH_CBT 5 窗口变化、焦点设定等消息 线程或系统
WH_DEBUG 9 是否执行其它Hook的Hook 线程或系统
WH_FOREGROUNDIDLE 11 前台程序空闲 线程或系统
WH_GETMESSAGE 3 投放至消息队列中的消息 线程或系统
WH_JOURNALPLAYBACK 1 将所记载的消息进行回放 系统
WH_JOURNALRECORD 0 监视并记录输入消息 系统
WH_KEYBOARD 2 键盘消息 线程或系统
WH_MOUSE 7 鼠标消息 线程或系统
WH_MSGFILTER -1 菜单滚动条、对话框消息 线程或系统
WH_SHELL 10 外壳程序的消息 线程或系统
WH_SYSMSGFILTER 6 所有线程的菜单滚动条、对话框消息 系统
  二、VB编程中钩子的实现

  (一)钩子函数(HOOK Function)的格式。Hook Function实际上是一个函数,如果是系统钩子,该函数必须放在动态链接库中。该函数有一定的参数格式,在VB中如下:

Private Function HookFunc(ByVal nCode As Long,ByVal wParam As Long,ByVal lParam As Long)As Long
  其中,nCode代表是什么情况之下所产生的钩子,随钩子的不同而有不同组的可能值;参数wParam,lParam传回值包括了所监视到的消息内容,它随Hook所监视消息的种类和nCode的值不同而不同。对于用VB所设置的钩子函数,一般的框架形式如下:

Private Function HookFunc(ByVal nCode As Long,ByVal wParam As Long,ByVal lParam As Long)As Long
 Select case of nCode
  case ncode<0:hookfunc=callnexthookex(hHookFunc,nCode,wParam,lParam)
  case值1:处理过程1:HookFunc=X1
  case2:处理过程2:HookFunc=X1
  ……
 end select
end Function
  函数的传回值,如果消息要被处理,则传0,否则传1,吃掉消息。

  (二)钩子的安装及执行。钩子的安装要用到几个API函数:可以使用API函数SetWindowsHookEx()把一个应用程序定义的钩子子程安装到钩子链表中。SetWindowsHookEx()函数的声明如下:

Declare function SetWindowsHookEx Lib “user32″ Alias “SetWindowsHookExA”(ByVal idHook As Long,ByVal lpfn As Long,ByVal hmod As Long,ByVal dwThreadId As Long)As Long
  idHook值为它处理的消息类型;lpfn值为钩子子程序的地址指针。如果dwThreadId参数为0或是一个由别的进程创建的线程的标识,lpfn必须指向DLL中的钩子子程。除此以外,lpfn可以指向当前进程的一段钩子子程代码。hMod值为应用程序的句柄,标识包含lpfn所指的子程的DLL。如果dwThreadId标识当前进程创建的一个线程,而且子程代码位于当前进程,hMod必须为0。dwThreadId值为与安装的钩子子程相关联的线程的标识符,如果为0,钩子子程与所有的线程关联。钩子安装成功则返回钩子子程的句柄,失败返回0。

  另外,一般应在钩子子程中调用CallNextHookEx()函数以执行钩子链表所指的下一个钩子子程,否则安装了别的钩子的应用程序就会收不到钩子通知,从而产生错误的结果。CallNextHookEx()函数的声明如下:

Declare Function CallNextHookEx Lib”user32″ Alias “CallNextHookEx”(ByVal hHook As Long,ByVal ncode As Lonog, ByVal wParam As Long,lParam As Any)As Long
  hHook值是SetWindowsHookEx()的传回值,nCode、wParam、lParam则是Hook函数中的三个参数。在程序终止之前,必须调用UnhookWindowsHookEx()函数释放与钩子关联的系统资源。UnhookWindowsEx()函数声明如下:

Declare Function Unhook WindowsHookEx Lib “user32″ Alias “Unhook WindowsHookEx(ByVal hHook As Long)As Long
  hHook为安装钩子时的返回值,即钩子子程的句柄。

  (三)VB中钩子安装应注意的问题。lpfn参数是一个HookFunc的地址,VB规定必须将HookFunc代码放到标准的.BAS模块中,并以”Address Of HookFunc”传入,而不可以将其放到类模块中,也不能将其附加到窗体上。而对于RemoteHook来说,HookFunc应包含在动态链接库中,因此如果在VB中使用RemoteHook,则还要用到GetModuleHandle()、GetProcAddress()两个API函数,它们的声明如下:

Declare Function GetModuleHandle Lib”kernel32″ Alias “GetModuleHandleA”(ByVal lpModuleName As String)As Long
Declare Function GetProcAddress Lib “kernel32″ Alias “GetProcAddress”(ByVal hModule As Long,ByVal lpProcName As String)As Long
  hmod值是含钩子过程的模块名柄,如果是LocalHook,该值可以是Null(VB中传0),而如果是RemoteHook,则可以使用GetModuleHandle(“名称.dll”)来传入。
三、实例–键盘消息的拦截

  在程序开发时常用的有对输入消息进行监视的键盘钩子,对于所监视到的消息应进行处理,下面对键盘钩子参数的具体内容组成进行说明:

  如果有键盘消息(WM_KEYUP或WM_KEYDOWN)将被处理时,则系统调用键盘钩子。

  nCode为HC_ACTION或HC_NOREMOVE,若小于0,则要求处理函数向下传递该消息。

  wParam表示按键键码常数,A键到Z键与其ASCII码的相应值’A’到’Z’是一致的,例如按C键,则wParam值为67。

  lParam与WM_KEYDOWN同,占四个字节,其包括的内容较多,其二进制结构如下:

0 1 …… 15 16 ……… 23 24 25 …… 28 29 30 31
  0-15位(Key repeat count),键码重复次数。16-23位(Scan code),按键的扫描码。24位(Extended_Key flag),扩展键(功能键、数字小键盘上的键)标志,为1则是扩展键,否则为0。25-28位被保留。29位(Context Code),状态描述码,ALT键被按下则为1,否则为0。30位(Previouskey_stateflag)指定先前的键状态,如果消息被发出之前键处于按下状态,则为1;键处于释放状态则为0。31位(Transiton_stateflag)状态转换标志,如果键是被按下值为1,如果键被放开值为0。

  本例中的钩子用来监视并记录应用程序中的按键信息。在程序中,ALT+F4组合键被屏蔽。下面是部分代码:

Public hHook as Long
Private Sub Form_Load()′程序启动时安装钩子
hHook=SetWindowsHookEx(2,Address of MyKBHook,0,App.ThreadID)
End Sub
′具体的钩子程序,本例中该过程被包含在Module1中
Public Function MyKBHook(ByVal nCode As Long,ByVal wParam As Long,ByVal lParam As Long)As Long
If nCode>=0 then
Open “C:\Keyfile.txt” For Append As #1 ‘将键盘的操作记录在Keyfile.txt文件之中
‘记录所操作的键、操作时间、日期操作时的按键状态,用16进制记录
Write #1,wParam,Hex(lParam),Date,time
Close #1
MyKBHook=0 ‘表示要处理这个消息
‘屏蔽ALT+F4组合键
if wParam=115 And(lParam And&H20000000)<>0 Then
if(lParam And &HC000000)=0 Then  ’是否进行ALT+F4操作
MyHBHook=1 ‘钩子吃掉这个消息
End if
End if
End if
Call CallNextHookEx(hHook,nCode,wParam,lParam)’将消息传给下一个钩子
End Function
‘程序退出时卸载钩子
Private Sub Form_Unload(Cancel As Interger)
Call Unhook WindowsHookEx(hHook)
End Sub
  四、总结

  钩子处理程序是Windows高级编程技术,一般程序员都使用VC++等程序设计工具实现,本文表明,对于VB来说,虽然很多人认为是非专业的设计工具,但实现钩子这样的高级技术也是非常方便的。另外在使用钩子时应注意到,钩子虽然功能比较强,但如果使用不当将会严重影响系统的效率,所以要尽量避免使用系统钩子,并且在不用钩子时,应将钩子及时卸载。

2004年09月17日

说到Winsock,可能很多人还不太了解,但说到OICQ、ICQ、Foxmail、Netants、CuteFTP以及大名鼎鼎的BO2K等等,大家都应该是很熟悉的。如今是网络时代,这些基于网络的软件真的是红红火火!那你有没有想过这些软件是怎么写出来的呢?这就是本文将要介绍的内容:Socket编程!

Socket(中文译名:套接字)最初在Unix上出现,并很快成为Unix上最流行的网络编程接口之一。后来,微软将它引入到Windows中并得到实现,于是从Windows 95、WinNT4开始,系统就内置了Winsock1.1,后来到了Windows98、Windows2000,它内置的Winsock DLL更新为Winsock2.2。Winsock1.1有2种I/O方式,2种I/O模型,到了Winsock2.2,则有了2种I/O方式,5种I/O模型。另外,Winsock2.2对Socket进行了很多扩充与改进,包括名字解析、异步处理等。这些都是很有用的内容,但也比较复杂,要想在短短一篇文章里讲清楚是不可能的,本文的目的只是为你开个头,俗话说:万事开头难!其实Winsock编程是很例行公式化的。不过值得注意的是:有时它也很难把握,因为它编程的对象是网络,有时你发现运行程序得不到预期的结果,但却很难调试出到底哪里出了问题!

下面将向你介绍基本的Socket的客户端函数,并给出了一个简单的多线程端口扫描器的源代码!

先讲一下基本的编程步骤:

1.由于Winsock目前有两个版本:2.2和1.1,所以我们首先必须判断系统所支持的Winsock版本!这就要靠WSAStartup函数了!另外还有一个WSACleanup函数!这两个函数是Winsock编程必须调用的,其中WSAStartup函数的功能是初始化Winsock DLL,因为在Windows下,Socket是以DLL的形式实现的。1.1版本的DLL为Winsock.dll,而2.2版本的DLL则为Wsock32.dll,其中在2.2版本的系统中,对Winsock1.1函数的调用会由Wsock32.dll自动映射到Winsock.dll。WSAStartup函数的功能就是初始化DLL,其函数原型为:

int WSAStartup (WORD wVersionRequested,LPWSADATA lpWSAData);

其中第一个参数为你所想需要的Winsock版本!低字节为主版本,高字节为副版本!由于目前Winsock有两个版本:1.1和2.2,因此该参数可以是0×101或0×202;第二个参数是一个WSADATA结构,用于接收函数的返回信息!WSAStartup函数调用成功会返回0,否则返回非0值!

示例代码:

WSADATA wsaData;

if(WSAStartup(0×101,&wsaData))

{

//错误处理!

}

这里有一点题外话,由于Win 95,Win NT4自带的Winsock是1.1版本的,所以如果你的程序是基于Winsock2.2的,那很可能无法在上面运行!因此,如果你希望你写的程序被所有Windows平台支持的话,最好将其声明成1.1版的,不过这样将无法使用很多Winsock2.2才有的特性!至于WSACleanup的用法很简单,用“WSACleanup();”就行了!另外,在DLL内部维持着一个计数器,只有第一次调用WSAStartup才真正装载DLL,以后的调用只是简单的增加计数器,而WSACleanup函数的功能则刚好相反,每调用一次使计数器减1,当计数器减到0时,DLL就从内存中被卸载!因此,你调用了多少次WSAStartup,就应相应的调用多少次的WSACleanup。

2.创建套接字

创建套接字有两个函数,socket和WSASocket,前者是标准的Socket函数,而后者是微软对Socket的扩展函数。socket函数有3个参数,第一个是指定通信发生的区域,在UNIX下有AF_UNIX、AF_INET、AF_NS等,而在Winsock1.1下只支持AF_INET,到了2.2则添了AF_IRDA(红外线通信)、AF_ATM(异步网络通信)、AF_NS、AF_IPX等;第2个参数是套接字的类型,在AF_INET地址族下,有SOCK_STREAM、SOCK_DGRAM、SOCK_RAW三种套接字类型。SOCK_STREAM也就是通常所说的TCP,而SOCK_DGRAM则是通常所说的UDP,而SOCK_RAW则是用于提供一些较低级的控制的;第3个参数依赖于第2个参数,用于指定套接字所用的特定协议,设为0表示使用默认的协议。socket函数调用成功返回一个套接字描述符,错误则返回SOCKET_ERROR。

示例代码:

SOCKET sk;

sk=socket(AF_INET,SOCK_STREAM,0);

if(sk==SOCKET_ERROR)

{

//错误处理

}

3.连接服务器

在成功调用了socket函数后,对客户端来说就是与服务器端建立连接。同样,建立连接需要两个函数:connect和WSAConnect。前者是标准的Socket函数,后者是微软的扩展函数。connect函数有3个参数,第1个是连接所使用的套接字描述符,第2个参数是一个sockaddr结构,sockaddr结构是一个通用的结构,它只是简单地定义了一个字节数组,在TCP/IP下一般将其解释为sockaddr_in结构,第3个参数则是该结构的长度,一般用sizeof函数来取得。connect函数调用失败则返回SOCKET_ERROR!

示例代码:

sockaddr_in sock;

sock.sin_family=AF_INET;

sock.sin_port=htons(80);

sock.sin_addr.s_addr=inet_addr(“202.205.210.1”);

if(connect(sk,(sockaddr*)&sock,sizeof(sock)==SOCKET_ERROR)

{ 

//错误处理 

}

这里有一点要说明的是,用于填写sockaddr_in结构的值必须是以网络字节顺序表示的值,而不能直接使用本机字节顺序的值。之所以这样规定是因为在网络上存在不同的系统,不同的系统中数据存储时所采用的字节排列顺序是不同的,有的是高字在前,低字在后,而有的刚好相反。为了统一,规定了一个所谓的网络字节顺序。htonl函数可以将本地的unsigned long数据转换为网络字节顺序的数据。htons则是将unsigned short的数据转换为网络字节顺序的数据。而ntohs、ntohl的功能则是刚好相反。另外,sockaddr_in结构的sin_addr.s_addr成员要求是用来描述对方地址的一个值,即网际地址值,而实际应用中,我们得到的大多是IP地址或域名,如202.210.205.1或www.cfan.cn.net,可以用inet_addr函数将点分法表示的IP地址转换为所要求的值,可以用gethostbyname、WSAAsynGetHostbyName取回用易用名表示的主机的信息。gethostbyname函数调用成功会返回一个hostent结构的指针,若错误则返回NULL。下面介绍一下gethostbyname函数的用法。

hostent *host;

…….

host=gethostbyname(“www.cfan.cn.net”)

if(host==NULL)

{ 

//错误处理 

sock.sin_addr.s_addr=*((unsigned long*)host→h_addr_list[0]);

……

4.发送和接收数据

由于这里建立的是SOCK_STREAM类型的连接,故发送可以采用的函数有send和WSASend,而接收可以采用recv和WSARecv,同样,全小写的函数是标准的Socket函数,以WSA开头的是微软的扩展函数send函数有4个参数:第一个是发送操作所用的套接字描述符,第二个是所要发送的数据缓冲区的地址,为char*类型,至于其它类型的数据可以用强制类型转换(char*)。在接收端再用强制类型转换转换回来!第3个参数是所发送的缓冲区的大小,也就是所要发送的字节数!第4个参数是一个附加标志,可以为0、MSG_OOB、MSG_DONTROUTE,熟悉电脑的用户应该对OOB这个字眼不陌生,因为Win95有一个很有名的系统漏洞就是所谓的“OOB错误”,一不小心就会系统崩溃(Win98则有个ICMP错误,用SOCK_RAW类型的套接字会涉及ICMP!)。如果对所发送的数据没特殊要求,直接设为0。recv函数的参数也是4个其涵义与send函数差不多。只是其第二个参数是指向用于接收数据的缓冲区的地址。Send、recv调用成功返回所发送或接收的字节数,如果调用失败则返回SOCKET_ERROR!

示例代码(send函数):

SOCKET sk;

char szTest[]=“This is an example!”

int iRet;

……(这里省略创建套接字,连接…)

iRet=send(sk,szTest,strlen(szTest),0);

if(iRet==SOCKET_ERROR) 

{

//错误处理 

}

else if(iRet!=strlen(szTest))

MessageBox(NULL,“未发送所有的数据”,“警告”,MB_OK);

示例代码(recv函数)

SOCKET sk;

char szTest[20]

int iRet;

……(这里省略创建套接字,连接……)

iRet=recv(sk,szTest,20,0);

if(iRet==SOCKET_ERROR)

{

//错误处理

}

szTest[iRet]=`\0`;//这一行代码不可少!因为recv函数不会自动将数据缓冲末尾设为表示数据结束的空中止符(`\0`),因此,一不留神就会出现缓冲区越界。当然也可以在调用recv函数前先将缓冲区清0(用ZeroMemory或memset),不过还是建议加上这一句。

5.断开连接

用closesocket.closesocket(sk);另外,也可以用shutdown来关闭套接字,这样可以提供更多的选项控制,由于篇幅所限,这里不再深入!

这样,客户端的基本(基本)内容就讲完了!下面小弟给出一个简单的多线程端口扫描器的源代码(警告:在未经允许的情况下用端口扫描器对他人的计算机进行扫描以及对他人的计算机实施端口攻击是违法的行为)。

这是一个典型的TCP端口扫描器,通过用connect函数对服务器进行尝试连接来判断该服务器上的端口是否开放。这个扫描器是多线程的,现在的Winsock编程大多数采用多线程技术,这样可以充分利用带宽,如Netants的5个蚂蚁下载,一些FTP软件的多线程上传,等等!为了增强代码的可读性,我没加错误处理!

//Source Code In C++Builder5

#include

#pragma hdrstop

#include “Unit1.h”

#include

#include

#define threadNum 10//线程数

#define mutexName “Welcome to LoveBcb.yeah.net”

#pragma package(smart_init)

#pragma resource “*.dfm”

typedef struct g_scan //这是一个自定义的结构

{

char szFile[40];//用于存放结果的文件名

char szMutex[40];//用于存放互斥体的名字,这是多线程保证线程安全的一种方法

unsigned short sPort;//扫描的起始端口,本机字节顺序

unsigned short ePort;//扫描的终止端口,本机字节顺序

unsigned long goalI;//目标主机IP,网络字节顺序

int Result;//用于存放结果

}*PG_SCAN;

TForLover *ForLover;//这是窗体

HANDLE hThread[threadNum];

g_scan gscan[threadNum];

DWORD dwThreadId,dwThreadCode;

unsigned short usPart;//用于分割所要扫描的端口数,分配给各个线程

unsigned long ulIp;

int iLiveThread;//用于存放活动的线程数

unsigned long ServerIp(char*serverip);

DWORD WINAPI ScanPort(LPVOID lp)

/*这是主线程函数ScanPort*/

DWORD WINAPI ScanPort(LPVOID lp)

{

PG_SCAN pgscan=(PG_SCAN)lp;

char szResult[40];

sockaddr_in sock;

unsigned short nowPort=pgscan→sPort-1;//用于存放当前扫描的端口号

FILE*fp;//文件指针

HANDLE hMutex=OpenMutex(MUTEX_ALL_ACCESS,false,pgscan→szMutex);

SOCKET sk=socket(AF_INET,SOCK_STREAM,0);

sock.sin_family=AF_INET;

sock.sin_addr.s_addr=pgscan→goalIp;

while(nowPort

{

sock.sin_port=htons(++nowPort)

if(connect(sk,(sockaddr*)&sock,sizeof(sock))==SOCKET_ERROR)

continue;

/*由于这里用的是阻塞方式的套接字,所以返回SOCKET_ERROR一般意味着无法连接,于是用continue结束本次循环,即重新开始一次循环。如果返回值不是SOCKET_ERROR的话,表示连接成功,也就是说目标主机上开放了此端口*/

wsprintf(szResult,“目标主机:%s端口:%d开放\r\n”,inet_ntoa(sock.sin_addr),nowPort); WaitForSingleObject(hMutex,INFINITE);

/*用WaitForSinleObject保证线程安全INFINITE表示一直等待,直到互斥体有信号*/

fp=fopen(pgscan→szFile,“a”);

fwrite(szResult,sizeof(char),strlen(szResult),fp);

fclose(fp);

pgscan→Result++;

ReleaseMutex(hMutex);//释放互斥体

closesocket(sk);//由于已经建立了连接,所以这里要关闭连接

sk=socket(AF_INET,SOCK_STREAM,0);//重新创建一个套接字

}

——————————————————————————–
这篇文章贡献自Akash kava,   翻译: bugfree/CSDN
环境: VC6

※HTTP 隧道※
——–
HTTP是基于文本的通过浏览器检索网页的协议。 大多数情况下你躲在代理服务器的后面,通过LAN接入互联网。 在IE的Connection Option中, 你给出你的LAN的设置。 这个代理服务器运行着基于文本的协议, 你从它那里可以得到外界的网络HTTP相关的数据。是的, 用HTTP通过它上面的小的望孔可以连接到外部世界, 并用二进制协议得到你想要的数据, 或者甚至是你的协议。 它通过HTTP。

※HTTPS 解释※
———
在HTTPS中, 数据以一种安全的方式从浏览器到服务器和从服务器到浏览器。 它是二进制的协议; 当他穿过代理时, 代理不知道是什么。 代理仅仅允许二进制流打开, 让服务器和客户两者之间交换数据。 代理服务器认为我们在进行某个安全的会话。

对于HTTPS, 你的浏览器连接到代理服务器,并送出一个命令

CONNECT neurospeech.com:443 HTTP/1.0 <CR><LF>
HOST neurospeech.com:443<CR><LF>
   【…如果需要,HTTP头部的其它行以<CR><LF>结束 】
<CR><LF>    // 最后的空行

接下来, 代理服务器把它作为某个HTTP安全会话, 打开一个到需求服务器和端口的二进制流。 如果连接确立, 代理服务器返回如下回应:

HTTP/1.0 200 Connection Established<CR><LF>
         【…忽略所有HTTP头部的其它行以<CR><LF>结束,】
<CR><LF>    // 最后的空行

现在, 浏览器连接到了终端服务器, 可以用二进制和安全的方式交换数据了。

※怎样做这个※
————-
现在是你的程序任务去愚弄代理服务器, 行为如IE一样进行 Secure HTTP。

1. Connect to Proxy Server first.
2. Issue CONNECT Host:Port HTTP/1.1<CR><LF>.
3. Issue <CR><LF>.
4. Wait for a line of response. If it contains HTTP/1.X 200 , the connection is successful.
5. Read further lines of response until you receive an empty line.
6. Now, you are connected to outside world through a proxy. Do any data exchange you want.

示例源代码

  // You need to connect to mail.yahoo.com on port 25
  // Through a proxy on 192.0.1.1, on HTTP Proxy 4480
  // CSocketClient is Socket wrapping class
  // When you apply operator << on CString, it writes CString
  // To Socket ending with CRLF
  // When you apply operator >> on CString, it receives
  // a Line of response from socket until CRLF

  try
  {
    CString Request,Response;
    CSocketClient Client;

    Client.ConnectTo(“192.0.1.1″,4480);

    // Issue CONNECT Command
    Request = “CONNECT mail.yahoo.com:25 HTTP/1.0″;
    Client<<Request;

    // Issue empty line
    Request = “”;
    Client<<Request;

    // Receive Response From Server
    Client>>Response;

    // Ignore HTTP Version

    int n = Response.Find(‘ ‘);
    Response = Response.Mid(n+1);

    // Http Response Must be 200 only
    if(Response.Left(3)!=”200″)
    {
      // Connection refused from HTTP Proxy Server
      AfxMessageBox(Response);
    }

    // Read Response Lines until you receive an empty line.
    do
    {
      Client>>Response;
      if (Response.IsEmpty())
        break;
    }while (true);

    // Coooooooool…. Now connected to mail.yahoo.com:25
    // Do further SMTP Protocol here..

  }
  catch (CSocketException * pE)
  {
    pE->ReportError();
  }

 

※库源码※
————-
文件Dns.h包含所有所有DNS相关的源代码。 它利用了其它的库, 如SocketEx.h, SocketClient.h, 和 NeuroBuffer.h

※CSocketEx※
————-

作为一个Socket功能的包裹(wapper)类。(如果你不是确切知道CSocket怎样工作的, 它是非常笨重和不可信的) 所有的函数根CSocket同名。 你可以直接应用这个类

※CSocketClient※
—————–

派生自CSocketEx, 并且根据详细的Winsock错误抛出适当地例外(exceptions). 为了方便的发送和接收,它定义了两个操作符, >> 和 <<; 如果需要它也交换网络序为主机序和主机序为网络序。

※CHttpProxySocketClient※
—————–

派生自CSocketClient, 你可以调用SetProxySettings(ProxyServer, Port) 方法和做代理设置。 接下来, 你可以连接到你想要的主机和端口。ConnnectTo 方法被覆盖, 它自动的实现了HTTP代理协议并无争论的给你了一个连接。

 

※怎样利用CHttpProxySocketClient※
———————————
  // e.g. You need to connect to mail.yahoo.com on port 25
  // Through a proxy on 192.0.1.1, on HTTP Proxy 4480
  // CSocketClient is Socket wrapping class
  // When you apply operator << on CString, it writes CString
  // To Socket ending with CRLF
  // When you apply operator >> on CString, it receives
  // Line of response from socket until CRLF
  try
  {
    CHttpProxySocketClient Client;

    Client.SetProxySettings(“192.0.1.1″,1979);

    // Connect to server mail.yahoo.com on port 25
    Client.ConnectTo(“mail.yahoo.com”,25);

    // You now have access to mail.yahoo.com on port 25
    // If you do not call SetProxySettings, then
    // you are connected to mail.yahoo.com directly if
    // you have direct access, so always use
    // CHttpProxySocketClient and no need to do any
    // extra coding.

  }
  catch(CSocketException * pE) {
    pE->ReportError();
  }

【bugfree译注】
    作者在它的文章中, 说了为什么不用.h .cpp风格编程的理由, 这个我也给出我的理由, 因为
    他的风格不可取, 大家有机会可以读读它的源代码, 有两个缺点,
    (一)整体感觉乱,不清爽, 对常常的一个类很难一下子从整体上把握。 
    (二)随着代码的增加, 编译时间过长也显现出来, 可能大家都知道.h .cpp的好处之一就是
         .cpp的代码之编译一次, 假如你没有改动该.cpp文件的内容, 此文件就不会重编译。
         还有, 这样你的类接口非常清楚, 只需在模块间共享类的.h文件即可。

2004年09月16日

初探数据包分析程序设计
Author :maigan
From : 第八军团-信息安全小组(www.cnhacking.com www.juntuan.org)
Mail : maigan@maigan.com
Warning: 转载本文请注明作者及出处

整天在网上转,也看到许多不错的文章,但我发现大多文章要么只停留在理论上,要么就是太高深。对问题详细分析介绍的很少。今天,我就想以数据包分析程序为主题和大家讨论一下网络编程的的相关问题,我也是新手,有不到之处,还望大家不吝指正。
通过对数据包的分析,我们可以判断通信双方的操作系统、网络信息流量、经过的路由、数据包的大小,以及数据包的内容等等。对于喜欢网络安全的人来说,掌握这方面的知识是相当重要的。现在的网络通信中,大部分数据都没有加密,我们可以轻易地从数据包中提取账号、密码之类我们关心的数据.大家在看本文时如有困难,可先读一读计算机网络及C程序设计还有协议分析方面的书。下面我将分TCP/IP族协议结构、程序部分函数及数据结构说明、案例程序剖析三个部分与大家共同学习数据包分析程序的设计方法。

一、TCP/IP族协议结构
在说TCP/IP之前,先让我们来认识一下以太网,因为我们现在接触最多的就是以太网,并且研究数据包又是离不开以太网的帧的。在以太网中,数据是以被称为帧的数据结构本为单位进行交换的。以太网中常用的协议是CSMA/CD(carrier sense multiple access with collision detection)即载波监听多点接入/碰撞检测,在这里,我们关注的是帧的格式。常用的以太网帧的格式有两种标准,一种是DIX Ethernet V2标准,另一种是IEEE的802.3标准。现在最常用的MAC帧是V2格式,这也是我们所要研究的格式,至于802.3帧我们不再讨论。以太网V2帧的格式如下:
(插入8字节)目的地址(6字节)->源地址(6字节)->类型(2字节)->数据(46-1500)->FCS(4字节)
以太网的地址由48位的二进制来表示,也就是我们常说的MAC地址及硬件地址。在MAC帧前还有8字节的前同步码和帧的开始定界符,之后才是地址等报头信息。接收端和发送端的地址之后是2字节的类型字段,存放帧中传送数据的上层协议类型,RFC1700号文档规定了这些,如下:
ETHER TYPES(十六进制) PROTOCOlS
800 IP
806 ARP
8035 Revese ARP
809B Apple Talk
8137/8138 Novel
814c SNMP
帧的数据部分长度为46-1500字节,当小于46时,会在后面加入一个整数字节的填充字段。FCS(Frame Check Sequence)在以太网常用循环冗佘校检(CRC:cyclic redandancy check)。
IP协议为网络层协议,网络层的数据结构体被称为IP数据报。IP地址及域名这两个概念我们就不说了,下面我们来看一看IP数据报的结构:
成员名 字节数 说明
version 1/2 IP的版本,现在为IPV4
IHL(报送长度) 1/2 最常用为20,取5-15之前的值,最大60字节
Type Of Service 1 优先和可靠性服务要求的数值
Total Lenth 2 IP数据报的全长
Identification 2 识别IP数据报的编号
Flags 3/8 1位为0表示有碎块,2位为0表示是最后的碎块,为1表示接收中。
Fragment Offset 13/8 分片在原分组中的位置
TTL 1 数据报寿命,建议值为32秒
Protocol 1 上层协议
Headerchecksum 2 报头检验码
Source Address 4 发送端IP地址
Destination Address 4 接收端IP地址
Options And Padding 4 选项及填充位
其中协议字段的值对我们分析数据包是很重要的,下面列出来给大家看看:
值 协议 意义
1 ICMP Internet Control Message Protocol
6 TCP              Tranfer Control Protocol
8 EGP Exterior Gateway Protocol
     9 IGP Interior Gateway Protocol
17 UDP User Datagram Protocol
下面这些协议的值在后面的程序中我们可以见到,请大家留心记一下。接着我们介绍地址解析协议(ARP/RARP):
成员名 字节数 说明
Hardware address 2 硬件类型,以太网为1
Protocol address 2 上层协议类型,IP为800
Byte length of each hardware 1     查询物理地址的字节长度,以太网为6
Byte length of each protocol address 1 查询上层协议的字节长度,IPv4时为4
Opcode 2 1为ARP请求,2为响应;3为RARP请求,4为响应
Hardware address of sender of this packet 6 发送端硬件地址
protocol address of sender of this packet 4 发送端IP地址
Hardware address of target of this packet 6 查询对象硬件地址
Protocol address of target of this packet 4 查询对象IP地址
ARP/RARP协议用来查询IP对应的硬件地址或反过来查询IP地址,这在我们分析数据包时也会见到。下面介绍ICMP协议。我们常用的PING命令就是用的这个协议,这个协议比较简单,由类型(1字节)、代码(1字节)、检验和(2字节)、还有四个字节的与类型相关的可变部分及数据构成。
数据包在运输层还有两个重要的协议,即TCP/UDP,TCP/UDP中使用端口的概念,以区别计算机上不同的程序。下面我们先来看看TCP数据报的首部构成:
成员名 字节数 说明
Source Port 2 发送端端口号
Destination Port 2 接收端端口号
Sequence NO 4 本报文段所发送的第一个字节的序号
ACk Number 4 期望收到的下一个报文段的序号
DAta Offset 1/2 首部的长度
Reserved 3/4 保留今后用
Contol Bits 3/4 控制位
Window 2 滑动窗口的大小
Checksum 2 检验和
Urgent Pointer 2 紧急指针
Options And Padding 4 可选,真充项
Tcp被使用在跨越路由器进行网络服务的网络应用程序中,如WWW、电子邮件、新闻、FTP等。UDP则是在IP的基础上加入了端口的概念,其结构很简单,只有八个字节首部如下:
源端口(2字节)->目的端口(2字节)->长度(2字节)->检验和(2字节)

二、程序部分函数及数据结构说明
在此部分我们将介绍后面程序中用到的部分函数及数据结构。在程序中我们使用了PCAP程序库,大家可以从
ftp://ftp.ee.lbl.gov/libpcap.tar.z下载。我们主要在Redhat Linux下测试程序,这里简单介绍一下程序库的安装方法,其它环境请大家自行解决。我的目的是给大家编写数据包分析程序提供思路,至于实用程序的实现这里不做介绍,第三部分给出的程序也不具实用性,为了演示,程序中实现的功能较多而有些地方又不够详细,编写实用程序时请适当取舍并加入你所需要的功能实现部分。PCAP程序库的安装方法如下:
1、解压文件
2、进入文件目录执行./configure 及make
3、使用Make命令,设定手册和Include文件(要有Root权限),执行以下命令:
make install -man
make install -incl
4、如出现不存在Include及Include/net目录,则建立此目录并重新执行 make install -incl
5、检查/usr/include/netinet/目录是否存在Protocols.h文件,不存在则拷贝过去。至此程序库安装完毕。
下面介绍程序中出现的部分函数及数据结构:
1、PCAP_t *pd;
此型数据结构称为数据包捕捉描述符。
2、Pcap_Open_Live(argv[1],DEFAUT_SNALEN,1,1000,ebuf)
此函数对Pcap程序库进行初始化并返回指向Pcap_t型数据的指针,其参数列表如下:
        char * 指定网络接口
int 取得数据的最大字节数
int 指定网络接口卡,一般用1
int 读出暂停时间
char * 错误消息用缓冲区
3、Pcap_loop(pd,-1,packet_proce,NUll)
     此函数程序的核心,反复执行,利用Pcap取得数据包,返回的是读入数据包的个数,错误时返回-1,其参数列表如下:
Pcap_t * 指定取得数据包的数据包捕捉描述符
int 取得数据包的个数,-1为无限
返回指向函数的指针 指定数据包处理的函数
U_char * 指向赋给数据包处理函数字符串的指针
4、struct ether_header * eth
此结构体存储以太网报头信息,其成员如下:
ether_dhost[6] 接收端的MAC地址
ether_shost[6] 发送端的MAC地址
ether_type 上层协议的种类
5、fflush(stdout)
此函数完成的是强制输出,参数Stdout,强制进行标准输出。
6、noths(((struct ether_header *P)->ether_type))
此函数将短整型网络字节顺序转换成主机字节顺序。此类函数还有:
ntohl 长整型 功能同上
htons 短整型 将主机字节顺序转换成网络字节顺序
htons 长整型 同上
7、struct IP *iph
ip型结构体在IPh文件中定义,其成员和第一部分讲到的IP数据报结构对应,如下:
成员名 类型 说明
ip_hl 4位无符号整数 报头长度
ip_v 同上 版本,现为4
ip_tos 8位无符号整数 Type of service
ip_len 16位无符号整数 数据报长度
ip_id 同上 标识
ip_off 同上 数据块偏移和标志
ip_ttl 8位无符号整数 TTL值
ip_p 同上 上层协议
ip_sum 16位无符号整数 检验和
ip_src in_addr结构体 发送端IP
ip_dst 同上 接收端IP
8、struct ether_arp *arph
ether_arp型结构体成员如下:
成员名 类型 说明
ea_hdr arphdr型结构体 报头中地址以外的部分
arp_sha 8位无符号整数数组 发送端MAC地址
arp_spa 同上 发送端IP地址
arp_tha 同上 目标MAC地址
arp_tpa 同上 目标IP地址
9、struct icmphdr * icmp
icmphdr型结构体中包含共用体根据数据报类型的不同而表现不同性质,这里不再列出,只列能通用的三个成员
成员名 说明
type 类型字段
code 代码
checksum 检验和

三、案例程序剖析
//example.c
//使用方法:example〈网络接口名〉 > 〈输出文件名〉
//例如:example etho > temp.txe
//结束方法:ctrl+c
//程序开始,读入头文件
#include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<netinet/in_systm.h>
#include<netinet/ip.h>
#include<netinet/if_ether.h>
#include<pcap.h> //pcap程序库
#include<netdb.h> //DNS检索使用
#define MAXSTRINGSIZE 256 //字符串长度
#define MAXSIZE 1024 //主机高速缓存中的最大记录条数
#fefine DEFAULT_SNAPLEN 68 /数据包数据的长度
typedef struct
{
    unsigned long int ipaddr; //IP地址
    char hostname[MAXSTRINGSIZE]; //主机名
}dnstable; //高速缓存数据结构
typedef struct
{
    dnstable table[MAXSIZE];
    int front;
    int rear;
}sequeue;
sequeue *sq; //定义缓存队列
sq->rear=sq->front=0; //初始化队列
//输出MAC地址函数
void print_hwadd(u_char * hwadd)
{
    for(int i=0,i<5;++i)
        printf(“%2x:”,hwadd[i]);
    printf(“%2x”,hwadd[i]);
}
//输出IP地址的函数
void print_ipadd(u_char *ipadd)
{
    for(int i=0;i<3;++i)
        printf(“%d.”,ipadd[i]);
    printf(“%d”,ipadd[i]);
}
//查询端口函数
void getportname(int portno,char portna[],char* proto)
{
    if(getservbyport(htons(portno),proto)!=NULL)
    {
        strcpy(portna,getservbyport(htons(portno),proto)->s_name);
    }
    else
        sprintf(portna,”%d”,portno);
}
//将IP转化为DNS名
void iptohost(unsigned long int ipad,char* hostn)
{
    struct hostent * shostname;
    int m,n,i;
    m=sq->rear;
    n=sq->front;
    for(i=n%MAXSIZE;i=m%MAXSIZE;i=(++n)%MAXSIZE)
    {
        //检查IP是否第一次出现
        if(sq->table[i].ipaddr==ipad)
        {
            strcpy(hostn,sq->table[i].hostname);
            break;
        }
    }
    if(i=m%MAXSIZE)
    {//不存在则从域名服务器查询并把结果放入高速缓存
        if((sq->rear+1)%MAXSIZE=sq->front) //判队满
            sq->front=(sq->front+1)%MAXSIZE; //出队列
        sq->table[i].ipaddr=ipad;
        shostname=gethostbyaddr((char*)&ipad,sizeof(ipad),AF_INET);
        if(shostname!=NULL)
            strcpy(sq->table[i].hostname,shostname->h_name);
        else
            strcpy(sq->table[i].hostname,”");
        sq->rear=(sq->rear+1)%MAXSIZE;
    }
}
void print_hostname(u_char* ipadd)
{
    unsigned long int ipad;
    char hostn[MAXSTRINTSIZE];
    ipad=*((unsigned long int *)ipadd);
    iptohost(ipad,hostn)
        if(strlen(hostn)>0)
            printf(“%s”,hostn);
        else
            print_ipadd(ipadd);
}
//处理数据包的函数
void packet_proce(u_char* packets,const struct pcap_pkthdr * header,const u_char *pp)
{
    struct ether_header * eth; //以太网帧报头指针
    struct ether_arp * arth; //ARP报头
    struct ip * iph; //IP报头
    struct tcphdr * tcph;
    struct udphdr * udph;
    u_short srcport,dstport; //端口号
    char protocol[MAXSTRINGSIZE]; //协议类型名
    char srcp[MAXSTRINGSIZE],dstp[MAXSTRINGSIZE]; //端口名
    unsigned int ptype; //协议类型变量
    u_char * data; //数据包数据指针
    u_char tcpudpdata[MAXSTRINGSIZE]; //数据包数据
    int i;
    eth=(struct ether_header *)pp;
    ptype=ntohs(((struct ether_header *)pp)->ether_type);
    if((ptype==ETHERTYPE_ARP)||(ptype==ETHERTYPE_RARP))
    {
        arph=(struct ether_arp *)(pp+sizeof(struct ether_header));
        if(ptype==ETHERTYPE_ARP)
            printf(“arp “);
        else
            printf(“rarp “); //输出协议类型
        print_hwadd((u_char *)&(arph->arp_sha));
        printf(“(“);
        print_hostname((u_char *)&(arph->arp_spa));
        printf(“)->”);
        print_hwadd((u_char *)&(arph->arp_tha));
        printf(“(“);
        print_hostname((u_char *)&(arph->arp_tpa));
        printf(“)\tpacketlen:%d”,header->len);
    }
    else if(ptype==ETHERTYPE_IP) //IP数据报
    {
        iph=(struct ip *)(pp+sizeof(struct ether_header));
        if(iph->ip_p==1) //ICMP报文
        {
            strcpy(protocol,”icmp”);
            srcport=dstport=0;
        }
        else if(iph->ip_p==6) //TCP报文
        {
            strcpy(protocol,”tcp”);
            tcph=(struct tcphdr *)(pp+sizeof(struct ether_header)+4*iph->ip_hl);
            srcport=ntohs(tcph->source);
            dstport=ntohs(tcph->dest);
            data=(u_char *)(pp+sizeof(struct ether_header)+4*iph->ip_hl+4*tcph->doff);
            for(i=0;i<MAXSTRINGSIZE-1;++i)
            {
                if(i>=header->len-sizeof(struct ether_header)-4*iph->ip_hl-4*tcph->doff);
                break;
                else
                    tcpudpdata[i]=data[i];
            }
        } //TCP数据处理完毕
        else if(iph->ip_p=17) //UDP报文
        {
            strcpy(protocol,”udp”);
            udph=(struct udphdr *)(pp+sizeof(struct ether_header)+4*iph->ip_hl);
            srcport=ntohs(udph->source);
            dstport=ntohs(udph->dest);
            data=(u_char *)(pp+sizeof(struct ether_header)+4*iph->ip_hl+8);
            for(i=0;i<MAXSTRINGSIZE-1;++i)
            {
                if(i>=header->len-sizeof(struct ether_header)-4*iph->ip_hl-8);
                break;
                else
                    tcpudpdata[i]=data[i];
            }
        }
        tcpudpdata[i]=’\0′;
        getportname(srcport,srcp,protocol);
        getportname(dstport,dstp,protocol);
        printf(“ip “);
        print_hwadd(eth->ether_shost);
        printf(“(“);
        print_hostname((u_char *)&(iph->ip_src));
        printf(“)[%s:%s]->”,protocol,srcp);
        print_hwadd(eth->ether_dhost);
        printf(“(“);
        print_hostname((u_char *)&(iph->ip_dst));
        printf(“)[%s:%s]“,protocol,dstp);
        printf(“\tttl:%d packetlen:%d,iph->ttl,header->len);
     printf(“\n”);
        printf(“%s”,tcpudpdata);
        printf(“==endpacket==”);
    }
    printf(“\n”);
}
//Main函数取数据包并初始化程序环境
int main(int argc,char ** argv)
{
    char ebuf[pcap_ERRBUF_SIZE];
    pcap * pd;
    if(argc<=1) //参数检查
    {
        printf(“usage:%s<network interface>\n”,argv[0]);
        exit(0);
    }
    //设置PCAP程序库
    if((pd=pcap_open_live(argv[1],DEFAULT_SNAPLEN,1,1000,ebuf))=NULL)
    {
        (void)fprintf(stderr,”%s”,ebuf);
        exit(1);
    }
    //循环取数据包
    //改变参数-1为其它值,可确定取数据包的个数,这里为无限个
    if(pcap_loop(pd,-1,packet_proce,NULL)<0)
    {
        (void)fprintf(stderr,”pcap_loop:%s\n”,pcap_geterr(pd));
        exit(1);
    }
    pcap_colse(pd);
    exit(0);
}
//程序结束
到此为止,我的这篇文章就写完了,希望能给大家以启发和帮助。平时和网友交流中发现大多数朋友在网上见到长的文章往往都没有信心看下去,其实以前我也是这样,但在这里我想告诉大家,不踏踏实实的深入进去学习,是得不到收获的。一分耕耘,一分收获。

TCP/IP是很多的不同的协议组成,实际上是一个协议组,TCP用户数据报表协议(也称作TCP传输控制协议,Transport Control Protocol。可靠的主机到主机层协议。这里要先强调一下,传输控制协议是OSI网络的第四层的叫法,TCP传输控制协议是TCP/IP传输的6个基本协议的一种。两个TCP意思非相同。 )。TCP是一种可靠的面向连接的传送服务。它在传送数据时是分段进行的,主机交换数据必须建立一个会话。它用比特流通信,即数据被作为无结构的字节流。 通过每个TCP传输的字段指定顺序号,以获得可靠性。是在OSI参考模型中的第四层,TCP是使用IP的网间互联功能而提供可靠的数据传输,IP不停的把报文放到 网络上,而TCP是负责确信报文到达。在协同IP的操作中TCP负责:握手过程、报文管理、流量控制、错误检测和处理(控制),可以根据一定的编号顺序对非正常顺序的报文给予从新排列顺序。关于TCP的RFC文档有RFC793、RFC791、RFC1700。

  在TCP会话初期,有所谓的“三握手”:对每次发送的数据量是怎样跟踪进行协商使数据段的发送和接收同步,根据所接收到的数据量而确定的数据确认数及数据发送、接收完毕后何时撤消联系,并建立虚连接。为了提供可靠的传送,TCP在发送新的数据之前,以特定的顺序将数据包的序号,并需要这些包传送给目标机之后的确认消息。TCP总是用来发送大批量的数据。当应用程序在收到数据后要做出确认时也要用到TCP。由于TCP需要时刻跟踪,这需要额外开销,使得TCP的格式有些显得复杂。下面就让我们看一个TCP的经典案例,这是后来被称为MITNICK攻击中KEVIN开创了两种攻击技术:

  TCP会话劫持
  SYN FLOOD(同步洪流)

  在这里我们讨论的时TCP会话劫持的问题。

  先让我们明白TCP建立连接的基本简单的过程。为了建设一个小型的模仿环境我们假设有3台接入互联网的机器。A为攻击者操纵的攻击机。B为中介跳板机器(受信任的服务器)。C为受害者使用的机器(多是服务器),这里把C机器锁定为目标机器。A机器向B机器发送SYN包,请求建立连接,这时已经响应请求的B机器会向A机器回应SYN/ACK表明同意建立连接,当A机器接受到B机器发送的SYN/ACK回应时,发送应答ACK建立A机器与B机器的网络连接。这样一个两台机器之间的TCP通话信道就建立成功了。

  B终端受信任的服务器向C机器发起TCP连接,A机器对服务器发起SYN信息,使C机器不能响应B机器。在同时A机器也向B机器发送虚假的C机器回应的SYN数据包,接收到SYN数据包的B机器(被C机器信任)开始发送应答连接建立的SYN/ACK数据包,这时C机器正在忙于响应以前发送的SYN数据而无暇回应B机器,而A机器的攻击者预测出B机器包的序列号(现在的TCP序列号预测难度有所加大)假冒C机器向B机器发送应答ACK这时攻击者骗取B机器的信任,假冒C机器与B机器建立起TCP协议的对话连接。这个时候的C机器还是在响应攻击者A机器发送的SYN数据。

  TCP协议栈的弱点:TCP连接的资源消耗,其中包括:数据包信息、条件状态、序列号等。通过故意不完成建立连接所需要的三次握手过程,造成连接一方的资源耗尽。

  通过攻击者有意的不完成建立连接所需要的三次握手的全过程,从而造成了C机器的资源耗尽。序列号的可预测性,目标主机应答连接请求时返回的SYN/ACK的序列号时可预测的。(早期TCP协议栈,具体的可以参见1981年出的关于TCP雏形的RFC793文档)

  TCP头结构

  TCP协议头最少20个字节,包括以下的区域(由于翻译不禁相同,文章中给出相应的英文单词):

  TCP源端口(Source Port):16位的源端口其中包含初始化通信的端口。源端口和源IP地址的作用是标示报问的返回地址。

  TCP目的端口(Destination port):16位的目的端口域定义传输的目的。这个端口指明报文接收计算机上的应用程序地址接口。

  TCP序列号(序列码,Sequence Number):32位的序列号由接收端计算机使用,重新分段的报文成最初形式。当SYN出现,序列码实际上是初始序列码(ISN),而第一个数据字节是ISN+1。这个序列号(序列码)是可以补偿传输中的 不一致。

  TCP应答号(Acknowledgment Number):32位的序列号由接收端计算机使用,重组分段的报文成最初形式。,如果设置了ACK控制位,这个值表示一个准备接收的包的序列码。

  数据偏移量(HLEN):4位包括TCP头大小,指示何处数据开始。

  保留(Reserved):6位值域,这些位必须是0。为了将来定义新的用途所保留。

  标志(Code Bits):6位标志域。表示为:紧急标志、有意义的应答标志、推、重置连接标志、同步序列号标志、完成发送数据标志。按照顺序排列是:URG、ACK、PSH、RST、SYN、FIN。

  窗口(Window):16位,用来表示想收到的每个TCP数据段的大小。

  校验位(Checksum):16位TCP头。源机器基于数据内容计算一个数值,收信息机要与源机器数值 结果完全一样,从而证明数据的有效性。

  优先指针(紧急,Urgent Pointer):16位,指向后面是优先数据的字节,在URG标志设置了时才有效。如果URG标志没有被设置,紧急域作为填充。加快处理标示为紧急的数据段。

  选项(Option):长度不定,但长度必须以字节。如果 没有 选项就表示这个一字节的域等于0。

  填充:不定长,填充的内容必须为0,它是为了数学目的而存在。目的是确保空间的可预测性。保证包头的结合和数据的开始处偏移量能够被32整除,一般额外的零以保证TCP头是32位的整数倍。 标志控制功能

  URG:紧急标志

  紧急(The urgent pointer) 标志有效。紧急标志置位,

  ACK:确认标志

  确认编号(Acknowledgement Number)栏有效。大多数情况下该标志位是置位的。TCP报头内的确认编号栏内包含的确认编号(w+1,Figure:1)为下一个预期的序列编号,同时提示远端系统已经成功接收所有数据。

  PSH:推标志

  该标志置位时,接收端不将该数据进行队列处理,而是尽可能快将数据转由应用处理。在处理 telnet 或 rlogin 等交互模式的连接时,该标志总是置位的。

  RST:复位标志

  复位标志有效。用于复位相应的TCP连接。

  SYN:同步标志

  同步序列编号(Synchronize Sequence Numbers)栏有效。该标志仅在三次握手建立TCP连接时有效。它提示TCP连接的服务端检查序列编号,该序列编号为TCP连接初始端(一般是客户端)的初始序列编号。在这里,可以把TCP序列编号看作是一个范围从0到4,294,967,295的32位计数器。通过TCP连接交换的数据中每一个字节都经过序列编号。在TCP报头中的序列编号栏包括了TCP分段中第一个字节的序列编号。

  FIN:结束标志

  带有该标志置位的数据包用来结束一个TCP回话,但对应端口仍处于开放状态,准备接收后续数据。

  服务端处于监听状态,客户端用于建立连接请求的数据包(IP packet)按照TCP/IP协议堆栈组合成为TCP处理的分段(segment)。

  分析报头信息: TCP层接收到相应的TCP和IP报头,将这些信息存储到内存中。

  检查TCP校验和(checksum):标准的校验和位于分段之中(Figure:2)。如果检验失败,不返回确认,该分段丢弃,并等待客户端进行重传。

  查找协议控制块(PCB{}):TCP查找与该连接相关联的协议控制块。如果没有找到,TCP将该分段丢弃并返回RST。(这就是TCP处理没有端口监听情况下的机制) 如果该协议控制块存在,但状态为关闭,服务端不调用connect()或listen()。该分段丢弃,但不返回RST。客户端会尝试重新建立连接请求。

  建立新的socket:当处于监听状态的socket收到该分段时,会建立一个子socket,同时还有socket{},tcpcb{}和pub{}建立。这时如果有错误发生,会通过标志位来拆除相应的socket和释放内存,TCP连接失败。如果缓存队列处于填满状态,TCP认为有错误发生,所有的后续连接请求会被拒绝。这里可以看出SYN Flood攻击是如何起作用的。

  丢弃:如果该分段中的标志为RST或ACK,或者没有SYN标志,则该分段丢弃。并释放相应的内存。

  发送序列变量

  SND.UNA : 发送未确认

  SND.NXT : 发送下一个

  SND.WND : 发送窗口

  SND.UP : 发送优先指针

  SND.WL1 : 用于最后窗口更新的段序列号

  SND.WL2 : 用于最后窗口更新的段确认号

  ISS : 初始发送序列号

  接收序列号

  RCV.NXT : 接收下一个

  RCV.WND : 接收下一个

  RCV.UP : 接收优先指针

  IRS : 初始接收序列号

当前段变量

  SEG.SEQ : 段序列号

  SEG.ACK : 段确认标记

  SEG.LEN : 段长

  SEG.WND : 段窗口

  SEG.UP : 段紧急指针

  SEG.PRC : 段优先级

  CLOSED表示没有连接,各个状态的意义如下:

  LISTEN : 监听来自远方TCP端口的连接请求。

  SYN-SENT : 在发送连接请求后等待匹配的连接请求。

  SYN-RECEIVED : 在收到和发送一个连接请求后等待对连接请求的确认。

  ESTABLISHED : 代表一个打开的连接,数据可以传送给用户。

  FIN-WAIT-1 : 等待远程TCP的连接中断请求,或先前的连接中断请求的确认。

  FIN-WAIT-2 : 从远程TCP等待连接中断请求。

  CLOSE-WAIT : 等待从本地用户发来的连接中断请求。

  CLOSING : 等待远程TCP对连接中断的确认。

  LAST-ACK : 等待原来发向远程TCP的连接中断请求的确认。

  TIME-WAIT : 等待足够的时间以确保远程TCP接收到连接中断请求的确认。

  CLOSED : 没有任何连接状态。

  TCP连接过程是状态的转换,促使发生状态转换的是用户调用:OPEN,SEND,RECEIVE,CLOSE,ABORT和STATUS。传送过来的数据段,特别那些包括以下标记的数据段SYN,ACK,RST和FIN。还有超时,上面所说的都会时TCP状态发生变化。

  序列号

  请注意,我们在TCP连接中发送的字节都有一个序列号。因为编了号,所以可以确认它们的收到。对序列号的确认是累积性的。TCP必须进行的序列号比较操作种类包括以下几种:

  ①决定一些发送了的但未确认的序列号。

  ②决定所有的序列号都已经收到了。

  ③决定下一个段中应该包括的序列号。

  对于发送的数据TCP要接收确认,确认时必须进行的:

  SND.UNA = 最老的确认了的序列号。

  SND.NXT = 下一个要发送的序列号。

  SEG.ACK = 接收TCP的确认,接收TCP期待的下一个序列号。

  SEG.SEQ = 一个数据段的第一个序列号。

  SEG.LEN = 数据段中包括的字节数。

  SEG.SEQ+SEG.LEN-1 = 数据段的最后一个序列号。

  如果一个数据段的序列号小于等于确认号的值,那么整个数据段就被确认了。而在接收数据时下面的比较操作是必须的:

  RCV.NXT = 期待的序列号和接收窗口的最低沿。

  RCV.NXT+RCV.WND:1 = 最后一个序列号和接收窗口的最高沿。

  SEG.SEQ = 接收到的第一个序列号。

  SEG.SEQ+SEG.LEN:1 = 接收到的最后一个序列号.