how to build IPsec packet with scapy
Page content
import modules
$ python
>>> from scapy.all import *
>>> from scapy.layers.ipsec import *
build plaintext packet
>>> p = IP(src='1.1.1.1', dst='2.2.2.2') / TCP(sport=45012, dport=80) / Raw('testdata')
>>> p = IP(str(p))
setup SA
>>> sa = SecurityAssociation(ESP, spi=0xdeadbeef, crypt_algo='AES-CBC',crypt_key='sixteenbytes key')
Encrypt w/o IV
>>> e = sa.encrypt(p, 5)
>>> e
<IP version=4L ihl=5L tos=0x0 len=76 id=1 flags= frag=0L ttl=64 proto=esp chksum=0x747a src=1.1.1.1 dst=2.2.2.2 |<ESP spi=0xdeadbeef seq=5 data='uD\x7fdj19\xe7\xc4\xff8\x10\xcdQ\xf0\xa6\x1e!\x84\xc3>!\x18\xa6\xf6\xb8\x93\xc6it\x9a\xfc\x1c\xee\xe5C\xcd\xf0\x7fD\xca\x8d\xadKh\xa8\xe5x' |>>
>>> e.show()
###[ IP ]###
version = 4L
ihl = 5L
tos = 0x0
len = 76
id = 1
flags =
frag = 0L
ttl = 64
proto = esp
chksum = 0x747a
src = 1.1.1.1
dst = 2.2.2.2
\options \
###[ ESP ]###
spi = 0xdeadbeef
seq = 5
data = 'uD\x7fdj19\xe7\xc4\xff8\x10\xcdQ\xf0\xa6\x1e!\x84\xc3>!\x18\xa6\xf6\xb8\x93\xc6it\x9a\xfc\x1c\xee\xe5C\xcd\xf0\x7fD\xca\x8d\xadKh\xa8\xe5x'
Encrypt w/ IV
>>> e = sa.encrypt(p, 5, "1234567890123456")
>>> e
<IP version=4L ihl=5L tos=0x0 len=76 id=1 flags= frag=0L ttl=64 proto=esp chksum=0x747a src=1.1.1.1 dst=2.2.2.2 |<ESP spi=0xdeadbeef seq=5 data='1234567890123456\xa4\x0b\xebZ\xa7\xc8\xb6\x95\xfb\x13\x07\xc5TD\xa2\xe7DP\xfcP\xa5y\xc4\x06W\xe8\xf5\xf0\x86\xe1\x0c\xfd' |>>
>>> e.show()
###[ IP ]###
version = 4L
ihl = 5L
tos = 0x0
len = 76
id = 1
flags =
frag = 0L
ttl = 64
proto = esp
chksum = 0x747a
src = 1.1.1.1
dst = 2.2.2.2
\options \
###[ ESP ]###
spi = 0xdeadbeef
seq = 5
data = '1234567890123456\xa4\x0b\xebZ\xa7\xc8\xb6\x95\xfb\x13\x07\xc5TD\xa2\xe7DP\xfcP\xa5y\xc4\x06W\xe8\xf5\xf0\x86\xe1\x0c\xfd'
data 의 시작 부분에 IV값 ‘1234567890123456’이 있음을 알 수 있다. ESP는 SPI(4B), SEQ(4B), IV(16B) , Encrypted Data 형태로 구성된다.
만일 IV값의 길이가 틀리면 다음와 같이 에러 출력
>>> e = sa.encrypt(p, 5, "1234567890")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "ipsec.py", line 903, in encrypt
return self._encrypt_esp(pkt, seq_num=seq_num, iv=iv)
File "ipsec.py", line 787, in _encrypt_esp
raise TypeError('iv length must be %s' % self.crypt_algo.iv_size)
TypeError: iv length must be 16
SecurityAssocaition 옵션
1. tunnel_header
Tunnel mode를 사용하려면 tunnel_header 인자로 outer IP header instance를 넘긴다.
>>> outer_ip = IP(src='10.10.10.10', dst='20.20.20.20')
>>> sa = SecurityAssociation(ESP, spi=0xdeadbeef, crypt_algo='AES-CBC',crypt_key='sixteenbytes key', tunnel_header=outer_ip)
>>> e = sa.encrypt(p, 5, "1234567890123456")
>>> e
<IP version=4L ihl=5L tos=0x0 len=108 id=1 flags= frag=0L ttl=64 proto=esp chksum=0x3e24 src=10.10.10.10 dst=20.20.20.20 |<ESP spi=0xdeadbeef seq=5 data='1234567890123456`\xc6\xa9\xe9Q\x97-\xd6\xe5\x07\xb3\x85\xfb\xc9\xeaz\x07~\xfe\x14\xd1\x19\xd8F\x8cS\x10[\x8f\xfe\x93_ut\xef1\xc0\xc4|\xdb\xccH\xea\xf8\xd2\xd0jj\xeeD\x88\x80\xc5}U\xb5_x)\xaal/,\x96' |>>
>>> e.show()
###[ IP ]###
version = 4L
ihl = 5L
tos = 0x0
len = 108
id = 1
flags =
frag = 0L
ttl = 64
proto = esp
chksum = 0x3e24
src = 10.10.10.10
dst = 20.20.20.20
\options \
###[ ESP ]###
spi = 0xdeadbeef
seq = 5
data = '1234567890123456`\xc6\xa9\xe9Q\x97-\xd6\xe5\x07\xb3\x85\xfb\xc9\xeaz\x07~\xfe\x14\xd1\x19\xd8F\x8cS\x10[\x8f\xfe\x93_ut\xef1\xc0\xc4|\xdb\xccH\xea\xf8\xd2\xd0jj\xeeD\x88\x80\xc5}U\xb5_x)\xaal/,\x96'
Tunnel mode를 사용하지 않은 경우와 별반 차이가 없어 보이지만, IP의 total length값이 기존 76바이에서 108바이트로 변경되었고, ESP data값도 달라졌다. Tunnel mode의 경우 ESP data에 원본 패킷 전체가 포함되는 반면 Transport mode이 경우 L4 layer이상만 포함되므로 값이 달라진다. IP total length의 경우 32바이트가 차이나는데 20 + 12 ???? FIMXE
2. nat_t_header
NAT traversal을 위해는 UDP encapsulation을 이용하는데 이 역시 SA 를 생성할 때 지정할 수 있다. IP total length의 경우 추가된 8byte의 UDP header가 고려되어 108byte에서 116byte로 커졌다.
>>> nat_t_udp = UDP(dport=4500)
>>> sa = SecurityAssociation(ESP, spi=0xdeadbeef, crypt_algo='AES-CBC',crypt_key='sixteenbytes key', tunnel_header=outer_ip, nat_t_header=nat_t_udp)
>>> e = sa.encrypt(p, 5, "1234567890123456")
>>> e
<IP version=4L ihl=5L tos=0x0 len=116 id=1 flags= frag=0L ttl=64 proto=udp chksum=0x3e3d src=10.10.10.10 dst=20.20.20.20 options=[] |<UDP sport=domain dport=ipsec_nat_t len=8 chksum=0x0 |<ESP spi=0xdeadbeef seq=5 data='1234567890123456`\xc6\xa9\xe9Q\x97-\xd6\xe5\x07\xb3\x85\xfb\xc9\xeaz\x07~\xfe\x14\xd1\x19\xd8F\x8cS\x10[\x8f\xfe\x93_ut\xef1\xc0\xc4|\xdb\xccH\xea\xf8\xd2\xd0jj\xeeD\x88\x80\xc5}U\xb5_x)\xaal/,\x96' |>>>
>>> e.show()
###[ IP ]###
version = 4L
ihl = 5L
tos = 0x0
len = 116
id = 1
flags =
frag = 0L
ttl = 64
proto = udp
chksum = 0x3e3d
src = 10.10.10.10
dst = 20.20.20.20
\options \
###[ UDP ]###
sport = domain
dport = ipsec_nat_t
len = 8
chksum = 0x0
###[ ESP ]###
spi = 0xdeadbeef
seq = 5
data = '1234567890123456`\xc6\xa9\xe9Q\x97-\xd6\xe5\x07\xb3\x85\xfb\xc9\xeaz\x07~\xfe\x14\xd1\x19\xd8F\x8cS\x10[\x8f\xfe\x93_ut\xef1\xc0\xc4|\xdb\xccH\xea\xf8\xd2\xd0jj\xeeD\x88\x80\xc5}U\xb5_x)\xaal/,\x96'