煎饼 发表于 2003-3-31 17:09:00

一个上课用的SOCKET编程的例子:SYN端口扫描的例子

老师用的这个例子,使我对TCP连接的握手过程有了深切的理解,以往的疑惑也豁然开朗,不敢独享,愿与大家共同分享。更多的东西来自材纺:
file:\\192.168.11.1\高级程序设计
有关IP数据包,TCP,UDP数据包的结构以及数据的功能,请查看:《TCP/IP协议详解 卷一:协议》或者
RFC文档http://www.china-pub.com/computers/eMook/emooknew/RFC/allrfc.asp?selectP=

/*
经典描器(全TCP连接)和SYN(半连接)扫描器
全TCP连接
  全TCP连接是长期以来TCP端口扫描的基础。扫描主机尝试(使用三次握手)与目的机指定端口建立建立正规的连接。
连接由系统调用connect()开始。对于每一个监听端口,connect()会获得成功,否则返回-1,表示端口不可访问。
  这种扫描方法很容易检测出来,在日志文件中会有大量密集的连接和错误记录)。
TCP SYN扫描
  在这种技术中,扫描主机向目标主机的选择端口发送SYN数据段。如果应答是RST,那么说明端口是关闭的,按照设定就探听其它端口;如果应答中包含SYN和ACK,说明目标端口处于监听状态。由于所有的扫描主机都需要知道这个信息,传送一个RST给目标机从而停止建立连接。由于在SYN扫描时,全连接尚未建立,所以这种技术通常被称为半打开扫描。
    SYN扫描的优点在于即使日志中对扫描有所记录,但是尝试进行连接的记录也要比全扫描少得多。缺点是在大部分操作系统下,发送主机需要构造适用于这种扫描的IP包,通常情况下,构造SYN数据包需要超级用户或者授权用户访问专门的系统调用。


一个TCP头包含6个标志位。它们的意义分别为:
SYN: 标志位用来建立连接,让连接双方同步序列号。如果SYN=1而ACK=0,则表示该数据包为连接请求,如果SYN=1而ACK=1则表示接受连接。
FIN: 表示发送端已经没有数据要求传输了,希望释放连接。
RST: 用来复位一个连接。RST标志置位的数据包称为复位包。一般情况下,如果TCP收到的一个分段明显不是属于该主机上的任何一个连接,则向远端发送一个复位包。
URG: 为紧急数据标志。如果它为1,表示本数据包中包含紧急数据。此时紧急数据指针有效。
ACK: 为确认标志位。如果为1,表示包中的确认号时有效的。否则,包中的确认号无效。
PSH: 如果置位,接收端应尽快把数据传送给应用层。

端口扫描技术(port scanning)
  
  端口扫描就是通过连接到目标系统的TCP或UDP端口,来确定什么服务正在运行。一般来说端口扫描有三个用途:
   * 识别目标系统上正在运行的TCP和UDP服务。
   * 识别目标系统的操作系统类型(Windows 9x, Windows NT,或UNIX,等)。
   * 识别某个应用程序或某个特定服务的版本号。
  
  端口扫描技术:
   1. TCP connect scan:这种方法最简单,直接连到目标端口并完成一个完整的三次握手过程(SYN, SYN/ACK, 和ACK)。缺点是容易被目标系统检测到。
   2. TCP SYN scan:这种技术也叫“半开式扫描”(half-open scanning),因为它没有完成一个完整的TCP连接。这种方法向目标端口发送一个SYN分组(packet),如果目标端口返回SYN/ACK,那么可以肯定该端口处于检听状态;否则,返回的是RST/ACK。这种方法比第一种更具隐蔽性,可能不会在目标系统中留下扫描痕迹。
   3. TCP FIN scan:这种方法向目标端口发送一个FIN分组。按RFC793的规定(http://www.ietf.org/rfc/rfc0793.txt),对于所有关闭的端口,目标系统应该返回RST标志。这种方法通常用在基于UNIX的TCP/IP堆栈。
   4. TCP Xmas Tree scan:这种方法向目标端口发送一个含有FIN, URG,和PUSH标志的分组。根据RFC793,对于所有关闭的端口,目标系统应该返回RST标志。
   5. TCP Null scan:这种方法向目标端口发送一个不包含任何标志的分组。根据RFC793,对于所有关闭的端口,目标系统应该返回RST标志。
   6. UDP scan:这种方法向目标端口发送一个UDP分组。如果目标端口以“ICMP port unreachable”消息响应,那么说明该端口是关闭的;反之,如果没有收到“ICMP port unreachable”响应消息,则可以肯定该端口是打开的。由于UDP协议是面向无连接的协议,这种扫描技术的精确性高度依赖于网络性能和系统资源。另外,如果目标系统采用了大量分组过滤技术,那么UDP扫描过程会变得非常慢。如果你想对Internet进行UDP扫描,那么你不能指望得到可靠的结果。
  
  另外,有某种系统的IP协议是这样实现的,对于所有扫描的端口,不管他们处于关闭或者监听状态,都返回RST标志(我们知道,这不符合RFC793的规定)。因此,扫描这种系统时,用不同的扫描技术可能得到不同的扫描结果。
  
  
*/
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdio.h>
#include "mstcpip.h"
#pragma comment(lib,"ws2_32")

#define SEQ 0x28376839




//ip数据包的首部数据结构
typedef struct _iphdr
{
    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 _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;


struct //定义TCP伪首部
{
    unsigned long saddr; //源地址
    unsigned long daddr; //目的地址
    char mbz;
    char ptcl; //协议类型
    unsigned short tcpl; //TCP长度
}psd_header;

SOCKET sockRaw = INVALID_SOCKET,
sockListen = INVALID_SOCKET;
struct sockaddr_in dest;

//SOCK错误处理程序
void CheckSockError(int iErrorCode, char *pErrorMsg)
{
    if(iErrorCode==SOCKET_ERROR)
    {
      printf("%s Error:%d\n", pErrorMsg, GetLastError());
      closesocket(sockRaw);
      ExitProcess(-1);
    }
}

//计算检验和
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解包程序
int DecodeIPHeader(char *recvbuf, int bytes)
{
    IP_HEADER *iphdr;
    TCP_HEADER *tcphdr;
    unsigned short iphdrlen;
    iphdr = (IP_HEADER *)recvbuf;
    iphdrlen = sizeof(unsigned long) * (iphdr->h_lenver & 0xf);
    tcphdr = (TCP_HEADER*)(recvbuf + iphdrlen);

    //是否来自目标IP
    if(iphdr->sourceIP != dest.sin_addr.s_addr) return 0;
    //序列号是否正确
    if((ntohl(tcphdr->th_ack) != (SEQ+1)) && (ntohl(tcphdr->th_ack) != SEQ)) return 0;
    //RST/ACK - 无服务
    if(tcphdr->th_flag == 20)
    {
      printf("RST+ACK 无服务.\n");
      return 1;
    }

    //SYN/ACK - 扫描到一个端口
    if(tcphdr ->th_flag == 18)
    {
    printf("%d\n",ntohs(tcphdr->th_sport));
    return 2;
    }

    return true;
}

//主函数
int main(int argc,char *argv[])
{
    int iErrorCode;
    int datasize;
    struct hostent *hp;
    IP_HEADER ip_header;
    TCP_HEADER tcp_header;
    char SendBuf={0};
    char RecvBuf={0};

    printf("Useage: SYNPing.exe Target_ip Target_port \n");

    if (argc!=3)
    { return false; }

    //初始化SOCKET
    WSADATA wsaData;
    iErrorCode = WSAStartup(MAKEWORD(2,2),&wsaData);
    CheckSockError(iErrorCode, "WSAStartup()");
    sockRaw = socket(AF_INET , SOCK_RAW , IPPROTO_IP);
    CheckSockError(sockRaw, "socket()");
    sockListen = socket(AF_INET , SOCK_RAW , IPPROTO_IP);
    CheckSockError(sockListen, "socket");

    //设置IP头操作选项
    BOOL bOpt = true;
    iErrorCode = setsockopt(sockRaw,IPPROTO_IP,IP_HDRINCL,(char *)&bOpt,sizeof(bOpt));
    CheckSockError(iErrorCode, "setsockopt()");

    //获得本地IP
    SOCKADDR_IN sa;
    unsigned char LocalName;

    iErrorCode = gethostname((char*)LocalName,sizeof(LocalName)-1);
    CheckSockError(iErrorCode, "gethostname()");
    if((hp = gethostbyname((char*)LocalName)) == NULL)
    {
      CheckSockError(SOCKET_ERROR, "gethostbyname()");
    }
    memcpy(&sa.sin_addr.S_un.S_addr,hp->h_addr_list,hp->h_length);
    sa.sin_family = AF_INET;
    sa.sin_port = htons(7000);
    iErrorCode = bind(sockListen, (PSOCKADDR)&sa, sizeof(sa));
    CheckSockError(iErrorCode, "bind");

    //设置SOCK_RAW为SIO_RCVALL,以便接收所有的IP包
    DWORD dwBufferLen ;
    DWORD dwBufferInLen = 1 ;
    DWORD dwBytesReturned = 0 ;
    iErrorCode=WSAIoctl(sockListen, SIO_RCVALL,&dwBufferInLen, sizeof(dwBufferInLen),
            &dwBufferLen, sizeof(dwBufferLen),&dwBytesReturned , NULL , NULL );
    CheckSockError(iErrorCode, "Ioctl");

    //获得目标主机IP
    memset(&dest,0,sizeof(dest));
    dest.sin_family = AF_INET;
    dest.sin_port = htons(atoi(argv));
    if((dest.sin_addr.s_addr = inet_addr(argv)) == INADDR_NONE)
    {
      if((hp = gethostbyname(argv)) != NULL)
      {
            memcpy(&(dest.sin_addr),hp->h_addr_list,hp->h_length);
            dest.sin_family = hp->h_addrtype;
            printf("dest.sin_addr = %s\n",inet_ntoa(dest.sin_addr));
      }
      else
      {
            CheckSockError(SOCKET_ERROR, "gethostbyname()");
      }
    }

    //填充IP首部
    ip_header.h_lenver=(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=sa.sin_addr.s_addr; //32位源IP地址
    ip_header.destIP=dest.sin_addr.s_addr; //32位目的IP地址

    //填充TCP首部
    tcp_header.th_sport=htons(7000); //源端口号
    tcp_header.th_dport=htons(atoi(argv)); //目的端口号
    tcp_header.th_seq=htonl(SEQ); //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校验和,计算校验和时需要包括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报文
    iErrorCode=sendto(sockRaw,SendBuf,datasize,0,(struct sockaddr*) &dest,sizeof(dest));
    CheckSockError(iErrorCode, "sendto()");

    //接收数据
    DWORD timeout = 200000;//2000
    DWORD start = GetTickCount();
    while(true)
    {
    //计时,2s超时
      if((GetTickCount() - start) >= timeout) break;

      memset(RecvBuf, 0, sizeof(RecvBuf));
      iErrorCode = recv(sockListen, RecvBuf, sizeof(RecvBuf), 0);
      CheckSockError(iErrorCode, "recv");

      if(int i = DecodeIPHeader(RecvBuf,iErrorCode))
      {
            if(i == 1) break;
            tcp_header.th_flag=4; //RST 标志
            //计算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报文
            iErrorCode=sendto(sockRaw,SendBuf,datasize,0,(struct sockaddr*) &dest,
            sizeof(dest));
            CheckSockError(iErrorCode, "sendto()");

            break;
      }
    }
    //退出前清理
    if(sockRaw != INVALID_SOCKET) closesocket(sockRaw);
    WSACleanup();
    return 0;
}

zero 发表于 2003-4-8 00:36:00

恩,有用,有用
页: [1]
查看完整版本: 一个上课用的SOCKET编程的例子:SYN端口扫描的例子