C/C++ 運用Npcap發(fā)送UDP數據包的完美過程
Npcap 是一個功能強大的開源網絡抓包庫,它是 WinPcap 的一個分支,并提供了一些增強和改進。特別適用于在 Windows 環(huán)境下進行網絡流量捕獲和分析。除了支持通常的網絡抓包功能外,Npcap 還提供了對數據包的拼合與構造,使其成為實現 UDP 數據包發(fā)包的理想選擇。本章將通過Npcap庫構造一個UDP原始數據包,并實現對特定主機的發(fā)包功能,通過本章的學習讀者可以掌握如何使用Npcap庫偽造特定的數據包格式。
Npcap 是一個功能強大的開源網絡抓包庫,它是 WinPcap 的一個分支,并提供了一些增強和改進。特別適用于在 Windows 環(huán)境下進行網絡流量捕獲和分析。除了支持通常的網絡抓包功能外,Npcap 還提供了對數據包的拼合與構造,使其成為實現 UDP 數據包發(fā)包的理想選擇。本章將通過Npcap庫構造一個UDP原始數據包,并實現對特定主機的發(fā)包功能,通過本章的學習讀者可以掌握如何使用Npcap庫偽造特定的數據包格式。
Npcap的主要特點和概述:
- 原始套接字支持: Npcap 允許用戶通過原始套接字在網絡層捕獲和發(fā)送數據包。這使得用戶能夠進行更底層的網絡活動監(jiān)控和分析。
- WinPcap 的增強版本: Npcap 是 WinPcap 的一個分支,對其進行了一些增強和改進。這些改進包括對新版本 Windows 的支持、更好的性能和穩(wěn)定性,以及一些額外的功能。
- 支持 Windows 10: Npcap 被設計用于支持 Windows 10 操作系統(tǒng)。它允許用戶在最新的 Windows 平臺上進行網絡抓包和分析。
- Loopback 模式: Npcap 允許在 Loopback 接口上進行抓包,使用戶能夠監(jiān)視本地主機上的網絡流量。
- 多種應用場景: Npcap 被廣泛應用于網絡安全、網絡管理、網絡調試等各種場景。它為開發(fā)人員、網絡管理員和安全專家提供了一個功能強大的工具,用于分析和理解網絡通信。
- 開源: Npcap 是開源項目,其源代碼可以在 GitHub 上獲得。這使得用戶可以自由查看、修改和定制代碼,以滿足特定需求。
UDP 是一種無連接、輕量級的傳輸層協議,與 TCP 相比,它不提供可靠性、流控制和錯誤恢復機制,但卻更加簡單且具有較低的開銷。UDP 主要用于那些對傳輸速度要求較高、可以容忍少量丟失的應用場景。
UDP 數據包結構: UDP 數據包由報頭和數據兩部分組成。
- 報頭(Header):
- 源端口號(16 位): 指定發(fā)送端口。
- 目標端口號(16 位): 指定接收端口。
- 長度(16 位): 報頭和數據的總長度,以字節(jié)為單位。
- 校驗和(16 位): 用于驗證數據在傳輸過程中的完整性。
- 數據(Payload):
- 實際傳輸的數據,長度可變。
UDP 的特點:
- 面向無連接: UDP 是一種無連接協議,通信雙方不需要在傳輸數據之前建立連接。這使得它的開銷較低,適用于一些實時性要求較高的應用。
- 不可靠性: UDP 不提供數據的可靠性保證,不保證數據包的到達、順序和完整性。因此,它更適合那些能夠容忍一些數據丟失的場景,如音視頻傳輸。
- 適用于廣播和多播: UDP 支持廣播和多播通信,可以通過一個發(fā)送操作同時向多個目標發(fā)送數據。
- 低開銷: 由于缺乏連接建立和維護的開銷,以及不提供可靠性保證的特性,UDP 具有較低的開銷,適用于對實時性要求較高的應用。
- 適用于短消息: 由于不需要建立連接,UDP 適合傳輸短消息,尤其是對實時性要求高的應用。
UDP 的應用場景:
- 實時性要求高的應用: 如實時音視頻傳輸、在線游戲等。
- 簡單的請求-響應通信: 適用于一些簡單的請求-響應場景,如 DNS 查詢。
- 廣播和多播應用: UDP 的支持廣播和多播特性使其適用于這類通信模式。
- 實時數據采集: 例如傳感器數據采集等場景。
輸出網卡
使用 WinPcap(Windows Packet Capture)庫列舉系統(tǒng)上的網絡接口以及它們的 IP 地址。WinPcap 是一個用于 Windows 操作系統(tǒng)的網絡數據包捕獲庫,可以用于網絡數據包的捕獲和分析。
代碼主要做了以下幾個事情:
- 使用
pcap_findalldevs_ex
函數查找系統(tǒng)上的所有網絡接口。 - 遍歷每個網絡接口,獲取其 IP 地址,并將地址列表打印出來。
pcap_findalldevs_ex
用于查找系統(tǒng)上所有網絡接口的函數。它的原型如下:
int pcap_findalldevs_ex(const char *source, struct pcap_rmtauth *auth, pcap_if_t **alldevs, char *errbuf);
函數參數說明:
source
:一個字符串,用于指定網絡接口的來源??梢詾?nbsp;NULL
,表示從系統(tǒng)獲取網絡接口信息。也可以指定為一個網絡地址,用于遠程捕獲。auth
:一個pcap_rmtauth
結構的指針,用于指定遠程捕獲的認證信息。一般情況下可以為NULL
。alldevs
:一個pcap_if_t
類型的指針的地址,用于保存查找到的網絡接口鏈表的頭指針。errbuf
:一個字符數組,用于保存錯誤信息。
函數返回值:
- 成功時返回 0。
- 失敗時返回 -1,錯誤信息保存在
errbuf
中。
函數功能:
pcap_findalldevs_ex
主要用于查找系統(tǒng)上的網絡接口信息。當調用成功后,alldevs
將指向一個鏈表,鏈表中的每個節(jié)點都包含一個網絡接口的信息。這個鏈表的頭指針是 alldevs
。
pcap_freealldevs
用于釋放 pcap_findalldevs_ex
函數分配的資源的函數。其原型如下:
void pcap_freealldevs(pcap_if_t *alldevs);
函數參數說明:
alldevs
:由pcap_findalldevs_ex
返回的鏈表的頭指針。
函數功能:
pcap_freealldevs
主要用于釋放 pcap_findalldevs_ex
函數返回的鏈表中分配的資源,包括每個節(jié)點和節(jié)點中保存的接口信息。
輸出當前系統(tǒng)中活動網卡信息,可以這樣來寫,如下代碼所示;
#include <WinSock2.h> #include <Windows.h> #include <iostream> #include <pcap.h> #pragma comment(lib,"ws2_32.lib") #pragma comment(lib, "packet.lib") #pragma comment(lib, "wpcap.lib") // 打開網卡返回的指針 pcap_t* m_adhandle; unsigned char* FinalPacket; unsigned int UserDataLen; int main(int argc, char *argv[]) { // 打開網卡 pcap_if_t* alldevs = NULL, *d = NULL; char szErr[MAX_PATH] = { 0 }; if (-1 == pcap_findalldevs_ex(PCAP_SRC_IF_STRING, NULL, &alldevs, szErr)) { return 0; } // 遍歷網卡 char* lpszIP = NULL; d = alldevs; while (NULL != d) { // 遍歷網卡IP char szAddress[1024] = { 0 }; pcap_addr_t* p = d->addresses; while (p) { lpszIP = inet_ntoa(((sockaddr_in*)p->addr)->sin_addr); strcpy(szAddress, lpszIP); p = p->next; } std::cout << "地址列表: " << szAddress << std::endl; d = d->next; } // 釋放資源 pcap_freealldevs(alldevs); system("pause"); return 0; }
輸出效果如下圖所示;
打開網卡
打開網絡適配器的函數,通過傳入本機的IP地址,該函數會查找與該IP地址匹配的網絡適配器并打開。以下是對該函數的簡要分析:
查找網卡設備指針:
if (-1 == pcap_findalldevs_ex(PCAP_SRC_IF_STRING, NULL, &alldevs, errbuf))
使用 pcap_findalldevs_ex
函數來獲取本機所有網卡設備的鏈表。如果返回值為 -1,說明發(fā)生了錯誤,這時函數會輸出錯誤信息并直接返回。
選取適合網卡:
for (d = alldevs; d; d = d->next)
通過遍歷網卡設備鏈表,查找與傳入的本機IP地址匹配的網卡。首先,通過檢查每個網卡的地址列表,找到第一個匹配的網卡。如果找到了,將 flag
標記設為1,然后跳出循環(huán)。如果未找到匹配的網卡,輸出錯誤信息并返回。
獲取子網掩碼:
netmask = ((sockaddr_in*)d->addresses->netmask)->sin_addr.S_un.S_addr;
獲取匹配網卡的子網掩碼。
打開網卡:
m_adhandle = pcap_open(d->name, 65536, PCAP_OPENFLAG_PROMISCUOUS, 1000, NULL, errbuf);
使用 pcap_open
函數打開選擇的網卡,該函數的聲明如下:
pcap_t *pcap_open(const char *source, int snaplen, int flags, int read_timeout, struct pcap_rmtauth *auth, char *errbuf);
這里是對參數的簡要解釋:
source
: 要打開的網絡適配器的名稱,例如 "eth0"。snaplen
: 指定捕獲數據包時每個數據包的最大長度。如果數據包超過這個長度,它將被截斷。通常設置為數據包的最大可能長度。
flags
: 控制捕獲的方式,可以使用位掩碼進行組合。常見的標志包括:
PCAP_OPENFLAG_PROMISCUOUS
: 開啟混雜模式,允許捕獲所有經過網卡的數據包。PCAP_OPENFLAG_MAX_RESPONSIVENESS
: 最大響應性標志,可能在某些平臺上影響性能。read_timeout
: 設置超時值,以毫秒為單位。如果設置為0,表示無限期等待數據包。auth
: 可以指定用于遠程捕獲的身份驗證信息,通常為NULL
。errbuf
: 用于存儲錯誤信息的緩沖區(qū),如果函數執(zhí)行失敗,會將錯誤信息寫入這個緩沖區(qū)。
函數返回一個 pcap_t
類型的指針,它是一個表示打開的網絡適配器的結構。如果打開失敗,返回 NULL
。
檢查以太網:
if (DLT_EN10MB != pcap_datalink(m_adhandle))
pcap_datalink
函數是 PCAP 庫中用于獲取網絡適配器數據鏈路類型(datalink type)的函數,確保是以太網,如果不是以太網,輸出錯誤信息并返回。
該函數的聲明如下:
int pcap_datalink(pcap_t *p);
這里是對參數的簡要解釋:
p
: 表示一個已經打開的網絡適配器的pcap_t
結構指針。
函數返回一個整數,表示數據鏈路類型。這個值通常是預定義的常量之一,用于標識不同類型的網絡數據鏈路。
常見的一些數據鏈路類型常量包括:
DLT_EN10MB
(Ethernet): 表示以太網數據鏈路。DLT_IEEE802
(802.5 Token Ring): 表示 IEEE 802.5 Token Ring 數據鏈路。DLT_PPP
(Point-to-Point Protocol): 表示點對點協議數據鏈路。DLT_ARCNET
(ARCNET): 表示 ARCNET 數據鏈路。
釋放網卡設備列表:
pcap_freealldevs(alldevs);
最后,釋放 pcap_findalldevs_ex
函數返回的網卡設備列表,避免內存泄漏。
該函數的其他全局變量 m_adhandle
,FinalPacket
,UserDataLen
已經在文章開頭聲明和定義。
// 通過傳入本機IP地址打開網卡 void OpenAdapter(std::string local_address) { pcap_if_t* alldevs = NULL, * d = NULL; char errbuf[256] = { 0 }; bpf_program fcode; u_int netmask; // 獲取網卡設備指針 if (-1 == pcap_findalldevs_ex(PCAP_SRC_IF_STRING, NULL, &alldevs, errbuf)) { std::cout << "獲取網卡設備指針出錯" << std::endl; return; } // 選取適合網卡 int flag = 0; for (d = alldevs; d; d = d->next) { pcap_addr_t* p = d->addresses; while (p) { if (local_address == inet_ntoa(((sockaddr_in*)p->addr)->sin_addr)) { flag = 1; break; } p = p->next; } if (1 == flag) break; } if (0 == flag) { std::cout << "請檢查本機IP地址是否正確" << std::endl; std::cout << local_address.c_str() << std::endl; return; } // 獲取子網掩碼 netmask = ((sockaddr_in*)d->addresses->netmask)->sin_addr.S_un.S_addr; // 打開網卡 m_adhandle = pcap_open(d->name, 65536, PCAP_OPENFLAG_PROMISCUOUS, 1000, NULL, errbuf); if (NULL == m_adhandle) { std::cout << "打開網卡出錯" << std::endl; pcap_freealldevs(alldevs); return; } //檢查以太網 if (DLT_EN10MB != pcap_datalink(m_adhandle)) { std::cout << "此程序僅在以太網下工作" << std::endl; pcap_freealldevs(alldevs); return; } // 釋放網卡設備列表 pcap_freealldevs(alldevs); }
構造數據
MAC地址轉換為Bytes字節(jié)
將MAC 地址的字符串表示形式轉換為字節(jié)數組(unsigned char
數組),函數首先創(chuàng)建了一個臨時緩沖區(qū) Tmp
來存儲輸入字符串的拷貝,然后使用 sscanf
函數將字符串中的每兩個字符解析為一個十六進制數,存儲到 Returned
數組中。最后,通過調整指針的位置,跳過已經處理的字符,實現了對整個字符串的解析。
下面是這段代碼的解釋:
// MAC地址轉Bytes unsigned char* MACStringToBytes(std::string String) { // 獲取輸入字符串的長度 int iLen = strlen(String.c_str()); // 創(chuàng)建一個臨時緩沖區(qū),用于存儲輸入字符串的拷貝 char* Tmp = new char[(iLen + 1)]; // 將輸入字符串拷貝到臨時緩沖區(qū) strcpy(Tmp, String.c_str()); // 創(chuàng)建一個用于存儲結果的unsigned char數組,數組大小為6 unsigned char* Returned = new unsigned char[6]; // 循環(huán)處理每個字節(jié) for (int i = 0; i < 6; i++) { // 使用sscanf將字符串中的兩個字符轉換為16進制數,存儲到Returned數組中 sscanf(Tmp, "%2X", &Returned[i]); // 移動臨時緩沖區(qū)的指針,跳過已經處理過的字符 memmove((void*)(Tmp), (void*)(Tmp + 3), 19 - i * 3); } // 返回存儲結果的數組 return Returned; }
Bytes字節(jié)轉換為16進制
將兩個字節(jié)(unsigned char
類型的 X
和 Y
)組成一個16位的無符號整數。函數的目的是將兩個字節(jié)的數據合并成一個16位的整數。首先,將 X
左移8位,然后與 Y
進行按位或操作,得到一個包含兩個字節(jié)信息的16位整數。最后,將這個16位整數返回。這種操作通常在處理網絡協議或二進制數據時會經常遇到。
下面是這段代碼的解釋:
// Bytes地址轉16進制 unsigned short BytesTo16(unsigned char X, unsigned char Y) { // 將 X 左移8位,然后與 Y 進行按位或操作,得到一個16位的無符號整數 unsigned short Tmp = X; Tmp = Tmp << 8; Tmp = Tmp | Y; return Tmp; }
計算 IP 數據報的校驗和
這個函數主要通過遍歷 IP 頭中的每兩個字節(jié),將它們合并為一個16位整數,并逐步累加到校驗和中。在每次累加時,還需要檢查是否發(fā)生了溢出,如果溢出則需要額外加1。最后,對累加得到的校驗和進行取反操作,得到最終的 IP 校驗和,并將其返回。這種校驗和計算通常用于驗證 IP 數據報的完整性。
下面是這段代碼的解釋:
// 計算IP校驗和 unsigned short CalculateIPChecksum(UINT TotalLen, UINT ID, UINT SourceIP, UINT DestIP) { // 初始化校驗和 unsigned short CheckSum = 0; // 遍歷 IP 頭的每兩個字節(jié) for (int i = 14; i < 34; i += 2) { // 將每兩個字節(jié)合并為一個16位整數 unsigned short Tmp = BytesTo16(FinalPacket[i], FinalPacket[i + 1]); // 計算校驗和 unsigned short Difference = 65535 - CheckSum; CheckSum += Tmp; // 處理溢出 if (Tmp > Difference) { CheckSum += 1; } } // 取反得到最終的校驗和 CheckSum = ~CheckSum; return CheckSum; }
計算 UDP 數據報的校驗和
這個函數主要通過構造 UDP 數據報的偽首部,包括源 IP、目標 IP、協議類型(UDP)、UDP 長度、源端口、目標端口以及 UDP 數據等字段,并通過遍歷偽首部的每兩個字節(jié)計算校驗和。最后取反得到最終的 UDP 校驗和,并將其返回。這種校驗和計算通常用于驗證 UDP 數據報的完整性。
下面是這段代碼的解釋:
// 計算UDP校驗和 unsigned short CalculateUDPChecksum(unsigned char* UserData, int UserDataLen, UINT SourceIP, UINT DestIP, USHORT SourcePort, USHORT DestinationPort, UCHAR Protocol) { unsigned short CheckSum = 0; // 計算 UDP 數據報的偽首部長度 unsigned short PseudoLength = UserDataLen + 8 + 9; // 長度包括 UDP 頭(8字節(jié))和偽首部(9字節(jié)) // 如果長度不是偶數,添加一個額外的字節(jié) PseudoLength += PseudoLength % 2; // 創(chuàng)建 UDP 偽首部 unsigned char* PseudoHeader = new unsigned char[PseudoLength]; RtlZeroMemory(PseudoHeader, PseudoLength); // 設置偽首部中的協議字段為 UDP (0x11) PseudoHeader[0] = 0x11; // 復制源和目標 IP 地址到偽首部 memcpy((void*)(PseudoHeader + 1), (void*)(FinalPacket + 26), 8); // 將 UDP 頭的長度字段拷貝到偽首部 unsigned short Length = UserDataLen + 8; Length = htons(Length); memcpy((void*)(PseudoHeader + 9), (void*)&Length, 2); memcpy((void*)(PseudoHeader + 11), (void*)&Length, 2); // 將源端口、目標端口和 UDP 數據拷貝到偽首部 memcpy((void*)(PseudoHeader + 13), (void*)(FinalPacket + 34), 2); memcpy((void*)(PseudoHeader + 15), (void*)(FinalPacket + 36), 2); memcpy((void*)(PseudoHeader + 17), (void*)UserData, UserDataLen); // 遍歷偽首部的每兩個字節(jié),計算校驗和 for (int i = 0; i < PseudoLength; i += 2) { unsigned short Tmp = BytesTo16(PseudoHeader[i], PseudoHeader[i + 1]); unsigned short Difference = 65535 - CheckSum; CheckSum += Tmp; if (Tmp > Difference) { CheckSum += 1; } } // 取反得到最終的校驗和 CheckSum = ~CheckSum; // 釋放偽首部的內存 delete[] PseudoHeader; return CheckSum; }
這段代碼的分析:
- 偽首部構造: UDP校驗和的計算需要使用UDP頭以及偽首部(包含源IP、目標IP、協議類型、UDP長度等信息)。這里使用
PseudoHeader
數組來構造偽首部。 - 偽首部填充: 通過
memcpy
等操作將源和目標IP地址、UDP頭的長度字段以及UDP的源端口、目標端口、UDP數據等內容填充到偽首部中。 - 偽首部遍歷: 通過遍歷偽首部的每兩個字節(jié),計算累加和。遍歷過程中,將兩個字節(jié)轉換為16位整數
Tmp
,然后進行累加。如果累加結果大于65535,則向結果中再加1。這是為了處理累加和溢出的情況。 - 取反: 計算完畢后,對累加和取反得到最終的UDP校驗和。
- 內存釋放: 最后釋放動態(tài)分配的偽首部內存。
需要注意的是,UDP校驗和是一個16位的值,用于驗證UDP數據報在傳輸過程中是否被修改。這段代碼主要完成了構造UDP偽首部和計算校驗和的過程。在實際網絡通信中,校驗和的計算是為了保證數據的完整性,防止在傳輸過程中的錯誤。
創(chuàng)建UDP數據包函數
創(chuàng)建一個UDP數據包,該代碼是一個簡單的網絡編程示例,用于創(chuàng)建和發(fā)送UDP數據包。其中,UDP數據包的內容和頭部信息都可以根據實際需求進行定制。
代碼的概述:
- 打開網卡: 通過
pcap_findalldevs_ex
函數獲取本機的網卡設備列表,并在控制臺輸出每個網卡的地址列表。 - 選擇網卡: 用戶輸入本機IP地址,程序通過遍歷網卡設備列表,找到與輸入IP地址匹配的網卡。
- 打開選定的網卡: 使用
pcap_open
函數打開選擇的網卡,獲取到網卡的句柄。 - 創(chuàng)建UDP數據包: 調用
CreatePacket
函數創(chuàng)建一個UDP數據包。該函數包括以下步驟:- 分配內存:使用
new
運算符為FinalPacket
分配內存,內存大小為UserDataLength + 42
字節(jié)。 - 填充以太網頭:拷貝目標MAC地址、源MAC地址和協議類型(IPv4)到
FinalPacket
的前12個字節(jié)。 - 填充IP頭:填充IPv4頭部,包括版本、標題長度、總長度、標識、標志、偏移、生存時間、協議(UDP為0x11),校驗和、源IP和目標IP。
- 填充UDP頭:填充UDP頭,包括源端口、目標端口、UDP長度(包括UDP頭和數據)和校驗和。
- 計算IP校驗和:調用
CalculateIPChecksum
函數計算IP頭的校驗和。 - 計算UDP校驗和:調用
CalculateUDPChecksum
函數計算UDP頭的校驗和。 - 返回數據包:生成的UDP數據包保存在
FinalPacket
中。
- 分配內存:使用
- 釋放資源: 在程序結束時,釋放分配的內存。
void CreatePacket(unsigned char* SourceMAC, unsigned char* DestinationMAC,unsigned int SourceIP, unsigned int DestIP,unsigned short SourcePort, unsigned short DestinationPort,unsigned char* UserData, unsigned int UserDataLength) { UserDataLen = UserDataLength; FinalPacket = new unsigned char[UserDataLength + 42]; // 為數據長度加上42字節(jié)的標頭保留足夠的內存 USHORT TotalLen = UserDataLength + 20 + 8; // IP報頭使用數據長度加上IP報頭長度(通常為20字節(jié))加上udp報頭長度(通常為8字節(jié)) // 開始填充以太網包頭 memcpy((void*)FinalPacket, (void*)DestinationMAC, 6); memcpy((void*)(FinalPacket + 6), (void*)SourceMAC, 6); USHORT TmpType = 8; memcpy((void*)(FinalPacket + 12), (void*)&TmpType, 2); // 使用的協議類型(USHORT)類型0x08是UDP??梢詾槠渌麉f議(例如TCP)更改此設置 // 開始填充IP頭數據包 memcpy((void*)(FinalPacket + 14), (void*)"\x45", 1); // 前3位的版本(4)和最后5位的標題長度。 memcpy((void*)(FinalPacket + 15), (void*)"\x00", 1); // 通常為0 TmpType = htons(TotalLen); memcpy((void*)(FinalPacket + 16), (void*)&TmpType, 2); TmpType = htons(0x1337); memcpy((void*)(FinalPacket + 18), (void*)&TmpType, 2); // Identification memcpy((void*)(FinalPacket + 20), (void*)"\x00", 1); // Flags memcpy((void*)(FinalPacket + 21), (void*)"\x00", 1); // Offset memcpy((void*)(FinalPacket + 22), (void*)"\x80", 1); // Time to live. memcpy((void*)(FinalPacket + 23), (void*)"\x11", 1); // 協議UDP為0x11(17)TCP為6 ICMP為1等 memcpy((void*)(FinalPacket + 24), (void*)"\x00\x00", 2); // 計算校驗和 memcpy((void*)(FinalPacket + 26), (void*)&SourceIP, 4); //inet_addr does htonl() for us memcpy((void*)(FinalPacket + 30), (void*)&DestIP, 4); // 開始填充UDP頭部數據包 TmpType = htons(SourcePort); memcpy((void*)(FinalPacket + 34), (void*)&TmpType, 2); TmpType = htons(DestinationPort); memcpy((void*)(FinalPacket + 36), (void*)&TmpType, 2); USHORT UDPTotalLen = htons(UserDataLength + 8); // UDP Length does not include length of IP header memcpy((void*)(FinalPacket + 38), (void*)&UDPTotalLen, 2); //memcpy((void*)(FinalPacket+40),(void*)&TmpType,2); //checksum memcpy((void*)(FinalPacket + 42), (void*)UserData, UserDataLength); unsigned short UDPChecksum = CalculateUDPChecksum(UserData, UserDataLength, SourceIP, DestIP, htons(SourcePort), htons(DestinationPort), 0x11); memcpy((void*)(FinalPacket + 40), (void*)&UDPChecksum, 2); unsigned short IPChecksum = htons(CalculateIPChecksum(TotalLen, 0x1337, SourceIP, DestIP)); memcpy((void*)(FinalPacket + 24), (void*)&IPChecksum, 2); return; }
對該代碼的分析:
- 分配內存: 使用
new
運算符為FinalPacket
分配內存,內存大小為UserDataLength + 42
字節(jié)。這足夠容納UDP數據以及以太網、IP和UDP頭的長度。 - 填充以太網頭: 使用
memcpy
函數將目標MAC地址、源MAC地址和協議類型(這里是IPv4)拷貝到FinalPacket
的前12個字節(jié)。 - 填充IP頭: 在
FinalPacket
的第14個字節(jié)開始,填充IPv4頭部。這包括版本、標題長度、總長度、標識、標志、偏移、生存時間、協議(UDP為0x11),校驗和、源IP和目標IP。 - 填充UDP頭: 在
FinalPacket
的第34個字節(jié)開始,填充UDP頭。這包括源端口、目標端口、UDP長度(包括UDP頭和數據)和校驗和。其中,UDP校驗和的計算通過調用CalculateUDPChecksum
函數完成。 - 計算IP校驗和: 在填充IP頭后,調用
CalculateIPChecksum
函數計算IP頭的校驗和。這個校驗和是IPv4頭的一個字段。 - 返回數據包: 函數執(zhí)行完畢后,生成的UDP數據包保存在
FinalPacket
中,可以將其用于發(fā)送到網絡。
需要注意的是,這段代碼中的硬編碼可能需要根據實際需求進行修改,例如協議類型、標識、生存時間等。此外,計算校驗和是網絡協議中用于檢測數據完整性的一種機制。
發(fā)送UDP數據包
代碼演示了如何打開網卡,生成UDP數據包,并通過pcap_sendpacket
函數發(fā)送數據包到網絡。需要注意的是,數據包的內容和地址是硬編碼的,實際應用中可能需要根據需要進行更改。
int main(int argc, char* argv[]) { // 打開網卡 OpenAdapter("10.0.66.24"); // 填充地址并生成數據包包頭 char SourceMAC[MAX_PATH] = "8C-ff-ff-ff-ff-ff"; char SourceIP[MAX_PATH] = "192.168.93.11"; char SourcePort[MAX_PATH] = "80"; char DestinationMAC[MAX_PATH] = "8C-dd-dd-dd-dd-dd"; char DestinationIP[MAX_PATH] = "192.168.93.11"; char DestinationPort[MAX_PATH] = "8080"; char DataString[MAX_PATH] = "hello lyshark"; CreatePacket(MACStringToBytes(SourceMAC), MACStringToBytes(DestinationMAC), inet_addr(SourceIP), inet_addr(DestinationIP), atoi(SourcePort), atoi(DestinationPort), (UCHAR*)DataString, (strlen(DataString) + 1)); // 循環(huán)發(fā)包 for (int x = 0; x < 10; x++) { if (0 != pcap_sendpacket(m_adhandle, FinalPacket, (UserDataLen + 42))) { char* szErr = pcap_geterr(m_adhandle); return 0; } } system("pause"); return 0; }
打開wireshark抓包工具,過濾目標地址為ip.dst==192.168.93.11
然后抓包,運行編譯后的程序,則你會看到我們自己構建的數據包被發(fā)送了10次,如下圖所示;
隨便打開一個數據包看下結構,源地址目標地址均是偽造的地址,數據包中的內容是hello lyshark
,如下圖所示;
到此這篇關于C/C++ 運用Npcap發(fā)送UDP數據包的文章就介紹到這了,更多相關C++ 發(fā)送UDP數據包內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
詳解Dev C++使用教程(使用Dev C++編寫C語言程序)
這篇文章主要介紹了詳解Dev C++使用教程(使用Dev C++編寫C語言程序),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2021-03-03C++中類的三種訪問權限解析:private、public與protect
這篇文章主要介紹了C++中類的三種訪問權限解析:private、public與protect,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-11-11