第一节 了解Windows机制
Windows 是一个“基于事件的, 消息驱动的”操作系统。
在Windows下执行一个程序, 只要用户进行了影响窗口的动作(如改变窗口大小或移动、单击鼠标等)该动作就会触发一个相应的“事件”
系统每次检测到一个事件时, 就会给程序发送一个“消息”, 从而使程序可以处理该事件。
每个Windows 应用程序都是基于事件和消息的, 而且包含一个主事件循环, 它不停地、反复地检测是否有用户事件发生。 每次检测到一个
用户事件, 程序就对该事件做出响应, 处理完再等待下一个事件的发生。
Windows 下的应用程序不断地重复这一过程, 直至用户终止程序, 用代码来描述实际上也就是一个消息处理过程的while循环语句。
下面便简单介绍一下与 Windows 系统密切相关的几个基本概念:
1:窗口:这是我要说的第一个概念。 窗口是Windows本身以及Windows 环境下的应用程序的基本界面单位, 但是很多人都误以为只有具有
标题栏、状态栏、最大化、最小化按钮这样标准的方框才叫窗口。 其实窗口的概念很广, 例如按钮和对话框等也是窗口, 只不过是一种特殊化
窗口罢了。
从用户的角度看, 窗口就是显示在屏幕上的一个矩形区域, 其外观独立于应用程序, 事实上它就是生成该窗口的应用程序与用户间的直观
接口;从应用程序的角度看, 窗口是受其控制的一部分矩形屏幕区。 应用程序生成并控制与窗口有关的一切内容, 包括窗口的大小、风格、位
置以及窗口内显示的内容等。 用户打开一个应用程序后, 程序将创建一个窗口, 并在那里默默地等待用户的要求。 每当用户选择窗口中的选
项, 程序即对此做出响应。
2:程序:通常说的程序都是指一个能让计算机识别的文件, 接触得最多的便是.exe型的可执行文件.
3:进程:说到进程, 学过《操作系统》的人都很清楚, 所谓进程就是应用程序的执行实例(或称一个执行程序) 需要注意的是:进程是
程序动态的描述, 而上面说到的程序是静态的描述, 两者有本质的区别。 举个例子, 从网上 Down了一个瑞星杀毒软件到C盘但没有运行, 那
个.exe 可执行文件叫做程序, 它是一个二进制码的文件。 一旦双击了exe文件图标运行程序, 那个“正在运行着的瑞星杀毒”便称为进程, 它
在双击的那一刻被系统创建, 当你关机或者在任务栏的图标上单击鼠标右键选“退出”时, 进程便消亡, 彻底结束了生命。 进程经历了由“创
建”到“消亡”的生命期, 而程序自始至终存在于你的硬盘上, 不管你的机器是否启动。
4:线程:线程是进程中的一个执行单元, 同一个进程中的各个线程对应于一组CPU指令、一组CPU寄存器以及一堆栈。 进程本来就具有动态
的含义, 然而实质上是通过线程来执行体现的, 从这个意义上说, Windows 中进程的动态性意义已经不是很明显了, 只算是给程序所占的资源
划定一个范围而已, 真正具有动态性意义的是线程。
5:消息:我们几乎做每一个动作都会产生一个消息,鼠标被移动会产生WM_MOUSEMOVE消息, 鼠标左键被按下会产生WM_LBUTTONDOWN的消
息, 鼠标右键按下便产生WM_RBUTTONDOWN消息等等。 所有的这些都可以通过GetMessage, SendMessage等函数得到.
6:事件:如在程序运行的过程中改变窗口的大小或者移动窗口等, 都会触发相应的“事件”。
7:句柄:单单一个“柄”字便可以解释它的意思了, 我们天气热摇扇子的时候只要抓住扇柄便可以控制整个扇子的运动了, 在程序中也
差不多是这个意思。 通常一个句柄就可以传递我们所要做的事情。 有经验的成员肯定清楚, 编写程序总是要和各种句柄打交道的, 句柄是
系统用来标识不同对象类型的工具, 如窗口、菜单等, 这些东西在系统中被视为不同类型的对象, 用不同的句柄将他们区分开来。
C++ 教材中给句柄下的定义是:“在Win32里, 句柄是指向一个无值型对象(void *)的指针, 是一个4字节长的数据”。 从结构
上看, 句柄的确是一个指针, 尽管它没有指向用于存储某个对象的内存位置, 而实际上句柄指向的是一个包含了对该对象进行的引用的位置。
在编程时, 只要抓住了对象的句柄就可以对该对象进行操作了.
8:API与SDK:API是英文 Application Programming Interface 的简称, 意为“应用程序接口”, 泛指系统为应用程序提供的一系列接
口函数。 其实质是程序内的一套函数调用, 在编程的时候可以直接调用, 而不必知道其内部实现的过程, 只知道它的原型和返回值就可以
了.
SDK是英文 Software Development Kit 的缩写,指“软件开发工具包”, 在防火墙的设计中就经常涉及到SDK。
第二节 Win API编程简介
下面介绍一下WIN API.
我们需要自己编写一个工具时,必然会用到很多操作windows和控制windows的函数,这些函数就是windows API.
API是Application Progamming Interface的缩写.就是说API是一系列已经定义的在windows内部的函数,是应用程序和系统之间的
桥梁,应用程序通过调用API来请求系统完成一系列的任务.窗口,菜单,文件操作等都是通过API实现的.
WIN32 API就是WINDOWS 32位平台的应用程序接口.现在可视化编程工具提供了大量控件,他们代替了API的功能.这些控件都是构建
在WIN32 API之上的.是封装了的API函数集合.但是对于比较复杂和特殊功能(想我们的黑客编程)来说,就必须用API函数来实现.
WIN API存放在动态链接库(DLL)中,在98系统中,有32位的GDI32.DLL,KERNEL32.DLL,16位的GDI.EXE,KRNL386.EXE.API就存放在这些
动态链接库中.
木马和后门其实就是使用了文件操作函数,这里做简要介绍:
删除文件:BOOL DeleteFile(LPCTSH lpFileName)
复制文件:BOOL CopyFile()
移动文件:BOOL MoveFile()等等
具体的API可以上网自己去查看,有很多介绍API的书籍.
第二节 Win API编程简介
下面介绍一下WIN API.
我们需要自己编写一个工具时,必然会用到很多操作windows和控制windows的函数,这些函数就是windows API.
API是Application Progamming Interface的缩写.就是说API是一系列已经定义的在windows内部的函数,是应用程序和系统之间的
桥梁,应用程序通过调用API来请求系统完成一系列的任务.窗口,菜单,文件操作等都是通过API实现的.
WIN32 API就是WINDOWS 32位平台的应用程序接口.现在可视化编程工具提供了大量控件,他们代替了API的功能.这些控件都是构建
在WIN32 API之上的.是封装了的API函数集合.但是对于比较复杂和特殊功能(想我们的黑客编程)来说,就必须用API函数来实现.
WIN API存放在动态链接库(DLL)中,在98系统中,有32位的GDI32.DLL,KERNEL32.DLL,16位的GDI.EXE,KRNL386.EXE.API就存放在这些
动态链接库中.
木马和后门其实就是使用了文件操作函数,这里做简要介绍:
删除文件:BOOL DeleteFile(LPCTSH lpFileName)
复制文件:BOOL CopyFile()
移动文件:BOOL MoveFile()等等
具体的API可以上网自己去查看,有很多介绍API的书籍.
第二节 Win API编程简介
下面介绍一下WIN API.
我们需要自己编写一个工具时,必然会用到很多操作windows和控制windows的函数,这些函数就是windows API.
API是Application Progamming Interface的缩写.就是说API是一系列已经定义的在windows内部的函数,是应用程序和系统之间的
桥梁,应用程序通过调用API来请求系统完成一系列的任务.窗口,菜单,文件操作等都是通过API实现的.
WIN32 API就是WINDOWS 32位平台的应用程序接口.现在可视化编程工具提供了大量控件,他们代替了API的功能.这些控件都是构建
在WIN32 API之上的.是封装了的API函数集合.但是对于比较复杂和特殊功能(想我们的黑客编程)来说,就必须用API函数来实现.
WIN API存放在动态链接库(DLL)中,在98系统中,有32位的GDI32.DLL,KERNEL32.DLL,16位的GDI.EXE,KRNL386.EXE.API就存放在这些
动态链接库中.
木马和后门其实就是使用了文件操作函数,这里做简要介绍:
删除文件:BOOL DeleteFile(LPCTSH lpFileName)
复制文件:BOOL CopyFile()
移动文件:BOOL MoveFile()等等
具体的API可以上网自己去查看,有很多介绍API的书籍.
第三节 Windows 网络协议
首先介绍一下网络协议:网络协议是网络上所有设备之间通信规则的集合,他定义了通信时信息必须采用的格式和这些格式的意义.大多数
网络协议都采用分层体系结,每一层都建立在他的下层之上,向他的上一层提供服务,而把如何实现这一服务的细节对上层加以屏蔽.一台设备上
的第N层与另一台设备上的第N层进行通信的规则就是第N曾协议.在网络上的个层之间中存在着许多协议,接受方和发送方同层的协议必须一致,
否则,一方就无法识别另一方发出的信息.网络协议使网络上的设备各种设备能相互交换信息.
常用的协议有:TCP/IP协议,IPX/SPX歇息等等.在局域网中常用的IPX/SPX协议.而访问INTERNET,就必须添加TCP/IP协议.
TCP/IP协议是传输控制协议/互联网络协议.他规范了网络上所有设备的通信,尤其是一个主机与另一个主机之间的数据往来格式以及传送
方式.
在网络的各层中还存在着许多协议,下面列出部分网络协议规范:
arp 地址解析协议
SNMP 网络管理协议
BOOTP 让无盘站从一个中心服务器上获得IP地址
DHCP 动态主机配置协议
下面介绍网络7层协议在WINDOWS的实现:
7层协议 WIN系统
________________________________________
7 应用层 7 应用程序
________________________________________________
6 表示层 6 WINSOCK API(DLL)
___________________________________________
5 会话层 5 SPI(DLL)
__________________________________________________
4 传输层 4 TDI(VXD,SYS)
___________________________________________________
3 网络层 3 NDIS(VXD,SYS)
__________________________________________________
2 数据链路层 2 网卡驱动程序(VXD,SYS)
___________________________________________
1 物理层 1 网卡
_________________________________________________
相信这个映射图可以让大家比较清楚了解他们的对应关系
TCP协议图示
应用程序协议 HTTP FTP TELNET
传输协议 TCP UDP
网际协议 IP
物理层协议 网卡
IP协议保证数据的传输,TCP协议保证数据传输的质量.
TCP/IP协议基于四层结构:应用层,传输层,网络层,接口层,数据在传输时每通过一层就要在数据上加个头,其中的数据供接受端同层使用,在
接收端,每经过一层就把头去掉,来保证传输数据格式的一致.
TCP头部结构:
16位源端口号 16位目的端口号
_______________________________________________________________________________
32位序列号
___________________________________________________________________________
32位确认号
_____________________________________________________________________________________
4位首部长度+6位保留字 6位标志 16位窗口大小
_______________________________________________________________________________________
16位效验和 16位紧急数据偏移量
_____________________________________________________________________________________
数据段
_______________________________________________________________________________
IP头部结构:
4位IP版本号 4位首部长度 8位服务类型 16位总长度
___________________________________________________________________________________________
16位标示 3位标志和偏移
__________________________________________________________________________
8位生存时间 8位协议 16位IP首部效验和
_________________________________________________________________________________________
32位源IP地址
___________________________________________________________________________________________
32位目的IP地址
________________________________________________________________________________________
TCP头和数据
____________________________________________________________________________
第四节 关于服务器和客户端编程
在网络编程中,最常用和最基础的就是WINSOCK. 现在我们讨论WINDOWS下的SOCKET编程.
大凡在WIN32平台上的WINSOCK编程都要经过下列步骤:
定义变量->获得WINDOCK版本->加载WINSOCK库->初始化->创建套接字->设置套接字选项->关闭套接字>卸载WINSOCK库->释放资源
下面介绍WINSOCK C/S的建立过程:
服务器 客户端
________________________________________________
1 初始化WSA 1 初始化WSA
____________________________________________________
2 建立一个SOCKET 2 建立一个SOCKET
_____________________________________________________
3 绑定SOCKET 3 连接到服务器
_____________________________________________________
4 在指定的端口监听 4 发送和接受数据
_____________________________________________________
5 接受一个连接 5 断开连接
______________________________________________________-
6 发送和接受数据
___________________________________________________
7 断开连接
__________________________________________________
大家注意,在VC中进行WINSOCK编程时,需要引入如下两个库文件:WINSOCK.H(这个是WINSOCK API的头文件,WIN2K以上支持WINSOCK2,所以
可以用WINSOCK2.H);Ws2_32.lib(WINSOCK API连接库文件).
使用方式如下:
#include <winsock.h>
#pragma comment(lib,"ws2_32.lib")
下面我们通过具体的代码演示服务器和客户端的工作流程:
首先,建立一个WSADATA结构,通常用wsaData
WSADATA wsaData;
然后,调用WSAStartup函数,这个函数是连接应用程序与winsock.dll的第一个调用.其中,第一个参数是WINSOCK 版本号,第二个参数是指向
WSADATA的指针.该函数返回一个INT型值,通过检查这个值来确定初始化是否成功.调用格式如下:WSAStartup(MAKEWORD(2,2),&wsaData),其中
MAKEWORD(2,2)表示使用WINSOCK2版本.wsaData用来存储系统传回的关于WINSOCK的资料.
if(iResuit=WSAStartup(MAKEWORD(2,2),&wsaData)!=0)
{
printf("WSAStartup failed:%d",GetLastError()); //返回值不等与0,说明初始化失败
ExitProcess(); //退出程序
}
应用程序在完成对请求的SOCKET库使用后,要调用WSACleanup函数来接触SOCKET库的绑定,并且释放资源.
注意WSAStartup初始化后,必须建立一个SOCKET结构来保存SOCKET句柄.
下面我们建立一个SOCKET.
首先我们建立一个m_socket的SOCKET句柄,接着调用socket()函数,函数返回值保存在m_socket中.我们使用AF_INFE,SOCK_STREAM,IPPROTO_TCP
三个参数.第一个表示地址族,AF_INFE表示TCP/IP族,第二个表示服务类型,在WINSOCK2中,SOCKET支持以下三种类型;
SOCK_STREAM 流式套接字
SOCK_DGRAM 数据报套接字
SOCK_RAW 原始套接字
第三个参数表示协议:
IPPROTO_UDP UDP协议 用于无连接数据报套接字
IPPROTO_TCP TCP协议 用于流式套接字
IPPROTO_ICMP ICMP协议用于原始套接字
m_socket=socket(AF_INFE,SOCK_STREAM,IPPROTO_TCP); //创建TCP协议
以下代码用于检查返回值是否有错误:
if(m_scoket==INVALID_SOCKET)
{
prinrf("Error at socket():%d\n",GetLastError());
WSACleanup(); //释放资源
return;
}
说明,如果socket()调用失败,他将返回INVALID_SOCKET.
为了服务器能接受一个连接,他必须绑定一个网络地址,下面的代码展示如何绑定一个已经初始化的IP和端口的Socket.客户端程序用这个
IP地址和端口来连接服务器.
sockaddr_in service;
service.sin_family=AF_INET; //INTERNET地址族
service.sin_addr.s_addr=inet_addr("127.0.0.1"); //将要绑定的本地IP地址
service.sin_port=htons(27015); //27015将要绑定的端口
下面我们调用BIND函数,把SOCKET和SOCKADDR以参数的形式传入,并检查错误.
if(bind(m_socket,(SOCKADDR*)&SERVICE,sizeof(service))==SOCKET_ERROR)
{
printf("bind() failed.\n");
closesocket(m_socket);
return;
}
当绑定完成后,服务器必须建立一个监听队列,以接受客户端的请求.listen()使服务器进入监听状态,该函数调用成功返回0,否则返回
SOCKET_ERROR.代码如下:
if(listen(m_socket,1)==SOCKET-ERROR)
{
printf("error listening on socket.\n");
}
服务器端调用完LISTEN()后,如果此时客户端调用CONNECT()函数,服务器端必须在调用ACCEPT().这样服务器和客户端才算正式完成通信程序的
连接动作.
一旦服务器开始监听,我们就要指定一个句柄来表示利用ACCEPT()函数接受的连接,这个句柄是用来发送和接受数据的表示.建立一个SOCKET句柄
Socket AcceptSocket 然后利用无限循环来检测是否有连接传入.一但有连接请求,ACCEPT()函数就会被调用,并且返回这次连接的句柄.
printf("waitong for a client to connect...\n");
while(1)
{
AcceptSocket=SOCKET_ERROR;
while(AcceptSocket==SOCKET_ERROR)
{
AcceptSocket=accept(m_socket,NULL,NULL);
}
}
下面看客户端端代码:
sockaddr_in clientService;
clientService.sin_family=AF_INET; //INTERNET地址族
clientService.sin_addr.s_addr=inet_addr("127.0.0.1"); //将要绑定的本地IP地址
clientService.sin_port=htons(27015); //27015将要绑定的端口
下面调用CONNECT()函数:
if ( connect( m_socket, (SOCKADDR*) &clientService, sizeof(clientService) ) == SOCKET_ERROR)
{
printf( "Failed to connect.\n" );
WSACleanup();
return;
} //如果调用失败清理退出
//调用成功继续读写数据
_____________________________________________________________________________________
到这里,服务器和客户端的基本流程介绍完毕,下面我们介绍数据交换.
send():
int send
{
SOCKET s, //指定发送端套接字
const char FAR?*buf, //指明一个存放应用程序要发送的数据的缓冲区
int len, //实际要发送的数据字节数
int flags //一般设置为0
};
C/S都用SEND函数向TCP连接的另一端发送数据.
recv():
int recv
{
SOCKET s, //指定发送端套接字
char FAR?*buf, //指明一个缓冲区 存放RECC受到的数据
int len, //指明BUF的长度
int flags //一般设置为0
};
C/S都使用RECV函数从TCP连接的另一端接受数据
_______________________________________________________________________________________________
下面将完整的程序代码提供如下,大家可直接编译运行
首先看客户端的代码:
#include <stdio.h>
#include <winsock2.h>
#pragma comment(lib, "ws2_32.lib")
void main() {
// 初始化 Winsock.
WSADATA wsaData;
int iResult = WSAStartup( MAKEWORD(2,2), &wsaData );
if ( iResult != NO_ERROR )
printf("Error at WSAStartup()\n");
// 建立socket socket.
SOCKET client;
client = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP );
if ( client == INVALID_SOCKET ) {
printf( "Error at socket(): %ld\n", WSAGetLastError() );
WSACleanup();
return;
}
// 连接到服务器.
sockaddr_in clientService;
clientService.sin_family = AF_INET;
clientService.sin_addr.s_addr = inet_addr( "127.0.0.1" );
clientService.sin_port = htons( 27015 );
if ( connect( client, (SOCKADDR*) &clientService, sizeof(clientService) ) == SOCKET_ERROR) {
printf( "Failed to connect.\n" );
WSACleanup();
return;
}
// 发送并接收数据.
int bytesSent;
int bytesRecv = SOCKET_ERROR;
char sendbuf[32] = "Client: Sending data.";
char recvbuf[32] = "";
bytesSent = send( client, sendbuf, strlen(sendbuf), 0 );
printf( "Bytes Sent: %ld\n", bytesSent );
while( bytesRecv == SOCKET_ERROR ) {
bytesRecv = recv( client, recvbuf, 32, 0 );
if ( bytesRecv == 0 bytesRecv == WSAECONNRESET ) {
printf( "Connection Closed.\n");
break;
}
if (bytesRecv < 0)
return;
printf( "Bytes Recv: %ld\n", bytesRecv );
}
return;
}
下面是服务器端代码:
#include <stdio.h>
#include <winsock2.h>
#pragma comment(lib, "ws2_32.lib")
void main() {
// 初始化
WSADATA wsaData;
int iResult = WSAStartup( MAKEWORD(2,2), &wsaData );
if ( iResult != NO_ERROR )
printf("Error at WSAStartup()\n");
// 建立socket
SOCKET server;
server = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP );
if ( server == INVALID_SOCKET ) {
printf( "Error at socket(): %ld\n", WSAGetLastError() );
WSACleanup();
return;
}
// 绑定socket
sockaddr_in service;
service.sin_family = AF_INET;
service.sin_addr.s_addr = inet_addr( "127.0.0.1" );
service.sin_port = htons( 27015 );
if ( bind( server, (SOCKADDR*) &service, sizeof(service) ) == SOCKET_ERROR ) {
printf( "bind() failed.\n" );
closesocket(server);
return;
}
// 监听 socket
if ( listen( server, 1 ) == SOCKET_ERROR )
printf( "Error listening on socket.\n");
// 接受连接
SOCKET AcceptSocket;
printf( "Waiting for a client to connect...\n" );
while (1) {
AcceptSocket = SOCKET_ERROR;
while ( AcceptSocket == SOCKET_ERROR ) {
AcceptSocket = accept( server, NULL, NULL );
}
printf( "Client Connected.\n");
server = AcceptSocket;
break;
}
// 发送接受数据
int bytesSent;
int bytesRecv = SOCKET_ERROR;
char sendbuf[32] = "Server: Sending Data.";
char recvbuf[32] = "";
bytesRecv = recv( server, recvbuf, 32, 0 );
printf( "Bytes Recv: %ld\n", bytesRecv );
bytesSent = send( server, sendbuf, strlen(sendbuf), 0 );
printf( "Bytes Sent: %ld\n", bytesSent );
return;
}
本程序仅仅描述了同步的情况!
第五节 多线程编程介绍
对于多线程的基本概念,我不在赘述,是个只要学习过一门编程语言就应该多进程和线程有个基本的了解.这里重点介绍一下如何实现多线程.
通常一个程序的主线程有操作系统创建,如果想让其创建额外的线程,可以调用CreateThread()函数来完成.函数原形如下:
HANDLE CreateThread()
{
LPSECURITY_ATTRIBUTES LPThreadAttributes, //指向SECURITY_ATTRIBUTES的指针
SIZE_T dwStackSize, //表示线程为自己所用堆栈分配的地址空间的大小 系统缺省值为0
LPTHREAD_START-TOUTINE lpStartAddress, //表示新线程开始执行时代码所在函数的地址 即线程函数名
LPVOID lpParameter, //是传入线程函数的参数
DWORD dwCreationFlags, //指定控制线程创建的附加标志 取0线程立即执行 取CREATE_SUSPENDED线程挂起
LPDWORD lpThreadld //是个DWORD类型的地址,返回赋给该新线程的ID
}
线程函数lpParameter必须有以下原形:
DWORD WINAPI XXXThreadFun(LPVOID lpParameter)
{
return(0);
}
________________________________________________________________________________________
下面我们来创建一个线程:
#include <windows.h>
#include <stdio.h>
DWORD WINAPI ThreadFunc( LPVOID lpParam ) //线程函数, 跟普通的函数没什么两样
{
printf( "Parameter = %d.", *(DWORD*)lpParam );
return 0;
}
VOID main( VOID )
{
DWORD dwThreadId, dwThrdParam = 1;
HANDLE hThread;
hThread = CreateThread( NULL,0,ThreadFunc,&dwThrdParam, 0,&dwThreadId);
if (hThread == NULL)
{
printf( "CreateThread failed (%d)\n", GetLastError() );
}
else
{
_getch();
CloseHandle( hThread );
}
}
关于线程同步的问题,这里就不再讲解,请大家自己查阅资料,不查阅以后可能会有困难啊.培养一下各位的自己动手能力.
……