黑客之旅――原始套接字(Raw Socket)透析(2)--Raw Socket基础

简介:
2.Raw Socket基础
在进入Raw Socket多种强大的应用之前,我们先讲解怎样建立一个Raw Socket及怎样用建立的Raw Socket发送和接收IP包。
2.1建立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 接受所有的数据
2.2Raw 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)
{
  //…发送字节 < 欲发送字节
}
2.3Raw 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头中对应的源地址与远端地址匹配,接收的数据才拷贝到套接字中。
2.4建立报文
 在利用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;
}


 本文转自 21cnbao 51CTO博客,原文链接:http://blog.51cto.com/21cnbao/120083,如需转载请自行联系原作者



相关文章
|
7天前
|
安全 Java 数据处理
Python网络编程基础(Socket编程)多线程/多进程服务器编程
【4月更文挑战第11天】在网络编程中,随着客户端数量的增加,服务器的处理能力成为了一个重要的考量因素。为了处理多个客户端的并发请求,我们通常需要采用多线程或多进程的方式。在本章中,我们将探讨多线程/多进程服务器编程的概念,并通过一个多线程服务器的示例来演示其实现。
|
7天前
|
程序员 开发者 Python
Python网络编程基础(Socket编程) 错误处理和异常处理的最佳实践
【4月更文挑战第11天】在网络编程中,错误处理和异常管理不仅是为了程序的健壮性,也是为了提供清晰的用户反馈以及优雅的故障恢复。在前面的章节中,我们讨论了如何使用`try-except`语句来处理网络错误。现在,我们将深入探讨错误处理和异常处理的最佳实践。
|
12天前
|
网络协议 程序员 Python
pythonTCP客户端编程创建Socket对象
【4月更文挑战第6天】本教程介绍了TCP客户端如何创建Socket对象。Socket作为网络通信的基础单元,包含协议、IP地址和端口等信息。在TCP/IP中,Socket分为流式(TCP)、数据报(UDP)和原始套接字。以Python为例,创建TCP Socket对象需调用`socket.socket(AF_INET, SOCK_STREAM)`。为确保健壮性,应使用异常处理处理可能的`socket.error`。学习本教程将帮助你掌握TCP客户端创建Socket对象的技能。
|
1月前
|
网络协议 安全 API
计算机网络之Socket编程
计算机网络之Socket编程
|
2月前
|
网络协议 安全 开发者
Python 中的 Socket 编程
Python 中的 Socket 编程
42 4
|
3月前
socket编程之回声服务器函数的陷阱
由connect函数使用不当导致的小错误 话不多说先看代码:
25 0
|
3月前
|
API C++
socket编程之常用api介绍与socket、select、poll、epoll高并发服务器模型代码实现(1)
前言   本文旨在学习socket网络编程这一块的内容,epoll是重中之重,后续文章写reactor模型是建立在epoll之上的。
34 0
|
3月前
|
监控 安全 Linux
socket编程之常用api介绍与socket、select、poll、epoll高并发服务器模型代码实现(3)
高并发服务器模型-poll poll介绍   poll跟select类似, 监控多路IO, 但poll不能跨平台。其实poll就是把select三个文件描述符集合变成一个集合了。
35 0