Packet Sniffing and Spoofing Lab
??网络中的数据包嗅探和伪造攻击是两种常见的攻击形式。在嗅探攻击中,攻击者可以对有线或无线的物理网络进行窃听,获取在网络中传输的数据包,这类似于在电话网络中进行搭线窃听。在伪造攻击中, 攻击者使用虚假身份发送数据包。例如,攻击者可以发送声称来自其他主机的数据包。这两种攻击是因特网上许多攻击的基础, 如DNS缓存中毒和TCP 会话劫持等。数据包嗅探和伪造可以通过Wireshark、Netwox和Scapy等工具实现。对于数据包嗅探,将展示如何通过pcap API实现一个简单的数据包嗅探器。对于数据包伪造,将学习如何使用raw socket 发送虚假的IP数据包,在数据包的头部填充任意伪造`的值。
数据包时如何被接收的
网络接口卡
??设备通过网络接口卡(network inter face card, NIC, 简称“ 网卡” )接入网络。网卡是位于计算机和网络之间的物理或者逻辑连接,每一个网卡都有一个硬件地址,称作MAC 地址。常用的本地通信网络(local communication network) 、以太网(Ethernet) 和WiFi本质上都是广播型介质意思是网络中的设备均连接到一个共享的介质上。当数据包在传播介质中流动时,网络中的每个网卡都能“ 听到“ 所有广播的数据帧(frame) 。这些数据帧会被复制到网卡的内存中,网卡会检查数据帧头部的目的地址。如果目的地址与该网卡的MAC地址相匹配,那么该数据帧就会通过直接存储器访问(direct memory access, DMA) 的方式被复制到操作系统内核的缓存中,如下图所示。接着网卡会以中断的方式告诉CPU它接收到了新的数据,然后CPU会将它们全部从缓存中复制到一个队列中,以使为新数据包的到来腾出空间。根据协议规定,内核在处理队列中的数据包时会调用不同的回调函数(callba ck handler function) 。下图展示了内核处理一个普通数据包的流程。 ??混杂模式(promiscuous mode)。 正如上面所讨论的, 网卡会丢弃目标地址和自身MAC地址不匹配的数据帧,而不会交给CPU处理因此嗅探程序也无法得到这些数据帧。幸运的是,大多数网卡都有一种特殊的模式,称为混杂模式。在该模式下,网卡把从网络中接收到的每个数据帧都传递给内核,而不管其目的地址是否与网卡的MAC地址相吻合。如果内核中注册了嗅探处理回调函数,那么所有数据帧都会由内核交给该嗅探程序处理。还有一点需要注意,操作系统通常需要较高的权限(如root 权限)才能将网卡设置成混杂模式。 ??监听模式(monitor mode)。 与有线网络中的混杂模式相似,无线网卡也可以通过监听模式进行嗅探。与以太网不同的是,无线设备存在相邻设备干扰的问题,这会严重影响网络连接的性能。为了解决这个问题,WiFi 设备通过不同的信道传递数据,接入点将相邻的设备用不 ??同的信道连接起来,从而减少了冲突带来的影响。WiFi 网卡的设计也做了相应的调整,可以在整个可用带宽和信道的分片上进行通信(Sanders, 2011) 。由于这样的设计, 当网卡处于监听模式时,它只能捕捉所监听信道中的802.11 数据帧。这意味着,与以太网能监听所有的数据帧不同,由于存在不同的信道, 可能会错过同一个网络中其他信道传输的信息。大多数无线网卡都不支持监听模式,即便支持,也默认被其制造商所禁用。
BSD数据包过滤器
??当进行网络嗅探时,嗅探器经常只会对某些特定类型的数据包感兴趣,例如TCP数据包或者DNS查询数据包。系统可以将所有捕捉到的数据包交给嗅探程序,嗅探程序会丢弃它不需要的数据包。不过这种处理方式的效率非常低下,因为把这些没用的数据包从内核传到嗅探程序是需要花费CPU时间的。通常情况下,嗅探器对大部分数据包都不感兴趣;如果能尽早过滤掉它们,将会节省不少宝贵的资源。 随着对数据包捕获需求的增加,UNIX操作系统定义了BSD数据包过滤器(BSD packet filter, BPF),用于在底层实现数据包的过滤。BPF 允许用户空间的程序将一个过滤器与一个套接字(socket) 进行绑定,其本质上是为了告知内核尽早丢弃不需要的数据包(Schulistet al., 2017)。过滤器一般是首先使用布尔操作符编写的可读性较强的代码,随后该代码被编译成伪代码传递给BPF 驱动。这种底层代码(伪代码)会被BPF 虚拟机(一个专门为包过滤设计的内核层的状态机)所解释[Dainotti et al., 2004] 。
一段编译过的BPF 伪代码可以通过setsockopt()函数与一个socket 进行绑定。例如:
setsockopt(sock, SOL_SOCKET, SO_ATTACH_FILTER, sizeof(bpf))
??BPF 一旦与socket 进行绑定,当数据包到达内核时,回调函数就会被调用, 用于判断该数据包是否应该被过滤。通过过滤的数据包被压入协议栈[McCanneet al. , 1993] 。这个机制在大多数UNIX 系统中都存在。其他平台(例如Windows ) 则采用了不同的数据包捕捉和过滤机制。
Lab Task Set 1: Using Tools to Sniff and Spoof Packets
这部分主要是利用工具来嗅探数据包,这里用的是 scapy。 可以用下面的命令进行安装。
sudo pip3 install scapy
简单测试一下,构造了一个IP数据包并打印其的一些信息:
Task 1.1: Sniffing Packets
下面是使用 scapy来嗅探数据包的一个例子:
from scapy.all import *
def print_pkt(pkt):
pkt.show()
pkt = sniff(filter="icmp",prn=print_pkt)
对于每个捕获的数据包,函数print pkt()将被调用;此函数将打印出有关数据包的一些信息。
Task 1.1A.
先使用如下命令添加执行权限:
chmod a+x sniffer.py
先使用root权限运行上面的程序 sudo ./sniffer.py, 结果如下,可以看到其成功嗅探到了不同协议的数据包,图中只包括ICMP包。 然后使用普通权限运行该程序 ./sniffer.py。结果如下,可以看到报错了,提示无权限。 这说明嗅探包是一件拥有高权限才能做的事情,没有高权限,系统是不让你嗅探数据包的。
Task 1.1B.
??一般在嗅探包时我们只对特定类型的数据包感兴趣,所以我们需要对数据包进行一些过滤。scapy的过滤机制使用BPF的语法,这部分我们需要实现几个过滤的方法。
from scapy.all import *
def print_pkt(pkt):
return pkt.summary()
pkt = sniff(filter="icmp",prn=print_pkt)
print(pkt)
运行结果如下,可以看到捕获到了ICMP数据包:
- 只捕捉来自特定IP,且目标端口号为23的TCP数据包,查看自己的IP地址如下:
这里随意拿个IP来测试, 我们使用 10.0.2.11 , 嗅探代码如下:
from scapy.all import *
def print_pkt(pkt):
return pkt.summary()
pkt = sniff(filter="tcp and src host 10.0.2.11 and dst port 23",prn=print_pkt)
print(pkt)
发送数据包的代码如下:
from scapy.all import *
ip = IP()
ip.src = "10.0.2.11"
ip.dst = "10.0.2.1"
tcp = TCP()
tcp.dport = 23
send(ip/tcp)
运行嗅探的程序,再运行发送数据包的程序(可以多试几次), 结果如下:
- 捕捉来自或发送到特定子网的数据包,这里我们使用的子网为128.230.0.0/16.嗅探代码如下:
from scapy.all import *
def print_pkt(pkt):
return pkt.summary()
pkt = sniff(filter="net 128.230.0.0/16",prn=print_pkt)
print(pkt)
发送包的代码如下:
from scapy.all import *
ip = IP()
ip.src = "10.0.2.11"
ip.dst = "128.230.0.1"
tcp = TCP()
tcp.dport = 23
send(ip/tcp)
ip.src = "128.230.0.1"
ip.dst = "10.0.2.11"
send(ip/tcp)
嗅探结果如下,可以看到嗅探到了发送给子网128.230.0.0/16 和 该子网发送过来的数据包:
Task 1.2: Spoofing ICMP Packets
??这部分主要是伪造任意的IP地址发IP包,这里我们用的是ICMP协议,使用的IP地址为 10.0.2.3 , 注意 在task 1.1B2中,我们查看了自己的IP地址为10.0.2.15,也就是这里我们伪造成ip地址为 10.0.2.3 进行发包。先启动wireshark,选择网卡,再运行发包的程序,发送的代码如下:
from scapy.all import *
ip = IP()
ip.src = "10.0.2.3"
ip.dst = "128.230.0.1"
icmp = ICMP()
send(ip/icmp)
在wireshark中可看到记录,伪造的包有了回复。
Task 1.3: Traceroute
??这个任务的目标是使用Scapy来估计虚拟机和选定的目标之间路由器的数量。这基本上是traceroute工具实现的。这个task中我们将编写自己的工具。这个想法非常简单:只需向服务器发送一个数据包(任何类型)目的地,其生存时间(TTL)字段首先设置为1。这个包将被第一个路由器丢弃,它将向我们发送一个ICMP错误消息,告诉我们生存时间已经超过。我们就是这样得到的第一个路由器的IP地址。然后我们将TTL字段增加到2,发送另一个数据包,并获得第二个路由器的IP地址。我们将重复这个过程,直到我们的包最终到达目的地。需要注意的是,这个实验只得到了一个估计的结果,因为在理论上,并不是所有这些数据包走同样的路线(但在实践中,他们可能会在短时间内)。代码如下:
from scapy.all import *
import sys
def traceroute(target, minttl=1, maxttl=30, dport=80):
print("target: %s(port=%s)" % (target, dport))
ans, unans = sr(IP(dst=target, ttl=(minttl,maxttl),id=RandShort())/TCP(flags=0x2, dport=dport), timeout=10)
for snd,rcv in ans:
print(snd.ttl, rcv.src)
if __name__ == '__main__':
if len(sys.argv) <= 1:
traceroute("baidu.com")
else:
traceroute(sys.argv[1])
运行效果如下, 可以看到打印除了不同TTL对应的IP: SeedUbuntu下运行结果: 注意如果出现,没有回复包,或者回复包过少,可能与环境有点关系。 阿里云ECS下运行结果:
Task 1.4: Sniffing and-then Spoofing
??在此Task中,我们将结合嗅探和欺骗技术来实现以下嗅探和欺骗程序。在同一个局域网上需要两个虚拟机。从虚拟机A,ping了一个ip x。这将生成ICMP回显请求包。如果X处于活动状态,ping程序将收到回复,并且把信息打印出来。嗅探和欺骗程序在虚拟机B上运行,虚拟机B通过网络监视局域网数据包嗅探。每当它看到ICMP回显请求时,不管目标IP地址是什么,程序应该使用数据包欺骗技术立即发送回显回复。因此,不管机器X是否处于活动状态,ping程序总是会收到一个回复,指示X他还活着。 ??一个机器ping任意IP x,另一个机器伪造ICMP回复请求,使得其有回复,而IP x所对应的机器可能根本不存在。我们准备的A机器 IP 地址为10.0.2.15,B机器IP地址为10.0.2.4. 我们用A机器去发送请求,B机器伪造响应。代码如下(图片截错了):
from scapy.all import *
def print_pkt(pkt):
send(IP(src=pkt[IP].dst, dst=pkt[IP].src)/ICMP(type="echo-reply", code= 0, id=pkt[ICMP].id, seq=pkt[ICMP].seq))
pkt = sniff(filter="icmp[icmptype]==icmp-echo",prn=print_pkt)
在断网情况下,仍然可以ping通其他网段的IP:
Lab Task Set 2: Writing Programs to Sniff and Spoof Packets
??这部分主要是使用pcap库写程序来嗅探和伪造包。有了pcap,嗅探器的任务就变成了在pcap库中调用一个简单的过程序列。在序列的末尾,数据包将一旦它们被捕获,就被放入缓冲区进行进一步的处理。包捕获的所有细节都是由pcap库处理。
Task 2.1: Writing Packet Sniffing Program
Task 2.1A: Understanding How a Sniffer Works
这部分主要是写一个打印捕获的包的源IP和目的IP地址。代码如下:
#include <pcap.h>
#include <stdio.h>
#include <arpa/inet.h>
struct ethheader {
u_char ether_dhost[6];
u_char ether_shost[6];
u_short ether_type;
};
struct ipheader {
unsigned char iph_ihl:4,
iph_ver:4;
unsigned char iph_tos;
unsigned short int iph_len;
unsigned short int iph_ident;
unsigned short int iph_flag:3,
iph_offset:13;
unsigned char iph_ttl;
unsigned char iph_protocol;
unsigned short int iph_chksum;
struct in_addr iph_sourceip;
struct in_addr iph_destip;
};
void got_packet(u_char *args, const struct pcap_pkthdr *header,
const u_char *packet)
{
struct ethheader *eth = (struct ethheader *)packet;
if (ntohs(eth->ether_type) == 0x0800) {
struct ipheader * ip = (struct ipheader *)
(packet + sizeof(struct ethheader));
printf(" From: %s\n", inet_ntoa(ip->iph_sourceip));
printf(" To: %s\n", inet_ntoa(ip->iph_destip));
switch(ip->iph_protocol) {
case IPPROTO_TCP:
printf(" Protocol: TCP\n\n");
return;
case IPPROTO_UDP:
printf(" Protocol: UDP\n\n");
return;
case IPPROTO_ICMP:
printf(" Protocol: ICMP\n\n");
return;
default:
printf(" Protocol: others\n\n");
return;
}
}
}
int main()
{
pcap_t *handle;
char errbuf[PCAP_ERRBUF_SIZE];
struct bpf_program fp;
char filter_exp[] = "ip proto icmp";
bpf_u_int32 net;
handle = pcap_open_live("enp0s3", BUFSIZ, 1, 1000, errbuf);
printf("listening on network card, ret: %p...\n", handle);
printf("try to compile filter...\n");
pcap_compile(handle, &fp, filter_exp, 0, net);
printf("try to set filter...\n");
pcap_setfilter(handle, &fp);
printf("start to sniff...\n");
pcap_loop(handle, -1, got_packet, NULL);
pcap_close(handle);
return 0;
}
使用如下命令编译:
gcc -o sniff sniff.c -lpcap
运行,并尝试ping baidu.com,可以看到发送的包出现在结果中: Q1: 描述在你的嗅探程序中的库函数的调用 A1: 第一步,启动pcap监听网卡。 第二步就是编译BPF过滤器并设置过滤器。 第三步就是设置嗅探的处理函数。 最后关闭嗅探即可。 Q2: 为什么需要root权限才能运行嗅探程序?不使用root权限运行该程序会在哪里报错? A2: 嗅探数据包是一个高权限的操作,因为涉及到隐私,安全相关问题。如果普通用户也能嗅探数据包,那么他就能窃取别人的隐私,甚至盗取账号密码等等。不使用root权限运行该程序。对比如下,可以看到在没有权限时第一步监听网卡就失败了: Q3: 打开嗅探程序的混杂模式。打开和关闭这个模式有什么区别? A3: 使用混杂模式可以监听所在网段下其他机器的数据包,关闭则不能。打开混杂模式,可以监听到本网段的机器 ping baidu.com的数据包,关闭后则嗅探不到。
Task 2.1B: Writing Filters
这部分主要是写一些过滤器。这部分还是复用 task 2.1A的代码,只是修改其中的过滤器而已。Pcap过滤器的例子:
-
只捕捉两个特定主机之间的ICMP包,使用的过滤器为 icmp and src host 10.0.2.15 and dst host 10.0.2.4, 只捕捉从 10.0.2.15 发送到 10.0.2.4的ICMP包。 结果如下,可以看到全是从 10.0.2.15 发送到 10.0.2.4的ICMP包,没有其他类型的包。 -
捕捉目的端口在10到100之间的TCP包:使用的过滤器为 tcp and dst portrange 10-100, 只捕捉从 10.0.2.15 发送到 10.0.2.4的ICMP包。 结果如下,用浏览器访问baidu.com的包出现在结果中,用浏览器访baidu.com:111的包没有出现在结果中。 结果如下,用浏览器访问baidu.com的包出现在结果中,用浏览器访baidu.com:111的包没有出现在结果中。
Task 2.1C: Sniffing Passwords
这部分是用嗅探去捕捉telent协议中的密码。我们使用scapy会更方便一些。代码如下:
from scapy.all import *
def print_pkt(pkt):
pkt.show()
print(sniff(filter="tcp port 23", prn=print_pkt))
然后 使用telnet 10.0.2.15 ,并输入账户密码远程登录。嗅探到的密码如下,其分成了几个包发送,如下: 所以输入的密码就是dees。获得telnet输入的用户名同理。
Task 2.2: Spoofing
??这部分主要是伪造包。当一个普通用户发送一个数据包时,操作系统通常不允许用户设置所有的数据包协议头中的字段(例如TCP、UDP和IP头)。OSes将设置大部分字段,而只允许用户设置一些字段,例如目标IP地址、目标端口号等。但是,如果用户具有root权限,则可以在包头中设置任意字段。这叫做包欺骗,它可以通过原始套接字来完成。原始套接字使程序员对包构造有绝对的控制权,允许程序员构造任意数据包,包括设置报头字段和有效负载。使用原始套接字为相当直截了当;它包括四个步骤:(1)创建一个原始套接字,(2)设置套接字选项,(3)构造分组 (4)通过原始套接字发送数据包。 myheader.h内容如下:
struct ethheader {
u_char ether_dhost[6];
u_char ether_shost[6];
u_short ether_type;
};
struct ipheader {
unsigned char iph_ihl:4,
iph_ver:4;
unsigned char iph_tos;
unsigned short int iph_len;
unsigned short int iph_ident;
unsigned short int iph_flag:3,
iph_offset:13;
unsigned char iph_ttl;
unsigned char iph_protocol;
unsigned short int iph_chksum;
struct in_addr iph_sourceip;
struct in_addr iph_destip;
};
struct icmpheader {
unsigned char icmp_type;
unsigned char icmp_code;
unsigned short int icmp_chksum;
unsigned short int icmp_id;
unsigned short int icmp_seq;
};
struct udpheader
{
u_int16_t udp_sport;
u_int16_t udp_dport;
u_int16_t udp_ulen;
u_int16_t udp_sum;
};
struct tcpheader {
u_short tcp_sport;
u_short tcp_dport;
u_int tcp_seq;
u_int tcp_ack;
u_char tcp_offx2;
#define TH_OFF(th) (((th)->tcp_offx2 & 0xf0) >> 4)
u_char tcp_flags;
#define TH_FIN 0x01
#define TH_SYN 0x02
#define TH_RST 0x04
#define TH_PUSH 0x08
#define TH_ACK 0x10
#define TH_URG 0x20
#define TH_ECE 0x40
#define TH_CWR 0x80
#define TH_FLAGS (TH_FIN|TH_SYN|TH_RST|TH_ACK|TH_URG|TH_ECE|TH_CWR)
u_short tcp_win;
u_short tcp_sum;
u_short tcp_urp;
};
struct pseudo_tcp
{
unsigned saddr, daddr;
unsigned char mbz;
unsigned char ptcl;
unsigned short tcpl;
struct tcpheader tcp;
char payload[1500];
};
checksum.c内容如下:
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include "myheader.h"
unsigned short in_cksum (unsigned short *buf, int length)
{
unsigned short *w = buf;
int nleft = length;
int sum = 0;
unsigned short temp=0;
while (nleft > 1) {
sum += *w++;
nleft -= 2;
}
if (nleft == 1) {
*(u_char *)(&temp) = *(u_char *)w ;
sum += temp;
}
sum = (sum >> 16) + (sum & 0xffff);
sum += (sum >> 16);
return (unsigned short)(~sum);
}
unsigned short calculate_tcp_checksum(struct ipheader *ip)
{
struct tcpheader *tcp = (struct tcpheader *)((u_char *)ip +
sizeof(struct ipheader));
int tcp_len = ntohs(ip->iph_len) - sizeof(struct ipheader);
struct pseudo_tcp p_tcp;
memset(&p_tcp, 0x0, sizeof(struct pseudo_tcp));
p_tcp.saddr = ip->iph_sourceip.s_addr;
p_tcp.daddr = ip->iph_destip.s_addr;
p_tcp.mbz = 0;
p_tcp.ptcl = IPPROTO_TCP;
p_tcp.tcpl = htons(tcp_len);
memcpy(&p_tcp.tcp, tcp, tcp_len);
return (unsigned short) in_cksum((unsigned short *)&p_tcp,
tcp_len + 12);
}
spoof.c内容如下:
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include "myheader.h"
void send_raw_ip_packet(struct ipheader* ip)
{
struct sockaddr_in dest_info;
int enable = 1;
int sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
printf("sock: %d\n", sock);
setsockopt(sock, IPPROTO_IP, IP_HDRINCL,
&enable, sizeof(enable));
dest_info.sin_family = AF_INET;
dest_info.sin_addr = ip->iph_destip;
sendto(sock, ip, ntohs(ip->iph_len), 0,
(struct sockaddr *)&dest_info, sizeof(dest_info));
close(sock);
}
Task 2.2A: Write a spoofing program
这部分主要是伪造IP包。IP头部和UDP头部如下: 这里伪造是UDP包, 代码如下:
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include "myheader.h"
void send_raw_ip_packet (struct ipheader* ip);
int main() {
char buffer[1500];
memset(buffer, 0, 1500);
struct ipheader *ip = (struct ipheader *) buffer;
struct udpheader *udp = (struct udpheader *) (buffer +
sizeof(struct ipheader));
char *data = buffer + sizeof(struct ipheader) +
sizeof(struct udpheader);
const char *msg = "Hello Server!\n";
int data_len = strlen(msg);
strncpy (data, msg, data_len);
udp->udp_sport = htons(12345);
udp->udp_dport = htons(9090);
udp->udp_ulen = htons(sizeof(struct udpheader) + data_len);
udp->udp_sum = 0;
ip->iph_ver = 4;
ip->iph_ihl = 5;
ip->iph_ttl = 20;
ip->iph_sourceip.s_addr = inet_addr("1.1.1.1");
ip->iph_destip.s_addr = inet_addr("8.8.8.8");
ip->iph_protocol = IPPROTO_UDP;
ip->iph_len = htons(sizeof(struct ipheader) +
sizeof(struct udpheader) + data_len);
send_raw_ip_packet (ip);
return 0;
}
使用如下命令编译:
gcc -o 2a 2a.c spoof.c -lpcap
sudo ./2a运行,查看后台wireshark,可以看到我们伪造的UDP包:
Task 2.2B: Spoof an ICMP Echo Request
这部分是伪造ICMP Echo请求。伪造的代码如下, 其中源IP10.0.2.15是局域网内另一个虚拟机的IP,代码如下:
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include "myheader.h"
unsigned short in_cksum (unsigned short *buf, int length);
void send_raw_ip_packet(struct ipheader* ip);
int main() {
char buffer[1500];
memset(buffer, 0, 1500);
struct icmpheader *icmp = (struct icmpheader *)
(buffer + sizeof(struct ipheader));
icmp->icmp_type = 8;
icmp->icmp_chksum = 0;
icmp->icmp_chksum = in_cksum((unsigned short *)icmp,
sizeof(struct icmpheader));
struct ipheader *ip = (struct ipheader *) buffer;
ip->iph_ver = 4;
ip->iph_ihl = 5;
ip->iph_ttl = 20;
ip->iph_sourceip.s_addr = inet_addr("10.0.2.15");
ip->iph_destip.s_addr = inet_addr("8.8.8.8");
ip->iph_protocol = IPPROTO_ICMP;
ip->iph_len = htons(sizeof(struct ipheader) +
sizeof(struct icmpheader));
send_raw_ip_packet (ip);
return 0;
}
使用如下命令编译:
gcc -o 2b 2b.c spoof.c checksum.c -lpcap
sudo ./2b运行,查看后台的wireshark,如下: 可以看到我们发送的源IP为10.0.2.15, 目的IP为8.8.8.8的ICMP包,并且还有回复。 Q4: 能把IP包的长度设置为任意数值,而不管实际的包的大小吗? A4: 将代码中设置长度该成下面的代码,运行可知其为28B,修改长度为10B,wireshark没有捕捉到包,说明没有发出去。
ip->iph_len = htons(10);
printf("iph_len: %d\n", sizeof(struct ipheader) +
sizeof(struct icmpheader));
修改成1000B,结果如下,可以看到正常发送出去,且收到了相应。说明可以调大length,不能调小length。 Q5: 使用 raw socket 编程, 我们要计算IP头部的checksum吗? A5: 不用计算IP头部的checksum,但是需要计算ICMP头部的checksum。 Q6: 为什么使用raw socket 编程需要root权限?没有root权限执行时程序会在哪里报错? A6: 因为能任意读取发送包意味着很大的安全风险,所以需要root权限。使用普通权限运行结果如下: 可以看到返回的socket 描述符为-1, 说明创建raw socket失败了。
Task 2.3: Sniff and then Spoof
??在本task中,我们需要结合嗅探和欺骗技术来实现以下嗅探和欺骗程序。在同一个局域网上需要两个虚拟机。从虚拟机A,你ping了一个ip x。这将生成ICMP回显请求包。如果X处于活动状态,ping程序将收到回复,并且把信息打印出来。你的嗅探和欺骗程序在 虚拟机B上运行,虚拟机B通过网络监视局域网数据包嗅探。每当它看到ICMP回显请求时,不管目标IP地址是什么,程序应该使用数据包欺骗技术立即发送回显回复。因此,不管机器X是否处于活动状态,ping程序总是会收到一个回复,指示X他还活着。 ??准备两个在同一个局域网的虚拟机,这部分主要是同时嗅探和伪造包,实现一个机器ping任意IP x,另一个机器伪造ICMP回复请求,使得其有回复,而IP x所对应的机器可能根本不存在。代码如下:
#include <pcap.h>
#include <stdio.h>
#include <arpa/inet.h>
#include "myheader.h"
void got_packet(u_char *args, const struct pcap_pkthdr *header,
const u_char *packet)
{
struct ethheader *eth = (struct ethheader *)packet;
if (ntohs(eth->ether_type) == 0x0800) {
struct ipheader * ip = (struct ipheader *)
(packet + sizeof(struct ethheader));
printf("From: %s ", inet_ntoa(ip->iph_sourceip));
printf("To: %s ", inet_ntoa(ip->iph_destip));
if (ip->iph_protocol == IPPROTO_ICMP)
printf("protocal: ICMP\n");
else
printf("protocal: Others\n");
struct icmpheader *icmp_pkt = (struct icmpheader *)(packet + sizeof(struct ethheader)
+ sizeof(struct ipheader));
if (ip->iph_protocol == IPPROTO_ICMP) {
char buffer[1500];
memset(buffer, 0, 1500);
struct icmpheader *icmp = (struct icmpheader *)
(buffer + sizeof(struct ipheader));
icmp->icmp_type = 0;
icmp->icmp_code = 0;
icmp->icmp_id = icmp_pkt->icmp_id;
icmp->icmp_seq = icmp_pkt->icmp_seq;
printf("icmp id: %d, seq: %d\n", ntohs(icmp_pkt->icmp_id), ntohs(icmp_pkt->icmp_seq));
icmp->icmp_chksum = 0;
icmp->icmp_chksum = in_cksum((unsigned short *)icmp,
sizeof(struct icmpheader));
struct ipheader *ipp = (struct ipheader *) buffer;
ipp->iph_ver = 4;
ipp->iph_ihl = 5;
ipp->iph_ttl = 64;
ipp->iph_sourceip.s_addr = ip->iph_destip.s_addr;
ipp->iph_destip.s_addr = ip->iph_sourceip.s_addr;
ipp->iph_protocol = IPPROTO_ICMP;
ipp->iph_len = htons(sizeof(struct ipheader) +
sizeof(struct icmpheader));
printf("send tt source :%s\n", inet_ntoa(ipp->iph_sourceip));
printf("send tt dest: %s\n", inet_ntoa(ipp->iph_destip));
send_raw_ip_packet (ipp);
}
}
}
int main()
{
pcap_t *handle;
char errbuf[PCAP_ERRBUF_SIZE];
struct bpf_program fp;
char filter_exp[] = "icmp[icmptype]==icmp-echo";
bpf_u_int32 net;
handle = pcap_open_live("enp0s3", BUFSIZ, 1, 1000, errbuf);
printf("listening on network card, ret: %p...\n", handle);
printf("try to compile filter...\n");
pcap_compile(handle, &fp, filter_exp, 0, net);
printf("try to set filter...\n");
pcap_setfilter(handle, &fp);
printf("start to sniff...\n");
pcap_loop(handle, -1, got_packet, NULL);
pcap_close(handle);
return 0;
}
使用如下命令编译程序:
gcc -o 3a 3a.c checksum.c spoof.c -lpcap
sudo ./3a运行。关闭网络,使用另一台机器ping 1.1.1.1,此机器运行上面的程序,结果如下: 要注意的是,程序中使用的inet_ntoa函数以字符串形式返回ip地址,该字符串存在函数内部的静态区域,下一次调用会刷新,之前的结果将会失效。
|