編寫思路:
1.首先將要讀取、寫入信息的兩個文件分別進行打開,接下來先進行文件讀操作,獲得指向該文件頭的指針fp
2.從文件中讀取pcap文件頭大小的信息存儲在pcap_head_buf里,再將偏移偏移量offset設置為pcap頭之后
3.將文件pkt(數據報)頭信息存儲在pkt_head_buf里,將偏移量置offset設置到pkt頭+pkt數據之后,即為下個數據報的偏移量,將fp移動到數據報頭之后。
4從pcap文件頭中讀出linktype從而獲得幀頭種類,再從幀頭中讀出改數據幀是ipv4還是ipv6,本文只處理ipv4。同時指針移動到幀頭部后面
5.當幀為ipv4時,首先將ip頭部取出存儲在ip_head_buf中,指針后移到ip頭后面
6.從ip頭中讀出Protocol,從而獲取是否為TCP鏈接,本文只處理TCP鏈接。當前是TCP連接時,直接將TCP頭信息存儲在tcp_head_buf里,fp移動至TCP頭后
7.接下來offset-fp大小即為數據域大小。首先將數據域最開始的四個字符存儲在tempBuf中,指針隨之后移。
8。將隨后部分的數據域全部存儲在tcp_data_buf中,並從中讀取出method、url、host、user-Agent信息。
9.將method、url、host、user-Agent信息進行格式化存儲。
數據域結構:1.開頭四個字節為請求方式,POST、GET或HEAD。
用到的一些函數:
1.ftell
函數原型:long ftell(FILE *fp);
作用:獲取當前指針和文件開頭便宜的字節數
2.memset
函數原型:void *memset(void *s, int ch, size_t n);
作用:將s中當前位置后面的n個字節 用 ch 替換並返回 s 。
mian.cpp
#include "pcap.h"
#include "method.hpp"
#include <iostream>
#include <memory.h>
#include <fstream>
using namespace std;
int main()
{
pcap_header pcap_head_buf;
pkt_header pkt_head_buf;
ip_header ip_head_buf;
tcp_header tcp_head_buf;
FILE *fp = fopen("Login.pcap" , "rw");
ofstream outfile("HttpMsg.txt",ios::app);
getPcapFileHead(fp , pcap_head_buf);//將文件中一個pkt_header大小的內容存入pcap_head_buf的內存地址
fseek(fp, 0, SEEK_END);
long fileSize=ftell(fp);
long fpOffset=sizeof( pcap_header ) ;
while( (fseek(fp, fpOffset, SEEK_SET) == 0) && ( fpOffset < fileSize ) ) //在循環中處理每一個網絡幀
{
getPktHead(fp , pkt_head_buf);
fpOffset += ( sizeof(pkt_header) + pkt_head_buf.capture_len );
//fpOffset 當前位置 +sizeof( pkt_header) +sizeof (pkt_data) ,得到下一網絡幀的 offset
u_int16 framType=getFramType(fp , pcap_head_buf.linktype); //framType 標識了該幀是否為 IPV6鏈接
if ( framType == 0xdd86 ) //IPV6鏈接 , 跳過該網絡幀
{
continue ;
}
else
{
getIpHead(fp , ip_head_buf);
if ( ip_head_buf.Protocol != 0x06 ) // Protocol != 0x06 表示非TCP鏈接 , 跳過該網絡幀
{
continue ;
}
else //TCP 鏈接類型
{
getTcpHead(fp ,tcp_head_buf) ;
int tcp_data_size = fpOffset - ftell(fp);
// 當前位置在一個 tcp_header 后 ,fpOffset - 當前位置 得到 tcp_data 的長度
if ( tcp_data_size !=0)
{
u_int8 tempBuf[4];
string methodBuf;
string urlBuf;
string hostBuf;
string uaBuf;
fread(tempBuf ,4 ,1 ,fp);
fseek(fp , -4 ,SEEK_CUR);
char tcp_data_buf[1024];
memset(tcp_data_buf,0,sizeof(tcp_data_buf)/sizeof(char));//將整個tcp_data_buf空間都置為0
if ( ( tempBuf[0]==0x50 && tempBuf[1]==0x4f && tempBuf[2]==0x53 && tempBuf[3]==0x54 ) ||
( tempBuf[0]==0x47 && tempBuf[1]==0x45 && tempBuf[2]==0x54 )
) //兩個條件分別表示 "POST " 和 " GET " ,判斷成功表明 該網絡幀包含了一個 HTTP get 或者 post 鏈接
{
fread(tcp_data_buf , tcp_data_size ,1 ,fp );
matchHttp(tcp_data_buf , methodBuf , urlBuf , hostBuf , uaBuf );
outfile<<"method :"<<methodBuf<<endl;
outfile<<"url :" <<urlBuf<<endl;
outfile<<"host :"<<hostBuf<<sendl;
outfile<<"ua :"<<uaBuf<<endl;
outfile<<"=======*===========*=========*==========="<<std::endl;
//將內容存在output.txt中
//
}
}
}
}
}
outfile.close();
}
pcap.h
#ifndef DEFINEPCAP_H #define DEFINEPCAP_H /* pacp文件構成: 1: pacp = pcap_head + pkt_head + pket_data + next->pkt_head + next->pkt_data : …… 2: pkt_data=frame_head + ip_head + tcp_head +tcp_data //其中 pacp_head 中的 linktype 又決定了 frame_head 的類型 */ typedef unsigned int u_int32; typedef unsigned short u_int16; typedef unsigned char u_int8; typedef int int32; /* Pcap文件頭24B各字段說明: Magic:4B:0x1A 2B 3C 4D:用來標示文件的開始 Major:2B,0x02 00:當前文件主要的版本號 Minor:2B,0x04 00當前文件次要的版本號 ThisZone:4B當地的標准時間;全零 SigFigs:4B時間戳的精度;全零 SnapLen:4B最大的存儲長度 LinkType:4B鏈路類型 常用類型: 0 BSD loopback devices, except for later OpenBSD 1 Ethernet, and Linux loopback devices 6 802.5 Token Ring 7 ARCnet 8 SLIP 9 PPP */ typedef struct pcap_header { u_int32 magic; u_int16 version_major; u_int16 version_minor; int32 thiszone; u_int32 sigfigs; u_int32 snaplen; u_int32 linktype; }pcap_header; /* Packet 包頭和Packet數據組成 字段說明: Timestamp:時間戳高位,精確到seconds Timestamp:時間戳低位,精確到microseconds Caplen:當前數據區的長度,即抓取到的數據幀長度,由此可以得到下一個數據幀的位置。 Len:離線數據長度:網絡中實際數據幀的長度,一般不大於caplen,多數情況下和Caplen數值相等。 Packet 數據:即 Packet(通常就是鏈路層的數據幀)具體內容,長度就是Caplen,這個長度的后面,就是當前PCAP文件中存放的下一個Packet數據包,也就 是說:PCAP文件里面並沒有規定捕獲的Packet數據包之間有什么間隔字符串,下一組數據在文件中的起始位置。我們需要靠第一個Packet包確定。 */ typedef struct timestamp{ u_int32 timestamp_s; u_int32 timestamp_ms; }timestamp; typedef struct pkt_header{ timestamp ts; u_int32 capture_len; u_int32 len; }pkt_header; /**以太網幀頭格式**/ typedef struct Ethernet { u_int8 DstMAC[6]; //目的MAC地址 u_int8 SrcMAC[6]; //源MAC地址 u_int16 FrameType; //幀類型 } Ethernet; /**另一種幀頭格式**/ typedef struct Linux_cooked_capture { u_int16 package_type; u_int16 address_type; u_int16 address_length; u_int16 un_used[4]; u_int16 FrameType; //幀類型 }Linux_cooked_capture; typedef struct ip_header { //IP數據報頭 u_int8 Ver_HLen; //版本+報頭長度 u_int8 TOS; //服務類型 u_int16 TotalLen; //總長度 u_int16 ID; //標識 u_int16 Flag_Segment; //標志+片偏移 u_int8 TTL; //生存周期 u_int8 Protocol; //協議類型 u_int16 Checksum; //頭部校驗和 u_int32 SrcIP; //源IP地址 u_int32 DstIP; //目的IP地址 }ip_header ; typedef struct tcp_header { //TCP數據報頭 u_int16 SrcPort; //源端口 u_int16 DstPort; //目的端口 u_int32 SeqNO; //序號 u_int32 AckNO; //確認號 u_int8 HeaderLen; //數據報頭的長度(4 bit) + 保留(4 bit) u_int8 Flags; //標識TCP不同的控制消息 u_int16 Window; //窗口大小 u_int16 Checksum; //校驗和 u_int16 UrgentPointer; //緊急指針 }tcp_header ; #endif // DEFINEPCAP_H
method.hpp
#ifndef METHOD_H #define METHOD_H #include "pcap.h" #include <cstdio> #include <iostream> #include <vector>
/**將文件中1個pcap_header大小的內容存放在內存中pcap_head的的地址中**/ void getPcapFileHead( FILE *fp , pcap_header &pcap_head ) { fread( &pcap_head , sizeof( pcap_header ) , 1 , fp); } Linux_cooked_capture /**將文件中1個pkt_header大小的內容存放在內存中pkt_head的的地址中**/ void getPktHead(FILE *fp , pkt_header &pkt_head) { fread( &pkt_head , sizeof( pkt_header ) , 1 , fp); }
u_int32 getFramType(FILE *fp , u_int32 linktype) // linktype 決定了 frame_head 的大小 { // FrameType 決定了 該網絡幀是 ipv6鏈接 或是 ipv4鏈接 if (linktype == 0x71) //是另一種幀時,從幀頭中讀取FrameType獲得該以太網幀是ipv4還是ipv6 { Linux_cooked_capture temp; fread(&temp ,sizeof(temp) ,1 , fp); return temp.FrameType; } if (linktype == 0x01) //是以太網幀時,從幀頭中讀取FrameType獲得該以太網幀是ipv4還是ipv6 { Ethernet temp; fread(&temp ,sizeof(temp) ,1 , fp); return temp.FrameType ; } } void getIpHead(FILE *fp , ip_header & ip_head_buf) { fread( &ip_head_buf , sizeof( ip_header ) , 1 , fp); } void getTcpHead(FILE *fp , tcp_header &tcp_head_buf) { fread( &tcp_head_buf , sizeof( tcp_header ) , 1 , fp); fseek(fp , ( tcp_head_buf.HeaderLen>>2 ) - sizeof(tcp_header) ,SEEK_CUR ); // fseek() 是因為 tcp_header 大小是變動的 ,且由 Headerlen>>2 可以計算出來 ,由於只需要關心前半部分數據 ,后半部分數據可以直接跳過 } void matchHttp(char tcp_data_buf[] , std::string & methodBuf ,std::string & urlBuf , std::string & hostBuf ,std::string &uaBuf) { std::vector<std::string> tempStrVector; std::string tempSring(tcp_data_buf); for(std::string::size_type beganPos=0 ; beganPos != tempSring.size() ; ) //將tcp_data_buf[] 內的字符串 { std::string::size_type endPos=beganPos; //按照 " \n " 分組放入tempStrVecor while(++endPos && endPos != tempSring.size()) { if( tempSring[endPos] =='\n' ) { break; } } tempStrVector.push_back( tempSring.substr(beganPos ,endPos - beganPos) ); if( endPos == tempSring.size() ) { break; } beganPos=endPos ; } for(std::vector<std::string>::iterator posVector =tempStrVector.begin() ; posVector !=tempStrVector.end() ; ++posVector ) { //遍歷 tempStrVecor 的包含的字符串 ,獲取 method url host ua 值 if ( std::string::size_type tempPos = (*posVector).find("GET") != (*posVector).npos ) { methodBuf="GET"; std::string::size_type endPos=(*posVector).find("HTTP/1.1"); urlBuf=(*posVector).substr(tempPos + sizeof("GET") - 1 , endPos - tempPos - sizeof("GET") ); } // “ GET ” 和 “ HTTP/1.1” 之間字符串為 url if ( std::string::size_type tempPos = (*posVector).find("POST") != (*posVector).npos ) { std::string::size_type endPos=(*posVector).find("HTTP/1.1"); methodBuf="POST"; urlBuf=(*posVector).substr(tempPos+sizeof("POST") -1 , endPos - tempPos - sizeof("POST") ); } // “ POST ” 和 “ HTTP/1.1” 之間的字符串為 url if ( std::string::size_type tempPos = (*posVector).find("Host:") != (*posVector).npos ) { hostBuf=(*posVector).substr(tempPos+sizeof("Host:" ) ); } //" Host:" 后的字符串為 host if ( std::string::size_type tempPos = (*posVector).find("User-Agent:") != (*posVector).npos ) { uaBuf=(*posVector).substr(tempPos+sizeof("User-Agent:") ); } // " User-Agent:" 后的字符串為 ua } } #endif // METHOD_H