LAB 13 数据包嗅探和伪造

Packet Sniffing and Spoofing Lab


  网络中的数据包嗅探和伪造攻击是两种常见的攻击形式。在嗅探攻击中,攻击者可以对有线或无线的物理网络进行窃听,获取在网络中传输的数据包,这类似于在电话网络中进行搭线窃听。在伪造攻击中, 攻击者使用虚假身份发送数据包。例如,攻击者可以发送声称来自其他主机的数据包。这两种攻击是因特网上许多攻击的基础, 如DNS缓存中毒和TCP 会话劫持等。数据包嗅探和伪造可以通过Wireshark、Netwox和Scapy等工具实现。对于数据包嗅探,将展示如何通过pcap API实现一个简单的数据包嗅探器。对于数据包伪造,将学习如何使用raw socket 发送虚假的IP数据包,在数据包的头部填充任意伪造`的值。

数据包时如何被接收的

Lab Task Set 1: Using Tools to Sniff and Spoof Packets

这部分主要是利用工具来嗅探数据包,这里用的是 scapy。 可以用下面的命令进行安装。

sudo pip3 install scapy

LAB 13 数据包嗅探和伪造
简单测试一下,构造了一个IP数据包并打印其的一些信息:
LAB 13 数据包嗅探和伪造

Task 1.1: Sniffing Packets

下面是使用 scapy来嗅探数据包的一个例子:

#!/usr/bin/python3
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 

LAB 13 数据包嗅探和伪造
先使用root权限运行上面的程序 sudo ./sniffer.py, 结果如下,可以看到其成功嗅探到了不同协议的数据包,图中只包括ICMP包。
LAB 13 数据包嗅探和伪造
然后使用普通权限运行该程序 ./sniffer.py。结果如下,可以看到报错了,提示无权限。
LAB 13 数据包嗅探和伪造
这说明嗅探包是一件拥有高权限才能做的事情,没有高权限,系统是不让你嗅探数据包的。

Task 1.1B.

  一般在嗅探包时我们只对特定类型的数据包感兴趣,所以我们需要对数据包进行一些过滤。scapy的过滤机制使用BPF的语法,这部分我们需要实现几个过滤的方法。

  • 只捕捉ICMP数据包,代码修改如下:
#!/usr/bin/python3
from scapy.all import *

def print_pkt(pkt):
return pkt.summary()
pkt = sniff(filter="icmp",prn=print_pkt)
print(pkt)

运行结果如下,可以看到捕获到了ICMP数据包:
LAB 13 数据包嗅探和伪造

  • 只捕捉来自特定IP,且目标端口号为23的TCP数据包,查看自己的IP地址如下:
    LAB 13 数据包嗅探和伪造
    这里随意拿个IP来测试, 我们使用 10.0.2.11 , 嗅探代码如下:
#!/usr/bin/python3
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)

发送数据包的代码如下:

#!/usr/bin/python3
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)

LAB 13 数据包嗅探和伪造
运行嗅探的程序,再运行发送数据包的程序(可以多试几次), 结果如下:
LAB 13 数据包嗅探和伪造

  • 捕捉来自或发送到特定子网的数据包,这里我们使用的子网为128.230.0.0/16.嗅探代码如下:
#!/usr/bin/python3
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)

发送包的代码如下:

#!/usr/bin/python3
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 和 该子网发送过来的数据包:
LAB 13 数据包嗅探和伪造

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,选择网卡,再运行发包的程序,发送的代码如下:

#!/usr/bin/python3
from scapy.all import *

ip = IP()
ip.src = "10.0.2.3"
ip.dst = "128.230.0.1"
icmp = ICMP()
send(ip/icmp)

LAB 13 数据包嗅探和伪造
在wireshark中可看到记录,伪造的包有了回复。
LAB 13 数据包嗅探和伪造

Task 1.3: Traceroute

  这个任务的目标是使用Scapy来估计虚拟机和选定的目标之间路由器的数量。这基本上是traceroute工具实现的。这个task中我们将编写自己的工具。这个想法非常简单:只需向服务器发送一个数据包(任何类型)目的地,其生存时间(TTL)字段首先设置为1。这个包将被第一个路由器丢弃,它将向我们发送一个ICMP错误消息,告诉我们生存时间已经超过。我们就是这样得到的第一个路由器的IP地址。然后我们将TTL字段增加到2,发送另一个数据包,并获得第二个路由器的IP地址。我们将重复这个过程,直到我们的包最终到达目的地。需要注意的是,这个实验只得到了一个估计的结果,因为在理论上,并不是所有这些数据包走同样的路线(但在实践中,他们可能会在短时间内)。代码如下:

#!/usr/bin/python3
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下运行结果:
LAB 13 数据包嗅探和伪造
注意如果出现,没有回复包,或者回复包过少,可能与环境有点关系。
阿里云ECS下运行结果:
LAB 13 数据包嗅探和伪造

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机器伪造响应。代码如下(图片截错了):

#!/usr/bin/python3
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)

LAB 13 数据包嗅探和伪造
在断网情况下,仍然可以ping通其他网段的IP:
LAB 13 数据包嗅探和伪造

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>

/* Ethernet header */
struct ethheader {
  u_char  ether_dhost[6]; /* destination host address */
  u_char  ether_shost[6]; /* source host address */
  u_short ether_type;     /* protocol type (IP, ARP, RARP, etc) */
};

/* IP Header */
struct ipheader {
  unsigned char      iph_ihl:4, //IP header length
                     iph_ver:4; //IP version
  unsigned char      iph_tos; //Type of service
  unsigned short int iph_len; //IP Packet length (data + header)
  unsigned short int iph_ident; //Identification
  unsigned short int iph_flag:3, //Fragmentation flags
                     iph_offset:13; //Flags offset
  unsigned char      iph_ttl; //Time to Live
  unsigned char      iph_protocol; //Protocol type
  unsigned short int iph_chksum; //IP datagram checksum
  struct  in_addr    iph_sourceip; //Source IP address
  struct  in_addr    iph_destip;   //Destination IP address
};

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) { // 0x0800 is IP type
    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));    

    /* determine protocol */
    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;

  // Step 1: Open live pcap session on NIC with name enp0s3
  handle = pcap_open_live("enp0s3", BUFSIZ, 1, 1000, errbuf);
  printf("listening on network card, ret: %p...\n", handle);

  // Step 2: Compile filter_exp into BPF psuedo-code
  printf("try to compile filter...\n");
  pcap_compile(handle, &fp, filter_exp, 0, net);
  printf("try to set filter...\n");
  pcap_setfilter(handle, &fp);

  // Step 3: Capture packets
  printf("start to sniff...\n");
  pcap_loop(handle, -1, got_packet, NULL);

  pcap_close(handle);   //Close the handle
  return 0;
}

使用如下命令编译:

gcc -o sniff sniff.c -lpcap

LAB 13 数据包嗅探和伪造
运行,并尝试ping baidu.com,可以看到发送的包出现在结果中:
LAB 13 数据包嗅探和伪造
Q1: 描述在你的嗅探程序中的库函数的调用
A1: 第一步,启动pcap监听网卡。
LAB 13 数据包嗅探和伪造
第二步就是编译BPF过滤器并设置过滤器。
LAB 13 数据包嗅探和伪造
第三步就是设置嗅探的处理函数。
LAB 13 数据包嗅探和伪造
最后关闭嗅探即可。
LAB 13 数据包嗅探和伪造
Q2: 为什么需要root权限才能运行嗅探程序?不使用root权限运行该程序会在哪里报错?
A2: 嗅探数据包是一个高权限的操作,因为涉及到隐私,安全相关问题。如果普通用户也能嗅探数据包,那么他就能窃取别人的隐私,甚至盗取账号密码等等。不使用root权限运行该程序。对比如下,可以看到在没有权限时第一步监听网卡就失败了:
LAB 13 数据包嗅探和伪造
Q3: 打开嗅探程序的混杂模式。打开和关闭这个模式有什么区别?
A3: 使用混杂模式可以监听所在网段下其他机器的数据包,关闭则不能。打开混杂模式,可以监听到本网段的机器 ping baidu.com的数据包,关闭后则嗅探不到。

Task 2.1B: Writing Filters

这部分主要是写一些过滤器。这部分还是复用 task 2.1A的代码,只是修改其中的过滤器而已。Pcap过滤器的例子:
LAB 13 数据包嗅探和伪造

  1. 只捕捉两个特定主机之间的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包。
    LAB 13 数据包嗅探和伪造
    结果如下,可以看到全是从 10.0.2.15 发送到 10.0.2.4的ICMP包,没有其他类型的包。
    LAB 13 数据包嗅探和伪造

  2. 捕捉目的端口在10到100之间的TCP包:使用的过滤器为 tcp and dst portrange 10-100, 只捕捉从 10.0.2.15 发送到 10.0.2.4的ICMP包。
    LAB 13 数据包嗅探和伪造
    结果如下,用浏览器访问baidu.com的包出现在结果中,用浏览器访baidu.com:111的包没有出现在结果中。
    LAB 13 数据包嗅探和伪造
    结果如下,用浏览器访问baidu.com的包出现在结果中,用浏览器访baidu.com:111的包没有出现在结果中。
    LAB 13 数据包嗅探和伪造

Task 2.1C: Sniffing Passwords

这部分是用嗅探去捕捉telent协议中的密码。我们使用scapy会更方便一些。代码如下:

#!/usr/bin/python3
from scapy.all import *

def print_pkt(pkt):
    pkt.show()

print(sniff(filter="tcp port 23", prn=print_pkt))

然后 使用telnet 10.0.2.15 ,并输入账户密码远程登录。嗅探到的密码如下,其分成了几个包发送,如下:
LAB 13 数据包嗅探和伪造
LAB 13 数据包嗅探和伪造
LAB 13 数据包嗅探和伪造
LAB 13 数据包嗅探和伪造
LAB 13 数据包嗅探和伪造
LAB 13 数据包嗅探和伪造
所以输入的密码就是dees。获得telnet输入的用户名同理。

Task 2.2: Spoofing

  这部分主要是伪造包。当一个普通用户发送一个数据包时,操作系统通常不允许用户设置所有的数据包协议头中的字段(例如TCP、UDP和IP头)。OSes将设置大部分字段,而只允许用户设置一些字段,例如目标IP地址、目标端口号等。但是,如果用户具有root权限,则可以在包头中设置任意字段。这叫做包欺骗,它可以通过原始套接字来完成。原始套接字使程序员对包构造有绝对的控制权,允许程序员构造任意数据包,包括设置报头字段和有效负载。使用原始套接字为相当直截了当;它包括四个步骤:(1)创建一个原始套接字,(2)设置套接字选项,(3)构造分组 (4)通过原始套接字发送数据包。
myheader.h内容如下:

/* Ethernet header */
struct ethheader {
    u_char  ether_dhost[6];    /* destination host address */
    u_char  ether_shost[6];    /* source host address */
    u_short ether_type;                     /* IP? ARP? RARP? etc */
};

/* IP Header */
struct ipheader {
  unsigned char      iph_ihl:4, //IP header length
                     iph_ver:4; //IP version
  unsigned char      iph_tos; //Type of service
  unsigned short int iph_len; //IP Packet length (data + header)
  unsigned short int iph_ident; //Identification
  unsigned short int iph_flag:3, //Fragmentation flags
                     iph_offset:13; //Flags offset
  unsigned char      iph_ttl; //Time to Live
  unsigned char      iph_protocol; //Protocol type
  unsigned short int iph_chksum; //IP datagram checksum
  struct  in_addr    iph_sourceip; //Source IP address
  struct  in_addr    iph_destip;   //Destination IP address
};

/* ICMP Header  */
struct icmpheader {
  unsigned char icmp_type; // ICMP message type
  unsigned char icmp_code; // Error code
  unsigned short int icmp_chksum; //Checksum for ICMP Header and data
  unsigned short int icmp_id;     //Used for identifying request
  unsigned short int icmp_seq;    //Sequence number
};

/* UDP Header */
struct udpheader
{
  u_int16_t udp_sport;           /* source port */
  u_int16_t udp_dport;           /* destination port */
  u_int16_t udp_ulen;            /* udp length */
  u_int16_t udp_sum;             /* udp checksum */
};

/* TCP Header */
struct tcpheader {
    u_short tcp_sport;               /* source port */
    u_short tcp_dport;               /* destination port */
    u_int   tcp_seq;                 /* sequence number */
    u_int   tcp_ack;                 /* acknowledgement number */
    u_char  tcp_offx2;               /* data offset, rsvd */
#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;                 /* window */
    u_short tcp_sum;                 /* checksum */
    u_short tcp_urp;                 /* urgent pointer */
};

/* Psuedo TCP header */
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;

   /*
    * The algorithm uses a 32 bit accumulator (sum), adds
    * sequential 16 bit words to it, and at the end, folds back all
    * the carry bits from the top 16 bits into the lower 16 bits.
    */
   while (nleft > 1)  {
       sum += *w++;
       nleft -= 2;
   }

   /* treat the odd byte at the end, if any */
   if (nleft == 1) {
        *(u_char *)(&temp) = *(u_char *)w ;
        sum += temp;
   }

   /* add back carry outs from top 16 bits to low 16 bits */
   sum = (sum >> 16) + (sum & 0xffff);  // add hi 16 to low 16
   sum += (sum >> 16);                  // add carry
   return (unsigned short)(~sum);
}

/****************************************************************
  TCP checksum is calculated on the pseudo header, which includes
  the TCP header and data, plus some part of the IP header.
  Therefore, we need to construct the pseudo header first.
*****************************************************************/


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);

   /* pseudo tcp header for the checksum computation */
   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"

/*************************************************************
  Given an IP packet, send it out using a raw socket.
**************************************************************/
void send_raw_ip_packet(struct ipheader* ip)
{
    struct sockaddr_in dest_info;
    int enable = 1;

    // Step 1: Create a raw network socket.
    int sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
    printf("sock: %d\n", sock);

    // Step 2: Set socket option.
    setsockopt(sock, IPPROTO_IP, IP_HDRINCL,
                     &enable, sizeof(enable));

    // Step 3: Provide needed information about destination.
    dest_info.sin_family = AF_INET;
    dest_info.sin_addr = ip->iph_destip;

    // Step 4: Send the packet out.
    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头部如下:
LAB 13 数据包嗅探和伪造
LAB 13 数据包嗅探和伪造
这里伪造是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);

/******************************************************************
  Spoof a UDP packet using an arbitrary source IP Address and port
*******************************************************************/
int main() {
   char buffer[1500];

   memset(buffer, 0, 1500);
   struct ipheader *ip = (struct ipheader *) buffer;
   struct udpheader *udp = (struct udpheader *) (buffer +
                                          sizeof(struct ipheader));

   /*********************************************************
      Step 1: Fill in the UDP data field.
    ********************************************************/
   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);

   /*********************************************************
      Step 2: Fill in the UDP header.
    ********************************************************/
   udp->udp_sport = htons(12345);
   udp->udp_dport = htons(9090);
   udp->udp_ulen = htons(sizeof(struct udpheader) + data_len);
   udp->udp_sum =  0; /* Many OSes ignore this field, so we do not
                         calculate it. */

   /*********************************************************
      Step 3: Fill in the IP header.
    ********************************************************/
   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; // The value is 17.
   ip->iph_len = htons(sizeof(struct ipheader) +
                       sizeof(struct udpheader) + data_len);

   /*********************************************************
      Step 4: Finally, send the spoofed packet
    ********************************************************/
   send_raw_ip_packet (ip);

   return 0;
}

使用如下命令编译:

gcc -o 2a 2a.c spoof.c -lpcap

LAB 13 数据包嗅探和伪造
sudo ./2a运行,查看后台wireshark,可以看到我们伪造的UDP包:
LAB 13 数据包嗅探和伪造

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);

/******************************************************************
  Spoof an ICMP echo request using an arbitrary source IP Address
*******************************************************************/
int main() {
   char buffer[1500];

   memset(buffer, 0, 1500);

   /*********************************************************
      Step 1: Fill in the ICMP header.
    ********************************************************/
   struct icmpheader *icmp = (struct icmpheader *)
                             (buffer + sizeof(struct ipheader));
   icmp->icmp_type = 8; //ICMP Type: 8 is request, 0 is reply.

   // Calculate the checksum for integrity
   icmp->icmp_chksum = 0;
   icmp->icmp_chksum = in_cksum((unsigned short *)icmp,
                                 sizeof(struct icmpheader));

   /*********************************************************
      Step 2: Fill in the IP header.
    ********************************************************/
   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));

   /*********************************************************
      Step 3: Finally, send the spoofed packet
    ********************************************************/
   send_raw_ip_packet (ip);

   return 0;
}

使用如下命令编译:

gcc -o 2b 2b.c spoof.c checksum.c -lpcap

LAB 13 数据包嗅探和伪造
sudo ./2b运行,查看后台的wireshark,如下:
LAB 13 数据包嗅探和伪造
可以看到我们发送的源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。
LAB 13 数据包嗅探和伪造
LAB 13 数据包嗅探和伪造
Q5: 使用 raw socket 编程, 我们要计算IP头部的checksum吗?
A5: 不用计算IP头部的checksum,但是需要计算ICMP头部的checksum。
Q6: 为什么使用raw socket 编程需要root权限?没有root权限执行时程序会在哪里报错?
A6: 因为能任意读取发送包意味着很大的安全风险,所以需要root权限。使用普通权限运行结果如下:
LAB 13 数据包嗅探和伪造
可以看到返回的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) { // 0x0800 is IP type
    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);

        /*********************************************************
             Step 1: Fill in the ICMP header.
            ********************************************************/
        struct icmpheader *icmp = (struct icmpheader *)
                                    (buffer + sizeof(struct ipheader));
        icmp->icmp_type = 0; //ICMP Type: 8 is request, 0 is reply.
        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));

        // Calculate the checksum for integrity
        icmp->icmp_chksum = 0;
        icmp->icmp_chksum = in_cksum((unsigned short *)icmp,
                                        sizeof(struct icmpheader));

        /*********************************************************
             Step 2: Fill in the IP header.
            ********************************************************/
        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));

        /*********************************************************
             Step 3: Finally, send the spoofed packet
            ********************************************************/
        // icmp_pkt->icmp_type = 0;
        // icmp_pkt->icmp_code = 0;
        // icmp->icmp_chksum = 0;
        // icmp->icmp_chksum = in_cksum((unsigned short *)icmp,
        //                                 sizeof(struct icmpheader));
        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;

  // Step 1: Open live pcap session on NIC with name enp0s3
  handle = pcap_open_live("enp0s3", BUFSIZ, 1, 1000, errbuf);
  printf("listening on network card, ret: %p...\n", handle);

  // Step 2: Compile filter_exp into BPF psuedo-code
  printf("try to compile filter...\n");
  pcap_compile(handle, &fp, filter_exp, 0, net);
  printf("try to set filter...\n");
  pcap_setfilter(handle, &fp);

  // Step 3: Capture packets
  printf("start to sniff...\n");
  pcap_loop(handle, -1, got_packet, NULL);

  pcap_close(handle);   //Close the handle
  return 0;
}

使用如下命令编译程序:

gcc -o 3a 3a.c checksum.c spoof.c -lpcap

sudo ./3a运行。关闭网络,使用另一台机器ping 1.1.1.1,此机器运行上面的程序,结果如下:
LAB 13 数据包嗅探和伪造
要注意的是,程序中使用的inet_ntoa函数以字符串形式返回ip地址,该字符串存在函数内部的静态区域,下一次调用会刷新,之前的结果将会失效。

上一篇:【图像分割】基于matlab超像素图像分割【含Matlab源码 720期】


下一篇:P5039 [SHOI2010]最小生成树