Promiscuous Mode

时间:2021-07-19 10:11:08
 

简介

Monitor mode 与 promiscuous mode 比较

这是在网卡上的的两个特殊的模式,简而言之,都是将网卡的过滤器关闭。

  • Monitor mode

这是我们常常提到的sniffer mode。它用于无线网络中,无线网卡开启监听模式,监听空气中的所有数据包,其中它还可以切换channel。如果设置得当,可以同时监控所有信道的帧(切换式,或者同时多个网卡监听)。在这个模式下面,STA是没有连接到AP的。除了能够得到数据包之外,还可以得到控制帧和管理帧。对于debug 802.11的问题(omnipeek or wireshark in linux)或者攻击一个802.11的网络(aircrack、reaver),常常会使网卡进入到这个模式。此文重点在于promiscuous mode,802.11的monitor mode不再赘述。

  • Promiscuos mode

不处于promiscuous mode的网卡只会收取DA会自身的数据包或者BC/MC的数据包。在promiscuos mode下面,网卡会收到DA不是自身的包,在进入这个模式的时候,常常会开启一个raw socket,来接收网卡传递上来的数据。

打开promiscuous mode

翻阅libpcap的源码,可以整理出这两种方式,可以打开promiscuous mode:

(activate_new) – new way

1.Try to open a packet socket using the new kernel PF_PACKET interface
2.Select promiscuous mode on

struct packet_mreq  mr; memset(&mr, 0, sizeof(mr));
mr.mr_ifindex = handlep->ifindex;
mr.mr_type = PACKET_MR_PROMISC;
sock_fd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
setsockopt(sock_fd, SOL_PACKET, PACKET_ADD_MEMBERSHIP, &mr, sizeof(mr))

setsockopt 原型如下:

int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);

对应于内核的 /include/net/sock.h

(setsockopt)(struct sock sk, int level, int optname, char __user *optval, unsigned int optlen);

net/packet/af_packet.c 里面有对应的操作:

static int
packet_setsockopt(struct socket *sock, int level, int optname, char __user *optval, unsigned int optlen)
{
struct sock *sk = sock->sk;
struct packet_sock *po = pkt_sk(sk);
int ret; if (level != SOL_PACKET)
return -ENOPROTOOPT; switch (optname) {
case PACKET_ADD_MEMBERSHIP:
case PACKET_DROP_MEMBERSHIP:
{
struct packet_mreq_max mreq;
int len = optlen;
memset(&mreq, 0, sizeof(mreq));
if (len sizeof(mreq))
len = sizeof(mreq);
if (copy_from_user(&mreq, optval, len))
return -EFAULT;
if (len < (mreq.mr_alen + offsetof(struct packet_mreq, mr_address)))
return -EINVAL;
if (optname == PACKET_ADD_MEMBERSHIP)
ret = packet_mc_add(sk, &mreq);
else
ret = packet_mc_drop(sk, &mreq);
return ret;
}

调用流程:

packet_mc_add -> packet_dev_mc -> dev_set_promiscuity(net/core/dev.c)

dev_set_promiscuity的定义如下:

int dev_set_promiscuity(struct net_device *dev, int inc)
{
unsigned int old_flags = dev->flags;
int err; err = __dev_set_promiscuity(dev, inc, true);
if (err <0 return err if dev->flags != old_flags)
dev_set_rx_mode(dev);
return err;
}
__dev_set_promiscuity =>
dev->flags |= IFF_PROMISC;
dev_change_rx_flags(dev, IFF_PROMISC);
ops(net_device_ops)->ndo_change_rx_flags(dev, flags);
dev_set_rx_mode(dev); ==>
ops(net_device_ops)->ndo_set_rx_mode(dev);

驱动基本上只实现了ndo_set_rx_mode

选择一个Ethernet驱动作为例子:

const struct net_device_ops ei_netdev_ops = {
....
.ndo_open = ei_open,
.ndo_stop = ei_close,
.ndo_start_xmit = ei_start_xmit,
.ndo_set_rx_mode = ei_set_multicast_list,
};
ei_set_multicast_list ->
__ei_set_multicast_list ->
do_set_multicast_list
static void do_set_multicast_list(struct net_device *dev)
{
....
if (dev->flags&IFF_PROMISC) {
memset(ei_local->mcfilter, 0xFF, 8);
ei_outb_p(E8390_RXCONFIG | 0x18, e8390_base + EN0_RXCR);
}
}

这边可以看到驱动设置了EN0_RXCR,含义猜测是RX config register,将RX的接收状态设置为accept all。所以说,设置设备进入promiscuous mode,实际上是设置硬件寄存器ndo_set_rx_mode,需要设备的驱动支持。

(activate_old) – old way

struct ifreq ifr;
ifr.ifr_flags |= IFF_PROMISC;
ioctl(handle->fd, SIOCSIFFLAGS, &ifr)

这种开启方法,多个开启混杂模式的程序可能会干扰,容易出问题,不推荐使用
在 net/core/dev_ioctl.c

=> dev_ifsioc
=> dev_change_flags
=> __dev_change_flags
=> dev_set_rx_mode

下面的路径就和上一种方法一样了,不再赘述。可以了解到,在底层的操作,两个做法都是一样的,都是关闭了网卡的过滤器。

如何判断一个机器位于promiscuous mode

既然网卡驱动放行了所有的数据包并且将数据包上传TCPIP协议栈,那么这里可以使用一个技巧。将进入promiscuous mode的主机设为A。从同一网段的B主机伪造一个目标MAC地址不是A,但是目标ip地址是A的包。当正常的主机收到这个包的时候,网卡驱动或者网卡的asic就会将它丢弃,当这个主机进入promiscuous mode之后,网卡驱动放行这个包,TCPIP协议栈检查这个包的目标地址是A,所以会产生一个回应到B主机。当然了,如果主机A特意将TCPIP协议栈的收发包路径关掉,那么就无法侦测到了。

另外,如果我伪造了一个不存在的mac地址,那么交换机就不知道这个mac地址是位于哪个端口,它也会帮忙转发到所有端口。

比如我从B主机伪造一个错误MAC地址的ICMP包给A主机,判断A主机是否回应,这样就可以判断出它是否处于promiscuous mode。

MAC IP Payload
局域网内没有出现过的mac地址 192.168.0.5 xxxxxxxx

做一个测试

测试环境: ubuntu 12.4 + win10
测试网卡:Ethernet 网卡
测试软件:wireshark,ICMP c语言测试程序
两端的ip:192.168.0.4,192.168.0.5
测试方式:直连,家用路由器LAN口转发

下面是测试的过程。首先我在目标机器上面(win10)开启了wireshark,并且监听本地连接。这时候我伪造了一个错误的MAC DA,但是其他参数,比如对方的ip地址是填写正确的。这时候本机的wireshark可以看到对方的ICMP回应。紧接着我关闭win10上面的wireshark,ICMP回应消失。随后我又开启wireshark,又可以得到ICMP回应了。测试的时候,两台机器使用网线直连或者通过家用路由器的LAN口转发都是成功的,但是使用无线网络的时候是失败的,可能是无线网卡不支持promiscuous mode。注意到,因为是要伪造MAC地址,所以需要使用PF_PACKET来操作RAW socket

下图是在ubuntu上面的wireshark截取的
Promiscuous Mode

另外,如果两个机器直连测试,需要在windows上面设置静态ip地址(192.168.0.5/24),在linux机器上面关闭界面上的网络连接,并且输入测试网络是否连通:

tanhangbo@ThinkPad:~$ sudo ifconfig eth0 up
tanhangbo@ThinkPad:~$ sudo ifconfig eth0 192.168.0.4
tanhangbo@ThinkPad:~$ ping 192.168.0.5
PING 192.168.0.5 (192.168.0.5) 56(84) bytes of data.
64 bytes from 192.168.0.5: icmp_req=1 ttl=128 time=0.312 ms
64 bytes from 192.168.0.5: icmp_req=2 ttl=128 time=0.384 ms
64 bytes from 192.168.0.5: icmp_req=3 ttl=128 time=0.408 ms

实际作用

一般来说,进入这个模式,就是为了多收一些包,进行网络诊断。一般开启wireshark的时候就会帮你打开promiscuous mode。底层操作在libpcap里面。

  • 参考资料:
  1. libpcap和linux kernel 源码
  2. http://*.com/questions/6666257/what-is-the-purpose-of-net-device-uc-promisc-field