I previously blogged about the Zeek OpenVPN Binpac and Spicy protocol analyzers, but that is only one quarter of the popular VPN protocols I see on networks I monitor.  The four main VPN protocols, in increasing complexity, I’ve seen on networks I monitor are:

  1. Wireguard
  2. OpenVPN
  3. IPSec
  4. DTLS (and possibly using SSL/TLS as fallback)

Most VPN clients I have encountered will run one or more of the four protocols above.  Up until recently, IPSec was a complex protocol and missing from Zeek’s functionality, so I decided to add a parser in Spicy’s analyzers.  If you would like to have the functionality described in this blog, please install Spicy and its analyzers described here:

If you would like to know more about OpenVPN, please read through the blogs linked above. DTLS and Wireguard do not have blogs, but their source code is also linked above. The rest of this blog will step through the basics of the IPSec protocol and how the IPSec protocol analyzer was implemented in Spicy.

The IPSec Protocol

IPSec is not a trivial protocol.  Some time must be spent digesting the RFC to understand the protocol enough so that we can create a protocol analyzer.  The RFCs used to develop this analyzer are:

  1. (ESP)
  2. (IKE v1)
  3. (IKE v1)
  4. (ESP packets encapsulated in UDP)
  5. (IKE v2)
  6. (IKE v2)
  7. (ESP packets encapsulated in TCP)

Reading protocol RFCs can be about as exhilarating as reading U.S. tax code, so hopefully this blog will highlight the parts of the RFCs that you care about when developing this analyzer.  I also want to highlight a package that previously implemented IKEv2 in Binpac:  Some of the constants and DPD signatures were adopted from this project.

The portion of IPSec that Zeek will see is anything over UDP or TCP.  It will not see IPSec ESP packets unless they are encapsulated in UDP (or TCP), commonly on port 4500/UDP (TCP).  This is not as bad as it sounds because IPSec will regularly switch to encapsulated ESP packets once a NAT device is located anywhere between the client and server during the IKE handshake.  Furthermore, IPSec requires IKE, and IKE will communicate over UDP.  Therefore, we will at a minimum see the key exchange attempts even if we are unable to see the native ESP (encrypted) packets.  In most cases we will see the ESP packets encapsulated over UDP when a NAT device is in the path.  The most notable scenario where we will not see encapsulated packets would be site-to-site IPSec where no address translation happens between the endpoints, but a lot of networks use mobile IPSec.  Mobile IPSec is used for “road warrior” VPN configurations where external employees will be connecting from unknown networks, therefore unable to control if there is a network address translation device between them and the VPN server.

There are two ports that IPSec commonly uses: 500/UDP for IKE traffic, and 4500/UDP for encapsulated IPSec.  There is also a TCP version of encapsulated IPSec on 4500/TCP.  In IPSec, a connection is initiated over 500/UDP for IKE negotiation and commonly will switch to encapsulated IPSec on port 4500/UDP once a NAT device is discovered between the client and server.

A short PCAP with an IPSec connection can be found here:

If you were to load this PCAP in Wireshark, you will see that a connection occurs over 500/UDP and then switches to 4500/UDP:

This means the basic activity of the protocol is to authenticate with the server using IKE, then IPSec switches to transferring data with ESP packets (or encapsulated ESP packets over UDP).  Do note that IKE comes in two versions: 1 and 2.  Version 2 is used by most modern devices, but version 1 is still used heavily on the networks I monitor.  Only one version is required for IPSec connections, but this protocol analyzer will be able to parse either version.  We will address each of these components of IPSec in the following subsections.

IKE v1

The first authentication mechanism is IKE (Internet Key Exchange) version 1.  The packet layout for this protocol starts on page 21 of the following RFC:

The corresponding IKE structures are defined in the following file:

Lines 263-281 implement the packet layout described in page 21 of RFC 2408.  Some shared data is required in line 261 to correctly parse the payloads stored in a linked list fashion.  It is passed to the IKE v1 payload unit on line 282.  IKE v1 payloads are parsed if the major version in line 266 is “1”, and IKE v2 payloads are parsed if the major version is “2”.  Additionally, IKEv1 payloads can be encrypted, signified with the “E” flag defined on line 272.  If so, line 282 will ignore them instead of parsing them.  The IKE v1 unit is found on line 506:

This IKEv1 message starts with a generic header, defined here:

This is implemented in Spicy with the following unit:

Then, depending on the payload value type the next unit is parsed.  There are thirteen payload types we support (linked rather than screen captured here, for space considerations):

  1. SA – Security Association
  2. P – Proposal
  3. T – Transform
  4. KE – Key Exchange
  5. ID – Identification
  6. CERT – Certificate
  7. CR – Certificate Request
  8. HASH – Hash
  9. SIG – Signature
  10. NONCE – Nonce
  11. N – Notification
  12. D – Delete
  13. VID – Vendor ID

Each of these packet layouts are described on pages 21-43 in RFC 2408.  As for how the protocol behaves on the network, the following PCAP gives a good example:

Notice how the IKEv1 message (listed as ISAKMP in Wireshark) contains several payloads, each linked to the next next-header style.  This is why we needed a shared_info data structure, so we could keep track of the payload types between payloads when parsing.  Next, we will discuss IKEv2, which is similar to IKEv1, but has enough differences that we must develop additional Spicy units.

IKE v2

IKEv2 is very similar to v1, but it rearranges some of the packet structures and adds a few more payload types.  The IKEv2 structure can be found here:

The following payload types are supported (linked rather than screen captured here, for space considerations):

  1. SA – Security Association 
  2. KE – Key Exchange 
  3. IDi – Initiator Identification 
  4. IDr – Responder Identification 
  5. CERT – Certificate 
  6. CERTREQ – Certificate Request 
  7. AUTH – Authentication 
  8. Ni – Nonce 
  9. N – Notify 
  10. D – Delete 
  11. V – Vendor ID 
  12. TSi – Initiator Traffic Selector 
  13. TSr – Responder Traffic Selector 
  14. E – Encrypted Payload 
  15. CP – Configuration 
  16. EAP – Extensible Authentication Protocol 
  17. NO_NEXT_PAYLOAD – No next payload

After the IKE negotiation occurs, the data is sent over ESP.  Since we cannot parse ESP over IP in Zeek, we will concentrate on parsing ESP packets over UDP instead.

ESP over UDP

Encapsulated ESP packets are discussed in the following RFC 3948.  Notice two types of packets can be sent:  ESP over UDP or IKE over UDP.  We already discussed IKE in the previous section, so all of the units we have written for port 500/UDP (IKE v1 or v2 traffic) can also work for 4500/UDP (IPSec encapsulated in UDP)!  The first four bytes will determine if the remaining bytes in the packet are an ESP or IKE message.  If the first four bytes (defined as “Non-ESP Marker” in the RFC) are zero, then the remainder of the packet is IKE.  If the Non-ESP Marker is not zero then the remainder of the packet is ESP.  The ESP packet structure is found in the following RFC:

That information is implemented in Spicy at the following line:

Notice that only the SPI and sequence number are in plain text in this unit.

IPSec over TCP

Since TCP is a stream, you can think of the stream as an array of the IPSec packets we have discussed so far.  When packets are presented to the analyzer in a stream, the packet length is needed to determine where one packet starts after the prior.  The required packet length is defined by the RFC8229 for TCP encapsulated IPSec packets:

Therefore, we only need this one additional field and we can use the rest of the units we designed previously.  We can easily add TCP to our protocol analyzer by adding the packet_len field here:

We also define TCP packets as an array, as defined here:

With these slight additions, we are able to support TCP as well as UDP with very little extra code.

Parsing in Zeek

The parsers are attached to UDP and TCP ports in lines 3-13 of the following file:

The UDP analyzer is attached to ports 4500/UDP and 4501/UDP (a well known IPSec port for Global Protect VPN) and 500/UDP.  Additionally, the TCP analyzer is attached to 4500/TCP.  Lines 18-80 create the events when each of the unit types are parsed correctly.

The dynamic protocol detection (DPD) signatures for this protocol can be found in the following file:

The notes in this file discuss how and why the signatures were developed the way that they were.  Concepts for these signatures were adopted from the following file, but I updated the signatures to support IKEv1 too:

Note that we have DPD signatures for UDP IKE, UDP-encapsulated, and TCP-encapsulated.  Upon detecting these DPD signatures, Zeek attaches the appropriate protocol analyzer to the connection.

The following file contains the helper functions called in the prior ipsec.evt file:

The new events and record types are defined in the main zeek file:

A new log called “ipsec.log” is created with the fields listed in lines 9-64:

The record types for each of the new IPSec events can be found on lines 73-356:

The new IPSec events can be found on lines 358-644:

Lines 683-852 construct the ipsec.log output, but IPSec connection information is also hashed so that similar connections can be grouped by hash on line 702:

Lastly, lookup data that remains a constant can be found in the following file, for convenience:


When the spicy-analyzers plugin is loaded the main.zeek script described above is loaded.  The main.zeek script logs detailed output to ipsec.log.  This is one example PCAP from the testing traces directory, and the information continues off to the right side of this screen capture (see below for a link to all images in this post):

I invite you to run these testing PCAPS through this protocol analyzer and review the results, or you can look at the testing output here despite the multiple X’s in some fields:


IPSec is one of four main VPN protocols, and Zeek lacked support until recently.  This blog discussed the process of adding IPSec support to Zeek using Spicy.  IPSec is a fairly complicated protocol, but we showed that we can use Zeek+Spicy to analyze it with relative ease.  The results of Zeek’s IPSec protocol analyzer are events you can use in your scripts and detailed output logs that should be useful to anyone desiring network based evidence of IPSec VPNs.

All images included in this post can be found here.

%d bloggers like this: