20170314_原始套接字编程

一些专业术语

SOCKET_STEAM  流式套接字TCP

SOCKET_DGRAM  数据包套接字UDP

SOCKET_RAW  原始套接字

IPPROTO_IP  IP协议

IPPROOTO_ICMP INTERNAT  控制消息协议:配合原始套接字可以实现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_TCP层次。参数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头长度和协议的类型输出来。

 

  

设计任务分析

 

(一)实验环境

 

操作系统:Windows 编程工具及集成开发环境:VC++ 

(二)实验目的和要求

 

实验目的:掌握原始套接字编程。 

实验要求:完成下列功能: 

(1)利用RAW SOCKET捕获网络数据包的程序模型SOCKET_STREAM 流式套接SOCKET_DGRAM 

 

(2)能够抓取第二节课的并发服务器程序的服务器端或客户端的应用层数据,即:时

间值,打印输出。

 

 

二、设计方案

 

同一台主机不同进程可以用进程号来唯一标识,但是在网络环境下进程号并不能唯一标2识该进程。TCP/IP主要引入了网络地址、端口和连接等概念来解决网络间进程标识问题。套接字(Socket)是一个指向传输提供者的句柄,TCP/IP协议支持种类型的套接字,分别是流式套接字、数据报式套接字和原始套接字。流式套接字(SOCKET_STREAM)提供面向连接、双向可靠的数据流传输服务。数据报式套接字(SOCKET_ DGRAM)提供了无连接不提供无错保证。原始套接字(SOCKET_RAW)允许对较低层次的协议直接访问,比如IP、ICMP

协议,它常用于检验新的协议实现,或者访问现有服务中配置的新设备,因为RAW SOCKET可以自如地控制Windows下的多种协议,能够对网络底层的传输机制进行控制,所以可以应用原始套接字来操纵网络层和传输层应用。比如,我们可以通过RAW SOCKET来接收发向本机的ICMP、IGMP协议包,或者接收TCP/IP栈不能够处理的IP包,也可以用来定包头或自定协议的IP包。网络监听技术很大程度上依赖于SOCKET_RAW。

 本实验采用原始套接字进行捕获通过本主机的包并对本主机的包进行处理


 

 头文件

ip.h

 

详细代码:

 //定义标准的的TCP头和IP头

 

#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_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地址 

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

 

// 定义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; 

}ICMP_HEADER; 

 

typedef struct udp_hdr // 8 Bytes 定义udp首部 

unsigned short uh_sport; 

unsigned short uh_dport; 

unsigned short uh_len; 

unsigned short uh_sum; 

}UDP_HEADER; 

 

源文件

详细代码:

 

#include<stdio.h> 

#include<Winsock2.h> 

#pragma comment(lib,"ws2_32") 

#define SIO_RCVALL _WSAIOW(IOC_VENDOR,1) 

struct ip // 定义IP首部

 unsigned char h_verlen;  // 4位首部长度,4位IP版本号

 unsigned char tos; // 8位服务类型TOS  

 unsigned short ip_length; // 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地址

 };

 // 定义TCP首部

 

struct 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位紧急数据偏移量

};  

void main() 

 

int sock,bytes_recieved,fromlen; 

char buffer[65535]; 

struct sockaddr_in from;  

struct ip *ip; 

struct tcp *tcp; 

struct ip *ip; 

 struct tcp *tcp;  

WORD wVersionRequested; //版本号

WSADATA wsaData; //启动SOCKET的 

int err;  

wVersionRequested = MAKEWORD( 2, 2 );//建立版本

err = WSAStartup( wVersionRequested,&wsaData );//启用socket 

 

if ( err != 0 ) //如果返回值不等于0,那么表示出错,直截退出程序

 return; 

 }  

sock=socket(AF_INET,SOCK_RAW,IPPROTO_IP);  

bool flag=true;  

setsockopt(sock, IPPROTO_IP, 2 , (char*)&flag, sizeof(flag));  

sockaddr_in addr; 

addr.sin_family = AF_INET; 

addr.sin_addr.S_un.S_addr=inet_addr("127.0.0.1"); //IP设置

addr.sin_port=htons(0);  

if(SOCKET_ERROR==bind(sock,(sockaddr *)&addr,sizeof(addr))) //绑定

closesocket(sock); 

 printf("绑定失败!"); 

exit(0);  

 

DWORD dwBytesRet; 

DWORD dwVal=1;  

ioctlsocket(sock, SIO_RCVALL, &dwVal); //设置网卡为混听模式

int i=0; 

while(true) {  

fromlen=sizeof(from); 

bytes_recieved=recvfrom(sock,buffer,sizeof(buffer),0,(struct 

sockaddr*)&from,&fromlen); //接收数据 

i++; 

ip=(struct ip *)buffer; //得到ip头

if(ip->proto==6) //过滤其他协议,只留下TCP协议

tcp=(struct tcp *)(buffer+(4*ip->h_verlen&0xf0>>4)); //得到tcp头

printf("Ip包字节数:%d\n",bytes_recieved); //打印ip数据包长度

printf("源IP:%s\n", inet_ntoa(*(in_addr*)&ip->sourceIP)); //打印源IP 

printf("目的IP:%s\n", inet_ntoa(*(in_addr*)&ip->destIP)); //打印目的IP 

printf("源端口:%d\n",ntohs(tcp->th_sport)); //打印源端口

printf("目的端口:%d\n",ntohs(tcp->th_dport)); //打印目的端口

printf("TCP的数据内容:"); 

char* ptr=buffer+5+ 4*((tcp->th_lenres&0xf0)>>4|0); //计算数据头指针

int cpysize=bytes_recieved-5- 4*((tcp->th_lenres&0xf0)>>4|0);//计算数据长度 

memcpy(buffer, ptr, cpysize); //取出数据

for(int i = 0; i < cpysize  i++) //打印数据

if(buffer[i] >= 32 && buffer[i] < 255) 

 { printf("%c", (unsigned char)buffer[i]); 

}else{ 

printf("."); 

}  

printf("\n");  

 

转载于:https://my.oschina.net/u/3344205/blog/858801

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值