Linux下C/C++ 网络扫描(主机扫描技术)

主机扫描是网络扫描的基础,通过对目标网络中主机IP地址的扫描,从一堆主机中扫描出存活的主机,然后以他们为目标进行后续的攻击。一般会借助于ICMP、TCP、UDP等协议的工作机制,检查打开的进程,开放的端口号等等。

主机扫描

主机扫描是在可达状态下检测,局域网下的 ARP 扫描和广域网下的 ICMP Echo 扫描、ICMP Sweep 扫描、ICMP Broadcast 扫描、ICMP Non-Echo 扫描都是基本的扫描技术。还有绕过防火墙和网络过滤设备的高级技术。

  • ARP 扫描

向目标主机所在的局域网发送 ARP 广播请求,在局域网连通状态下目标主机必定会响应正常的 ARP 广播请求。故而便可获得IP地址和 MAC 地址等信息。

...
int convertNetworkAddr(const char *inString,uint8_t outNetAddr[PROTOCOL_ADDRESS_LENGTH],uint8_t *netPrefix) 
{
    char *invalidCharPtr;
    // 读取3个八位字节
    for (int i = 0; i < PROTOCOL_ADDRESS_LENGTH - 1; ++i) 
    {
        unsigned long octet = strtoul(inString, &invalidCharPtr, 10);
        if (octet >= 256 || *invalidCharPtr != '.') 
        {
            return 1;
        }
        inString = invalidCharPtr + 1;
        outNetAddr[i] = (uint8_t) octet;
    }
    // 读取最后一个八位字节
    unsigned long octet = strtoul(inString, &invalidCharPtr, 10);
    if (octet >= 256 || *invalidCharPtr != '/') 
    {
        return 1;
    }
    inString = invalidCharPtr + 1;
    outNetAddr[PROTOCOL_ADDRESS_LENGTH - 1] = octet;
    unsigned long prefix = strtoul(inString, &invalidCharPtr, 10);
    if (prefix > 32 || invalidCharPtr == inString) 
    {
        return 1;
    }
    *netPrefix = prefix;
    // 按位AND ip地址和网络掩码
    uint8_t hostPartLength = PROTOCOL_ADDRESS_LENGTH * 8 - prefix;
    for (int j = 0; j < hostPartLength / 8; ++j) 
    {
        outNetAddr[PROTOCOL_ADDRESS_LENGTH - 1 - j] = 0x00;
    }
    if (hostPartLength % 8) 
    {
        uint8_t mask = (0xFF >> hostPartLength % 8) << hostPartLength % 8;
        outNetAddr[PROTOCOL_ADDRESS_LENGTH - hostPartLength / 8 - 1] &= mask;
    }
    return 0;
}
...
void printPacket(struct ArpPacket *p) 
{
    for (int i = 0; i < sizeof(struct ArpPacket); ++i) 
    {
        int c = *((char*)p+i) & 0xFF;
        printf("%2x ", c);
        if (i%8 == 7)
            printf("\n");
    }
    printf("\n");
}

int readNetworkAddr(const char *string,uint8_t dstAddr[PROTOCOL_ADDRESS_LENGTH],uint8_t *dstPrefix) 
{
    if (convertNetworkAddr(string, dstAddr, dstPrefix)) 
    {
        fprintf(stderr, "Cannot recognize network address from: %s\n", string);
        exit(1);
    }
    DEBUG(
            printf("========= RECOGNIZED NETWORK ADDRESS ===========\n");
            printf("IP address: ");
            for (int j = 0; j < PROTOCOL_ADDRESS_LENGTH; ++j) 
            {
                printf("%i ", (int)dstAddr[j]);
            }
            printf("\nNet prefix: %i\n", (int)(*dstPrefix) );
    )
}

int main(int argc, char **argv) 
{
...
    if (argc != 3) 
    {
        fprintf(stderr, "Usage: <interface name> <network address>.\n"
                "For example: scan eth0 192.168.0.1/24\n");
        return 1;
    }
    // 打开套接字
    int s = socket(AF_PACKET, SOCK_DGRAM, htons(ETH_P_ARP));
    if (s == -1) {
        perror("Error in opening socket: ");
        return 1;
    }
    // 读取网络地址
    uint8_t netAddr[PROTOCOL_ADDRESS_LENGTH];
    uint8_t netPrefix;
    readNetworkAddr(argv[2], netAddr, &netPrefix);
    DEBUG(
        setHostPart(netAddr, 255, 8);
        printf("IP address: ");
        for (int j = 0; j < PROTOCOL_ADDRESS_LENGTH; ++j) 
        {
            printf("%i ", (int)netAddr[j]);
        }
        printf("\n");
    )

    struct sockaddr_ll addr;
    prepareSockaddrll(&addr, argv[1]);

    struct ArpPacket packet;
    prepareArpPacket(&packet, &addr, argv[1]);
    DEBUG(
        printf("Packet hex dump: \n");
        printPacket(&packet);
    )
    for (int i = 1; i < powl(2, (32 - netPrefix) ) - 1; ++i) 
    {
        setHostPart(netAddr, i, 32 - netPrefix);
        setDstIP(netAddr, &packet);
        DEBUG(
            printf("Sending ARP request to IP ");
            printIP(netAddr, stdout);
            printf(" ..............");
        )
        if(sendto(s, &packet, sizeof(packet), 0,(struct sockaddr*) &addr, sizeof(addr)) == -1) {
            perror("Error in sending ARP request: ");
            fprintf(stderr, "Error in sending ARP request: %s\n", strerror(errno));
            return 1;
        }
        DEBUG(printf("[ DONE ]\n");)
        // waiting for response
        usleep(200);
        struct ArpPacket response;
        ssize_t receivedResponseSize;
        if (receivedResponseSize = recv(s, &response, sizeof(response), MSG_DONTWAIT | MSG_TRUNC) > 0) 
        {
            DEBUG(
                printf("Received %ld bytes of response: \n", (long) receivedResponseSize);
                printPacket(&response);
            )
            if (memcmp(response.senderLogicAddress, packet.senderLogicAddress, sizeof(packet.senderLogicAddress))) 
            {
                printf("IP ");
                printIP(response.senderLogicAddress, stdout);
                printf(" has MAC: ");
                for (int j = 0; j < HARDWARE_ADDRESS_LENGTH; ++j) 
                {
                    printf("%x:", (int)response.senderHardwareAddress[j]);
                }
                printf("\n");
            }
        }
    }
    return 0;
}

运行结果:

If you need the complete source code, please add the WeChat number (c17865354792)

tcpdump抓包

  • ICMP Echo 扫描

向目标主机发送ICMP Echo Request (type 8)数据包,等待回复的ICMP Echo Reply 包(type 0) 。如果能收到,则表明目标系统可达,否则表明目标系统已经不可达或发送的包被对方的设备过滤掉。

...
/*
获取网络接口的本地信息。
 */
int loadLocalData( LocalData *dst, const char *ifname )
{
	struct ifreq nic;
	int sock = socket( AF_INET, SOCK_DGRAM, 0 );

	strncpy( nic.ifr_name, ifname, IFNAMSIZ-1 );
	nic.ifr_name[IFNAMSIZ-1] = '\0';


	if( ioctl( sock, SIOCGIFINDEX, &nic ) < 0 ){
		close( sock );
		return -1;
	}
	dst->ifindex = nic.ifr_ifindex;

	// 分配的IP地址
	if( ioctl( sock, SIOCGIFADDR, &nic ) < 0 ){
		close( sock );
		return -1;
	}
	memcpy( &dst->ip, nic.ifr_addr.sa_data + 2, INET_ALEN );

	// MAC地址 
	if( ioctl( sock, SIOCGIFHWADDR, &nic ) < 0 ){
		close( sock );
		return -1;
	}
	memcpy( &dst->mac, nic.ifr_hwaddr.sa_data, ETH_ALEN );

	// 子网掩码
	if( ioctl( sock, SIOCGIFNETMASK, &nic ) < 0 ){
		close( sock );
		return -1;
	}
	memcpy( &dst->netmask, nic.ifr_netmask.sa_data + 2, INET_ALEN ); 
	close( sock );
	return 0;
}

/**
 * 为特定协议创建套接字。
 */
int createSocket( SocketType type, int msecs )
{
	int sfd;
	struct timeval timer;

	if( type == ICMPSocket )
		sfd = socket( AF_INET, SOCK_RAW, IPPROTO_ICMP );
	else
		sfd = socket( AF_PACKET, SOCK_DGRAM, htons(ETH_P_ARP) );

	if( sfd < 0 )
		return -1;

	// 设置接收数据的最大时间
	timer.tv_sec = msecs / 1000;
	timer.tv_usec = msecs % 1000 * 1000;
	if( setsockopt( sfd, SOL_SOCKET, SO_RCVTIMEO, &timer, sizeof(timer) ) < 0 ){
		close( sfd );
		return -1;
	}
	return sfd;
}
...

/**
 * 计算增加一个IP地址的结果。
 */
struct in_addr ipAddOne( struct in_addr ip )
{
	struct in_addr aux = { ntohl( ip.s_addr ) };
	aux.s_addr++;
	aux.s_addr = htonl( aux.s_addr );
	return aux;
}

/**
 * 交换两个IP地址的值
 */
void ipSwap( struct in_addr *a, struct in_addr *b )
{
    a->s_addr = a->s_addr ^ b->s_addr;    
    b->s_addr = a->s_addr ^ b->s_addr;
    a->s_addr = a->s_addr ^ b->s_addr;
}
...

int main( int argc, char **argv )
{
...

	// 验证接口
	if( !interface )
	{
		fprintf( stderr, "Error: No interface given. Use -h for help\n" );
		return -1;
	}

	// 加载本地数据
	if( loadLocalData( &data, interface ) < 0 )
	{
		perror( interface );
		return -1;
	}

	// 验证是否有特定的IP 
	if( strFirst )
	{
		if( !inet_aton( strFirst, &first ) )
		{
			fprintf( stderr, "%s: Invalid address\n", strFirst );
			return -1;
		}
		if( strLast )
		{ // 验证是否有范围
			if( !inet_aton( strLast, &last ) )
			{
				fprintf( stderr, "%s: Invalid address\n", strLast );
				return -1;
			}
			else
			{
				total = ntohl( last.s_addr ) - ntohl( first.s_addr );
				if( total < 0 ){
					total = -total;
					ipSwap( &first, &last );
				}
				total++;
			}
		}
		else
		{
			total = 1;
		}
	}
	else
	{ // 如果没有,则从本地网络数据中获取范围。
		first.s_addr = data.ip.s_addr & data.netmask.s_addr;
		first = ipAddOne( first );
		last.s_addr = data.ip.s_addr | ~data.netmask.s_addr;
		total = ntohl( last.s_addr ) - ntohl( first.s_addr );
	}

	// 创建套接字
	if( (sfd = createSocket( type, waitTime ) ) < 0 )
	{
		perror( "Failed to create socket" );
		return 2;
	}

...
	// 扫描周期
	for( int i = 1 ; i <= total && running ; i++, first = ipAddOne(first) )
	{
		printf( "\r(%d%%) Testing %s...", (int)(100.0 / total * i), inet_ntoa(first) );
		fflush( stdout );
		if( first.s_addr == data.ip.s_addr )
		{
			printf( " (this host)\n" );
			ups++;
		}
		else
		{
			switch( isUp(sfd, first, &data) )
			{
				case -1:
					perror( " send request" );
					break;
				case 1:
					puts( " is up" );
					ups++;
			}
		}
	}
	close( sfd );
	printf( "\n%d hosts up\n", ups );
	return 0;
}

运行结果:

If you need the complete source code, please add the WeChat number (c17865354792)

tcpdump抓包:

  • ICMP Sweep 扫描


总结

这里我们就知道了其实主机扫描很简单,只需要证明其主机存活就好。我们只需要对目标主机发送特定的数据包,如果目标主机有回应,那么我们就认为该主机是存活的;反之如果对方不回应,我们就认为其不是存活主机。

Welcome to follow WeChat official account【程序猿编码

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值