Skip to content

Instantly share code, notes, and snippets.

@rectalogic
Last active June 3, 2021 05:12
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save rectalogic/ee2a48e47584fc0825dad9ffe571ec92 to your computer and use it in GitHub Desktop.
Save rectalogic/ee2a48e47584fc0825dad9ffe571ec92 to your computer and use it in GitHub Desktop.
Parse spis and keys from "ip xfrm state" output and generate a Wireshark ESP decryption config that can decrypt an ESP capture.
#!/usr/bin/env python
"""
Runs "ip xfrm state" and outputs lines to be added to ~/.wireshark/esp_sa
This process must be run using sudo.
This allows Wireshark to decrypt ipsec traffic captured with 'sudo tcpdump -vni any -U -w /tmp/esp.pcap "ip proto 50"'
"""
import sys
import subprocess
AUTH = {
("hmac(sha1)", "96"): "HMAC-SHA-1-96 [RFC2404]",
("hmac(sha512)", "256"): "HMAC-SHA-512-256 [RFC4868]",
}
ENC = {
"cbc(aes)": "AES-CBC [RFC3602]",
}
def parse_xfrm(ip=None):
"""Parse "ip xfrm state" output of the form
src 10.0.0.161 dst 69.27.252.3
proto esp spi 0x66a336c8 reqid 6 mode tunnel
replay-window 32 flag af-unspec
auth-trunc hmac(sha1) 0x0472ec471f7342db23904ccae9091303c710a318 96
enc cbc(aes) 0xc033ab0b0b7d0b28841ffc8c2746da60a6cfd32c19fcfcddbd0e318c430a94cd
src 69.27.252.3 dst 10.0.0.161
proto esp spi 0xc36ee45f reqid 6 mode tunnel
replay-window 32 flag af-unspec
auth-trunc hmac(sha1) 0xccd0880af3650626adda310aa385661c6e100ec0 96
enc cbc(aes) 0xbadc9e716a0cdb11cd86f7c4986e5a70200fd353ed06b2ee30680fb7c6bd320d
"""
connections = []
connection = None
for line in subprocess.check_output(["ip", "xfrm", "state"]).split("\n"):
if line.startswith("src "):
if connection is not None:
connections.append(connection)
if ip is None or ip in line:
_, src, _, dst = line.split(" ")
connection = {"src": src, "dst": dst}
else:
connection = None
elif connection is not None:
if line.startswith("\tproto esp"):
connection["spi"] = line.split(" ")[3]
elif line.startswith("\tauth-trunc "):
_, auth, key, bits = line.split(" ")
connection["auth"] = AUTH[(auth, bits)]
connection["auth_key"] = key
elif line.startswith("\tenc "):
_, enc, key = line.split(" ")
connection["enc"] = ENC[enc]
connection["enc_key"] = key
if connection is not None:
connections.append(connection)
return connections
def output_wireshark(connections):
"""Output ~/.wireshark/esp_sa lines of the form
"IPv4","10.0.0.161","69.27.252.3","0x66a336c8","AES-CBC [RFC3602]","0xc033ab0b0b7d0b28841ffc8c2746da60a6cfd32c19fcfcddbd0e318c430a94cd","HMAC-SHA-1-96 [RFC2404]","0x0472ec471f7342db23904ccae9091303c710a318"
"IPv4","69.27.252.3","10.0.0.161","0xc36ee45f","AES-CBC [RFC3602]","0xbadc9e716a0cdb11cd86f7c4986e5a70200fd353ed06b2ee30680fb7c6bd320d","HMAC-SHA-1-96 [RFC2404]","0xccd0880af3650626adda310aa385661c6e100ec0"
"""
for connection in connections:
print('"IPv4","{src}","{dst}","{spi}","{enc}","{enc_key}","{auth}","{auth_key}"'.format(**connection))
if __name__ == "__main__":
ip = sys.argv[1] if len(sys.argv) > 1 else None
connections = parse_xfrm(ip)
output_wireshark(connections)
@rbdixon
Copy link

rbdixon commented Aug 23, 2019

Thank you for sharing. I made a few tweaks locally that may be useful to others working with GCM(AES) tunnels:

ENC = {"cbc(aes)": "AES-CBC [RFC3602]", "rfc4106(gcm(aes))": "AES-GCM [RFC4106]"}

Parse two additional stanzas in ip xfrm state output:

            elif line.startswith("\taead"):
                _, enc, key, _ = line.split(" ")
                connection["enc"] = ENC[enc]
                connection["enc_key"] = key
                connection["auth"] = None
                connection["auth_key"] = None
            # encap type espinudp sport 10002 dport 10001 addr 10.0.10.58
            elif line.startswith("\tencap"):
                parsed = line.split(" ")
                connection["port"] = parsed[4]

@rlaager
Copy link

rlaager commented Dec 2, 2020

I forked this, merged in rbdixon's changes, changed to Python 3 (as I'm on Ubuntu 20.04 without Python 2), fixed AES-GCM dissection (by mapping the ip xfrm state bits value to a wireshark "ANY x bit authentication [no checking]" authentication type), and added some other authentication types (we had sha384 used in one case). You can find my fork here: https://gist.github.com/rlaager/c5b0ee093c96fc3401b83bcd2efae356
https://github.com/rlaager/ipsec2wireshark

Thanks for this script!

@cdoyle-druid
Copy link

Hey Andrew,

I work for a company that uses the strongswan service, as part of an IPSec product.
We build strongswan from source, as we need the latest version of the code and include some tools.

I came across Richard Laager's ipsec2wireshark.py tool, which he branched from your original script.
I was considering including the tool in our strongswan RPM but was wondering if you would be ok
with this? and if so, what licensing applies?

Thanks,
Conor Doyle

@rectalogic
Copy link
Author

@cdoyle-druid You can include it. I'll license it under MIT license https://choosealicense.com/licenses/mit/

@cdoyle-druid
Copy link

Hey andrew, ok thanks. Conor

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment