1. secdev
  2. Untitled project
  3. scapy
  4. Pull requests

Pull requests

#54 Merged at 25a1ec7
Repository
6wind
Branch
ipsec
Repository
secdev
Branch
default

layers: full implementation of ipsec (esp & ah)

Author
  1. Robin Jarry
Reviewers
Description

Hello all,

Some of you may already know the old scapysec implementation by Frédéric Roudault. Unfortunately, this was for scapy 1.X and it was never adapted from 2.X. After spending some time trying to rebase it on the head of scapy, I decided to start from scratch.

While the old implementation works fine, I found some problems:

  • Overly complex system with a SAD object hacked into scapy.conf.setkey. Which does not cover the whole SP/SA mechanism of a real ipsec implementation anyways (like linux).
  • The user is forced to "know" the ipsec RFC when building ESP packets
  • No support for AH
  • And of course, does not work with scapy 2.X

I developed a re-implementation which has the following highlights:

  • Support for both ESP and AH
  • Simple SecurityAssociation object with 2 services "encrypt" and "decrypt". One can give any IP(v6) packet to encrypt and get a valid ESP or AH encrypted/authenticated packet in return. No complex "lookup" mechanism.
  • Has unit tests

Cheers, Robin

PS: I tried to add "ipsec" in scapy.config.Conf.load_layers but it seems to break the startup imports. No layer classes are available except ESP AH and SecurityAssociation. Maybe you can help?

Comments (12)

  1. Pierre Lalet

    Hi,

    You must not declare __all__ in your module, due to the way layers are loaded in Scapy.

    Can you just remove that line ? Once this is done, I think it would be OK to load your module by default, so you could update that pull request to add "ipsec" to the config.Conf.load_layers list.

    Thanks a lot for your work, and sorry for the delay.

  2. Robin Jarry author

    Hi Pierre,

    Sorry, for the delay, I didn't get any notification from BB :)

    I added 2 more commits to the PR. One to fix that __all__ problem, the other to stick to the RFC about padding. Feel free to squash the 3 commits when merging.

    By the way, I tried to run the unit tests again but all I get are errors saying that SecurityAssociation is not defined.

    Here's my command line:

    $ ./bin/UTscapy -t test/ipsec.uts
    ...
    NameError: name 'SecurityAssociation' is not defined
    ...
    

    I also tried to install scapy in a virtualenv thinking it would be a python path problem but I get errors too.

    $ virtualenv venv
    (venv)$ python setup.py install
    ...
    (venv)$ UTscapy -t test/ipsec.uts
    ...
    NameError: name 'IP' is not defined
    ...
    NameError: name 'IPv6' is not defined
    ...
    

    Is there something I missed?

  3. Daniel Steglich

    Hi, I just had a look at this great extension of scapy and already wrote some automated Network tests using this new layer. Works great!

    Do you see any possibility to add NAT-T layer between IP and ESP header? Just inserting a UDP Header and remove the UDP Header seems not to work.

  4. Robin Jarry author

    Hi Daniel,

    This may be because the IP checksum and next header are not recomputed. I'll try to incorporate a nat_t_header attribute to the SecurityAssociation class to manage this.

  5. Daniel Steglich

    Hi Robin,

    thank you for your work! I just have a small remark:

    It seems like UDP header lenght is not set correctly (wireshark complains about maleformed packet and decryption is not possible if using NAT-T)

    maybe replace line 798:

    del nat_t_header.len
    

    by

     nat_t_header.len = len(esp) + len(nat_t_header)
    
  6. Robin Jarry author

    That's weird, since the attribute is deleted it should be recomputed automatically when scapy converts the packet to bytes.

    But yes, this should fix the problem. Can you try it? If it works, I'll add another commit.

    1. Daniel Steglich

      maybe it does not when using srp() function? When using your code without modification, received frames look like this:

      ###[ Ethernet ]###
        dst       = 00:13:46:3a:17:7c
        src       = 00:1c:28:ff:2d:b0
        type      = 0x800
      ###[ IP ]###
           version   = 4L
           ihl       = 5L
           tos       = 0x0
           len       = 132
           id        = 1
           flags     = 
           frag      = 0L
           ttl       = 61
           proto     = udp
           chksum    = 0xc4a
           src       = 10.154.65.120
           dst       = 192.168.100.100
           \options   \
      ###[ UDP ]###
              sport     = 4500
              dport     = 53479
              len       = 8
              chksum    = 0x0
      ###[ Padding ]###
                 load      = '\xde\xad\xbe\xef\x00\x00\x00\x02\xca\xd7\xbb\x0e@&\xeb\xa7\x03\x9e#\xf2\xeeV5\xf4\xb1\x08\xc9D\xba(\x8aOQ\x96\x89\xf0\x93A%\xb0\xf2\x835u\x9e\x91\xa4Cq\x1c\xf6\x18>\xaen\xc3:\xb6\x92g\x92N\x93Sf\x00j\xe2\x96G?p/A\x9e&\xb0)\x90`\xb5\x8d\x97\x0f\x15\xd8K\xe2\xa9fqV\xc07YJ\xd7\xef\xd1\xad\xcc\xec\xe4\x93'
      

      when using modified code, it looks like this:

      ###[ Ethernet ]###
        dst       = 00:13:46:3a:17:7c
        src       = 00:1c:28:ff:2d:b0
        type      = 0x800
      ###[ IP ]###
           version   = 4L
           ihl       = 5L
           tos       = 0x0
           len       = 132
           id        = 1
           flags     = 
           frag      = 0L
           ttl       = 61
           proto     = udp
           chksum    = 0xc4a
           src       = 10.154.65.120
           dst       = 192.168.100.100
           \options   \
      ###[ UDP ]###
              sport     = 4500
              dport     = 53479
              len       = 112
              chksum    = 0x0
      ###[ Raw ]###
                 load      = '\xde\xad\xbe\xef\x00\x00\x00\x02\n6\x0eR\x1a\xdc\xc5\xe6Qx-P\xfc\xa7\xf8\xfc\xc1\x80g\xdc\x16\xfe\x8a\xcf^\xbb\xa0?a \xe1I\xb3\xe1\xec0\x96"\xcb\xde\xb1A\xc0g,\xf3\xa7\x82\xb7=0\xc2\xd1\xf2Q-\x98\xcf\xe6G\xbbEd\n\x13\xe0rS\x86\xedl2X1\xa9Y\xb5\r\xff\xf3a@\xca\x97\xeb\x94e{\xde>\xdd\xfca!~\xc7'
      

      But I'm stil not sure if it finaly works because as you can see, scapy does not interpret the ESP Part of the frame as ESP (btw. wireshark does) and if I call the decrypt function on this frame, i.e. like this:

                      recivepacket = sniff(iface="eth0", filter="proto UDP and dst port 53479", count=1, timeout=self.SENDTIMEOUT)             
                      sa = SecurityAssociation(ESP, spi=0xdeadbeef, crypt_algo='AES-CBC',crypt_key="sixteenbytes key", nat_t_header=UDP(sport=4500, dport=53479))
                      decrypted = sa.decrypt(recivepacket[0].getlayer(IP))
                      decrypted.show()
      

      I get the following:

      ###[ IP ]###
        version   = 4L
        ihl       = 5L
        tos       = 0x0
        len       = 132
        id        = 1
        flags     = 
        frag      = 0L
        ttl       = 61
        proto     = udp
        chksum    = 0xc4a
        src       = 10.154.65.120
        dst       = 192.168.100.100
        \options   \
      ###[ UDP ]###
           sport     = 4500
           dport     = 53479
           len       = 112
           chksum    = 0x0
      ###[ Raw ]###
              load      = '\xde\xad\xbe\xef\x00\x00\x00\x02\n6\x0eR\x1a\xdc\xc5\xe6Qx-P\xfc\xa7\xf8\xfc\xc1\x80g\xdc\x16\xfe\x8a\xcf^\xbb\xa0?a \xe1I\xb3\xe1\xec0\x96"\xcb\xde\xb1A\xc0g,\xf3\xa7\x82\xb7=0\xc2\xd1\xf2Q-\x98\xcf\xe6G\xbbEd\n\x13\xe0rS\x86\xedl2X1\xa9Y\xb5\r\xff\xf3a@\xca\x97\xeb\x94e{\xde>\xdd\xfca!~\xc7'
      

      but I would expect:

      ###[ IP ]###
        version   = 4L
        ihl       = 5L
        tos       = 0x0
        len       = 92
        id        = 1
        flags     = 
        frag      = 0L
        ttl       = 61
        proto     = icmp
        chksum    = 0xf37f
        src       = 192.168.125.102
        dst       = 10.154.65.120
        \options   \
      ###[ ICMP ]###
           type      = echo-request
           code      = 0
           chksum    = 0x7272
           id        = 0x0
           seq       = 0x0
      ###[ Raw ]###
              load      = 'THISISATESTFRAMETHISISATESTFRAMETHISISATESTFRAMETHISISATESTFRAME'
      
      1. Robin Jarry author

        I think I know what the problem is. In my last patch, I added this:

        bind_layers(UDP, ESP, dport=4500)  # NAT-Traversal encapsulation
        

        Since your packet has sport=4500 and dport=53479, scapy cannot identify the ESP layer. Since the packet has no ESP layer, the SA returns the packet untouched (the SA should raise an error, I will fix this right away).

        try again with this before:

        bind_layers(UDP, ESP, sport=4500)
        

        I'm not sure if scapy should consider all UDP packets having sport=4500 or dport=4500 as containing ESP. What do you think?

        1. Daniel Steglich

          This would explain the behaviour but I have to wait until monday to test it. I think it would be nice to identify the ESP layer by means of sport or dport, because the frame I posted above was a answer frame to the first frame (so first frame was dport=4500 / sport=53479, second frame was opposite direction).

          I would expect it is verry common to do so (switch sport/dport during communication)?

  7. Robin Jarry author

    All right that's good news :)

    I made one last commit adding

    bind_layers(UDP, ESP, sport=4500)
    

    And fixing the unit tests. The tests now run with 100% success:

    $ ./bin/UTscapy -m scapy/layers/ipsec.py -t test/ipsec.uts -F
    passed 7AB2F29A IPv4 / ESP - Transport - AES-CBC - NULL
    passed 0B230DDF IPv4 / ESP - Transport - NULL - HMAC-SHA1-96
    passed 20F36773 IPv4 / ESP - Transport - NULL - HMAC-SHA1-96 - altered packet
    passed 651F1008 IPv4 / ESP - Tunnel - AES-CTR - NULL
    passed 08252694 IPv4 / ESP - Tunnel - NULL - SHA2-256-128
    passed 090A416C IPv4 / ESP - Tunnel - NULL - SHA2-256-128 - altered packet
    passed 669EC28A IPv6 / ESP - Transport - DES - NULL
    passed 0B99A49E IPv6 / ESP - Transport - NULL - HMAC-MD5-96
    passed E1B65822 IPv6 / ESP - Transport - NULL - HMAC-MD5-96 - altered packet
    passed 7785E4F8 IPv6 / ESP - Tunnel - 3DES - NULL
    passed 9B526C56 IPv6 / ESP - Tunnel - NULL - SHA2-384-192
    passed B09DB528 IPv6 / ESP - Tunnel - NULL - SHA2-384-192 - altered packet
    passed B03EB8DA IPv4 / AH - Transport - HMAC-SHA1-96
    passed EB1E5952 IPv4 / AH - Transport - HMAC-SHA1-96 - altered packet
    passed 9D66AD64 IPv4 / AH - Tunnel - SHA2-256-128
    passed FDB28DF9 IPv4 / AH - Tunnel - HMAC-SHA1-96 - altered packet
    passed CAB62C18 IPv6 / AH - Transport - HMAC-SHA1-96
    passed A07D2DAA IPv6 / AH - Transport - HMAC-SHA1-96 - altered packet
    passed CAAF58C6 IPv6 / AH - Tunnel - HMAC-SHA1-96
    passed B6E6288E IPv6 / AH - Tunnel - HMAC-SHA1-96 - altered packet
    passed A90C88E2 IPv6 + Extensions / AH - Transport
    passed F4C9CB36 IPv6 + Routing Header / AH - Transport
    Campaign CRC=BA79D4F4  SHA=35AE42CF8B6819B058605E96D2B1A1144AF149A0
    PASSED=22 FAILED=0