一、实验内容:
包嗅探和欺骗是网络安全中的两个重要概念;它们是网络通信中的两大威胁。能够理解这两种威胁对于理解网络中的安全措施至关重要。有许多包嗅探和欺骗工具,如Wireshark、Tcpdump、Netwox等。其中一些工具被安全专家以及攻击者广泛使用。能够使用这些工具对学生来说很重要,但对于网络安全课程的学生来说,更重要的是了解这些工具是如何工作的,即包嗅探和欺骗是如何在软件中实现的。
本实验的目标是让学生掌握大多数嗅探和欺骗工具的基本技术。学生们将使用一些简单的嗅探和欺骗程序,阅读它们的源代码,修改它们,并最终对这些程序的技术方面有深入的了解。在本实验结束时,学生应该能够编写自己的嗅探和欺骗程序。
二、实验步骤与结果
一)、Lab Task Set 1: Using Scapy to Sniff and Spoof Packets
使用python,通过简单的测试,构造了一个IP数据包并且打印出了其中的信息。
Task 1.1: Sniffifing Packets
首先获取网络接口名称。因为我们使用组合文件创建了容器,所以将会创建一个新的网络接口用于链接虚拟机和容器。
Task1.1a
执行python编写的嗅探代码。
并且在另一个控制台使用ping发出icmp数据包。
使用chmod a+x sniffer.py赋予程序执行权限,运行程序。可以看到程序已经成功嗅探到了数据包。
注意,如果不使用sudo执行程序的话,将会出现下面的报错。说明嗅探需要高权限,如果没有高权限,将禁止嗅探。
Task1.1b
可以在嗅探程序中设置过滤器来筛选需要嗅探的数据包。只返回ICMP
1.只嗅探ICMP数据包。
结果如下,可以看到嗅探到的数据包都是ICMP。
2.指定IP为10.9.0.1和端口号为23。并编写一个python代码发送一个符合要求的包。
3.指定子网128.230.0.0/16。
发送数据包。
可以看到成功嗅探到也只嗅探到该子网的数据包。
Task1.2: Spoofifing ICMP Packets
使用欺骗ICMP包来请求回复。
可以在我们的嗅探程序中发现我们发送的请求被人接受,然后对方发出ARP请求。
Task1.3: Traceroute
使用Scapy来估计虚拟机和选定目的地之间的路由器数量方面的距离。代码如下:
发送我们的数据包。
在wireshark中,我们可以看到不同的路由器返回的包,可以通过这些包来判断路由器的数量。
Task1.4: Sniffifing and-then Spoofifing
在同一个局域网下的两个机器,其中一个机器使用ping指令,另一个机器只要检测到ICMP请求,不管对方的目标IP是多少,立刻回复。这样,不管ping的机器是不是打开的,都会收到回复。
编写代码如下:
1.ping baidu.com
先在一个控制台ping baidu.com,可以看到,发送回来的ICMP包ttl都是49。
运行程序,开始嗅探以及欺骗。
此时再使用ping baidu.com 指令。可以看到收到了ttl为64的包,这就是我们发送的包。
2.Ping 8.8.8.8
运行嗅探和欺骗程序。
可以看到ttl为64的ICMP包就是我们发送的包。
二)、Lab Task Set 2: Writing Programs to Sniff and Spoof Packets
Task 2.1: Writing Packet Sniffifing Program
根据实验手册,使用C语言编写的嗅探程序如下:
编译运行之后,使用ping baidu.com指令进行测试。
为程序附加执行权限后,使用root权限运行程序,可以看到成功嗅探到包。
2.1a:Understanding How a Sniffer Works
问题1:概括函数调用顺序用意。
首先handle使用pcap_open_live打开一个活跃监听句柄,其次利用pcap_compile在监听句柄中编译一个过滤器语法,并在编译错误时结束程序;如果编译通过,则开始监听,直至程序退出。
问题2:为什么需要root权限?
因为如果任意权限用户都能够监听,将会导致安全危机与隐私泄露。当没有root权限时,进程将由于没有监听网络区域的存储器的访问权限,出现权限冲突而退出。
问题3:测试在关闭和打开混杂模式的情况下有什么不同。
测试思路:使用10.9.0.5向10.9.0.6发送ping报文,并在主机或10.9.0.1使用程序进行监听。若开启混杂则应收到,否则没有响应。
进入10.9.0.5,并且使用ping 10.9.0.6。
首先打开混杂模式,可以看到成功嗅探到数据包。然后关闭混杂模式,可以看到此时无法嗅探到数据包。
Task2.1b:: Writing Filters.
编写如下代码,需要再执行程序之后根据提示输入你的筛选条件。
1.icmp and dst host 10.9.0.5 and src host 10.9.0.6
2.tcp and dst portrange 10-100。此处使用远程连接登录通过23号端口发送数据包。
task2.1c: Sniffing Passwords
观察wireshark中接受到的包发现,数据包携带的信息都从数据包的第66位开始,所以我们这里选择直接输出66位的信息。
运行代码。
使用远程连接登录,并且输入用户名和密码
可以看到,成功打印除了用户的名称和密码。
代码:
#include <pcap.h>
#include <stdio.h>
#include <stdlib.h>
void got_packet(u_char *args, const struct pcap_pkthdr *header, const u_char *packet)
{
printf("Got a packet. Dta:");
printf("\t%s\n",(char *)(packet+66));
}
int main()
{
pcap_t *handle;
char errbuf[PCAP_ERRBUF_SIZE];
struct bpf_program fp;
printf("type in your filter:\n");
char filter_exp[100] ={0};
fgets(filter_exp,100,stdin);
int a=sizeof(filter_exp);
filter_exp[a-1]='\0';
printf("your filter is:\n");
printf("%s\n",filter_exp);
bpf_u_int32 net;
handle = pcap_open_live("br-fec681197df8", BUFSIZ, 1, 1000, errbuf);
pcap_compile(handle, &fp, filter_exp, 0, net);
if (pcap_setfilter(handle, &fp) !=0) {
pcap_perror(handle, "Error:");
exit(EXIT_FAILURE);
}
pcap_loop(handle, -1, got_packet, NULL);
pcap_close(handle); //Close the handle
return 0;
}
Task2.2A: Write a spoofing program.
编写程序当程序抓到包时,发送自己编写的虚假的ICMP请求信息。
再wireshark中抓包,可以看到,成功发送。说明程序可以嗅探到包,也可以成功发送虚假的ICMP包。
Task 2.2B: Spoof an ICMP Echo Request.
编写代码,发送100个虚假的ICMP请求报文信息。
在10.9.0.1运行代码,发送虚假的源IP为 10.9.0.5,目的IP为8.8.8.8的icmp请求包。
使用wireshark抓包,可以看到,发送到8.8.8.8的包的源IP变成了10.0.2.4,说明原来的包在经过主虚拟机之后,源IP被修改。同时8.8.8.8发送的回复包的目的IP为10.9.0.5,说明我们欺骗成功。
问题4:可以将IP数据包的长度字段设置为任意值吗?
确实可以随机设置IP报文头中的报文长度参数,但必须保证向socket发送的原始报文是正确的长度,这样当下一条进行转发时会将错误的长度进行修正(即根据我们现有的报文写一个新的发送出去)。如果长度参数与我们发送的大小一致,则可能发送出去报文之后检验和不匹配,无法收到回显报文。
问题5:使用原始套接字必须计算IP报头的校验值吗?
是的,必须计算检验和,否则默认填写0x00,在终点服务器会检验不通过直接丢弃该报文。导致我们无法成功将构造的报文发送出去。
问题6:为什么需要使用root权限运行原始套接字?没有root权限会在哪里失败?
不允许无权限的用户执行发送、接收报文的操作。
Task 2.3: Sniff and then Spoof.
修改之前的欺骗代码,将发送的欺骗包的源IP修改为抓到的数据包的目的IP,将发送的欺骗包的目的IP修改为抓到的数据包的源IP。再将数据包发送出去。
在10.9.0.1上ping 10.9.0.6
启动我们的抓包-欺骗代码。
使用wireshark在any抓包,观察得到的ICMP包,可以观察到带有我们编写的特征的ICMP回复包,并且源IP和目的IP与请求包相对应,说明欺骗成功。
代码参考:(我记得我是参考了别人的代码,但是我现在找不到出处了TvT)
#include <pcap.h>
#include <stdio.h>
#include <stdlib.h>
#include<string.h>
#include<netinet/in.h>
#include<time.h>
#include <signal.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/timerfd.h>
#include <unistd.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <netdb.h>
#include <errno.h>
#include <malloc.h>
#include <stdint.h>
#include <pthread.h>
#include <arpa/inet.h>
#include <asm/byteorder.h>
#define BYTE u_char
#define DWORD u_long
#define USHORT u_short
//IP报头
typedef struct IP_HEADER {
#if defined(__LITTLE_ENDIAN_BITFIELD)
__u8 ihl:4, //4位头部长度 一位4个字节,,最多15*4个字节(可选项)
version:4; //4位版本号
#elif defined (__BIG_ENDIAN_BITFIELD)
__u8 version:4,
ihl:4;
#else
#error "Please fix <asm/byteorder.h>"
#endif
__u8 tos; //8位服务类型
__be16 tot_len; //16位总长度
__be16 id; //16位标识符
__be16 frag_off; //3位标志加13位片偏移
__u8 ttl; //8位生存时间
__u8 protocol; //8位上层协议号
__sum16 check; //16位校验和
__be32 saddr; //32位源IP地址
__be32 daddr; //32位目的IP地址
/*The options start here. */
} IP_HEADER;
//ICMP报头
typedef struct ICMP_HEADER
{
u_char type; //8位类型字段
u_char code; //8位代码字段
u_short cksum; //16位校验和
u_short id; //16位标识符
u_short seq; //16位序列号
} ICMP_HEADER;
//计算网际校验和函数
u_short checksum(u_short* pBuf, int iSize)
{
unsigned long cksum = 0;
while (iSize > 1)
{
cksum += *pBuf++;
iSize -= sizeof(u_short);
}
if (iSize)//如果 iSize 为正,即为奇数个字节
{
cksum += *(u_char*)pBuf; //则在末尾补上一个字节,使之有偶数个字节
}
cksum = (cksum >> 16) + (cksum & 0xffff);
cksum += (cksum >> 16);
return (u_short)(~cksum);
}
int main()
{
int i=0;
for(i=0;i<100;i++)
{
int sd;
struct sockaddr_in sin;
char buffer[1024]; // You can change the buffer size
/* Create a raw socket with IP protocol. The IPPROTO_RAW parameter
* tells the sytem that the IP header is already included;
* this prevents the OS from adding another IP header. */
//构造ICMP回显请求消息,并以TTL递增的顺序发送报文
//ICMP类型字段
const BYTE ICMP_ECHO_REQUEST = 8; //请求回显
const BYTE ICMP_ECHO_REPLY = 0; //回显应答
const BYTE ICMP_TIMEOUT = 11; //传输超时
//其他常量定义
const int DEF_ICMP_DATA_SIZE = 32; //ICMP报文默认数据字段长度
const int MAX_ICMP_PACKET_SIZE = 1024; //ICMP报文最大长度(包括报头)
const DWORD DEF_ICMP_TIMEOUT = 3000; //回显应答超时时间
const int DEF_MAX_HOP = 30; //最大跳站数
//填充ICMP报文中每次发送时不变的字段
char * IcmpSendBuf = buffer+sizeof(IP_HEADER);//发送缓冲区
memset(IcmpSendBuf, 0, sizeof(IcmpSendBuf)); //初始化发送缓冲区
//char IcmpRecvBuf[MAX_ICMP_PACKET_SIZE]; //接收缓冲区
//memset(IcmpRecvBuf, 0, sizeof(IcmpRecvBuf)); //初始化接收缓冲区
/*填写ICMP头,回显请求*/
ICMP_HEADER* pIcmpHeader = (ICMP_HEADER*)IcmpSendBuf;
pIcmpHeader->type = ICMP_ECHO_REQUEST;
pIcmpHeader->code = 0;
/*ID字段为当前进程号*/
//pIcmpHeader->id = (USHORT)GetCurrentProcessId();
pIcmpHeader->id = 0x002a;
memset(IcmpSendBuf + sizeof(ICMP_HEADER), 'E', DEF_ICMP_DATA_SIZE);//数据字段
//填充ICMP报文中每次发送变化的字段
((ICMP_HEADER*)IcmpSendBuf)->cksum = 0; //校验和先置为0
//((ICMP_HEADER*)IcmpSendBuf)->seq = htons(usSeqNo++); //填充序列号
((ICMP_HEADER*)IcmpSendBuf)->seq = 256;
((ICMP_HEADER*)IcmpSendBuf)->cksum = checksum((USHORT*)IcmpSendBuf, sizeof(ICMP_HEADER) + DEF_ICMP_DATA_SIZE); //计算校验和
//printf("%d$$$$$$$$$$\n",sizeof(char));
IP_HEADER* pIPHeader = (IP_HEADER*)buffer;
pIPHeader->version = 4;
pIPHeader->ihl = 5;
pIPHeader->tos = 0;
pIPHeader->tot_len = (sizeof(IP_HEADER) + sizeof(ICMP_HEADER) + DEF_ICMP_DATA_SIZE);
pIPHeader->id = 1;
pIPHeader->frag_off = 0x000;
pIPHeader->ttl = 100;
pIPHeader->protocol = 1; //TCP的协议号为6,UDP的协议号为17。ICMP的协议号为1,IGMP的协议号为2
pIPHeader->saddr = inet_addr("10.9.0.5");
pIPHeader->daddr = inet_addr("8.8.8.8");
//memcpy(&pIPHeader->saddr,packet+30,4);
//memcpy(&pIPHeader->daddr,packet+26,4);
pIPHeader->check = checksum((USHORT*)buffer, sizeof(IP_HEADER)+sizeof(ICMP_HEADER)+DEF_ICMP_DATA_SIZE);
sd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
if(sd < 0) {
perror("socket() error"); exit(-1);
}
/* This data structure is needed when sending the packets
* using sockets. Normally, we need to fill out several
* fields, but for raw sockets, we only need to fill out
* this one field */
sin.sin_family = AF_INET;
// Here you can construct the IP packet using buffer[]
// - construct the IP header ...
// - construct the TCP/UDP/ICMP header ...
// - fill in the data part if needed ...
// Note: you should pay attention to the network/host byte order.
/* Send out the IP packet.
* ip_len is the actual size of the packet. */
int ip_len = (sizeof(IP_HEADER) + sizeof(ICMP_HEADER) + DEF_ICMP_DATA_SIZE);
if(sendto(sd, buffer, ip_len, 0, (struct sockaddr *)&sin, sizeof(sin)) < 0) {
perror("sendto() error"); exit(-1);
}
else {
printf("SEND OUT!!!%d\n",pIPHeader->tot_len);
}
}
return 0;
}