I am trying to understand how to add a custom dissector in Scapy. I am using Python 3.4 and Scapy3 if that has any bearing on the result.
I have a stupid class, and the packet.show2() command correctly renders the nested packet. But I can not access the new Layers field values.
Scary Class and bind_layer follows...
from scapy.all import *
#Create simple Class
class DUMBO(Packet):
fields_desc = [
ShortField('ears',0),
ShortField('legs',0),
ShortField('trunk',0)
]
#Inform TCP that ports 9898 are this protocol
bind_layers(TCP, DUMBO, sport=9898, dport=9898)
I make a packet like this
#Make a Packet
pack=IP()/TCP(sport=9898, dport=9898)/Raw(load=b'\x00\x02\x00\x04\x00\x01')
Looking at the Packet I have created using ls yields
version : BitField = 4 (4)
ihl : BitField = None (None)
tos : XByteField = 0 (0)
len : ShortField = None (None)
id : ShortField = 1 (1)
flags : FlagsField = 0 (0)
frag : BitField = 0 (0)
ttl : ByteField = 64 (64)
proto : ByteEnumField = 6 (0)
chksum : XShortField = None (None)
src : Emph = '127.0.0.1' (None)
dst : Emph = '127.0.0.1' ('127.0.0.1')
options : PacketListField = [] ([])
--
sport : ShortEnumField = 9898 (20)
dport : ShortEnumField = 9898 (80)
seq : IntField = 0 (0)
ack : IntField = 0 (0)
dataofs : BitField = None (None)
reserved : BitField = 0 (0)
flags : FlagsField = 2 (2)
window : ShortField = 8192 (8192)
chksum : XShortField = None (None)
urgptr : ShortField = 0 (0)
options : TCPOptionsField = {} ({})
--
load : StrField = b'\x00\x02\x00\x04\x00\x01' (b'')
And display it using Show2 it all looks good
pack.show2()
###[ IP ]###
version = 4
ihl = 5
tos = 0x0
len = 46
id = 1
flags =
frag = 0
ttl = 64
proto = tcp
chksum = 0x7cc7
src = 127.0.0.1
dst = 127.0.0.1
\options \
###[ TCP ]###
sport = monkeycom
dport = monkeycom
seq = 0
ack = 0
dataofs = 5
reserved = 0
flags = S
window = 8192
chksum = 0x447f
urgptr = 0
options = []
###[ DUMBO ]###
ears = 2
legs = 4
trunk = 1
I now want to access the DUMBO Layer fields
But
PACK[DUMBO].ears
Is not correct - as the packet when displayed as pack.show() still has the Payload as Raw....
What am I missing ??
Ok - This is my solution....
pack=IP()/TCP(sport=19898, dport=19898)/Raw(load=b'\x00\x02\x00\x04\x00\x01')
#Cast this packet back
pack=IP(bytes(pack))
pack.show2()
pack.show()
if DUMBO in pack:
print('Elephant in the house')
print('Ears -> {}'.format(pack[DUMBO].ears))
If anyone else can improve on this I would be happy on seeing the solution.
Note: I'm just getting started with Scapy, so I can't promise this is the correct/only way to go.
As per Documentation: Add new protocols to Scapy, put the code with the protocol definition in a seperate python file. Make sure you also set the required headers. Then place that file either in scapy/layers or scapy/contrib.
After that, the protocol can be loaded with load_layer(...) or load_contrib(...) where you plan on using it.
For DUMBO we'll go with contrib.
dumbo.py:
# scapy.contrib.description = Dumbo the elephant
# scapy.contrib.status = loads
from scapy.packet import Packet, bind_layers
from scapy.fields import ShortField
from scapy.layers.inet import TCP
#Create simple Class
class DUMBO(Packet):
fields_desc = [
ShortField('ears',0),
ShortField('legs',0),
ShortField('trunk',0)
]
#Inform TCP that ports 9898 are this protocol
bind_layers(TCP, DUMBO, sport=9898, dport=9898)
Now let's use it:
$ scapy
>>> load_contrib("dumbo")
>>> pack1=IP()/TCP(sport=9898, dport=9898)/DUMBO(b'\x00\x02\x00\x04\x00\x01')
>>> pack2=IP()/TCP(sport=9898, dport=9898)/DUMBO(ears=2, legs=4, trunk=1)
>>> pack1
<IP frag=0 proto=tcp |<TCP sport=9898 dport=9898 |<DUMBO ears=2 legs=4 trunk=1 |>>>
>>> pack2
<IP frag=0 proto=tcp |<TCP sport=9898 dport=9898 |<DUMBO ears=2 legs=4 trunk=1 |>>>
>>> pack1[DUMBO].ears
2
>>> pack2[DUMBO].ears
2
Hope this helps somebody who stumbles upon this question.
Versions used: Python v3.8.5 ; Scapy v2.4.5
Related
I'm trying to parse question field in a DNS packet where I can read domain and DNS response from a DNS server. I can extract a DNS header, but I'm having trouble to parse the question field because the size of the data is unknown.
I follow this example, but the part of extracting the question field is not working.
What I need is someone to show me the way to do it properly.
I have this code where everything is right...
This is my code:
#!/usr/bin/env python3
from socket import *
import struct
import binascii
def ethernet_frame(raw_data):
mac_dest, mac_src, protocol = struct.unpack('! 6s 6s H',
raw_data[:14])
return byte_to_hex_mac(mac_dest), byte_to_hex_mac(mac_src),
htons(protocol), raw_data[14:]
def byte_to_hex_mac(mac_bytes):
addr = binascii.hexlify(mac_bytes).decode("ascii")
return ":".join([addr[i:i+2] for i in range(0,12,2)])
def data_packet_udp(data):
tuple_data_udp = struct.unpack('! H H H H', data[:8])
port_src = tuple_data_udp[0]
port_dest = tuple_data_udp[1]
udp_len = tuple_data_udp[2]
udp_checksum = tuple_data_udp[3]
return port_src, port_dest, udp_len, udp_checksum, data[8:]
def data_packet_ipv4(data):
tuple_data_ipv4 = struct.unpack("!BBHHHBBH4s4s", data[:20])
version = tuple_data_ipv4[0]
header_len = version >> 4
type_service = tuple_data_ipv4[1]
length_total = tuple_data_ipv4[2]
identification = tuple_data_ipv4[3]
offset_fragment = tuple_data_ipv4[4]
ttl = tuple_data_ipv4[5]
protocols = tuple_data_ipv4[6]
checksum_header = tuple_data_ipv4[7]
ip_src = inet_ntoa(tuple_data_ipv4[8])
ip_dest = inet_ntoa(tuple_data_ipv4[9])
length_header_bytes = (version & 15) * 4
return version, header_len, type_service, + \
length_total, identification, offset_fragment, + \
ttl, protocols, checksum_header, ip_src, ip_dest,
data[length_header_bytes:]
def data_packet_dns(data):
tuple_data_dns = struct.unpack('!HHHHHH', data[:12])
identification = tuple_data_dns[0]
flags = tuple_data_dns[1]
number_queries = tuple_data_dns[2]
number_response = tuple_data_dns[3]
number_authority = tuple_data_dns[4]
number_additional = tuple_data_dns[5]
qr = (flags & 32768) != 0
opcode = (flags & 30720 ) >> 11
aa = (flags & 1024) != 0
tc = (flags & 512) != 0
rd = (flags & 256) != 0
ra = (flags & 128) != 0
z = (flags & 112) >> 4
rcode = flags & 15
return identification, flags, number_queries, number_response, + \
number_authority, number_additional, qr, opcode, aa, tc, + \
rd, ra, z, rcode
sock = socket(AF_PACKET, SOCK_RAW, ntohs(0x0003))
while True:
raw_dados, addr = sock.recvfrom(65536)
mac_dest, mac_src, protocol, payload = ethernet_frame(raw_dados)
if protocol == 8:
( version, header_len, type_service,
length_total, identification, offset_fragment,
ttl, protocols, checksum_header,
ip_src, ip_dest, data ) = data_packet_ipv4(payload)
if protocols == 17:
port_src, port_dest, udp_len, udp_checksum, data =
data_packet_udp(data)
print("--------- HEADER UDP ----------")
print("Port Source : {}".format(port_src))
print("Port Dest : {}".format(port_dest))
print("UDP Length : {}".format(udp_len))
print("UDP Checksum : {}\n".format(udp_checksum))
if port_src == 53 or port_dest == 53:
(identification, flags, number_queries, \
number_response,number_authority,number_additional, \
qr, opcode, aa, tc, rd, ra, z, rcode) = data_packet_dns(data)
print("\t--------- HEADER DNS ----------")
print("\tidentification : {}".format(identification))
print("\tFlags : {}".format(flags))
print("\tnumber_queries : {}".format(number_queries))
print("\tnumber_response : {}".format(number_response))
print("\tnumber_authority : {}".format(number_authority))
print("\tnumber_additional : {}".format(number_additional))
print("\tQr : {}".format(qr))
print("\tOpcode : {}".format(opcode))
print("\tAA : {}".format(aa))
print("\tTC : {}".format(tc))
print("\tRD : {}".format(rd))
print("\tRA : {}".format(ra))
print("\tZ : {}".format(z))
print("\tRCODE : {}".format(rcode))
I tried to write TCP RST atack with Scapy,but my code doesn't work.Please,help me to solve my problem.
from scapy.all import *
def poison(packet):
packet[TCP].flags='RST'
sendp(packet)
sniff(filter='tcp',prn=poison)
There's something wrong with flags,I think.There's an error:
Traceback (most recent call last):
File "Univer.py", line 6, in
sniff(filter='tcp',prn=poison)
File "/usr/lib/pymodules/python2.6/scapy/sendrecv.py", line 559, in sniff
r = prn(p)
File "Univer.py", line 3, in poison
packet[TCP].flags='RST'
File "/usr/lib/pymodules/python2.6/scapy/packet.py", line 186, in
setattr
self.setfieldval(attr,val)
File "/usr/lib/pymodules/python2.6/scapy/packet.py", line 175, in
setfieldval
self.fields[attr] = any2i(self, val)
File "/usr/lib/pymodules/python2.6/scapy/fields.py", line 785, in any2i
y |= 1 << self.names.index(i)
ValueError: substring not found
The correct way to set the TCP flags in Scapy is to use the short (one letter) form packet[TCP].flags = 'R'. With the current development version of Scapy, you can get the accepted flags using ls():
>>> ls(TCP, verbose=True)
sport : ShortEnumField = (20)
dport : ShortEnumField = (80)
seq : IntField = (0)
ack : IntField = (0)
dataofs : BitField (4 bits) = (None)
reserved : BitField (3 bits) = (0)
flags : FlagsField (9 bits) = (2)
F, S, R, P, A, U, E, C, N
window : ShortField = (8192)
chksum : XShortField = (None)
urgptr : ShortField = (0)
options : TCPOptionsField = ({})
I've written a function for scapy which helps dissecting a packet. The function receives a raw packet and returns the class needed to dissect the contained data. The function should work, yet it somehow returns the wrong classes. Now I tested something. I just added the line
struct.unpack("!B", packet)
at the beginning of the function, which actually does nothing, but - and that's what's strange to me - somehow this makes the function do what it's supposed to. How can this be? I've tested it multiple times. Without this one line at the beginning of the function it does not work properly, with this line, however, it behaves like it should, although the output seems a bit distorted and I don't know why either.
Output with the line. It's distorted, some lines are at the wrong place and some are even duplicated.
###[ Ethernet ]###
src = 00:30:de:09:c7:76
dst = ff:ff:ff:ff:ff:ff
type = 0x800
src = 00:30:de:09:c7:83
###[ IP ]###
type = 0x800 version = 4L
ihl = 5L
###[ IP ]###
tos = 0x0
version = 4L
len = 52
ihl = 5L
id = 312
tos = 0x0 flags = DF
frag = 0L
len = 52
ttl = 64
id = 91
proto = udp
flags = DF
frag = 0L
ttl = 64
proto = udp
chksum = 0xb52d
chksum = 0xb60b
src = 192.168.1.4
src = 192.168.1.3
dst = 192.168.1.255 dst = 192.168.1.255
\options \
\options \
###[ UDP ]###
###[ UDP ]### sport = 47808
dport = 47808
sport = 47808
len = 32
dport = 47808
chksum = 0x6cec
len = 32
###[ BVLC ]###
chksum = 0x79eb
type = 0x81
###[ BVLC ]###
function = ORIGINAL_BROADCAST_NPDU
type = 0x81
length = 24
function = ORIGINAL_BROADCAST_NPDU###[ NPDU ]###
version = 1
length = 24
control = 32L
###[ NPDU ]###
dnet = 65535
version = 1
dlen = 0 control = 32L
hopCount = 255
dnet = 65535
###[ APDU ]###
dlen = 0
pduType = UNCONFIRMED_SERVICE_REQUEST
hopCount = 255
reserved = None
###[ APDU ]###
serviceChoice= I_AM
pduType = UNCONFIRMED_SERVICE_REQUEST
\tagsField \
reserved = None
|###[ Raw ]###
serviceChoice= I_AM
| load = '\xc4\x02\t\xc7\x83"\x01\xe0\x91\x00!\xde'
\tagsField \
|###[ Raw ]###
| load = '\xc4\x02\t\xc7v"\x01\xe0\x91\x00!\xde'
Without the line the output looks like this, but the data is actually wrong. The Raw layer should actually be an APDU layer.
###[ Ethernet ]###
dst = ff:ff:ff:ff:ff:ff
src = 00:30:de:09:c7:83
type = 0x800
###[ IP ]###
version = 4L
ihl = 5L
tos = 0x0
len = 52
id = 105
flags = DF
frag = 0L
ttl = 64
proto = udp
chksum = 0xb5fd
src = 192.168.1.3
dst = 192.168.1.255
\options \
###[ UDP ]###
sport = 47808
dport = 47808
len = 32
chksum = 0x6cec
###[ BVLC ]###
type = 0x81
function = ORIGINAL_BROADCAST_NPDU
length = 24
###[ NPDU ]###
version = 1
control = 32L
dnet = 65535
dlen = 0
hopCount = 255
###[ Raw ]###
load = '\x10\x00\xc4\x02\t\xc7\x83"\x01\xe0\x91\x00!\xde'
The function looks like this
def guessBACnetTagClass(packet, **kargs):
""" Returns the correct BACnetTag Class needed to dissect
the current tag
#type packet: binary string
#param packet: the current packet
#type cls: class
#return cls: the correct class for dissection
"""
struct.unpack("!B", packet) # <------ with this line it works
# Convert main tag Byte to binary format
tagByteBinary = "{0:08b}".format(int(struct.unpack("!B", packet[0])[0]))
# Extract information from main tag Byte
tagNumber = int(tagByteBinary[0:4], 2)
tagClass = BACnetTagClass.revDict()[int(tagByteBinary[4:5], 2)]
lengthValueType = int(tagByteBinary[5:8], 2)
# Tag is Application Tag
if tagClass == BACnetTagClass.APPLICATION:
clsName = BACnetApplicationTagClasses[tagNumber]
cls = globals()[clsName]
print cls
return cls(packet, **kargs)
# Tag is Context Specific Tag
if tagClass == BACnetTagClass.CONTEXT_SPECIFIC:
if lengthValueType == BACnetConstructedLVT.OPENING_TAG:
cls = BACnetTag_Opening
if lengthValueType == BACnetConstructedLVT.CLOSING_TAG:
cls = BACnetTag_Closing
# Check if a class was selected
if cls is not None:
print cls
return cls(packet, **kargs)
Does this make any sense? How can executing this one function change output this much?
I have an application which read live SIP Packets and decode information in real time.
when packet is small UDP/TCP is able to get the information, but when packet is large, it arrives in different segments:
The following is an extract from Wireshark:
3 Reassembled TCP Segments (3331 bytes): #1(1448), #3(1448), #5(435)
Frame: 1, payload: 0-1447 (1448 bytes)
Frame: 3, payload: 1448-2895 (1448 bytes)
Frame: 5, payload: 2896-3330 (435 bytes)
Segment count: 3
Reassembled TCP length: 3331
My application believes there is a new SIP Packet for each fragment and fails to decode info.
How can I do this? I need to read the packet, assemble all sip message if fragmented and pass the info to my control module. This is my current code:
s = socket.socket( socket.AF_PACKET , socket.SOCK_RAW , socket.ntohs(0x0003))
while (True):
packet = s.recvfrom(65565)
#packet string from tuple
packet = packet[0]
#parse ethernet header
eth_length = 14
eth_header = packet[:eth_length]
eth = unpack('!6s6sH' , eth_header)
eth_protocol = socket.ntohs(eth[2])
if eth_protocol == 8 :
#Parse IP header
#take first 20 characters for the ip header
ip_header = packet[eth_length:20+eth_length]
#now unpack them :)
iph = unpack('!BBHHHBBH4s4s' , ip_header)
version_ihl = iph[0]
version = version_ihl >> 4
ihl = version_ihl & 0xF
iph_length = ihl * 4
ttl = iph[5]
protocol = iph[6]
s_addr = socket.inet_ntoa(iph[8]);
d_addr = socket.inet_ntoa(iph[9]);
#TCP protocol
if protocol == 6 :
t = iph_length + eth_length
tcp_header = packet[t:t+20]
#now unpack them :)
tcph = unpack('!HHLLBBHHH' , tcp_header)
source_port = tcph[0]
dest_port = tcph[1]
sequence = tcph[2]
acknowledgement = tcph[3]
doff_reserved = tcph[4]
tcph_length = doff_reserved >> 4
if dest_port == sipLocatorConfig.SIP_PORT:
print
logging.info("------------------------------------------------------SIP Packet detected------------------------------------------------------")
h_size = eth_length + iph_length + tcph_length * 4
data_size = len(packet) - h_size
#get data from the packet
data = packet[h_size:]
ipInfo = {}
ipInfo['protocol'] = protocol
ipInfo['s_addr'] = str(s_addr)
ipInfo['source_port'] = source_port
ipInfo['d_addr'] = str(d_addr)
ipInfo['dest_port'] = dest_port
processSipPacket(data,ipInfo)
I believe this is what I wrote bufsock for:
http://stromberg.dnsalias.org/~strombrg/bufsock.html
It allows you to say "give me all the data until the next null" or "give me the next 64 bytes" and similar things. It deals intelligently with fragmented and aggregated packets.
Unlike many such tools, it does not require that you have bufsock at both the producer and the consumer - you can use it fine on one end and not the other. It is a little bit like stdio for sockets, in python.
It works on CPython 2.x, CPython 3.x, Pypy, Pypy3 (which is still beta at this time) and Jython.
I'm trying to create some scapy layers and want them to adapt their size on the fly. I use the following code:
class Foo(Packet):
name = "Testpacket"
fields_desc = [
ByteField("length", None),
ByteField("byte2", None),
ByteField("byte3", None),
ByteField("byte4", None),
ByteField("byte5", None),
ByteField("byte6", None),
ByteField("byte7", None),
ByteField("byte8", None),
ByteField("byte9", None),
ByteField("byte10", None),
ByteField("byte11", None)
]
def post_build(self, p, pay):
if self.length is None:
if self.byte11 is not None:
x = 0xa
elif self.byte10 is not None:
x = 0x9
elif self.byte9 is not None:
x = 0x8
elif self.byte8 is not None:
x = 0x7
elif self.byte7 is not None:
x = 0x6
elif self.byte6 is not None:
x = 0x5
elif self.byte5 is not None:
x = 0x4
elif self.byte4 is not None:
x = 0x3
elif self.byte3 is not None:
x = 0x2
elif self.byte2 is not None:
x = 0x1
print "byte2 is set, x is %s"%(x,)
else:
x = 0x0
p = p[:0] + struct.pack(">b", x)
p += pay
return p
When I do the following in my scapy interpreter:
>>> aa=Foo(); aa.byte2=0x14; aa.show2();
I get:
>>> aa=Foo(); aa.byte2=0x14; aa.show2(); aa.show();
###[ Testpacket ]###
length= 1
byte2= None
byte3= None
byte4= None
byte5= None
byte6= None
byte7= None
byte8= None
byte9= None
byte10= None
byte11= None
###[ Testpacket ]###
length= None
byte2= 20
byte3= None
byte4= None
byte5= None
byte6= None
byte7= None
byte8= None
byte9= None
byte10= None
byte11= None
Now, according to my understanding, show2() should compute the length of the packet etc. In my case, this should set length and byte2. Unfortunatelly this is not the case. Any idea what I'm doing wrong? I have been searching the bug for several hours now, and I'm out of ideas :-S any suggestion would be welcome.
With best regards
Martin, your understanding is mistaken... .show2() computes the packet after assembly. .show() is not supposed to calculate the length... for example, with IP...
>>> from scapy.all import IP
>>> bar = IP(dst='4.2.2.2')/"Yo mama is ugly. So ugly. Aaahhhhhh my eyes"
results of .show2()...
>>> bar.show2()
###[ IP ]###
version = 4L
ihl = 5L
tos = 0x0
len = 65
id = 1
flags =
frag = 0L
ttl = 64
proto = ip
chksum = 0x6b45
src = 10.109.61.6
dst = 4.2.2.2
\options \
###[ Raw ]###
load = 'Yo mama is ugly. So ugly. Aaahhhhhh my eyes'
>>>
results of .show()... notice that ihl, len and chksum are None..
>>> bar.show()
###[ IP ]###
version = 4
ihl = None <-------
tos = 0x0
len = None <-------
id = 1
flags =
frag = 0
ttl = 64
proto = ip
chksum = None <-------
src = 10.109.61.6
dst = 4.2.2.2
\options \
###[ Raw ]###
load = 'Yo mama is ugly. So ugly. Aaahhhhhh my eyes'
>>>