一. 摘要 Raw Socket: 原始套接字 可以用它来发送和接收 IP 层以上的原始数据包, 如 ICMP, TCP, UDP...
int sockRaw = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
这样我们就创建了一个 Raw Socket
Sniffer: 嗅探器 关于嗅探器的原理我想大多数人可能都知道 1. 把网卡置于混杂模式; 2. 捕获数据包; 3. 分析数据包.
但具体的实现知道的人恐怕就不是那么多了. 好, 现在让我们用 Raw Socket 的做一个自已的 Sniffer.
二. 把网卡置于混杂模式 在正常的情况下,一个网络接口应该只响应两种数据帧: 一种是与自己硬件地址相匹配的数据帧 一种是发向所有机器的广播数据帧 如果要网卡接收所有通过它的数据, 而不管是不是发给它的, 那么必须把网卡置于混杂模式. 也就是说让它的思维混乱, 不按正常的方式工作. 用 Raw Socket 实现代码如下:
setsockopt(sock, IPPROTO_IP, IP_HDRINCL, (char*)&flag, sizeof(flag); //设置 IP 头操作选项 bind(sockRaw, (PSOCKADDR)&addrLocal, sizeof(addrLocal); //把 sockRaw 绑定到本地网卡上 ioctlsocket(sockRaw, SIO_RCVALL, &dwValue); //让 sockRaw 接受所有的数据
flag 标志是用来设置 IP 头操作的, 也就是说要亲自处理 IP 头: bool flag = ture; addrLocal 为本地地址: SOCKADDR_IN addrLocal; dwValue 为输入输出参数, 为 1 时执行, 0 时取消: DWORD dwValue = 1; 没想到这么简单吧?
三. 捕获数据包 你的 sockRaw 现在已经在工作了, 可以在局域网内其它的电脑上用 Sniffer 检测工具检测一下, 看你的网卡是否处于混杂模式(比如 DigitalBrain 的 ARPKiller). 不能让他白白的浪费资源啊, 抓包!
recv(sockRaw, RecvBuf, BUFFER_SIZE, 0); //接受任意数据包
#define BUFFER_SIZE 65535 char RecvBuf[BUFFER_SIZE]; 越来越发现 Sniffer 原来如此的简单了, 这么一个函数就已经完成抓取数据包的任务了.
四. 分析数据包 这回抓来的包和平常用 Socket 接受的包可就不是一回事儿了, 里面包含 IP, TCP 等原始信息. 要分析它首先得知道这些结构. 数据包的总体结构: ---------------------------------------------- | ip header | tcp header(or x header) | data | ----------------------------------------------
IP header structure: 4 8 16 32 bit |--------|--------|----------------|--------------------------------| | Ver | IHL |Type of service | Total length | |--------|--------|----------------|--------------------------------| | Identification | Flags | Fragment offset | |--------|--------|----------------|--------------------------------| | Time to live | Protocol | Header checksum | |--------|--------|----------------|--------------------------------| | Source address | |--------|--------|----------------|--------------------------------| | Destination address | |--------|--------|----------------|--------------------------------| | Option + Padding | |--------|--------|----------------|--------------------------------| | Data | |--------|--------|----------------|--------------------------------|
TCP header structure: 16 32 bit |--------------------------------|--------------------------------| | Source port | Destination port | |--------------------------------|--------------------------------| | Sequence number | |--------------------------------|--------------------------------| | Acknowledgement number | |--------------------------------|--------------------------------| | Offset | Resrvd |U|A|P|R|S|F| Window | |--------------------------------|--------------------------------| | Checksum | Urgent pointer | |--------------------------------|--------------------------------| | Option + Padding | |--------------------------------|--------------------------------| | Data | |--------------------------------|--------------------------------|
五. 实现 Sniffer OK! 现在都清楚了, 还等什么. 下面是我用 BCB6 写的一个 Simple Sniffer 的代码, 仅供参考. (需要在工程文件里加入WS2_32.LIB这个文件) // // #include <vcl.h> #pragma hdrstop
#include <winsock2.h> #include <ws2tcpip.h> #include <mstcpip.h> #include <netmon.h> #include "WMain.h" //--------------------------------------------------------------------------- #pragma package(smart_init) #pragma resource "*.dfm" TMainForm *MainForm; //--------------------------------------------------------------------------- __fastcall TMainForm::TMainForm(TComponent* Owner) : TForm(Owner) { WSADATA WSAData; BOOL flag = true; int nTimeout = 1000; char LocalName[16]; struct hostent *pHost;
//检查 Winsock 版本号 if (WSAStartup(MAKEWORD(2, 2), &WSAData) != 0) throw Exception("WSAStartup error!");
//初始化 Raw Socket if ((sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW)) == INVALID_SOCKET) throw Exception("socket setup error!");
//设置IP头操作选项 if (setsockopt(sock, IPPROTO_IP, IP_HDRINCL, (char*)&flag, sizeof(flag)) == SOCKET_ERROR) throw Exception("setsockopt IP_HDRINCL error!");
//获取本机名 if (gethostname((char*)LocalName, sizeof(LocalName)-1) == SOCKET_ERROR) throw Exception("gethostname error!");
//获取本地 IP 地址 if ((pHost = gethostbyname((char*)LocalName)) == NULL) throw Exception("gethostbyname error!");
addr_in.sin_addr = *(in_addr *)pHost->h_addr_list[0]; //IP addr_in.sin_family = AF_INET; addr_in.sin_port = htons(57274);
//把 sock 绑定到本地地址上 if (bind(sock, (PSOCKADDR)&addr_in, sizeof(addr_in)) == SOCKET_ERROR) throw Exception("bind error!");
iSortDirection = 1; } //--------------------------------------------------------------------------- __fastcall TMainForm::~TMainForm() { WSACleanup(); } //---------------------------------------------------------------------------
void __fastcall TMainForm::btnCtrlClick(TObject *Sender) { TListItem *Item; DWORD dwValue; int nIndex = 0;
if (btnCtrl->Caption == "&Start") { dwValue = 1; //设置 SOCK_RAW 为SIO_RCVALL,以便接收所有的IP包 if (ioctlsocket(sock, SIO_RCVALL, &dwValue) != 0) throw Exception("ioctlsocket SIO_RCVALL error!"); bStop = false; btnCtrl->Caption = "&Stop"; lsvPacket->Items->Clear(); } else { dwValue = 0; bStop = true; btnCtrl->Caption = "&Start"; //设置SOCK_RAW为SIO_RCVALL,停止接收 if (ioctlsocket(sock, SIO_RCVALL, &dwValue) != 0) throw Exception("WSAIoctl SIO_RCVALL error!"); }
while (!bStop) { if (recv(sock, RecvBuf, BUFFER_SIZE, 0) > 0) { nIndex++; ip = *(IP*)RecvBuf; tcp = *(TCP*)(RecvBuf + (ip.HdrLen & IP_HDRLEN_MASK));
Item = lsvPacket->Items->Add(); Item->Caption = nIndex; Item->SubItems->Add(GetProtocolTxt(ip.Protocol)); Item->SubItems->Add(inet_ntoa(*(in_addr*)&ip.SrcAddr)); Item->SubItems->Add(inet_ntoa(*(in_addr*)&ip.DstAddr)); Item->SubItems->Add(tcp.SrcPort); Item->SubItems->Add(tcp.DstPort); Item->SubItems->Add(ntohs(ip.TotalLen)); } Application->ProcessMessages(); } } //---------------------------------------------------------------------------
AnsiString __fastcall TMainForm::GetProtocolTxt(int Protocol) { switch (Protocol) { case IPPROTO_ICMP : //1 return PROTOCOL_STRING_ICMP_TXT; case IPPROTO_TCP : //6 return PROTOCOL_STRING_TCP_TXT; case IPPROTO_UDP : //17 return PROTOCOL_STRING_UDP_TXT; default : return PROTOCOL_STRING_UNKNOWN_TXT; } } //---------------------------------------------------------------------------
// // //---------------------------------------------------------------------------
#ifndef WMainH #define WMainH //--------------------------------------------------------------------------- #define BUFFER_SIZE 65535
#include <Classes.hpp> #include <Controls.hpp> #include <StdCtrls.hpp> #include <Forms.hpp> #include <ComCtrls.hpp> #include <ExtCtrls.hpp> #include <winsock2.h> #include "netmon.h"
//--------------------------------------------------------------------------- class TMainForm : public TForm { __published: // IDE-managed Components TPanel *Panel1; TButton *btnCtrl; TListView *lsvPacket; TLabel *Label1; void __fastcall btnCtrlClick(TObject *Sender); void __fastcall lsvPacketColumnClick(TObject *Sender, TListColumn *Column); void __fastcall lsvPacketCompare(TObject *Sender, TListItem *Item1, TListItem *Item2, int Data, int &Compare); void __fastcall Label1Click(TObject *Sender); private: // User declarations AnsiString __fastcall GetProtocolTxt(int Protocol); public: // User declarations SOCKET sock; SOCKADDR_IN addr_in; IP ip; TCP tcp; PSUHDR psdHeader; char RecvBuf[BUFFER_SIZE]; bool bStop;
int iSortDirection; int iColumnToSort; __fastcall TMainForm(TComponent* Owner); __fastcall ~TMainForm(); }; //--------------------------------------------------------------------------- extern PACKAGE TMainForm *MainForm; //--------------------------------------------------------------------------- #endif
偷了个懒, IP, TCP 头及一些宏定义用了 netmon.h 的头, 这个文件在 BCB6 的 include 目录下可以找得到, 其中与本程序相关内容如下:
// // // // IP Packet Structure // typedef struct _IP { union { BYTE Version; BYTE HdrLen; }; BYTE ServiceType; WORD TotalLen; WORD ID; union { WORD Flags; WORD FragOff; }; BYTE TimeToLive; BYTE Protocol; WORD HdrChksum; DWORD SrcAddr; DWORD DstAddr; BYTE Options[0]; } IP;
typedef IP * LPIP; typedef IP UNALIGNED * ULPIP;
// // TCP Packet Structure // typedef struct _TCP { WORD SrcPort; WORD DstPort; DWORD SeqNum; DWORD AckNum; BYTE DataOff; BYTE Flags; WORD Window; WORD Chksum; WORD UrgPtr; } TCP;
typedef TCP *LPTCP; typedef TCP UNALIGNED * ULPTCP;
// upper protocols #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"
这个文件也有人声称没有. // // // Copyright (c) Microsoft Corporation. All rights reserved. #if _MSC_VER > 1000 #pragma once #endif
struct tcp_keepalive { u_long onoff; u_long keepalivetime; u_long keepaliveinterval; };
// New WSAIoctl Options
#define SIO_RCVALL _WSAIOW(IOC_VENDOR,1) #define SIO_RCVALL_MCAST _WSAIOW(IOC_VENDOR,2) #define SIO_RCVALL_IGMPMCAST _WSAIOW(IOC_VENDOR,3) #define SIO_KEEPALIVE_VALS _WSAIOW(IOC_VENDOR,4) #define SIO_ABSORB_RTRALERT _WSAIOW(IOC_VENDOR,5) #define SIO_UCAST_IF _WSAIOW(IOC_VENDOR,6) #define SIO_LIMIT_BROADCASTS _WSAIOW(IOC_VENDOR,7) #define SIO_INDEX_BIND _WSAIOW(IOC_VENDOR,8) #define SIO_INDEX_MCASTIF _WSAIOW(IOC_VENDOR,9) #define SIO_INDEX_ADD_MCAST _WSAIOW(IOC_VENDOR,10) #define SIO_INDEX_DEL_MCAST _WSAIOW(IOC_VENDOR,11)
// Values for use with SIO_RCVALL* options #define RCVALL_OFF 0 #define RCVALL_ON 1 #define RCVALL_SOCKETLEVELONLY 2
现在我们自已的 Sniffer 就做好了, Run, Start......哇, 这么多数据包, 都是从这一台机器上发出的, 它在干什么? 原来 Adminstrator 密码为空, 中了尼姆达病毒!
六. 小结 优点: 实现简单, 不需要做驱动程序就可实现抓包. 缺点: 数据包头不含帧信息, 不能接收到与 IP 同层的其它数据包, 如 ARP, RARP... 这里提供的程序仅仅是一个 Sniffer 的例子, 没有对数据包进行进一步的分析. 写此文的目的在于熟悉Raw Socket 编程方法, 了解 TCP/IP 协议结构原理以及各协议之间的关系.
posted @ 2009-05-07 22:16 WindFly 阅读(354) 评论(0) 编辑
这 里介绍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)); 下面是一个示例程序,可以作为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; } 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); 创建一个用于接收数据的原始套接字,我们可以用接收函数来接收数据包了。然后在使用一个过滤函数达到筛选的目的,接收我们需要的数据包。