手动构建tcp数据包
SOCKET_RAW 手动封装TCP协议
/*
使用IP头包含选项
创建原始套接字之后,再打开IP_HDRINCL 选项,即可在IP头中封装自己的协议,而不是仅仅使用系统预定义的协议。
一般可以使用这种方法来发送UDP和TCP数据
IP数据报格式:
Version域:这4位指定了数据报的IP版本。对IPV4来说此域值为4.
IHL(IP header length 的缩写):因为IP头长度不是固定的,所以需要这4位来确定IP数据报中数据部分的开始位置。大多数IP数据报不包含此选项,所以通常IP数据报有20个字节的头长度。
Type of service(服务端类型,TOS):包含在IPV4头中,用来区分不同类型的IP数据报
Total length:这是IP数据报的总长度,IP头加数据。这个域是16位(2字节)长,所以IP数据报大小的理论最大值是65535字节。然而数据报的长度很少有超过1500字节的。
Identification域:用来表示已发送的IPV4封包。通常,系统每发送一次封包就增加一次这个值。
Falgs和Fragment offset域:当IPV4分包被分割为较小的包时使用这2个域。DF代表不要分割(dont fragment),这是一个给路由器的命令,告诉他们不要分割此数据报,因为目标主机没有能力将它们恢复回来(例如:机器在启动时)。MF代表更多的分割(More Fragments)。
Time to live(生存时间,TTL)域:包含TTL域是为了确保数据报不会永远呆在网络里打圈。每当数据报被路由器处理时,这个域就会减1,如果减到0,此数据报就会被丢弃。
Protocol域:当IP数据报到达目的地时才使用此域,它指定了IP数据报的数据部分将要传递给哪个传输层协议,例如,值6表示数据部分要被传递给TCP,值17表示数据部分要被传递给UDP。
Header checksum 域:头校验和帮助路由器检测接收到的IP数据报中的位错误。
Source address 和 Destianation address域:指定此数据报的源IP地址和目的IP地址。
Options域:选项域是一个长度可变的域,它包含了可选的信息。最大值是40字节。
*/
#include "pch.h"
#include <iostream>
#include <WinSock2.h>
#include <ws2ipdef.h>
#include <process.h>
#pragma warning(disable:4996)
#pragma comment(lib,"ws2_32.lib")
typedef struct _IPHeader {
u_char VIHL; //版本和首部长度 各占4bit
u_char ToS; //服务类型
u_short TotalLen; //总长度
u_short ID; //标识号
u_short Frag_Flags; //片偏移量
u_char TTL; //生存时间
u_char Protocol; //协议
u_short Checksum; //首部校验和
ULONG SrcIP;
ULONG DestIP;
//struct in_addr SrcIP; //源IP地址
//struct in_addr DestIP; //目的地址
}IPHDR, *PIPHDR;
//伪tcp头 用来计算校验和
typedef struct _psdheader
{
unsigned long saddr; //源地址
unsigned long daddr; //目的地址
char mbz; //强制置空
char ptcl; //协议类型
unsigned short tcpl; //TCP长度
}PSD_HDR, *PPSD_HDR;
/*TCP头*/
typedef struct _tcpheader {
USHORT sourcePort; //来源端口
USHORT destinationPort;//目标端口
UINT sequence; //序列号
UINT acknowledgement;//确认号
USHORT len;//4位首部长度,6位保留字 所以下面的flag其实是用了6位
UCHAR flag;//6位标志符
USHORT win;//16位窗口大小
USHORT checkSum;//校验和
USHORT urp;//16位紧急数据偏移量
}TCP_HDR, *PTCP_HDR;
//计算校验和
WORD checkSum(WORD* wBuff, int nSize) {
DWORD dwSum = 0;;
//将数据以WORD为单位累加到wSum
while (nSize > 1) {
dwSum += *wBuff++;
nSize -= sizeof(WORD);
}
//若出现最后还剩一个字节继续与前面结果相加(也就是为奇数的情况)。
if (nSize) {
dwSum += *(BYTE*)wBuff++;
}
while (dwSum >> 16) { //如果有高16位,将一直与低16位相加
dwSum = (dwSum >> 16) + (dwSum & 0xFFFF);
}
//取反
return (WORD)(~dwSum);
}
//原始套接字 发送TCP封包
//没有写接收,在win中我们不能用recv来接收原始套接字上的数据,因为IP包都是先递交给系统核心,再传输到用户程序。
//当发送一个raw socket包时,比如syn,核心并不知道,也没有这个数据被发送或者连接建立的记录,
//因此当远端主机回应的时候,系统核心就把这些包全部丢弃,从而到不了应用程序上。
//要达到接收的目的,需要使用嗅探来完成,之前我写过,简单的嗅探器。
int main()
{
WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData);
SOCKET s = WSASocket(AF_INET, SOCK_RAW, IPPROTO_RAW, NULL, 0, WSA_FLAG_OVERLAPPED);
//SOCKET s = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
if (s == INVALID_SOCKET) {
printf_s("请使用管理员运行\n");
return false;
}
BOOL flag = TRUE;
if (setsockopt(s, IPPROTO_IP, IP_HDRINCL, (char*)&flag, sizeof(flag)) == SOCKET_ERROR) {
printf_s("设置flag失败\n");
return false;
}
int nTimeOver = 1000;
if (setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, (char*)&nTimeOver, sizeof(nTimeOver)) == SOCKET_ERROR) {
printf_s("设置发送时间失败\n");
return false;
}
SOCKADDR_IN sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(4567);
sin.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
//填充IP首部
IPHDR ipHdr;
ipHdr.VIHL = (4 << 4 | (sizeof(IPHDR) / sizeof(ULONG)));
ipHdr.TotalLen = htons(sizeof(IPHDR) + sizeof(TCP_HDR));
ipHdr.ID = 1;
ipHdr.Frag_Flags = 0;
ipHdr.TTL = 128;
ipHdr.Protocol = IPPROTO_TCP;
ipHdr.Checksum = 0; //稍后计算
ipHdr.SrcIP = inet_addr("127.0.0.1");
ipHdr.DestIP = inet_addr("127.0.0.1");
//构造tcp伪首部
PSD_HDR pdsHdr;
pdsHdr.saddr = ipHdr.SrcIP;
pdsHdr.daddr = ipHdr.DestIP;
pdsHdr.mbz = 0; //必须留空一个字节
pdsHdr.ptcl = ipHdr.Protocol;
pdsHdr.tcpl = 0; //稍后计算 因为需要发送内容长度
//填充tcp
TCP_HDR tcpHdr;
tcpHdr.destinationPort = htons(4567);
tcpHdr.sourcePort = htons(8888);
tcpHdr.sequence = htonl(0x12345678);
tcpHdr.acknowledgement = 0;
tcpHdr.len = (sizeof(tcpHdr) / 4 << 4 | 0);
tcpHdr.flag = 0x2;//这里用来实现不同标志位探测,2是SYN,1是FIN,16是ACK探测 等等.
tcpHdr.win = htons(512);
tcpHdr.urp = 0;
tcpHdr.checkSum = 0; //稍后计算
char szSendBuff[1024] = { 0 };
char msgBuff[4] = { 0 };
int iBuffLen = 1024;
//拷贝IP头和计算IP头校验和
memcpy_s(szSendBuff, iBuffLen, &ipHdr, sizeof(ipHdr));
iBuffLen -= sizeof(ipHdr);
ipHdr.Checksum = checkSum((WORD*)szSendBuff, sizeof(ipHdr));
//清空,因为上面其实只是用来计算IP头的校验和
memset(szSendBuff,0,iBuffLen);
//计算机TCP校验和
const char tcpSendData[]={"发送的内容!"};
pdsHdr.tcpl = htons(sizeof(tcpHdr)+sizeof(tcpSendData)); //修改长度
memcpy(szSendBuff,&pdsHdr,sizeof(pdsHdr)); //拷贝伪TCP头
memcpy(szSendBuff+sizeof(pdsHdr),&tcpHdr,sizeof(tcpHdr)); //拼接真实tcp头
memcpy(szSendBuff+sizeof(pdsHdr)+sizeof(tcpHdr),tcpSendData,sizeof(tcpSendData)); //再拼接内容
tcpHdr.checkSum= checkSum((WORD*)szSendBuff,sizeof(pdsHdr)+sizeof(tcpHdr)+sizeof(tcpSendData)); //计算TCP校验和
//清空,因为上面其实只是用来计算TCP头的校验和
memset(szSendBuff,0,iBuffLen);
//构造真实数据
memcpy(szSendBuff,&ipHdr,sizeof(ipHdr));//拷贝计算好的IP头
memcpy(szSendBuff +sizeof(ipHdr),&tcpHdr,sizeof(tcpHdr)); //拼接计算好的TCP头
memcpy(szSendBuff +sizeof(ipHdr)+sizeof(tcpHdr),tcpSendData,sizeof(tcpSendData)); //拼接内容
int nRet = sendto(s, szSendBuff, sizeof(ipHdr) + sizeof(tcpHdr) + sizeof(msgBuff), 0, (sockaddr*)&sin, sizeof(sin));
if (nRet == SOCKET_ERROR)
printf_s("发送失败\n");
printf_s("发送程序完成\n");
Sleep(10000);
WSACleanup();
}