第一法则:站在巨人肩膀上 &&不要重复造轮子。
对于这种复杂的过程,第一选择是使用现成的,节约时间,提升效率。
Wireshark(前称Ethereal)是一个网络封包分析软件。网络封包分析软件的功能是撷取网络封包,并尽可能显示出最为详细的网络封包资料。Wireshark使用WinPCAP作为接口,直接与网卡进行数据报文交换。
网络封包分析软件的功能可想像成 "电工技师使用电表来量测电流、电压、电阻" 的工作 - 只是将场景移植到网络上,并将电线替换成网络线。在过去,网络封包分析软件是非常昂贵,或是专门属于营利用的软件。Ethereal的出现改变了这一切。在GNUGPL通用许可证的保障范围底下,使用者可以以免费的代价取得软件与其源代码,并拥有针对其源代码修改及客制化的权利。Ethereal是目前全世界最广泛的网络封包分析软件之一。
第二法则:学习 &&提升。
如果是单纯的学习知识,可以直接尝试写一些具有部分功能的程序,过程会有点艰难,但非常有意义。学习网络编绝敏程,需要了解 开放系统互连参考模型的的七层每一层的意义以及现实当中实现的四层的网络协议。然后就可以知道抓包的包位于模型当中的传输层协议,包括UDP和TCP的协议。进一步要学习每种协议的格式,表头,数据包等等。一句话,冰冻三尺非一日之寒。
Windows下的抓包及简单的编程。
Windows2000在TCP/IP协议组件上做了很多改进,功能也有增强。比如在协议栈上的调整,增大了默认窗口大小,以及高延迟链接新算法。同时在安全性上,可应用IPSec加强安全性,比NT下有不少的改进。
Microsoft TCP/IP 组件包含“核心协议”、“服务”及两者之间的“接口”。传输驱动程序接口 (TDI) 与网络设备接口规范 (NDIS) 是公用的。 此外,还有许多用户模型应用程序的更高级接口。最常用的接口是 Windows Sockets、远程过程调用 (RPC) 和 NetBIOS。
Windows Sockets 是一个编程接口,它是在加州大学伯克利分校开发的套接字接口的基础上定义的。它包括了一组扩展件,以充分利用 Microsoft Windows 消息驱动的特点。规范的 1.1 版是在 1993 年 1 月发行的,2.2.0 版在 1996 年 5 月发行。Windows 2000 支持 Winsock 2.2 版。在Winsock2中,支持多个传输协议的原始套接字,重叠I/O模型、服务质量控制等。
这里介绍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)
对于原始套接字在接收数据报的时候,要注意这么几点:
a、如果接收并数枝的数据报中协议类型和定义的原始套接字匹配,那么,接收的所有数据就拷贝到套接字中。
b、如果绑定了本地地址,那么只有接收数据IP头中对应的远端地址匹配,接收的数据就拷贝到套接字中。
c、如果定义的是外部地址,比如使用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: [email protected]\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)
创建一个用于接收数据的原始套接字,我们可以用接收函数来接收数据包了。然后在使用一个过滤函数达到筛选的目的,接收我们需要的数据包。
如果在XP以上的 *** 作系统,微软封杀了Raw Soccket,只能用wincpap之类的开发包了。
struct pcap_pkthdr {struct timeval ts /* time stamp */时间戳
bpf_u_int32 caplen /誉让* length of portion present */在线抓到包的长度,无符号整形
bpf_u_int32 len /* length this packet (off wire) */离线包长伏模度
}
pcap_pkthdr是.pcap文件中包的头部,typedef struct pcap pcap_t,pcap_t是定义的一个结构体的对象。你看看编程的环境配的对不对,Winpcap这种抓包和分析包必须庆厅局装好相应的驱动和配好include和lib文件,找Winpcap的函数和数据文档看看,其中定义了好多数据类型和结构体,我也开始学,可以多看看这些,了解下编起来就游刃有余了,你看我这样回答行不行,如果不行咱们再交流。
#include <unistd.h>#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netdb.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/ip.h>
#include <netinet/in.h>
#include <netinet/if_ether.h>
#include <netpacket/packet.h>
#include <net/ethernet.h>
#include <net/if.h>
#include <arpa/inet.h>
#include <errno.h>
/* 接收缓冲区大小 */
#define RCV_BUF_SIZE 1024 * 5
/* 接收缓冲区伍哗慧 */
static int g_iRecvBufSize = RCV_BUF_SIZE
static char g_acRecvBuf[RCV_BUF_SIZE] = {0}
/* 物理网卡接口,需要根据具体情况修改 */
static const char *g_szIfName = "eth1"
/* 以太网帧封装的协议芦悄类型 */
static const int g_iEthProId[] = { ETHERTYPE_PUP,
ETHERTYPE_SPRITE,
ETHERTYPE_IP,
ETHERTYPE_ARP,
ETHERTYPE_REVARP,
ETHERTYPE_AT,
ETHERTYPE_AARP,
ETHERTYPE_VLAN,
ETHERTYPE_IPX,
ETHERTYPE_IPV6,
ETHERTYPE_LOOPBACK
}
static const char g_szProName[][24] = {
"none", "xerox pup", "sprite", "ip", "arp",
"rarp", "apple-protocol", "apple-arp",
"802.1q", "ipx", "ipv6", "loopback"
}
/* 输出MAC地址 */
static void ethdump_showMac(const int iType, const char acHWAddr[])
{int i = 0
if (0 == iType)
{
printf("SMAC=[")
}
else
{
printf("DMAC=[")
}
for(i = 0i <ETHER_ADDR_LEN - 1i++)
{
printf("%02x:", *((unsigned char *)&(acHWAddr[i])))
}
printf("%02x] ", *((unsigned char *)&(acHWAddr[i])))
}
/* 物理网卡混杂腔答模式属性 *** 作 */
static int ethdump_setPromisc(const char *pcIfName, int fd, int iFlags)
{ int iRet = -1
struct ifreq stIfr
/* 获取接口属性标志位 */
strcpy(stIfr.ifr_name, pcIfName)
iRet = ioctl(fd, SIOCGIFFLAGS, &stIfr)
if (0 >iRet)
{ perror("[Error]Get Interface Flags")
return -1
}
if (0 == iFlags)
{ /* 取消混杂模式 */
stIfr.ifr_flags &= ~IFF_PROMISC
}
else
{/* 设置为混杂模式 */
stIfr.ifr_flags |= IFF_PROMISC
}
iRet = ioctl(fd, SIOCSIFFLAGS, &stIfr)
if (0 >iRet)
{ perror("[Error]Set Interface Flags")
return -1
}
return 0
}
/* 获取L2帧封装的协议类型 */
static char *ethdump_getProName(const int iProNum)
{ int iIndex = 0
for(iIndex = 0iIndex <sizeof(g_iEthProId) / sizeof(g_iEthProId[0])iIndex++)
{if (iProNum == g_iEthProId[iIndex])
{
break
}
}
return (char *)(g_szProName[iIndex + 1])
}
/* Init L2 Socket */
static int ethdump_initSocket()
{ int iRet = -1
int fd = -1
struct ifreq stIf
struct sockaddr_ll stLocal = {0}
/* 创建SOCKET */
fd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL))
if (0 >fd)
{ perror("[Error]Initinate L2 raw socket")
return -1
}
/* 网卡混杂模式设置 */
ethdump_setPromisc(g_szIfName, fd, 1)
/* 设置SOCKET选项 */
iRet = setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &g_iRecvBufSize,sizeof(int))
if (0 >iRet)
{perror("[Error]Set socket option")
close(fd)
return -1
}
/* 获取物理网卡接口索引 */
strcpy(stIf.ifr_name, g_szIfName)
iRet = ioctl(fd, SIOCGIFINDEX, &stIf)
if (0 >iRet)
{ perror("[Error]Ioctl operation")
close(fd)
return -1
}
/* 绑定物理网卡 */
stLocal.sll_family = PF_PACKET
stLocal.sll_ifindex = stIf.ifr_ifindex
stLocal.sll_protocol = htons(ETH_P_ALL)
iRet = bind(fd, (struct sockaddr *)&stLocal, sizeof(stLocal))
if (0 >iRet)
{ perror("[Error]Bind the interface")
close(fd)
return -1
}
return fd
}
/* 解析Ethernet帧首部 */
static int ethdump_parseEthHead(const struct ether_header *pstEthHead)
{ unsigned short usEthPktType
if (NULL == pstEthHead)
{return -1}
/* 协议类型、源MAC、目的MAC */
usEthPktType = ntohs(pstEthHead->ether_type)
printf(">>>\nEth-Pkt-Type:0x%04x(%s) ", usEthPktType, ethdump_getProName(usEthPktType))
ethdump_showMac(0, pstEthHead->ether_shost)
ethdump_showMac(1, pstEthHead->ether_dhost)
return 0
}
/* 解析IP数据包头 */
static int ethdump_parseIpHead(const struct ip *pstIpHead)
{ struct protoent *pstIpProto = NULL
if (NULL == pstIpHead)
{return -1}
/* 协议类型、源IP地址、目的IP地址 */
pstIpProto = getprotobynumber(pstIpHead->ip_p)
if(NULL != pstIpProto)
{ printf("\nIP-Pkt-Type:%d(%s) ", pstIpHead->ip_p, pstIpProto->p_name)}
else
{ printf("\nIP-Pkt-Type:%d(%s) ", pstIpHead->ip_p, "None")}
printf("SAddr=[%s] ", inet_ntoa(pstIpHead->ip_src))
printf("DAddr=[%s]\n", inet_ntoa(pstIpHead->ip_dst))
return 0
}
/* 数据帧解析函数 */
static int ethdump_parseFrame(const char *pcFrameData)
{ int iRet = -1
struct ether_header *pstEthHead = NULL
struct ip *pstIpHead = NULL
/* Ethnet帧头解析 */
pstEthHead = (struct ether_header*)g_acRecvBuf
iRet = ethdump_parseEthHead(pstEthHead)
if (0 >iRet)
{return iRet}
/* IP数据包类型 */
pstIpHead = (struct ip *)(pstEthHead + 1)
iRet = ethdump_parseIpHead(pstIpHead)
return iRet
}
/* 捕获网卡数据帧 */
static void ethdump_startCapture(const int fd)
{ int iRet = -1
socklen_t stFromLen = 0
/* 循环监听 */
while(1)
{ /* 清空接收缓冲区 */
memset(g_acRecvBuf, 0, RCV_BUF_SIZE)
/* 接收数据帧 */
iRet = recvfrom(fd, g_acRecvBuf, g_iRecvBufSize, 0, NULL, &stFromLen)
if (0 >iRet)
{ continue}
/* 解析数据帧 */
ethdump_parseFrame(g_acRecvBuf)
}
}
/* Main */
int main(int argc, char *argv[])
{ int iRet = -1
int fd = -1
/* 初始化SOCKET */
fd = ethdump_initSocket()
if(0 >fd) {
return -1
}
/* 捕获数据包 */
ethdump_startCapture(fd)
/* 关闭SOCKET */
close(fd)
return 0
}
编译命令
gcc -o a a.c
./a
实现效果图
...
>>>Eth-Pkt-Type:0x0800(ip) SMAC=[00:1a:92:ef:b6:dd] DMAC=[00:24:7e:dc:99:18] IP-Pkt-Type:6(tcp) SAddr=[192.168.0.111] DAddr=[192.168.0.100]
>>>Eth-Pkt-Type:0x0800(ip) SMAC=[00:24:7e:dc:99:18] DMAC=[00:1a:92:ef:b6:dd] IP-Pkt-Type:6(tcp) SAddr=[192.168.0.100] DAddr=[192.168.0.111]
>>>Eth-Pkt-Type:0x0800(ip) SMAC=[00:24:7e:dc:99:18] DMAC=[00:1a:92:ef:b6:dd] IP-Pkt-Type:1(icmp) SAddr=[192.168.0.100] DAddr=[192.168.0.111]
>>>Eth-Pkt-Type:0x0800(ip) SMAC=[00:1a:92:ef:b6:dd] DMAC=[00:24:7e:dc:99:18] IP-Pkt-Type:1(icmp) SAddr=[192.168.0.111] DAddr=[192.168.0.100]
>>>Eth-Pkt-Type:0x0800(ip) SMAC=[00:1a:92:ef:b6:dd] DMAC=[00:24:7e:dc:99:18] IP-Pkt-Type:6(tcp) SAddr=[192.168.0.111] DAddr=[192.168.0.100]
...
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)