scapy on openwrt

scapy on openwrt

背景

scapy是个很强大的python工具,可以用来发送,监听和分析伪造网络数据包。在openwrt的路由器上可以用来监听和探测上线设备
设备的类型等。
但是在openwrt上发现它无法使用sniffer(filter)加载filter到内核,出现如下错误 socket.error: [Errno 99] Protocol not available

root@UBNT-Pro /root [#]# ./dd.sh
WARNING: IPv6 support disabled in Python. Cannot load scapy IPv6 layers.
WARNING: can't import layer sctp: can't use AF_INET6, IPv6 is disabled
WARNING: can't import layer ipsec: can't use AF_INET6, IPv6 is disabled
Traceback (most recent call last):
  File "./dd.sh", line 8, in <module>
    sniff(prn=arp_monitor_callback, filter="arp", store=0)
  File "/usr/lib/python2.7/site-packages/scapy/sendrecv.py", line 561, in sniff
    s = L2socket(type=ETH_P_ALL, *arg, **karg)
  File "/usr/lib/python2.7/site-packages/scapy/arch/linux.py", line 471, in __init__
    attach_filter(self.ins, filter)
  File "/usr/lib/python2.7/site-packages/scapy/arch/linux.py", line 139, in attach_filter
    s.setsockopt(SOL_SOCKET, SO_ATTACH_FILTER, bpfh)
  File "/usr/lib/python2.7/socket.py", line 224, in meth
    return getattr(self._sock,name)(*args)
socket.error: [Errno 99] Protocol not available
root@UBNT-Pro /root [#]#

重现步骤

  1. 安装scapy到openwrt下
wget http://image.oakridge.vip:8000/images/ap/ap152/scapy_2.3.1-1_ar71xx.ipk /tmp/1.ipk
opkg install /tmp/1.ipk
  1. 运行如下arp monitor示例代码
#! /usr/bin/env python
from scapy.all import *

def arp_monitor_callback(pkt):
    if ARP in pkt and pkt[ARP].op in (1,2): #who-has or is-at
        return pkt.sprintf("%ARP.hwsrc% %ARP.psrc%")

sniff(prn=arp_monitor_callback, filter="arp", store=0)

错误分析

WARNING: IPv6 support disabled in Python. Cannot load scapy IPv6 layers.
WARNING: can't import layer sctp: can't use AF_INET6, IPv6 is disabled
WARNING: can't import layer ipsec: can't use AF_INET6, IPv6 is disabled
Traceback (most recent call last):
  File "./dd.sh", line 8, in <module>
    sniff(prn=arp_monitor_callback, filter="arp", store=0)
  File "/usr/lib/python2.7/site-packages/scapy/sendrecv.py", line 561, in sniff
    s = L2socket(type=ETH_P_ALL, *arg, **karg)
  File "/usr/lib/python2.7/site-packages/scapy/arch/linux.py", line 471, in __init__
    attach_filter(self.ins, filter)
  File "/usr/lib/python2.7/site-packages/scapy/arch/linux.py", line 139, in attach_filter
    s.setsockopt(SOL_SOCKET, SO_ATTACH_FILTER, bpfh) ### 可以看到是setsockeopt(SO_ATTACH_FILTER)返回的错误
  File "/usr/lib/python2.7/socket.py", line 224, in meth
    return getattr(self._sock,name)(*args)
socket.error: [Errno 99] Protocol not available
root@UBNT-Pro /root [#]#

分析scapy/arch/linux.py代码发现大致如下流程

目前使用tcpdump -i eth0 'arp’没有问题,于是分析strace tcpdump -i eth0 'arp’的系统调用过程如下:

root@testondesk /usr/lib/python2.7/site-packages/scapy/arch [#]# strace tcpdump 
-i eth0 'icmp'
execve("/usr/sbin/tcpdump", ["tcpdump", "-i", "eth0", "icmp"], [/* 38 vars */]) = 0
old_mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|0x4000000, -1, 0) = 0x77755000
stat("/etc/ld.so.cache", 0x7fc87fc8)    = -1 ENOENT (No such file or directory)
open("/lib/libpcap.so.1.1", O_RDONLY)   = -1 ENOENT (No such file or directory)
open("/usr/lib/libpcap.so.1.1", O_RDONLY) = 3

….


setsockopt(3, SOL_SOCKET, SO_ATTACH_FILTER, "\0\1\0\0ws\261\10", 8) = 0
fcntl64(3, F_GETFL)                     = 0x2 (flags O_RDWR)
fcntl64(3, F_SETFL, O_RDWR|O_NONBLOCK)  = 0
recv(3, 0x7fc880cc, 1, MSG_TRUNC)       = -1 EAGAIN (Resource temporarily unavailable)
fcntl64(3, F_SETFL, O_RDWR)             = 0
setsockopt(3, SOL_SOCKET, SO_ATTACH_FILTER, "\0\6\1\33\0\315[\370", 8) = 0 ################# 这个应该就是跟scapy调用一样的,传入了一个结构体,其中uint size=6, 然后是一个指针.

write(2, "tcpdump", 7tcpdump)                  = 7
write(2, ": verbose output suppressed, use"..., 68: verbose output suppressed, use -v or -vv for full protocol decode
) = 68
write(2, "listening on ", 13listening on )           = 13
write(2, "eth0", 4eth0)                     = 4
write(2, ", link-type ", 12, link-type )            = 12
write(2, "EN10MB", 6EN10MB)                   = 6
write(2, " (", 2 ()                       = 2
write(2, "Ethernet", 8Ethernet)

比较scapy和tcpdump的系统调用参数后,发现scapy里参考内核头文件,自己定义了很多宏,于是失去了跨平台的支持。其中SOL_SOCKET在mips/alpha/sparc下是0xFFFF,而scapy在linux.py里定义为了1.

alpha/include/asm/socket.h:8: * Note: we only bother about making the SOL_SOCKET options
alpha/include/asm/socket.h:13:#define SOL_SOCKET        0xffff
arm/include/asm/socket.h:7:#define SOL_SOCKET   1
avr32/include/asm/socket.h:7:#define SOL_SOCKET 1
cris/include/asm/socket.h:9:#define SOL_SOCKET  1
frv/include/asm/socket.h:7:#define SOL_SOCKET   1
h8300/include/asm/socket.h:7:#define SOL_SOCKET 1
ia64/include/asm/socket.h:16:#define SOL_SOCKET 1
m32r/include/asm/socket.h:7:#define SOL_SOCKET  1
m68k/include/asm/socket.h:7:#define SOL_SOCKET  1
mips/include/asm/socket.h:19:#define SOL_SOCKET 0xffff
mn10300/include/asm/socket.h:7:#define SOL_SOCKET       1
parisc/include/asm/socket.h:7:#define SOL_SOCKET        0xffff
powerpc/include/asm/socket.h:14:#define SOL_SOCKET      1
s390/include/asm/socket.h:15:#define SOL_SOCKET 1
sparc/include/asm/socket.h:7:#define SOL_SOCKET 0xffff
um/drivers/port_user.c:121:     if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &arg, sizeof(arg)) < 0) {
um/drivers/umcast_user.c:87:    if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) < 0) {
um/os-Linux/drivers/tuntap_user.c:117:  if ((cmsg->cmsg_level != SOL_SOCKET) ||
um/os-Linux/file.c:494: if ((cmsg->cmsg_level != SOL_SOCKET) ||
xtensa/include/asm/socket.h:17:#define SOL_SOCKET       1

scapy/arch/linux.py的代码如下,这种做法没有考虑不同硬件体系的问题。

# From bits/ioctls.h
SIOCGIFHWADDR  = 0x8927          # Get hardware address    
SIOCGIFADDR    = 0x8915          # get PA address          
SIOCGIFNETMASK = 0x891b          # get network PA mask     
SIOCGIFNAME    = 0x8910          # get iface name          
SIOCSIFLINK    = 0x8911          # set iface channel       
SIOCGIFCONF    = 0x8912          # get iface list          
SIOCGIFFLAGS   = 0x8913          # get flags               
SIOCSIFFLAGS   = 0x8914          # set flags               
SIOCGIFINDEX   = 0x8933          # name -> if_index mapping
SIOCGIFCOUNT   = 0x8938          # get number of devices
SIOCGSTAMP     = 0x8906          # get packet timestamp (as a timeval)

# From if.h
IFF_UP = 0x1               # Interface is up.
IFF_BROADCAST = 0x2        # Broadcast address valid.
IFF_DEBUG = 0x4            # Turn on debugging.
IFF_LOOPBACK = 0x8         # Is a loopback net.
IFF_POINTOPOINT = 0x10     # Interface is point-to-point link.
IFF_NOTRAILERS = 0x20      # Avoid use of trailers.
IFF_RUNNING = 0x40         # Resources allocated.
IFF_NOARP = 0x80           # No address resolution protocol.
IFF_PROMISC = 0x100        # Receive all packets.

# From netpacket/packet.h
PACKET_ADD_MEMBERSHIP  = 1
PACKET_DROP_MEMBERSHIP = 2
PACKET_RECV_OUTPUT     = 3
PACKET_RX_RING         = 5
PACKET_STATISTICS      = 6
PACKET_MR_MULTICAST    = 0
PACKET_MR_PROMISC      = 1
PACKET_MR_ALLMULTI     = 2

# From bits/socket.h
SOL_PACKET = 263
# From asm/socket.h
SO_ATTACH_FILTER = 26
SOL_SOCKET = 1          #########只考虑了x86的构架

# From net/route.h
RTF_UP = 0x0001  # Route usable
RTF_REJECT = 0x0200

# From if_packet.h
PACKET_HOST = 0  # To us
PACKET_BROADCAST = 1  # To all
PACKET_MULTICAST = 2  # To group
PACKET_OTHERHOST = 3  # To someone else
PACKET_OUTGOING = 4  # Outgoing of any type
PACKET_LOOPBACK = 5  # MC/BRD frame looped back
PACKET_USER = 6  # To user space
PACKET_KERNEL = 7  # To kernel space
PACKET_FASTROUTE = 6  # Fastrouted frame
# Unused, PACKET_FASTROUTE and PACKET_LOOPBACK are invisible to user space

Fix

scapy/arch/linux.py

- s.setsockopt(SOL_SOCKET, SO_ATTACH_FILTER, bpfh)
+ s.setsockopt(socket.SOL_SOCKET, SO_ATTACH_FILTER, bpfh)

后续

提了个bug给scapy的维护者,马上收到了回复,因为有些平台没有socket.SOL_SOCKET的定义,他给出了更好的解决patch
https://github.com/secdev/scapy/pull/1014/files
如下:

@@ -66,7 +66,7 @@
SOL_PACKET = 263
# From asm/socket.h
SO_ATTACH_FILTER = 26
-SOL_SOCKET = 1
+SOL_SOCKET = socket.SOL_SOCKET if hasattr(socket, "SOL_SOCKET") else 1

nicephil@gmail.com – 2017-12-29

posted on 2017-12-29 10:43  nicephil  阅读(744)  评论(0编辑  收藏  举报

导航