Pierre LALET | 22 Aug 22:54
Favicon

RPC and ... states

Hi all,

I'm not using trac for this it, because I think it's a good idea to
discuss it there.

To add an (early) support for SunRPC protocol, I had to implement
something to keep states of previously seen packets (and I think this
might be usefull for many other uses).

I've tried to write something as general as possible, but I'm not
neither a Python nor a Scapy expert, so this is probably not perfect.

Here is a short description of this state system:

A new method has been added to the generic Packet() class, called
update_states(). If this method is not overloaded, it only calls the
same method on its payload.

When called for a packet that needs performing state updates, it does it
stuff before calling the same method on its payload.

The update_states() method is called by rdpcap() and sniff() when the
new 'update' optional parameter is set to True.

The attached patch provides the implementation and an example of use,
the SunRPC* packet classes --- see SunRPCCall.update_states() and
SunRPCReply.guess_payload_class() where the states are used to try to
guess the payload type (this information is only in the Call and is not
reminded in the Reply).

NB: the RPC support included in this patch is really an *early* support.
This is sent here to check whether the state implementation is correct
or not.

Hope this migth be usefull. Regards,

Pierre
--- scapy.py.official	2006-08-11 14:47:52.000000000 +0200
+++ scapy.py	2006-08-22 22:35:09.000000000 +0200
@@ -1886,6 +1886,22 @@ try:
 except IOError:
     log_loading.info("Can't open /etc/services file")

+RPC_PROGRAMS={}
+try:
+    f=open("/etc/rpc")
+    for l in f:
+        try:
+            if l[0] in ["#","\n"]:
+                continue
+            lt = tuple(re.split(spaces, l))
+            if len(lt) < 2:
+                continue
+            RPC_PROGRAMS.update({lt[0]:int(lt[1])})
+        except:
+            log_loading.info("Couldn't parse one line from rpc file (" + l + ")")
+    f.close()
+except IOError,msg:
+    log_loading.info("Can't open /etc/rpc file")

 
 ###########
@@ -5618,8 +5634,11 @@ A side effect is that, to obtain "{" and
         pc = self.payload.command()
         if pc:
             c += "/"+pc
-        return c                    
-                       
+        return c
+    
+    def update_states(self):
+        if type(self.payload) is not NoPayload:
+            self.payload.update_states()

         

@@ -7446,6 +7465,102 @@ class SebekV2Sock(SebekV3Sock):
         else:
             return self.sprintf("Sebek v2 socket (%SebekV2Sock.command%)")

+
+class SunRPC(Packet):
+    name = "SunRPC"
+    longname = "Sun Remote Procdeure Call"
+    fields_desc = [ BitField("lastfrag", 1, 1),
+                    #FieldLenField("fragment-length", None, "", fmt="B"),
+                    BitField("fraglen", 0, 31),
+                    XIntField("XID", 0),
+                    IntEnumField("msgtype", 0, {0:"Call", 1:"Reply"}) ]
+
+class SunRPCCall(Packet):
+    name = "SunRPCCall"
+    longname = "Sun RPC Call"
+    fields_desc = [ IntField("RPCversion", 2),
+                    IntEnumField("program", 100000, RPC_PROGRAMS),
+                    IntField("progversion", 0),
+                    IntEnumField("procedure", 0, {4:"Dump"}),
+                    IntEnumField("credflavor", 0, {0:"null"}),
+                    FieldLenField("credlen", None, "credentials", fmt="I"),
+                    StrLenField("credentials", "", "credlen"),
+                    IntEnumField("verifflavor", 0, {0:"null"}),
+                    FieldLenField("veriflen", None, "verifier", fmt="I"),
+                    StrLenField("verifier", "", "veriflen") ]
+    def update_states(self):
+        if type(self.underlayer) is SunRPC:
+            xid = self.underlayer.XID
+            s=states.RPCStates.get(xid)
+            if s is None:
+                s = RPCState()
+            else:
+                # FIXME : two calls with same xid ?
+                log_interactive.warning("redefining state for xid " + str(xid))
+            s.prog = self.program
+            s.request = self
+            states.RPCStates.update({xid: s})
+        if type(self.payload) is not NoPayload:
+            self.payload.update_states()
+    
+
+class SunRPCReply(Packet):
+    name = "SunRPCReply"
+    longname = "Sun RPC Reply"
+    fields_desc = [ IntEnumField("state", 0, {0:"accepted"}),
+                    IntEnumField("verifflavor", 0, {0:"null"}),
+                    FieldLenField("veriflen", None, "verifier", fmt="I"),
+                    StrLenField("verifier", "", "veriflen"),
+                    IntEnumField("acceptstate", 0, {0:"success"}) ]
+    def guess_payload_class(self, payload):
+        if self.underlayer is None:
+            return Raw
+        if states.RPCStates.has_key(self.underlayer.XID):
+            prog = states.RPCStates.get(self.underlayer.XID).prog
+            if prog == RPC_PROGRAMS.get('portmapper'):
+                return PortmapEntry
+            #elif prog == RPC_PROGRAMS.get('nfs'):
+            #    return NFS
+        transport = self.underlayer.underlayer
+        if type(transport) in [ TCP, UDP ]:
+            if type(transport) == TPC:
+                services = TCP_SERVICES
+            else:
+                services = UDP_SERVICES
+            if transport.sport == services.get('sunrpc'):
+                return PortmapEntry
+            #elif transport.sport == services.get('nfs'):
+            #    return NFS
+            else:
+                return Raw
+        else:
+            return Raw
+        
+
+class PortmapEntry(Packet):
+    name = "PortmapEntry"
+    longname = "Portmap Entry"
+    show_indent = 0
+    fields_desc = [ IntEnumField("valuefollows", 1, {1:"Yes", 0:"No"}),
+                    IntEnumField("program", 0, RPC_PROGRAMS),
+                    IntField("version", 0),
+                    IntEnumField("protocol", 0, IP_PROTOS),
+                    IntField("port", 0)
+                    ]
+    def guess_payload_class(self, payload):
+        if len(payload) >= 4 and struct.unpack("!I", payload[:4])[0] == 1:
+            return PortmapEntry
+        else:
+            return PortmapEnd
+
+
+class PortmapEnd(Packet):
+    name = "PortmapEnd"
+    longname = "Portmap End"
+    show_indent = 0
+    fields_desc = [ IntEnumField("valuefollows", 0, {1:"Yes", 0:"No"}) ]
+
+
 class MGCP(Packet):
     name = "MGCP"
     longname = "Media Gateway Control Protocol"
@@ -8404,7 +8519,12 @@ layer_bonds = [ ( Dot3,   LLC,      { } 

                 ( NetflowHeader, NetflowHeaderV1, { "version" : 1 } ),
                 ( NetflowHeaderV1, NetflowRecordV1, {} ),
-
+                (UDP, SunRPC, {"dport":111}),
+                (UDP, SunRPC, {"sport":111}),
+                (TCP, SunRPC, {"dport":111}),
+                (TCP, SunRPC, {"sport":111}),
+                (SunRPC, SunRPCCall, {"msgtype":0}),
+                (SunRPC, SunRPCReply, {"msgtype":1}),
                 ]

 for l in layer_bonds:
@@ -9306,10 +9426,11 @@ linktype: force linktype value
 endianness: "<" or ">", force endianness"""
     PcapWriter(filename, *args, **kargs).write(pkt)

-def rdpcap(filename, count=-1):
+def rdpcap(filename, count=-1, update=True):
     """Read a pcap file and return a packet list
-count: read only <count> packets"""
-    return PcapReader(filename).read_all(count=count)
+count: read only <count> packets
+update: run update_states() method for each packet"""
+    return PcapReader(filename).read_all(count=count, update=update)

 class PcapReader:
     """A stateful pcap reader
@@ -9381,7 +9502,7 @@ class PcapReader:
             callback(p)
             p = self.read_packet()

-    def read_all(self,count=-1):
+    def read_all(self,count=-1,update=True):
         """return a list of all packets in the pcap file
         """
         res=[]
@@ -9390,6 +9511,8 @@ class PcapReader:
             p = self.read_packet()
             if p is None:
                 break
+            if update:
+                p.update_states()
             res.append(p)
         return PacketList(res,name = os.path.basename(self.filename))

@@ -10089,9 +10212,9 @@ def nmap_sig2txt(sig):
 ###################

 
-def sniff(count=0, store=1, offline=None, prn = None, lfilter=None, L2socket=None, timeout=None,
*arg, **karg):
+def sniff(count=0, store=1, offline=None, prn = None, lfilter=None, update=True, L2socket=None,
timeout=None, *arg, **karg):
     """Sniff packets
-sniff([count=0,] [prn=None,] [store=1,] [offline=None,] [lfilter=None,] + L2ListenSocket args) ->
list of packets
+sniff([count=0,] [prn=None,] [store=1,] [offline=None,] [lfilter=None,] [update=False,] +
L2ListenSocket args) -> list of packets

   count: number of packets to capture. 0 means infinity
   store: wether to store sniffed packets or discard them
@@ -10102,6 +10225,7 @@ lfilter: python function applied to each
          if further action may be done
          ex: lfilter = lambda x: x.haslayer(Padding)
 offline: pcap file to read packets from, instead of sniffing them
+update:  wether to run update_states() method when a packet is sniffed or not 
 timeout: stop sniffing after a given time (default: None)
 L2socket: use the provided L2socket
     """
@@ -10138,6 +10262,8 @@ L2socket: use the provided L2socket
                     r = prn(p)
                     if r is not None:
                         print r
+                if update:
+                    p.update_states()
                 if count > 0 and c >= count:
                     break
         except KeyboardInterrupt:
@@ -11352,6 +11478,37 @@ warning_threshold : how much time betwee

 conf=Conf()

+class StatesClass(ConfClass):
+    def __str__(self):
+        s=""
+        keys = self.__class__.__dict__.copy()
+        keys.update(self.__dict__)
+        keys = keys.keys()
+        keys.sort()
+        for i in keys:
+            if i[0] != "_":
+                r = repr(getattr(self, i))
+                if len(r) > 20:
+                    r = r[:17] + "..."
+                s += "%-10s = %s\n" % (i, r)
+        return s[:-1]
+
+class States(StatesClass):
+    """This object contains the states of scapy from previously dissected packets.
+    For now, it will be used for RPC meta informations (some infos about the response packets can only be found
in the request packet).
+"""
+    def __init__(self):
+        self.RPCStates = {}
+
+class RPCState(StatesClass):
+    prog=0
+    request=''
+    response=''
+
+
+states=States()
+
+
 if PCAP:
     conf.L2listen=L2pcapListenSocket
     if DNET:

---------------------------------------------------------------------
Desinscription: envoyez un message a: scapy.ml-unsubscribe <at> secdev.org
Pour obtenir de l'aide, ecrivez a: scapy.ml-help <at> secdev.org

Gmane