raw socket


1.原始套接字(raw socket)

   1.1 原始套接字工作原理与规则
         原始套接字是一个特殊的套接字类型,它的创建方式跟TCP/UDP创建方法几乎是
一摸一样,例如,通过
  1.        int sockfd;
  2.        sockfd = socktet(AF_INET, SOCK_RAW, IPPROTO_ICMP);
复制代码

这两句程序你就可以创建一个原始套接字.然而这种类型套接字的功能却与TCP或者UDP类型套接字的功能有很大的不同:TCP/UDP类型的套接字只能够访问传输层以及传输层以上的数据,因为当IP层把数据传递给传输层时,下层的数据包头已经被丢掉了.而原始套接字却可以访问传输层以下的数据,,所以使用raw套接字你可以实现上至应用层的数据操作,也可以实现下至链路层的数据操作.
         比如:通过
  1. 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地址。

    综合以上种种功能和特点,我们可以使用原始套接字来实现很多功能,比如最基本的数据包分析,主机嗅探等.其实也可以使用原始套接字作一个自定义的传输层协议.

1.2一个简单的应用

    下面的代码创建一个直接读取链路层数据包的原始套接字,并从中分析出源MAC地址和目的MAC地址,源IP和目的IP,以及对应的传输层协议,如果是TCP/UDP协议的话,打印其目的和源端口.为了方便阅读,程序中避免了使用任何与协议有关的数据结构,如
struct ether_header ,struct iphdr  等,当然, 要完全理解代码,你需要关于指针以及位运算的知识
  1. /***************SimpelSniffer.c*************/
  2. //auther:duanjigang@2006s
  3. #include <stdio.h>
  4. #include <unistd.h>
  5. #include <sys/socket.h>
  6. #include <sys/types.h>
  7. #include <linux/if_ether.h>
  8. #include <linux/in.h>
  9. #define BUFFER_MAX 2048

  10. int main(int argc, char *argv[])
  11. {
  12.         
  13.         int sock, n_read, proto;        
  14.         char buffer[BUFFER_MAX];
  15.         char  *ethhead, *iphead, *tcphead, 
  16.                          *udphead, *icmphead, *p;
  17.         
  18. if((sock = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_IP))) < 0)
  19.     {
  20.         fprintf(stdout, "create socket error\n");
  21.         exit(0);
  22.     }
  23.         
  24. while(1) 
  25. {
  26.      n_read = recvfrom(sock, buffer, 2048, 0, NULL, NULL);
  27.         /*
  28.         14   6(dest)+6(source)+2(type or length)
  29.         +
  30.         20   ip header 
  31.         +
  32.         8   icmp,tcp or udp header
  33.         = 42
  34.         */
  35. if(n_read < 42) 
  36.    {
  37.       fprintf(stdout, "Incomplete header, packet corrupt\n");
  38.       continue;
  39.    }
  40.                 
  41.         ethhead = buffer;
  42.         p = ethhead;
  43.         int n = 0XFF;
  44.                 printf("MAC: %.2X:%02X:%02X:%02X:%02X:%02X==>"
  45.                            "%.2X:%.2X:%.2X:%.2X:%.2X:%.2X\n",
  46.         p[6]&n, p[7]&n, p[8]&n, p[9]&n, p[10]&n, p[11]&n,
  47.         p[0]&n, p[1]&n, p[2]&n,p[3]&n, p[4]&n, p[5]&n);

  48.                    iphead = ethhead + 14;  
  49.                    p = iphead + 12;
  50.         
  51.            printf("IP: %d.%d.%d.%d => %d.%d.%d.%d\n",
  52.            p[0]&0XFF, p[1]&0XFF, p[2]&0XFF, p[3]&0XFF,
  53.            p[4]&0XFF, p[5]&0XFF, p[6]&0XFF, p[7]&0XFF);
  54.             proto = (iphead + 9)[0];
  55.             p = iphead + 20;
  56.              printf("Protocol: ");
  57.             switch(proto)
  58.               {
  59.                 case IPPROTO_ICMP: printf("ICMP\n");break;
  60.                 case IPPROTO_IGMP: printf("IGMP\n");break;
  61.                 case IPPROTO_IPIP: printf("IPIP\n");break;
  62.                 case IPPROTO_TCP :
  63.                 case IPPROTO_UDP : 
  64.     printf("%s,", proto == IPPROTO_TCP ? "TCP": "UDP"); 
  65.     printf("source port: %u,",(p[0]<<8)&0XFF00 |  p[1]&0XFF);
  66.     printf("dest port: %u\n", (p[2]<<8)&0XFF00 | p[3]&0XFF);
  67.          break;
  68.     case IPPROTO_RAW : printf("RAW\n");break;
  69.     default:printf("Unkown, please query in include/linux/in.h\n");
  70.         }
  71.    }
  72. }
复制代码

这里介绍Windows   Sockets的一些关于原始套接字(Raw   Socket)的编程。同Winsock1相比,最明显的就是支持了Raw   Socket套接字类型,通过原始套接字,我们可以更加自如地控制Windows下的多种协议,而且能够对网络底层的传输机制进行控制。   

1、创建一个原始套接字,并设置IP头选项。   

SOCKET   sock;   
sock   =   socket(AF_INET,SOCK_RAW,IPPROTO_IP);   
或者:   
s   =   WSASoccket(AF_INET,SOCK_RAW,IPPROTO_IP,NULL,0,WSA_FLAG_OVERLAPPED);   

    这里,我们设置了SOCK_RAW标志,表示我们声明的是一个原始套接字类型。创建原始套接字后,IP头就会包含在接收的数据中,如果我们设定   IP_HDRINCL   选项,那么,就需要自己来构造IP头。注意,如果设置IP_HDRINCL   选项,那么必须具有   administrator权限,要不就必须修改注册表:   
HKEY_LOCAL_MACHINE/System/CurrentControlSet/Services/Afd/Parameter/   
修改键:DisableRawSecurity(类型为DWORD),把值修改为   1。如果没有,就添加。   

BOOL   blnFlag=TRUE;   
setsockopt(sock,   IPPROTO_IP,   IP_HDRINCL,   (char   *)&blnFlag,   sizeof(blnFlag);   

    对于原始套接字在接收数据报的时候,要注意这么几点:   
1、如果接收的数据报中协议类型和定义的原始套接字匹配,那么,接收的所有数据就拷贝到套接字中。   
2、如果绑定了本地地址,那么只有接收数据IP头中对应的远端地址匹配,接收的数据就拷贝到套接字中。   
3、如果定义的是外部地址,比如使用connect(),那么,只有接收数据IP头中对应的源地址匹配,接收的数据就拷贝到套接字中。   


2、构造IP头和TCP头   

这里,提供IP头和TCP头的结构:   
//   Standard   TCP   flags   
#define   URG   0x20   
#define   ACK   0x10   
#define   PSH   0x08   
#define   RST   0x04   
#define   SYN   0x02   
#define   FIN   0x01   
typedef   struct   _iphdr   //定义IP首部   
{   
unsigned   char   h_lenver;   //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地址   
}IP_HEADER;   

typedef   struct   psd_hdr   //定义TCP伪首部   
{   
unsigned   long   saddr;   //源地址   
unsigned   long   daddr;   //目的地址   
char   mbz;   
char   ptcl;   //协议类型   
unsigned   short   tcpl;   //TCP长度   
}PSD_HEADER;   

typedef   struct   _tcphdr   //定义TCP首部   
{   
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位紧急数据偏移量   
}TCP_HEADER;   

TCP伪首部并不是真正存在的,只是用于计算检验和。校验和函数:   

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);   
}   

    当需要自己填充IP头部和TCP头部的时候,就同时需要自己计算他们的检验和。   

3、发送原始套接字数据报   

    填充这些头部稍微麻烦点,发送就相对简单多了。只需要使用sendto()就OK。   

sendto(sock,   (char*)&tcpHeader,   sizeof(tcpHeader),   0,   (sockaddr*)&addr_in,sizeof(addr_in));  

 

4、接收数据   
    和发送原始套接字数据相比,接收就比较麻烦了。因为在WIN我们不能用recv()来接收raw   socket上的数据,这是因为,所有的IP包都是先递交给系统核心,然后再传输到用户程序,当发送一个raws   socket包的时候(比如syn),核心并不知道,也没有这个数据被发送或者连接建立的记录,因此,当远端主机回应的时候,系统核心就把这些包都全部丢掉,从而到不了应用程序上。所以,就不能简单地使用接收函数来接收这些数据报。   

    要达到接收数据的目的,就必须采用嗅探,接收所有通过的数据包,然后进行筛选,留下符合我们需要的。可以再定义一个原始套接字,用来完成接收数据的任务,需要设置SIO_RCVALL,表示接收所有的数据。   

SOCKET   sniffersock;   
sniffsock   =   WSASocket(AF_INET,   SOCK_RAW,   IPPROTO_IP,   NULL,   0,   WSA_FLAG_OVERLAPPED);   

DWORD   lpvBuffer   =   1;   
DWORD   lpcbBytesReturned   =   0   ;   
WSAIoctl(sniffersock,   SIO_RCVALL,   &lpvBuffer,   sizeof(lpvBuffer),   NULL,   0,   &   lpcbBytesReturned,   NULL,   NULL);   

    创建一个用于接收数据的原始套接字,我们可以用接收函数来接收数据包了。然后在使用一个过滤函数达到筛选的目的,接收我们需要的数据包。  

 

下面是一个示例程序,可以作为SYN扫描的一部分。   

#include   <stdio.h>   
#include   <winsock2.h>   
#include   <ws2tcpip.h>   

#define   SOURCE_PORT   7234   
#define   MAX_RECEIVEBYTE   255   

typedef   struct   ip_hdr   //定义IP首部   
{   
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;   

typedef   struct   tsd_hdr   //定义TCP伪首部   
{   
unsigned   long   saddr;   //源地址   
unsigned   long   daddr;   //目的地址   
char   mbz;   
char   ptcl;   //协议类型   
unsigned   short   tcpl;   //TCP长度   
}PSDHEADER;   

typedef   struct   tcp_hdr   //定义TCP首部   
{   
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;   

//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);   
}   

void   useage()   
{   
printf( "******************************************/n ");   
printf( "TCPPing/n ");   
printf( "/t   Written   by   Refdom/n ");   
printf( "/t   Email:   refdom@263.net/n ");   
printf( "Useage:   TCPPing.exe   Target_ip   Target_port   /n ");   
printf( "*******************************************/n ");   
}   

int   main(int   argc,   char*   argv[])   
{   
WSADATA   WSAData;   
SOCKET   sock;   
SOCKADDR_IN   addr_in;   
IPHEADER   ipHeader;   
TCPHEADER   tcpHeader;   
PSDHEADER   psdHeader;   

char   szSendBuf[60]={0};   
BOOL   flag;   
int   rect,nTimeOver;   

useage();   

if   (argc!=   3)   
{   return   false;   }   

if   (WSAStartup(MAKEWORD(2,2),   &WSAData)!=0)   
{   
printf( "WSAStartup   Error!/n ");   
return   false;   
}   

if   ((sock=WSASocket(AF_INET,SOCK_RAW,IPPROTO_RAW,NULL,0,WSA_FLAG_OVERLAPPED))==INVALID_SOCKET)   
{   
printf( "Socket   Setup   Error!/n ");   
return   false;   
}   
flag=true;   
if   (setsockopt(sock,IPPROTO_IP,   IP_HDRINCL,(char   *)&flag,sizeof(flag))==SOCKET_ERROR)   
{   
printf( "setsockopt   IP_HDRINCL   error!/n ");   
return   false;   
}   

nTimeOver=1000;   
if   (setsockopt(sock,   SOL_SOCKET,   SO_SNDTIMEO,   (char*)&nTimeOver,   sizeof(nTimeOver))==SOCKET_ERROR)   
{   
printf( "setsockopt   SO_SNDTIMEO   error!/n ");   
return   false;   
}   
addr_in.sin_family=AF_INET;   
addr_in.sin_port=htons(atoi(argv[2]));   
addr_in.sin_addr.S_un.S_addr=inet_addr(argv[1]);   

//   
//   
//填充IP首部   
ipHeader.h_verlen=(4 < <4   |   sizeof(ipHeader)/sizeof(unsigned   long));   
//   ipHeader.tos=0;   
ipHeader.total_len=htons(sizeof(ipHeader)+sizeof(tcpHeader));   
ipHeader.ident=1;   
ipHeader.frag_and_flags=0;   
ipHeader.ttl=128;   
ipHeader.proto=IPPROTO_TCP;   
ipHeader.checksum=0;   
ipHeader.sourceIP=inet_addr( "本地地址 ");   
ipHeader.destIP=inet_addr(argv[1]);   

//填充TCP首部   
tcpHeader.th_dport=htons(atoi(argv[2]));   
tcpHeader.th_sport=htons(SOURCE_PORT);   //源端口号   
tcpHeader.th_seq=htonl(0x12345678);   
tcpHeader.th_ack=0;   
tcpHeader.th_lenres=(sizeof(tcpHeader)/4 < <4|0);   
tcpHeader.th_flag=2;   //修改这里来实现不同的标志位探测,2是SYN,1是FIN,16是ACK探测   等等   
tcpHeader.th_win=htons(512);   
tcpHeader.th_urp=0;   
tcpHeader.th_sum=0;   

psdHeader.saddr=ipHeader.sourceIP;   
psdHeader.daddr=ipHeader.destIP;   
psdHeader.mbz=0;   
psdHeader.ptcl=IPPROTO_TCP;   
psdHeader.tcpl=htons(sizeof(tcpHeader));   

//计算校验和   
memcpy(szSendBuf,   &psdHeader,   sizeof(psdHeader));   
memcpy(szSendBuf+sizeof(psdHeader),   &tcpHeader,   sizeof(tcpHeader));   
tcpHeader.th_sum=checksum((USHORT   *)szSendBuf,sizeof(psdHeader)+sizeof(tcpHeader));   

memcpy(szSendBuf,   &ipHeader,   sizeof(ipHeader));   
memcpy(szSendBuf+sizeof(ipHeader),   &tcpHeader,   sizeof(tcpHeader));   
memset(szSendBuf+sizeof(ipHeader)+sizeof(tcpHeader),   0,   4);   
ipHeader.checksum=checksum((USHORT   *)szSendBuf,   sizeof(ipHeader)+sizeof(tcpHeader));   

memcpy(szSendBuf,   &ipHeader,   sizeof(ipHeader));   

rect=sendto(sock,   szSendBuf,   sizeof(ipHeader)+sizeof(tcpHeader),   
0,   (struct   sockaddr*)&addr_in,   sizeof(addr_in));   
if   (rect==SOCKET_ERROR)   
{   
printf( "send   error!:%d/n ",WSAGetLastError());   
return   false;   
}   
else   
printf( "send   ok!/n ");   

closesocket(sock);   
WSACleanup();   

return   0;   
}  

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值