原始套接字学习总结

raw socket(原始套接字)工作原理与规则



原始套接字是一个特殊的套接字类型,它的创建方式跟TCP/UDP创建方法几乎是
一摸一样,例如,通过
       int sockfd;
       sockfd = socktet(AF_INET, SOCK_RAW, IPPROTO_ICMP);


这两句程序你就可以创建一个原始套接字.然而这种类型套接字的功能却与TCP或者UDP类型套接字的功能


有很大的不同:TCP/UDP类型的套接字只能够访问传输层以及传输层以上的数据,因为当IP层把数据传递给


传输层时,下层的数据包头已经被丢掉了.而原始套接字却可以访问传输层以下的数据,,所以使用raw套接


字你可以实现上至应用层的数据操作,也可以实现下至链路层的数据操作.
         比如:通过
sock = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_IP))


方式创建的raw socket就能直接读取链路层的数据.


1)使用原始套接字时应该注意的问题(参考<<unix网络编程>>以及网上的优秀文档)


(1):对于UDP/TCP产生的IP数据包,内核不将它传递给任何原始套接字,而只是将这些数据交给对应的


UDP/TCP数据处理句柄(所以,如果你想要通过原始套接字来访问TCP/UDP或者其它类型的数据,调用socket


函数创建原始套接字第三个参数应该指定为htons(ETH_P_IP),也就是通过直接访问数据链路层来实现.(


我们后面的密码窃取器就是基于这种类型的).


(2):对于ICMP和EGP等使用IP数据包承载数据但又在传输层之下的协议类型的IP数据包,内核不管是否已


经有注册了的句柄来处理这些数据,都会将这些IP数据包复制一份传递给协议类型匹配的原始套接字.


(3):对于不能识别协议类型的数据包,内核进行必要的校验,然后会查看是否有类型匹配的原始套接字负


责处理这些数据,如果有的话,就会将这些IP数据包复制一份传递给匹配的原始套接字,否则,内核将会丢


弃这个IP数据包,并返回一个ICMP主机不可达的消息给源主机.


(4): 如果原始套接字bind绑定了一个地址,核心只将目的地址为本机IP地址的数包传递给原始套接字,如


果某个原始套接字没有bind地址,核心就会把收到的所有IP数据包发给这个原始套接字.


(5): 如果原始套接字调用了connect函数,则核心只将源地址为connect连接的IP地址的IP数据包传递给


这个原始套接字.


(6):如果原始套接字没有调用bind和connect函数,则核心会将所有协议匹配的IP数据包传递给这个原始


套接字.


2).编程选项
     原始套接字是直接使用IP协议的非面向连接的套接字,在这个套接字上可以调用bind和connect函数


进行地址绑定.说明如下:


(1)bind函数:调用bind函数后,发送数据包的源IP地址将是bind函数指定的地址。如是不调用bind,则内


核将以发送接口的主IP地址填充IP头. 如果使用setsockopt设置了IP_HDRINCL(header including)选项,


就必须手工填充每个要发送的数据包的源IP地址,否则,内核将自动创建IP首部.


(2)connetc函数:调用connect函数后,就可以使用write和send函数来发送数据包,而且内核将会用这个绑


定的地址填充IP数据包的目的IP地址,否则的话,则应使用sendto或sendmsg函数来发送数据包,并且要在


函数参数中指定对方的IP地址。


    综合以上种种功能和特点,我们可以使用原始套接字来实现很多功能,比如最基本的数据包分析,主机


嗅探等.其实也可以使用原始套接字作一个自定义的传输层协议.
========

网络编程原始套接字

SOCKET_STREAM 流式套接字     


SOCKET_DGRAM       


SOCKET_RAW 原始套接字   


IPPROTO_IP IP协议   


IPPROTO_ICMP INTERNET控制消息协议,配合原始套接字可以实现ping的功能   


IPPROTO_IGMP INTERNET 网关服务协议,在多播中用到


在AF_INET地址族下,有SOCK_STREAM、SOCK_DGRAM、SOCK_RAW三种套接字类型。SOCK_STREAM也就是通常


所说的TCP,而SOCK_DGRAM则是通常所说的UDP,而SOCK_RAW则是用于提供一些较低级的控制的;第3个参


数依赖于第2个参数,用于指定套接字所用的特定协议,设为0表示使用默认的协议。


RAW SOCKET能够对较低层次的协议直接访问,网络监听技术很大程度上依赖于它。该文介绍了利用RAW 


SOCKET捕获网络底层数据包的步骤和方法,并开发了一个程序模型来进一步探讨了利用RAW SOCKET捕获


数据包的方法。


1 引言


随着信息技术的快速发展,网络已成为信息交换的主要手段,一些网络新业务在不断地兴起,如电子商


务、移动支付等,这些都对网络安全提出了较高的要求。与此同时,黑客对网络的攻击从未停止,网络


的安全问题变得日趋严峻。


很多网络攻击都是从监听开始的,网络监听最重要一步就是捕获局域网中的数据帧,因此,研究数据捕


获技术对于保障网络安全有着重要的意义。


2 RAW SOCKET简介


    同一台主机不同进程可以用进程号来唯一标识,但是在网络环境下进程号并不能唯一标识该进程。


TCP/IP主要引入了网络地址、端口和连接等概念来解决网络间进程标识问题。套接字(Socket)是一个


指向传输提供者的句柄,TCP/IP协议支持3种类型的套接字,分别是流式套接字、数据报式套接字和原始


套接字。


流式套接字(SOCKET_STREAM)提供了面向连接、双向可靠的数据流传输服务。数据报式套接字(SOCKET_ 


DGRAM)提供了无连接服务,不提供无错保证。原始套接字(SOCKET_RAW)允许对较低层次的协议直接访问


,比如IP、 ICMP协议,它常用于检验新的协议实现,或者访问现有服务中配置的新设备,因为RAW 


SOCKET可以自如地控制Windows下的多种协议,能够对网络底层的传输机制进行控制,所以可以应用原始


套接字来操纵网络层和传输层应用。比如,我们可以通过RAW SOCKET来接收发向本机的ICMP、IGMP协议


包,或者接收TCP/IP栈不能够处理的IP包,也可以用来发送一些自定包头或自定协议的IP包。网络监听


技术很大程度上依赖于SOCKET_RAW。


3 RAW SOCKET编程


要使用原始套接字,必须经过创建原始套接字、设置套接字选项和创建并填充相应协议头这三个步骤,


然后用send、WSASend函数将组装好的数据发送出去。接收的过程也很相似,只是需要用recv或WSARecv


函数接收数据。下面介绍使用RAW SOCKET编程的几个步骤。


3.1 创建原始套接字


    我们可以用socket或WSASocket函数来创建原始套接字,因为原始套接字能直接控制底层协议,因此


只有属于“管理员”组的成员,才有权创建原始套接字。下面是用socket函数创建原始套接字的代码。


SOCKET sock;


Sock=socket (AF_INET, SOCK_RAW, IPPROTO_UDP);


    上述创建原始套接字的代码使用的是UDP协议,如果要使用其它的协议,比如ICMP、IGMP、IP等协议


,只需要把相应的参数改为IPPROTO_ICM、IPPROTO_ IGMP、IPPROTO_IP就可以了。另外,IPPROTO_UDP、


IPPROTO_IP、IPPROTO_RAW这几个协议标志要求使用套接字选项IP_HDRINCL,而目前只有Windows 2000和


Windows XP提供了对IP_HDRINCL的支持,这意味着在Windows 2000以下平台创建原始套接字时是不能使


用IP、UDP、TCP协议的。


3.2 设置套接字选项


    创建了原始套接字后,就要设置套接字选项,这要通过setsocketopt函数来实现,setsocketopt函


数的声明如下:


int setsocketopt (


SOCKET s,


int level,


int optname,


const char FAR *optval,


int optlen


);


在该声明中,参数s是标识套接口的描述字,要注意的是选项对这个套接字必须是有效的。参数Level表


明选项定义的层次,对TCP/IP协议族而言,支持SOL_SOCKET、IPPROTO_IP和IPPROTO_CP层次。参数


Optname是需要设置的选项名,这些选项名是在Winsock头文件内定义的常数值。参数optval是一个指针


,它指向存放选项值的缓冲区。参数optlen指示optval缓冲区的长度


3.3 创建并填充相应协议头


   这一步就是创建IP和TCP协议头的数据结构,根据相关协议的定义进行编写即可,下面是一个TCP协议


头的数据结构。


struct TCP


{


    unsigned short   tcp_sport;  
    unsigned short   tcp_dport;   
    unsigned int     tcp_seq;     
    unsigned int     tcp_ack;      
    unsigned char    tcp_lenres;
    unsigned char    tcp_flag;    
    unsigned short   tcp_win;     
    unsigned short   tcp_sum;      
    unsigned short   tcp_urp;      


};


4 一个利用RAW SOCKET捕获网络数据包的程序模型


   下面介绍一个利用RAW SOCKET捕获网络数据包的程序模型。这个程序模型演示了如何使用RAW SOCKET


捕获局域网中的数据包,它完成了网络底层数据的接收,能显示源地址、目标地址、源端口、目标端口


和接收的字节数等信息。这个程序模型也说明了网络监听的基本原理,给捕获局域网中的数据包提供了


一种方法,即先把网卡设置为混杂模式,然后利用RAW SOCKET接收IP层的数据。


    程序在Visual C++.net 2003中调试并编译通过,运行环境为以太网, 程序代码可同时在Linux与


windows环境下编译和运行,当然在编译时需要不同的头文件以及需要对代码作相应的改动。本程序模型


在Windows下能直接运行,如果在Linux下运行,则需要先用手工把网卡设置为混杂模式,在root权限下


用如下命令设置:ifconfig eth0 promisc。


    在Unix/Linux下程序要包含以下这几个进行调用系统和网络函数的头文件:


#include〈stdio.h〉
#include〈sys/socket.h〉
#include〈netinet/in.h〉
#include〈arpa/inet.h〉
#include"headers.h"


为了方便基于Berkeley套接口的已有源程序的移植,Windows Sockets支持许多Berkeley头文件。这些


Berkeley头文件被包含在WINSOCK2.H中,所以一个Windows Sockets应用程序只需包含WINSOCK2.H头文件


就足够了,这也是目前推荐使用的一种方法。在Windows平台下程序改用以下这几个头文件:


#include "stdafx.h"
#include<stdio.h>
#include<Winsock2.h>
#include"headers.h"


headers.h是自己编写的头文件,它的作用是定义IP和TCP包的头结构。在程序中首先定义几个变量和结


构,然后调用函数socket()建立socket连接,主要代码如下:


int _tmain(int argc, _TCHAR* argv[])
{
int sock,bytes_recieved,fromlen;
char buffer[65535];
struct sockaddr_in from;
struct ip *ip;
struct tcp *tcp;
sock=socket(AF_INET,SOCK_RAW,IPPROTO_TCP);
     ……
return 0;
}


程序的第二步用一个while(1)语句来建立一个死循环,用来不停地接收网络信息。首先用函数sizeof()


取出一个socket结构的长度,这个参数是recvfrom()函数所必须的。从建立的socket连接中接收数据是


通过函数recvfrom()是来实现的,因为recvfrom()函数需要一个sockaddr数据类型,所以用了一个强


制类型转换,代码如下:fromlen=sizeof(from);


bytes_recieved=recvfrom(sock,buffer,sizeof(buffer),0,(struct sockaddr*)&from,&fromlen);


接下来用一条语句把接收到的数据转化为我们预先定义的结构,以便于查看,代码为:


ip=(struct ip *)buffer


    还要用一条语句来指向TCP头,因为接收的数据中,IP头的大小是固定的4字节,所以用IP长度乘以4


就能指向TCP头部分,代码为:


tcp=(struct tcp *)(buffer+(4*ip->ip_length))


    最后就可以用打印语句把接收的字节数、数据的源地址、目标地址、源端口、目标端口、IP头长度


和协议的类型输出来。


========

原始套接字和普通的TCP套接字有什么不同



我们常见的就是原始、tcp、udp3种套接字,主要区别:
原始套接字可以读写内核没有处理的IP数据包,而流套接字(就是TCP流)只能读取TCP协议的数据,数


据包套接字只能读取UDP协议的数据。因此,如果要访问其他协议发送数据必须使用原始套接字。


========

WinSocket编程——原始套接字



原始套接字是WINSOCK公开的一个套接字编程接口,它让我们可以在 IP 层对套接字进行编程,控制其行


为,常见的应用有抓包 (Sniffer)、分析包、洪水攻击、ICMP ping等,但它不能截取包(所谓的截取包


就是把包拦截下来,要做到这种“防火墙”的功能,还需要再低一层的驱动层才可以做到)。但是能把


网络上的包复制到本机就已经是一个很有用的功能了。我就曾经试过在一个局域网里打开我的SNIFFER,


然后把同事们电脑发出的包都复制过来了。然后如果再对包进行一些分析,哼哼。。。


其实原始套接字最根本的就是可以在IP层构造自己的IP包,然后把这个IP包发送出去。同样,我们可以


把从TCP/UDP传输层过来的包抓取过来并进行分析。。。


要写原始套接字的程序其实也很容易,因为WINDOWS已经帮我们定义实现好了这些接口(WINSOCK)。另


外我们还要有一些定义,就是IP包头、UDP包头等的那些结构定义,具体请查看下面的代码。


下面是我定义的一个RawSniffer类,这个类是使用了原始套接字来实现一个侦听器,这个类还依赖于MFC


(我一直都在WINDOWS平台下开发,用的比较多的也是MFC,所以对MFC有点偏好。。。)。下面是类的代


码:


// 头文件


#ifndef RAW_DEF_H
#define RAW_DEF_H


#include <winsock2.h> 
#pragma comment(lib,"ws2_32")


#include <ws2tcpip.h>
#include <mstcpip.h> // 此文件是 Windows platform SDK 的函数,如果找不到,请安装SDK


#define PROTOCOL_STRING_ICMP_TXT "ICMP"
#define PROTOCOL_STRING_TCP_TXT "TCP"
#define PROTOCOL_STRING_UDP_TXT "UDP"
#define PROTOCOL_STRING_SPX_TXT "SPX"
#define PROTOCOL_STRING_NCP_TXT "NCP"
#define PROTOCOL_STRING_UNKNOW_TXT "UNKNOW"


// 定义IP首部 
typedef struct ip_hdr 

 unsigned char h_verlen;   // 4位首部长度,4位IP版本号 
 unsigned char tos;    // 8位服务类型TOS 
 unsigned short total_len;       // 16位总长度(字节) 
 unsigned short ident;      // 16位标识 
 unsigned short frag_and_flags;  // 3位标志位 
 unsigned char ttl;       // 8位生存时间 TTL 
 unsigned char proto;      // 8位协议 (TCP, UDP 或其他) 
 unsigned short checksum;  // 16位IP首部校验和 
 unsigned int sourceIP;   // 32位源IP地址 
 unsigned int destIP;   // 32位目的IP地址 
}IPHEADER;


// 定义TCP伪首部 
typedef struct tsd_hdr 

 unsigned long saddr;    // 源地址 
 unsigned long daddr;    // 目的地址 
 char mbz;                        // 0
 char ptcl;       // 协议类型 UDP的协议类型为17,TCP为6 
 unsigned short tcpl;    // TCP数据包长度 
}PSDHEADER;


// 定义TCP首部 
typedef struct tcp_hdr 

 USHORT th_sport;     // 16位源端口 
 USHORT th_dport;     // 16位目的端口 
 unsigned int th_seq;    // 32位序列号 
 unsigned int th_ack;    // 32位确认号 
 unsigned char th_lenres;   // 4位首部长度/6位保留字 
 unsigned char th_flag;    // 6位标志位 
 USHORT th_win;      // 16位窗口大小 
 USHORT th_sum;      // 16位校验和 
 USHORT th_urp;      // 16位紧急数据偏移量 
}TCPHEADER;


// 定义ICMP首部
typedef struct icmp_hdr
{
 unsigned char  i_type;           // 类型
 unsigned char  i_code;           // 代码
 unsigned short i_cksum;          // 校验码
 unsigned short i_id;             // 非标准的ICMP首部  
 unsigned short i_seq;
 unsigned long  timestamp;
}ICMPHEADER;


// 定义UDP首部
// The UDP packet is lick this. Took from RFC768.
//                  0      7 8     15 16    23 24    31  
//                 +--------+--------+--------+--------+ 
//                 |     Source      |   Destination   | 
//                 |      Port       |      Port       | 
//                 +--------+--------+--------+--------+ 
//                 |                 |                 | 
//                 |     Length      |    Checksum     | 
//                 +--------+--------+--------+--------+ 
//                 |                                     
//                 |          data octets ...            
//                 +---------------- ...      


typedef struct udp_hdr  // 8 Bytes
{
 unsigned short uh_sport;         
 unsigned short uh_dport;
 unsigned short uh_len;
 unsigned short uh_sum;
} UDPHEADER;




/* 
// 函数实现不要放在头文件,否则会导致在不同的地方重复定义
//CheckSum:计算校验和的子函数 
USHORT checksum(USHORT *buffer, int size) 

 unsigned long cksum=0; 
 while(size >1) 
 { 
  cksum+=*buffer++; 
  size -=sizeof(USHORT); 
 } 
 if(size ) 
 { 
  cksum += *(UCHAR*)buffer; 
 } 
 
 cksum = (cksum >> 16) + (cksum & 0xffff); 
 cksum += (cksum >>16); 
 return (USHORT)(~cksum); 
}
*/


USHORT checksum(USHORT *buffer, int size);




// 回调函数


// 抓到一个包就会调用这个回调函数
typedef int (CALLBACK *CaptureDef)(CString  &strMsg);


class YRawSniffer
{
public:
 YRawSniffer();
 ~YRawSniffer();
 BOOL StartAll();
 BOOL ExitAll();
 BOOL Capture(CaptureDef CaptureFunc = NULL);
 BOOL StopCapture();


 static DWORD WINAPI CaptureThread(LPVOID lpParam);
 HANDLE m_hCaptureThread;


 // Filter 过滤条件
 BOOL m_bCapTCP;
 BOOL m_bCapUDP;
 BOOL m_bCapICMP;
 CString m_strSrcIP;
 CString m_strDstIP;
 
 SOCKET m_rawSock;
// CString m_strFilePath;


private:
 CaptureDef m_CaptureFunc;
 BOOL m_bExitCapture;
 
 IPHEADER m_ipHeader; 
 TCPHEADER m_tcpHeader;
 ICMPHEADER m_icmpheader;
 UDPHEADER m_udpheader;
};


#endif


// CPP 文件


#include "stdafx.h"
#include "RawDef.h"


//CheckSum:计算校验和的子函数 
USHORT checksum(USHORT *buffer, int size) 

 unsigned long cksum=0; 
 while(size >1) 
 { 
  cksum+=*buffer++; 
  size -=sizeof(USHORT); 
 } 
 if(size ) 
 { 
  cksum += *(UCHAR*)buffer; 
 } 
 
 cksum = (cksum >> 16) + (cksum & 0xffff); 
 cksum += (cksum >>16); 
 return (USHORT)(~cksum); 
}


char * GetProtocol(int proto)
{
 switch(proto)
 {
     case IPPROTO_ICMP: return PROTOCOL_STRING_ICMP_TXT;
  case IPPROTO_TCP:  return PROTOCOL_STRING_TCP_TXT;
  case IPPROTO_UDP:  return PROTOCOL_STRING_UDP_TXT;
  default:     return PROTOCOL_STRING_UNKNOW_TXT;
 }
}


DWORD WaitForObjectEx( HANDLE hHandle, DWORD dwMilliseconds )
{
 BOOL bRet;
 MSG msg;
 INT iWaitRet;
 DWORD nTimeOut = 0;
 while( (bRet = ::GetMessage( &msg, NULL, 0, 0 )) != 0)
 { 
  if( nTimeOut++ * 100 >= dwMilliseconds )
   break;


  iWaitRet = WaitForSingleObject(hHandle, 100);
  if(iWaitRet != WAIT_TIMEOUT)
  {
   break;
  }
  if (bRet == -1)
  {
   break;
  }
  else
  {
   ::TranslateMessage(&msg); 
   ::DispatchMessage(&msg); 
  }
 }


 return iWaitRet;
}


 




///////////////////////////////////////////////////////////////////////////////////////////


////////////////////////////////////
YRawSniffer *g_rawSniffer;


YRawSniffer::YRawSniffer()
{
 m_bCapTCP = FALSE;
 m_bCapUDP = FALSE;
 m_bCapICMP = FALSE;
 m_strSrcIP = "";
 m_strDstIP = "";


 m_rawSock = INVALID_SOCKET;
// m_strFilePath = "";


 m_bExitCapture = FALSE;
 m_CaptureFunc = NULL;
 m_hCaptureThread = NULL;


 g_rawSniffer = this;


 WSADATA wsaData;
 if(WSAStartup(MAKEWORD(2, 2), &wsaData)!=0)
 {
  TRACE1("WSAStartup() ERROR! %d", GetLastError());
  return;
 }


}


YRawSniffer::~YRawSniffer()
{
 WSACleanup(); 
}


BOOL YRawSniffer::ExitAll()
{
 m_bExitCapture = TRUE;


 shutdown(m_rawSock, SD_BOTH);
 
 if(m_hCaptureThread != NULL)
 {
  DWORD dwRet = 0;
  dwRet = WaitForObjectEx(m_hCaptureThread, INFINITE);
  if(dwRet == WAIT_OBJECT_0)
  {
   TRACE("CaptureThread exit Success!");
  }
  closesocket(m_rawSock);
  m_rawSock = INVALID_SOCKET;
  CloseHandle(m_hCaptureThread);
  m_hCaptureThread = NULL;
 }


 TRACE("ExitAll OK!");


 return TRUE;
}


BOOL YRawSniffer::StartAll()
{
 m_bExitCapture = FALSE;


 SOCKADDR_IN addr_in;


 if(m_rawSock == INVALID_SOCKET)
  m_rawSock = socket(AF_INET, SOCK_RAW, IPPROTO_IP);


    BOOL flag = TRUE;


 if(setsockopt(m_rawSock, IPPROTO_IP, IP_HDRINCL, (char*)&flag, sizeof(flag)) != 0)
 {
        TRACE1("setsockopt() ERROR! %d", WSAGetLastError());
  return FALSE;
 }
 
 char  LocalName[16];
 struct hostent *pHost;


 // 获取本机名 
 if (gethostname((char*)LocalName, sizeof(LocalName)-1) == SOCKET_ERROR) 
 {
  TRACE1("gethostname error! %d", WSAGetLastError());
  return FALSE;
 }
 
 // 获取本地 IP 地址 
 if ((pHost = gethostbyname((char*)LocalName)) == NULL) 
 {
  TRACE1("gethostbyname error! %d", WSAGetLastError());
  return FALSE;
 }
 
 // m_strSrcIP = pHost->h_addr_list[0];
 addr_in.sin_addr  = *(in_addr *)pHost->h_addr_list[0]; // IP 
 addr_in.sin_family = AF_INET; 
 addr_in.sin_port  = htons(57274);
 
 if( bind(m_rawSock, (struct sockaddr *)&addr_in, sizeof(addr_in)) != 0)
 {
  TRACE1("bind error! %d", WSAGetLastError());
  return FALSE;
 }
 
 // 设置网卡的I/O行为,接收网络上所有的数据包
 DWORD dwValue = 1; 
 if( ioctlsocket(m_rawSock, SIO_RCVALL, &dwValue) != 0)
 {
  TRACE1("ioctlsocket error! %d", WSAGetLastError());
  return FALSE;
 } 


 return TRUE;
}


DWORD WINAPI YRawSniffer::CaptureThread(LPVOID lpParam)
{
 //CFile fLog;
 //BOOL bLogFile = FALSE;
 // 打开记录文件
 //if(g_rawSniffer->m_strFilePath == "")
 // g_rawSniffer->m_strFilePath = "c://Capture.txt";
 //if(g_rawSniffer->m_strFilePath != "")
// {
//  if( !fLog.Open(g_rawSniffer->m_strFilePath, CFile::modeCreate|CFile::modeReadWrite) )
//   TRACE1("file fLog Open failed! %d", GetLastError());
//  else
//   bLogFile = TRUE;
// }


 const int MAX_RECEIVEBUF = 1000;
 char recvBuf[MAX_RECEIVEBUF] = {0};
 char msg[MAX_RECEIVEBUF] = {0};


 char *ptr = NULL;


 CString strLog, strTmp, strContent;


 DWORD nTCPCnt = 0, nUDPCnt = 0, nICMPCnt = 0;


 while(!g_rawSniffer->m_bExitCapture)
 {
  int ret = recv(g_rawSniffer->m_rawSock, recvBuf, MAX_RECEIVEBUF, 0);
  if(ret == SOCKET_ERROR)
   TRACE1("%d, recv(g_rawSniffer->m_rawSock, recvBuf, MAX_RECEIVEBUF, 0) failed!", 


GetLastError());
  
  strLog = "";
  strContent = "";


  if(ret > 0)
  {   
   g_rawSniffer->m_ipHeader = *(IPHEADER*)recvBuf;
            
   // 取得正确的IP头长度
   int iIphLen = sizeof(unsigned long) * (g_rawSniffer->m_ipHeader.h_verlen & 0xf);
   int cpysize = 0;


   // 过滤目标IP或者源IP
   //if(g_rawSniffer->m_strSrcIP.Find(".") > 0 
   // || g_rawSniffer->m_strDstIP.Find(".") > 0)
   {
    if(g_rawSniffer->m_strSrcIP != "" 
     || g_rawSniffer->m_strDstIP != "")
    {
     if( inet_ntoa(*(in_addr*)&g_rawSniffer->m_ipHeader.sourceIP) != g_rawSniffer-


>m_strSrcIP
      && inet_ntoa(*(in_addr*)&g_rawSniffer->m_ipHeader.destIP) != g_rawSniffer-


>m_strDstIP)
      continue;
    }
   }


   /*
   // 过滤目标IP或者源IP
   if(g_rawSniffer->m_strSrcIP != "")
   {
    if( inet_ntoa(*(in_addr*)&g_rawSniffer->m_ipHeader.sourceIP) != g_rawSniffer-


>m_strSrcIP)
     continue;
   }


   if(g_rawSniffer->m_strDstIP != "")
   {
    if( inet_ntoa(*(in_addr*)&g_rawSniffer->m_ipHeader.destIP) != g_rawSniffer->m_strDstIP)
     continue;
   }
   */
   
   if(g_rawSniffer->m_ipHeader.proto == IPPROTO_TCP && g_rawSniffer->m_bCapTCP)
   {
    nTCPCnt++;
    g_rawSniffer->m_tcpHeader = *(TCPHEADER*)(recvBuf + iIphLen);
    strTmp.Format("取得 %d TCP包", nTCPCnt); strLog += strTmp;
    strTmp.Format("协议: %s /r/n", GetProtocol(g_rawSniffer->m_ipHeader.proto)); strLog += 


strTmp;
    strTmp.Format("IP源地址: %s /r/n", inet_ntoa(*(in_addr*)&g_rawSniffer-


>m_ipHeader.sourceIP)); strLog += strTmp;
    strTmp.Format("IP目标地址: %s /r/n", inet_ntoa(*(in_addr*)&g_rawSniffer-


>m_ipHeader.destIP)); strLog += strTmp;
    strTmp.Format("TCP源端口号: %d /r/n", g_rawSniffer->m_tcpHeader.th_sport); strLog += 


strTmp;
    strTmp.Format("TCP目标端口号:%d /r/n", g_rawSniffer->m_tcpHeader.th_dport); strLog += 


strTmp;
    strTmp.Format("数据包长度: %d /r/n", ntohs(g_rawSniffer->m_ipHeader.total_len)); 


strLog += strTmp;
    strTmp.Format("TCP数据包的报文内容:/r/n"); strLog += strTmp;
    
    ptr = recvBuf + iIphLen + (4 * ((g_rawSniffer->m_tcpHeader.th_lenres & 0xf0)>>4|0));
    cpysize = ntohs(g_rawSniffer->m_ipHeader.total_len) - (iIphLen + (4 * ((g_rawSniffer-


>m_tcpHeader.th_lenres & 0xf0)>>4|0)));
    
    // ASCII码
    memcpy(msg, ptr, cpysize);
    for(int i = 0; i < cpysize ; i++)
    {
     if(msg[i] >= 32 && msg[i] < 255)
     {
      strContent.Format("%c", (unsigned char)msg[i]); strLog += strContent;
     }
     else
     {
      strContent.Format("."); strLog += strContent;
     }
    }
    strTmp.Format("/r/n /r/n"); strLog += strTmp;
   }
   
   
   if(g_rawSniffer->m_ipHeader.proto == IPPROTO_ICMP  && g_rawSniffer->m_bCapICMP)
   {
    nICMPCnt++;
    g_rawSniffer->m_icmpheader = *(ICMPHEADER*)(recvBuf + iIphLen);
    strTmp.Format("取得 %d ICMP包", nICMPCnt); strLog += strTmp;
    strTmp.Format("协议: %s/r/n", GetProtocol(g_rawSniffer->m_ipHeader.proto)); strLog += 


strTmp;
    strTmp.Format("IP源地址: %s/r/n", inet_ntoa(*(in_addr*)&g_rawSniffer-


>m_ipHeader.sourceIP)); strLog += strTmp;
    strTmp.Format("IP目标地址: %s/r/n", inet_ntoa(*(in_addr*)&g_rawSniffer-


>m_ipHeader.destIP)); strLog += strTmp;
    strTmp.Format("ICMP返回类型:%d/r/n", g_rawSniffer->m_icmpheader.i_type); strLog += 


strTmp;
    strTmp.Format("ICMP返回代码:%d/r/n", g_rawSniffer->m_icmpheader.i_code); strLog += 


strTmp;
    strTmp.Format("数据包长度: %d/r/n/r/n/r/n", ntohs(g_rawSniffer-


>m_ipHeader.total_len)); strLog += strTmp;    
   }
  
   if(g_rawSniffer->m_ipHeader.proto == IPPROTO_UDP && g_rawSniffer->m_bCapUDP)
   {
    nUDPCnt++;
    g_rawSniffer->m_udpheader = *(UDPHEADER*)(recvBuf + iIphLen);
    strTmp.Format("取得 %d UDP包", nUDPCnt); strLog += strTmp;
    strTmp.Format("协议: %s/r/n", GetProtocol(g_rawSniffer->m_ipHeader.proto)); strLog += 


strTmp;
    strTmp.Format("IP源地址: %s/r/n", inet_ntoa(*(in_addr*)&g_rawSniffer-


>m_ipHeader.sourceIP)); strLog += strTmp;
    strTmp.Format("IP目标地址: %s/r/n", inet_ntoa(*(in_addr*)&g_rawSniffer-


>m_ipHeader.destIP)); strLog += strTmp;
    strTmp.Format("UDP源端口号: %d/r/n", g_rawSniffer->m_udpheader.uh_sport); strLog += 


strTmp;
    strTmp.Format("UDP目标端口号:%d/r/n", g_rawSniffer->m_udpheader.uh_dport); strLog += 


strTmp;
    strTmp.Format("数据包长度: %d/r/n", ntohs(g_rawSniffer->m_ipHeader.total_len)); strLog 


+= strTmp;
    strTmp.Format("UDP数据包的报文内容:/r/n"); strLog += strTmp;
    
    ptr = recvBuf + iIphLen + 8;
    cpysize = ntohs(g_rawSniffer->m_ipHeader.total_len) - (iIphLen + 8);
    memcpy(msg, ptr, cpysize);
    
    strTmp.Format("ASCII码格式: /r/n");
    for(int i = 0; i < cpysize; i++)
    {
     if(msg[i] >= 32 && msg[i] < 255)
     {
      strContent.Format("%c",(unsigned char)msg[i]); strLog += strContent;
     }
     else
     {
      strContent.Format("."); strLog += strContent;
     }
    }
    strTmp.Format("/r/n/r/n");  strLog += strTmp;    
    
    strTmp.Format("16进制码格式: /r/n");  strLog += strTmp;
    for(i = 0; i < cpysize; i++)
    {
     strTmp.Format("%2.2X ", (unsigned char)msg[i]);  strLog += strTmp;
    }
    strTmp.Format("/r/n/r/n");  strLog += strTmp;
    
   }


   if(g_rawSniffer->m_CaptureFunc != NULL && strLog.GetLength() > 0 && 


strContent.GetLength() > 0)
    g_rawSniffer->m_CaptureFunc(strLog);


   Sleep(10);
  }
 }




 // 关闭记录文件
// if(bLogFile)
//  fLog.Close(); 


 return 0;
}


BOOL YRawSniffer::Capture(CaptureDef CaptureFunc /*= NULL*/)
{
 StartAll();


 if(CaptureFunc != NULL)
  m_CaptureFunc = CaptureFunc;


 // 创建线程截取包
 m_bExitCapture = FALSE;
 m_hCaptureThread = CreateThread(NULL, 0, CaptureThread, NULL, 0, NULL);
 if(NULL == m_hCaptureThread)
  TRACE1(" /"m_hCaptureThread = CreateThread(NULL, 0, CaptureThread, NULL, 0, NULL)/" 


failed! %d ", GetLastError());


 return TRUE;
}


BOOL YRawSniffer::StopCapture()
{
 return ExitAll();
}


这个类的使用很简单,声明一个全局的对象(或者成员变量),使用它的Capture和StopCapture就可以


了。记得在程序退出的时候也调用一下StopCapture。


调用Capture 函数的时候,需要把一些过滤条件赋给它:


  // 过滤条件
  g_sniffer.m_bCapTCP = m_bCapTCP; // 抓TCP包,TRUE为抓,FALSE不抓
  g_sniffer.m_bCapUDP = m_bCapUDP; // 抓UDP包,TRUE为抓,FALSE不抓
  g_sniffer.m_bCapICMP = m_bCapICMP; // 抓ICMP包,TRUE为抓,FALSE不抓
  g_sniffer.m_strSrcIP = m_strSrcIP; // 包的源IP串,空为不限制
  g_sniffer.m_strDstIP = m_strDstIP; // 包的目标IP串,空为不限制


  g_sniffer.Capture(CapFunc);


抓到的包会在一个回调函数中把抓到的包格式化好了传给你。


例如我的回调函数是这样的:


int CALLBACK CapFunc(CString &strLog)
{
 g_rawDlg->AddLog(strLog); // 把抓到的包在DLG的一个编辑框里输出
 g_log.WriteLogFileRaw(strLog); // 写进一个LOG文件里


 return 0;
}


有了上面的这些知识就可以使用这个类实现一个SNIFFER了,很简单的3步:1、把类加进工程 2、包含头


文件,声明对象 3、使用这个对象的Capture和StopCapture。


上面还可以看出我原来的设计是把记录包的功能放在了类里。但是后面觉得这个类还是不要干那么多的


活,让记录包的工作都放在上层吧。所以就用了回调函数。


通过上面代码的研究,你应该很明白原始套接字了吧,这项技术不但可以做SNIFFER,还可以实现洪水攻


击等等(因为可以自己构造IP包)。


最后给出我使用这个类实现的一个程序界面,当然界面那块的逻辑需要你自己去处理了,需要的朋友请


留下地址,我把例程发给你。
http://blog.csdn.net/dylgsy/article/details/1957138
========

你可能感兴趣的:(socket,原始套接字)