关于TCP/IP数据包结构一文的进一步说明(常见协议数据报结构及TCP三次握手机制)...

上周发一篇文章:http://blog.csdn.net/prsniper/article/details/6762145

大家反应比较活跃,看来对大家帮助不少,于是有此文。

================================================================

先声明一下,文章被很多人转载,我欢迎大家转载,但是我发现已有一个人转载时,没有保留出处,而且连转载也没写明,竟然挂着“原创”的标题,实在是……

================================================================

这篇文章可能表较长,所以这里写个目录,也可以说是摘要吧:

1.TCP三次握手机制

2.数据包拦截的C++源码(VS6/VC++)

3.TCP通信C源码,包括服务端和客户端

4.常见协议数据包头部的C语言定义(包括在抓包源码中)

5.WinHex分析数据报数据

//请在转载的时候,保留出处,至少声明是转载,尊重他人即尊重自己。限于水平,同时精力有限,不足指出望大家批评指正。

用到一张图片,以便理解:(大家可以阅读http://blog.csdn.net/prsniper/article/details/6762145便于理解)

1.TCP三次握手机制

  第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确认;SYN:同步序列编号(Synchronize Sequence Numbers)。

  第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;

  第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。

  完成三次握手,客户端与服务器开始传送数据,简单来说呢,就是:

A告诉B:“我要去你家做客。”,B收到,说:“欢迎”,杀鸡宰羊,开门等候,A得到确认,沐浴更衣,刷牙洗脸,带上筷子,告诉B:“我将要到达。”,双方会面…关于三次握手得到的数据分析,参见下面WinHex部分。

2.数据包拦截的C++源码(VS6/VC++)

VC++源码,可能比较长,大家可以滚动鼠标往下看

================================================== /* Analyser.cpp - TCP/IP Pack Analysing Module * Coded by http://blog.csdn.net/prsniper * Please retain this information when copying *///================================================== #include "ANALYSER.H" int main(int argc, char* argv[]) { //初始化SOCKET WSADATA wsaData; int ret = WSAStartup(MAKEWORD(2, 2), &wsaData); if(ret != NO_ERROR) { printf("Error at WSAStartup();\n"); return 0; } //创建套接字,soket_raw SOCKET lpRawSocket = socket(AF_INET, SOCK_RAW, IPPROTO_IP); if (lpRawSocket == INVALID_SOCKET) { printf("Error at socket(): %ld\n", WSAGetLastError()); WSACleanup(); return 0; } //获取本机IP地址 char strHost[200]; if(gethostname((char*)strHost, sizeof(strHost) - 1) == SOCKET_ERROR) { strcpy(strHost, "localhost"); //主机名获取失败,使用localhost printf("using \"localhost\" as host name!\n"); } hostent *lpHost = gethostbyname((char*)strHost); if(lpHost == NULL) { printf("gethostbyname(); failed!\n"); lpHost = gethostbyaddr("127.0.0.1", 4, PF_INET); if(lpHost == NULL) { printf("get host failed!\n"); WSACleanup(); return 0; } } //struct in_addr addr; //if(lpHost->h_addrtype == AF_INET) //{ while(lpHost->h_addr_list[ret] != 0) // { addr.s_addr = *(u_long*)lpHost->h_addr_list[ret++]; // printf("\tIPv4 Address #%d: %s\n", ret, inet_ntoa(addr)); // } //} //绑定端口,把hostname与sa绑定,sa与lpRawSocket绑定 SOCKADDR_IN sa; memcpy(&sa.sin_addr.S_un.S_addr, lpHost->h_addr_list[0], lpHost->h_length); sa.sin_family = AF_INET; sa.sin_port = htons(8972); if(bind(lpRawSocket, (PSOCKADDR)&sa, sizeof(sa)) == SOCKET_ERROR) { printf("bind(); failed!\n"); WSACleanup(); return 0; } //设置RAW socket DWORD dwBuffer[10]; DWORD dwCount = 0; DWORD dwSize = 1; /* WSAIoctl()函数参数 s:一个套接口的句柄。 dwIoControlCode:将进行的操作的控制代码。 lpvInBuffer:输入缓冲区的地址。 cbInBuffer:输入缓冲区的大小。 lpvOutBuffer:输出缓冲区的地址。 cbOutBuffer:输出缓冲区的大小。 lpcbBytesReturned:输出实际字节数的地址。 lpOverlapped:WSAOVERLAPPED结构的地址。 lpCompletionRoutine:一个指向操作结束后调用的例程指针。 */ ret = WSAIoctl(lpRawSocket, SIO_RCVALL, &dwSize, sizeof(dwSize), &dwBuffer, sizeof(dwBuffer), &dwCount, NULL, NULL); if(ret == SOCKET_ERROR) { printf("WSAIoctl(); failed!\n"); WSACleanup(); return 0; } //侦听IP报文 char Buffer[8192] = {0}; //Windows 一般一个包裹8K=8192字节(Byte) BYTE *lpBuffer = (BYTE *)&Buffer; FILE *lpFile = fopen("TCPIP.LOG","wb"); //存在会覆盖 int i = 1; while (1) { //memset(Buffer, 0, sizeof(Buffer)); ret = recv(lpRawSocket, Buffer, sizeof(Buffer), 0); if(ret > 0) { Buffer[ret] = NULL; //结束字符串 //收到数据包,解码(这个就算了) //ret = *(int*)Buffer[12]; printf("from: %d.%d.%d.%d To %d.%d.%d.%d\n", Buffer[12], Buffer[13], Buffer[14], Buffer[15], Buffer[16], Buffer[17], Buffer[18], Buffer[19]); printf("\nBegin Pack ID: %d ==================", i); if(lpFile != NULL) { fseek(lpFile, 0, SEEK_END); fwrite(lpBuffer, ret, 1, lpFile); fclose(lpFile); lpFile = fopen("TCPIP.LOG","ab"); //二进制追加模式 } printf("\nEnd Pack ID: %d ==================\n", i); i++; } } return 1; } inline void fnAnalyse(BYTE *pData) { static ICMPHEADER hICMPData; static IPHEADER hIPData; static TCPHEADER hTCP; static UDPHEADER hUDP; static BYTE *lpStr; //copy pack data memcpy(&hIPData, pData, sizeof(IPHEADER)); //转换为网络字节序,即大端模式(big-endian) //hIPData.iSrcAddr = htons(hIPData.iSrcAddr); //32位要另外写htons函数,或分成两个short //hIPData.iDstAddr = htons(hIPData.iDstAddr); // hIPData.sLength = htons(hIPData.sLength); hIPData.sRepare = htons(hIPData.sRepare); //显示信息 lpStr = pData + sizeof(IPHEADER); printf("IPv%x Head Length:%d Bytes. Serve Type:%d\n", hIPData.cLenVer >> 4, (hIPData.cLenVer & 0x0F) * 4, hIPData.cServer); //不写了... }


编译运行,会将得到的数据包,保存在当前文件夹的TCPIP.LOG中,二进制模式

3.TCP通信C源码,包括服务端和客户端

这是服务端,也就是监听端的C语言源代码

#include <winsock2.h> #include <stdio.h> #pragma comment(lib,"ws2_32.lib") #define MAX_RECV_LENGTH 1024 //1K Bytes #define MAX_SEND_LENGTH 1024 int main(int argc, char* argv[]) { SOCKET sckServer; SOCKADDR_IN lpServer; SOCKADDR_IN lpClient; WORD wVersionRequested; char lpSend[MAX_SEND_LENGTH]; char lpGet[MAX_RECV_LENGTH]; WSADATA wsaData; int ret; wVersionRequested = MAKEWORD(1, 1); ret = WSAStartup( wVersionRequested, &wsaData ); if (ret != 0) return 0; if((LOBYTE( wsaData.wVersion ) != 1)||(HIBYTE( wsaData.wVersion ) != 1)) { WSACleanup(); return 0; } sckServer= socket(AF_INET, SOCK_STREAM, 0); //监听的套接字 lpServer.sin_addr.S_un.S_addr = htonl(INADDR_ANY); lpServer.sin_family = AF_INET; lpServer.sin_port = htons(1987); //using port:1987 if( SOCKET_ERROR == bind(sckServer, (SOCKADDR*)&lpServer, sizeof(SOCKADDR)) ) { printf("bind error\n"); WSACleanup(); return 0; } if( SOCKET_ERROR == listen(sckServer,5) ) { printf("listen error"); WSACleanup(); return 0; } //变量重用是大侠一大风格,不过请勿模仿,最简单的建议是尽量不对全局变量重用 ret = sizeof(SOCKADDR); while(1) { SOCKET sckReq = accept(sckServer, (SOCKADDR*)&lpClient, &ret); if(sckReq == INVALID_SOCKET) { printf("accept error\n"); return 0; } //测试TCP三次握手,注释下面收发部分 sprintf(lpSend,"%s","what the fuck are you doing?\n"); if( SOCKET_ERROR == send(sckReq,lpSend,strlen(lpSend)+1,0) ) { printf("send err\n"); return 0; } recv(sckReq,lpGet,MAX_RECV_LENGTH,0); printf("%s\n",lpGet); closesocket(sckReq); } return 1; }


以下是客户端,连接请求端的C语言源码:

#include <winsock2.h> #include <stdio.h> #pragma comment(lib,"ws2_32.lib") #define MAX_RECV_LENGTH 1024 //1K #define SEND_MAX_LENGTH 1024 int main(int argc, char* argv[]) { SOCKET sckClient; SOCKADDR_IN lpAddr; char lpGet[MAX_RECV_LENGTH]; char lpSend[SEND_MAX_LENGTH]; WORD wVersionRequested; WSADATA wsaData; int ret; wVersionRequested = MAKEWORD(1, 1); //为了低版本的Windows ret = WSAStartup(wVersionRequested, &wsaData); if(ret != 0) return 0; if(LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1) { WSACleanup(); return 0; } sckClient = socket(AF_INET,SOCK_STREAM,0); lpAddr.sin_addr.S_un.S_addr = inet_addr("192.168.0.2"); //connect to host lpAddr.sin_family = AF_INET; lpAddr.sin_port = htons(1987); //server port:1987 if( SOCKET_ERROR == connect(sckClient,(SOCKADDR*)&lpAddr,sizeof(SOCKADDR)) ) { printf("connect error\n"); WSACleanup(); return 0; } //测试TCP三次握手,注释下面收发部分 if(recv(sckClient, lpGet, MAX_RECV_LENGTH, 0) == SOCKET_ERROR) { printf("recive error\n"); WSACleanup(); return 0; }else { printf("%s\n", lpGet); } strcpy(lpSend, "fucking this fucking code is what the fuck i\'m doing!\n"); send(sckClient, lpSend, sizeof(lpSend) + 1, 0); closesocket(sckClient); WSACleanup(); return 1; }


4.常见协议数据包头部的C语言定义(包括在抓包源码中)

这里列出TCP,UDP,ICMP,IGMP的数据包头部C语言结构定义,ICMP常见应用是ping命令,IGMP是个危险的协议。也许有人会说,那HTTP协议呢,FTP协议呢,TELNET协议呢?这些都是基于TCP/IP的,也可能使用UDP,但是他们的命令都是ASCII明文,比如HTTP协议的头部包含在TCP包的数据部分

// ========================================================== // ANALYSER.H - Struct, Global Variable, Function Definitions // Coded by http://blog.csdn.net/prsniper // ========================================================== #if !defined(__RANGER_ANA_H_) #define __RANGER_ANA_H_ #if _MSC_VER >= 1000 //Visual Studio 6.0 #pragma once #endif // _MSC_VER >= 1000 #include "Winsock2.h" #include <stdio.h> #define SIO_RCVALL _WSAIOW(IOC_VENDOR, 1) #pragma comment(lib,"ws2_32.lib") typedef struct _IPHEADER { BYTE cLenVer; //4位首部长度+4位IP版本号 BYTE cServer; //8位服务类型 USHORT sLength; //16位总长度(字节) USHORT sRepare; //16位标识 USHORT sFlagOffset; //3位标志位+13段偏移量 BYTE cTimeToLife; //8位生存时间 TTL BYTE cProtocol; //8位协议 (1=ICMP,2=IGMP,6=TCP,17=UDP等) USHORT sChecksum; //16位IP首部校验和 ULONG iSrcAddr; //32位源始IP地址 ULONG iDstAddr; //32位目的IP地址 }IPHEADER; typedef struct _TCPHEADER { USHORT sSrcPort; //16位源端口 USHORT sDstPort; //16位目的端口 ULONG iDataNum; //32位数据序号 ULONG iCheckNum; //32位确认序号 BYTE cHeadLen; //4位首部长度+6位保留字(其中四位) BYTE cUAPRSF; //6位标志位 USHORT sWindow; //16位窗口大小 USHORT sPackChecksum; //16位校验和 USHORT sEmergency; //16位紧急数据偏移量(紧急指针) }TCPHEADER; typedef struct _UDPHEADER { USHORT sSrcPort; //16位原始端口 USHORT sDstPort; //16位目的端口 USHORT sPackLen; //16位数据包长度 USHORT sChecksum; //16位校验和,只提示,不强制重发 }UDPHEADER; typedef struct _ICMPHEADER { BYTE cType; //8位类型 BYTE cCode; //8位代码 USHORT sChecksum; //16位校验和 USHORT sRecCode; //16位识别号(一般为进程号) USHORT sPackNum; //16位报文序列号 ULONG iTimeValue; //32位时间戳,GetTickCount(); }ICMPHEADER; //IGMP[Internet Group Management Protocol]是IP主机用作向相邻多目路由器报告多目组成员。 typedef struct _IGMPHEADER //畸形IGMP包会试TCPIP堆栈崩溃,这里不解析了 { UCHAR cVerType; //4位版本 4位类型 UCHAR cUnKnow; //未使用 USHORT sChecksum; //16位校验和 ULONG iGroupAddr; //32位组地址 }IGMPHEADER; #endif //!defined(__RANGER_ANA_H_)

源码到此结束,因为是控制台窗口,数据也保存到文件,运行效果就不截图了。

5.WinHex分析数据报数据

用WinHex打开保存的文件,如下图

TCP/IP数据包结构参见这篇文章:http://blog.csdn.net/prsniper/article/details/6762145,下面的大端模式即高位在低地址(在前面),举个例子IP为127.0.0.1在x86中是这样存储:0x0100007F,而大端模式是:0x7F000001。

首先,这是IP数据包,第一个字节是4位版本+首部长度,如图前四位是4,后四位是5,则这个包裹是IPv4,头长5*32bit=20Byte。第二个字节是服务类型,其值是0x00,第三第四字节是包裹总长,值为大端模式0x0034=52,可以据此计算包裹为红色部分。第五第六字节为重组标识,值为大端模式0x4986=18822。第七第八字节为3位标识和13位段偏移量,这两个字节(0x4000)的二进制值为01000000,可以算出该数据报不允许分段,段偏移量为0。第9字节为生存时间TTL=0x40=64,第十字节为协议代码,值为0x06=6(TCP)第11,12字节为头部校验和,值为0x6F77。下面为双方IP地址,0xC0A80164是大端模式为192.168.1.100,0xC0A80002一样,为192.168.0.2。也就是从192.168.1.100发向192.168.0.2(我给路由分了几个网段,呵呵)。因为头部长度已经标志为5,即没有选项,那么IP包头部到此结束。

然后,TCP头部部分,前面两个短整型是始末端口,为大端模式0x06F9=1785,请求连接的话,本地端口是随机的;目的端口0x07C3=1987,这个就是服务端的监听端口。下来是数据序号0x73F5BBBE=1945484222,因为是手提,一直不关机,合上待机就拿到办公室,所以已经累计发了很多数据了;确认序号0x00000000,说明两者还没有开始传输数据。然后看偏移,值为80,前四位是8=1000,也就是数据距离包头8*32=32Byte(32正好是这个字节到IP包头的偏移?!),后四位保留为0。下一个字节为0x02=00000010,前两位保留为0,后六位分别对应:UAPRSF,得出SYN=1,这是一个请求或者接受请求报文!窗口字段0x4000=16384,这个不管它。包校验和0x6DF1,紧急指针0x0000,因为URG位已经为0,即使紧急指针不为0也无效。

后面的数据究竟是什么意义,我还不太清楚,不过对TCP三次握手的说明,应该没有影响了。

下面看第二个包包,类似的我就不说了,它是从192.168.0.2发向192.168.1.100的,数据序号0x73F5BBBF,正好是请求包的数据序号+1;确认序号0xD4CF3750;标志0x10=00010000,所以ACK=1,说明确认号有效,这是服务端给请求方发的“同意连接”确认包。

再下面第三个包是绿色部分,一样道理分析之,我这里就不写这么多了。时间有限,偷懒一下。另一方面来说,我认为我表达的不是很通俗。这里有价值之处在于C/C++的源代码,大家对fnAnalyse()函数稍作修改就可以做全自动的协议分析,这才是本文的核心,也是它的价值所在。

可能是使用了代码的原因,不同形式的语言或者说表达方式很难融合到一起,熟悉C/C++的朋友(Java跟C++很像,简直就是C++生的)就好理解多了。

好啦,就这么多吧,下面发下使用编译出来的控制台的方法(可以直接双击运行,或者在VS6 IDE运行),我用的是命令行:

:\>cd /D F:\ F:\>cd codes F:\Codes>cd "Visual C++" F:\Codes\Visual C++>cd PackAnalyser F:\Codes\Visual C++\PackAnalyser>cd.. F:\Codes\Visual C++>cd tcpClient F:\Codes\Visual C++\tcpClient>cd debug F:\Codes\Visual C++\tcpClient\Debug>dir 驱动器 F 中的卷是 FILES 卷的序列号是 0005-DD75 F:\Codes\Visual C++\tcpClient\Debug 的目录 2011-09-15 10:37 <DIR> . 2011-09-15 10:37 <DIR> .. 2011-09-15 13:45 12,268 tcp.obj 2011-09-15 13:45 155,714 tcpClient.exe 2011-09-15 13:45 176,624 tcpClient.ilk 2011-09-15 10:37 2,863,224 tcpClient.pch 2011-09-15 13:45 410,624 tcpClient.pdb 2011-09-15 13:49 41,984 vc60.idb 2011-09-15 13:45 69,632 vc60.pdb 7 个文件 3,730,070 字节 2 个目录 61,248,696,320 可用字节 F:\Codes\Visual C++\tcpClient\Debug>tcpclient.exe what the fuck are you doing?


我放在这个目录,呵呵。大家编译时候不要忘记更改IP哦,不然又有人来这里发牢骚说源码不对了……

虎胆游侠

2011-09-15 15:25:00 于深圳

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值