计算机网络安全之端口扫描器

这篇博客介绍了如何使用C/C++编程实现一个简单的网络扫描程序,通过socket函数扫描局域网内的主机,获取主机名和开放端口。程序采用ARP广播扫描存活主机,使用TCP connect进行端口扫描,利用多线程加速扫描过程。内容包括程序框架、获取本机信息、扫描主机和端口的方法,以及实验验证和参考资料。
摘要由CSDN通过智能技术生成

作者:shmily

实验题目

实验题目:SCANNER简单网络扫描程序实现

实验目的:熟悉并实现网络扫描的基本原理。了解网络扫描的几种常用的方法。

实验环境:linux/windows

实验内容:用C/C++语言(必须用socket函数)编写一个扫描局域网内主机的程序。要求可以显示局域网内的主机名列表,IP地址列表,并可以显示哪些主机开放了哪些端口。

实验环境

主运行机器:Linux kali 4.19.0-kali1-686-pae #1 SMP Debian 4.19.13-1kali1 (2019-01-03) i686 GNU/Linux

其他机器:Linux ubuntu 5.3.0-42-generic #34~18.04.1-Ubuntu SMP Fri Feb 28 13:42:26 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux

程序运行方法

make

程序实现说明及源码分析

程序框架

在这里插入图片描述
扫描端口部分:

在这里插入图片描述
如框图所示,在获取本机ip等信息之后,可以知道当前主机位于哪一个局域网,进而利用ARP广播报文获取该局域网内存活的主机。

扫描存活主机的实现运用了双进程,新开启一个进程向所有主机发送ARP询问报文,主进程用来接收存活主机发来的ARP 应答报文。

得到所有的存活主机之后,将其保存下来,再一一获取他们的主机名称,由于linux系统提供了 gethostbyaddr()函数,可以通过ip地址获取到规范名、别名等信息,因此只需要调用这个函数即可。

获取到主机名称之后,就可以对每台主机进行端口扫描,使用的方法为TCP connect扫描。对于每个 IP 来说,扫描器要扫 65536=2^16 个端口。实验中通过多线程的方式加快其扫描速度,开设 128=2^7 个线程同时扫描,直到最后一个ip的最后一组。

global.h

这个文件中包含了可能会用到的头文件,定义了一些全局变量,主要有存活的主机信息、物理地址信息、本地IP、子网掩码和本地MAC和网卡名称。备注中由更具体的说明

#ifndef _NET_GLOBAL_H_
#define _NET_GLOBAL_H_

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/ioctl.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <net/if.h>
#include <sys/types.h>
#include <asm/types.h>
#include <features.h>
#if __GLIBC__ >= 2 && __GLIBC_MINOR__ >= 1
#include <netpacket/packet.h>
#include <net/ethernet.h> 
#else
#include <asm/types.h>
#include <linux/if_packet.h>
#include <linux/if_ether.h> 
#endif
#include <netinet/if_ether.h>
#include <net/if_arp.h>

/* 以太网帧首部长度 */
#define ETHER_HEADER_LEN sizeof(struct ether_header)
/* 整个arp结构长度 */
#define ETHER_ARP_LEN sizeof(struct ether_arp)
/* 以太网 + 整个arp结构长度 */
#define ETHER_ARP_PACKET_LEN ETHER_HEADER_LEN + ETHER_ARP_LEN
/* MAC地址长度 */
#define MAC_ADDR_LEN 6
/* IP地址长度 */
#define IP_ADDR_LEN 4
#define IP_CHAR_MAX_LEN 50
/* 网关地址长度 */
// #define
/* 广播地址 */
#define BROADCAST_ADDR{ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}
void err_exit(const char *err_msg)
{
   
    perror(err_msg);
    exit(1);
}
// 活动的IP
struct hosts_alive
{
   
    struct in_addr ip;
    unsigned char mac[MAC_ADDR_LEN];
    char name[0x200];
} alive_hosts[0x200];
// 子网中的IP段
struct in_addr ip_list[0x300];
// 数据链路层
struct sockaddr_ll saddr_ll;
// 本地IP、子网掩码和本地MAC
struct in_addr local_ip, subnet_mask;
unsigned char local_mac_addr[MAC_ADDR_LEN];
// 网卡名称
char if_name[0x20];
#endif

获取本机信息

获取的本机信息有网卡接口名称、ip地址、mac地址、子网掩码和本机开放端口。其中本机开放端口调用了scan_port()函数,在之后会做说明。

实验中需要将获取到的信息保存在ifreq 结构体当中。ifreq结构体定义在/usr/include/net/if.h,用来配置ip地址,激活接口,配置MTU等接口信息的。其中包含了一个接口的名字和具体内容,是个共用体,有可能是IP地址,广播地址,子网掩码,MAC号,MTU或其他内容。

结构如下:

struct ifreq
  {
   
	# define IFHWADDRLEN	6
	# define IFNAMSIZ	IF_NAMESIZE
    union
      {
   
		char ifrn_name[IFNAMSIZ];	/* Interface name, e.g. "en0".  */
      } ifr_ifrn;

    union
      {
   
		struct sockaddr ifru_addr;
		struct sockaddr ifru_dstaddr;
		struct sockaddr ifru_broadaddr;
		struct sockaddr ifru_netmask;
		struct sockaddr ifru_hwaddr;
		short int ifru_flags;
		int ifru_ivalue;
		int ifru_mtu;
		struct ifmap ifru_map;
		char ifru_slave[IFNAMSIZ];	/* Just fits the size */
		char ifru_newname[IFNAMSIZ];
		__caddr_t ifru_data;
      } ifr_ifru;
  };

因此实验中需要定义一个 struct ifreq ifr 存储网络接口信息。

接着,用PF_PACKET选项创建ARP类型的原始套接字。用 ioctl()函数 通过网卡接口名来获取该接口对应的mac地址,ip地址,接口索引。接口索引填充到设备无关的物理层地址结构sockaddr_ll里面。该结构体位于 /usr/include/netpacket/packet.h中:

struct sockaddr_ll
{
   
    unsigned short int sll_family;
    unsigned short int sll_protocol;
    int sll_ifindex;
    unsigned short int sll_hatype;
    unsigned char sll_pkttype;
    unsigned char sll_halen;
    unsigned char sll_addr[8];
};

sll_ifindex是网络(网卡)接口索引,代表从这个接口收发数据包;protocol是按照网络字节顺序(network byte order),大部分定义在头文件中,设置协议时,例如 htons(ETH_P_ALL),来接收 所有的 数据包。如果要获取从指定以太网接口卡上的数据包时,在struct sockaddr_ll中指定网络接口卡,绑定(bind)数据包到该interface上。当发送数据包时,指定sll_family, sll_addr, sll_halen, sll_ifindex, sll_protocol就足够了。其它字段设置为0;sll_hatypesll_pkttype是在接收数据包时使用的; 如果要bind, 只需要使用 sll_protocolsll_ifindex 就足够了。

程序中还用到了inet_pton()inet_ntop()函数将ip地址在点分十进制和二进制之间做转换。

GetNetInfo()函数如下:

#include "init_net.h"
void GetNetInfo()
{
   
    /* 网络接口 */
    struct ifreq ifr;
    /* socket接口 */
    int socket_fd;
    /* MAC */
    unsigned char src_mac_addr[MAC_ADDR_LEN];
    /* 源IP */
    char src_ip[IP_CHAR_MAX_LEN + 1];
    char netmask[IP_CHAR_MAX_LEN + 1];
    char buf[ETHER_ARP_PACKET_LEN];
    int i;
    // 打开 socket,set as promiscuous mode
    if ((socket_fd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ARP))) == -1)
        err_exit("socket()");
    // 填充 0
    bzero(&saddr_ll, sizeof(struct sockaddr_ll));
    bzero(&ifr, sizeof(struct ifreq));
    // 获取网卡接口名称
    printf("interface: %s\n", if_name);
    memcpy(ifr.ifr_name, if_name, strlen(if_name));
    // 获取网卡接口索引
    // by ioctl func,index is sent to ifr
    if (ioctl(socket_fd, SIOCGIFINDEX, &ifr) == -1)
        err_exit("ioctl() get ifindex");
    saddr_ll.sll_ifindex = ifr.ifr_ifindex;//if_ifru.ifru_value
    saddr_ll.sll_family = PF_PACKET;
    // 获取网卡接口IP
    // by ioctl func,sddr is senr to ifr
    if (ioctl(socket_fd, SIOCGIFADDR, &ifr) == -1)
        err_exit("ioctl() get ip");
    strcpy(src_ip, inet_ntoa(((struct sockaddr_in 
  • 0
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值