作者: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_hatype
和sll_pkttype
是在接收数据包时使用的; 如果要bind, 只需要使用 sll_protocol
和sll_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