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 [#]#
重现步骤
- 安装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
- 运行如下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