pcapdiff is a python tool that will compare two packet captures (one from the src system and one from the dst system) and highlight suspicious, mangled or possibly injected packets. It requires pcapy and, of course, python. Saw this over on Nate Lawson’s blog in discussing how to detect TCP RSTs, which is apparently being used by Comcast to combat BitTorrent file sharing.
3 thoughts on “pcapdiff compares two packet captures for anomalies”
Comments are closed.
Hi Michael,
Did you try this tool? I gave it a shot but did not have much luck. For example, I saved a trace like this:
$ tcpdump -nqt -r icmp.hacom.1.lpc
reading from file icmp.hacom.1.lpc, link-type EN10MB (Ethernet)
IP 69.255.105.234 > 82.98.86.161: ICMP echo request, id 23074, seq 1, length 64
IP 82.98.86.161 > 69.255.105.234: ICMP echo reply, id 23074, seq 1, length 64
IP 69.255.105.234 > 82.98.86.161: ICMP echo request, id 23074, seq 2, length 64
IP 82.98.86.161 > 69.255.105.234: ICMP echo reply, id 23074, seq 2, length 64
IP 69.255.105.234 > 82.98.86.161: ICMP echo request, id 23074, seq 3, length 64
IP 82.98.86.161 > 69.255.105.234: ICMP echo reply, id 23074, seq 3, length 64
then removed a few packets like this:
$ tcpdump -nqt -r icmp.hacom.1.edited.lpc
reading from file icmp.hacom.1.edited.lpc, link-type EN10MB (Ethernet)
IP 69.255.105.234 > 82.98.86.161: ICMP echo request, id 23074, seq 1, length 64
IP 69.255.105.234 > 82.98.86.161: ICMP echo request, id 23074, seq 2, length 64
IP 69.255.105.234 > 82.98.86.161: ICMP echo request, id 23074, seq 3, length
When I ran the tool I got this:
$ python pcapdiff.py -VV icmp.hacom.1.lpc 69.255.105.234 icmp.hacom.1.edited.lpc 69.255.105.234
Parsed. Local num of unique packets: 2, num of duplicate packets: 0
Remote num of unmatching packets: 2
————–
Traceback (most recent call last):
File “pcapdiff.py”, line 346, in ?
print “ERROR: ip addrs should have been dropped %d -> %d” % (ip_from, ip_to)
TypeError: int argument required
Oh well…
Yeah, I got it to work pretty nicely. I’ll see if I can show what I did here. For starters, I think your second IP should be the destination IP address, but that should still output properly even if it is wrong. I think you have a pcapy problem?
I first installed python-pcapy through apt-get (yay Ubuntu!).
I then downloaded and unzipped pcapdiff.
SOURCE (192.168.10.108) CAPTURE:
michael@orion:~/Desktop/pcapdiff$ tcpdump -nqt -r src1.cap
reading from file src1.cap, link-type EN10MB (Ethernet)
IP 192.168.10.108 > 192.168.10.110: ICMP echo request, id 52584, seq 1, length 64
IP 192.168.10.110 > 192.168.10.108: ICMP echo reply, id 52584, seq 1, length 64
IP 192.168.10.108 > 192.168.10.110: ICMP echo request, id 52584, seq 2, length 64
IP 192.168.10.110 > 192.168.10.108: ICMP echo reply, id 52584, seq 2, length 64
IP 192.168.10.108 > 192.168.10.110: ICMP echo request, id 52584, seq 3, length 64
IP 192.168.10.110 > 192.168.10.108: ICMP echo reply, id 52584, seq 3, length 64
IP 192.168.10.108 > 192.168.10.110: ICMP echo request, id 52584, seq 4, length 64
IP 192.168.10.110 > 192.168.10.108: ICMP echo reply, id 52584, seq 4, length 64
DESTINATION (192.168.10.110) CAPTURE:
michael@orion:~/Desktop/pcapdiff$ tcpdump -nqt -r dst1.cap
reading from file dst1.cap, link-type EN10MB (Ethernet)
IP 192.168.10.108 > 192.168.10.110: ICMP echo request, id 52584, seq 1, length 64
IP 192.168.10.110 > 192.168.10.108: ICMP echo reply, id 52584, seq 1, length 64
IP 192.168.10.108 > 192.168.10.110: ICMP echo request, id 52584, seq 2, length 64
IP 192.168.10.110 > 192.168.10.108: ICMP echo reply, id 52584, seq 2, length 64
IP 192.168.10.108 > 192.168.10.110: ICMP echo request, id 52584, seq 3, length 64
IP 192.168.10.110 > 192.168.10.108: ICMP echo reply, id 52584, seq 3, length 64
IP 192.168.10.108 > 192.168.10.110: ICMP echo request, id 52584, seq 4, length 64
IP 192.168.10.110 > 192.168.10.108: ICMP echo reply, id 52584, seq 4, length 64
I edited out a few packets like you, and have this as my DESTINATION capture:
michael@orion:~/Desktop/pcapdiff$ tcpdump -nqt -r dst1-edit.cap
reading from file dst1-edit.cap, link-type EN10MB (Ethernet)
IP 192.168.10.108 > 192.168.10.110: ICMP echo request, id 52584, seq 1, length 64
IP 192.168.10.110 > 192.168.10.108: ICMP echo reply, id 52584, seq 1, length 64
IP 192.168.10.108 > 192.168.10.110: ICMP echo request, id 52584, seq 2, length 64
IP 192.168.10.108 > 192.168.10.110: ICMP echo request, id 52584, seq 3, length 64
IP 192.168.10.108 > 192.168.10.110: ICMP echo request, id 52584, seq 4, length 64
IP 192.168.10.110 > 192.168.10.108: ICMP echo reply, id 52584, seq 4, length 64
Now let’s compare:
michael@orion:~/Desktop/pcapdiff$ python pcapdiff.py -VV src1.cap 192.168.10.108 dst1-edit.cap 192.168.10.110
Parsed. Local num of unique packets: 2, num of duplicate packets: 0
Remote num of unmatching packets: 2
————–
!!! FORGED: 1
ip_version: 4
ip_header_length: 5
ip_total_length: 84
ip_ident: 61810
ip_flags_plus_offset: 0000
ip_protocol: 1
ip_src: 192.168.10.110
ip_dest: 192.168.10.108
ip_options:
ip_payload_length: 62
ip_payload_data: \x00\x00#\xa0\xcdh\x00\x03\x9c\x89\x9eG\xe1\x1f\x08\x00\x08\t\n\x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !”#$%&\'()*+,-./012345
————–
!!! FORGED: 1
ip_version: 4
ip_header_length: 5
ip_total_length: 84
ip_ident: 61809
ip_flags_plus_offset: 0000
ip_protocol: 1
ip_src: 192.168.10.110
ip_dest: 192.168.10.108
ip_options:
ip_payload_length: 62
ip_payload_data: \x00\x00\xf6\xa0\xcdh\x00\x02\x9b\x89\x9eG\x0f \x08\x00\x08\t\n\x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !”#$%&\'()*+,-./012345
Here are the IP identification fields for forged and dropped packets:
list of inbound forged packets: 61810 61809
list of outbound forged packets:
list of inbound dropped packets:
list of outbound dropped packets:
————-
Packet counts
————-
inbound outbound
sent: 2 4
received: 4 4
forged: 2 0
dropped: 0 0
I see — here’s what I tried.
$ tcpdump -nqt -r pcap.1.lpc
reading from file pcap.1.lpc, link-type EN10MB (Ethernet)
IP 192.168.2.101 > 64.233.161.147: ICMP echo request, id 9239, seq 1, length 64
IP 64.233.161.147 > 192.168.2.101: ICMP echo reply, id 9239, seq 1, length 64
IP 192.168.2.101 > 64.233.161.147: ICMP echo request, id 9239, seq 2, length 64
IP 64.233.161.147 > 192.168.2.101: ICMP echo reply, id 9239, seq 2, length 64
IP 192.168.2.101 > 64.233.161.147: ICMP echo request, id 9239, seq 3, length 64
IP 64.233.161.147 > 192.168.2.101: ICMP echo reply, id 9239, seq 3, length 64
IP 192.168.2.101 > 64.233.161.147: ICMP echo request, id 9239, seq 4, length 64
IP 64.233.161.147 > 192.168.2.101: ICMP echo reply, id 9239, seq 4, length 64
For the edited version I deleted the first three ICMP echo replies.
$ tcpdump -nqt -r pcap.1.edited.lpc reading from file pcap.1.edited.lpc, link-type EN10MB (Ethernet)
IP 192.168.2.101 > 64.233.161.147: ICMP echo request, id 9239, seq 1, length 64
IP 192.168.2.101 > 64.233.161.147: ICMP echo request, id 9239, seq 2, length 64
IP 192.168.2.101 > 64.233.161.147: ICMP echo request, id 9239, seq 3, length 64
IP 192.168.2.101 > 64.233.161.147: ICMP echo request, id 9239, seq 4, length 64
IP 64.233.161.147 > 192.168.2.101: ICMP echo reply, id 9239, seq 4, length 64
$ python pcapdiff.py -VV pcap.1.lpc 192.168.2.101 pcap.1.edited.lpc 64.233.161.147
Parsed. Local num of unique packets: 3, num of duplicate packets: 0
Remote num of unmatching packets: 3
————–
!!! FORGED: 1
ip_version: 4
ip_header_length: 5
ip_total_length: 84
ip_ident: 0
ip_flags_plus_offset: 4000
ip_protocol: 1
ip_src: 64.233.161.147
ip_dest: 192.168.2.101
ip_options:
ip_payload_length: 64
ip_payload_data: \x00\x00\x89\x00$\x17\x00\x02@S\x9fGzH\x0e\x00\x08\t\n\x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !”#$%&\'()*+,-./01234567
————–
!!! FORGED: 1
ip_version: 4
ip_header_length: 5
ip_total_length: 84
ip_ident: 0
ip_flags_plus_offset: 4000
ip_protocol: 1
ip_src: 64.233.161.147
ip_dest: 192.168.2.101
ip_options:
ip_payload_length: 64
ip_payload_data: \x00\x00l\xff$\x17\x00\x03AS\x9fG\x95H\x0e\x00\x08\t\n\x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !”#$%&\'()*+,-./01234567
————–
!!! FORGED: 1
ip_version: 4
ip_header_length: 5
ip_total_length: 84
ip_ident: 0
ip_flags_plus_offset: 4000
ip_protocol: 1
ip_src: 64.233.161.147
ip_dest: 192.168.2.101
ip_options:
ip_payload_length: 64
ip_payload_data: \x00\x00[\x0c$\x17\x00\x01?S\x9fG\xa9=\x0e\x00\x08\t\n\x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !”#$%&\'()*+,-./01234567
Here are the IP identification fields for forged and dropped packets:
list of inbound forged packets: 0 0 0
list of outbound forged packets:
list of inbound dropped packets:
list of outbound dropped packets:
————-
Packet counts
————-
inbound outbound
sent: 1 4
received: 4 4
forged: 3 0
dropped: 0 0
Here’s the reverse output:
$ python pcapdiff.py -VV pcap.1.edited.lpc 64.233.161.147 pcap.1.lpc 192.168.2.101
Parsed. Local num of unique packets: 3, num of duplicate packets: 0
Remote num of unmatching packets: 3
————–
!!! FORGED: -1
ip_version: 4
ip_header_length: 5
ip_total_length: 84
ip_ident: 0
ip_flags_plus_offset: 4000
ip_protocol: 1
ip_src: 64.233.161.147
ip_dest: 192.168.2.101
ip_options:
ip_payload_length: 64
ip_payload_data: \x00\x00\x89\x00$\x17\x00\x02@S\x9fGzH\x0e\x00\x08\t\n\x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !”#$%&\'()*+,-./01234567
————–
!!! FORGED: -1
ip_version: 4
ip_header_length: 5
ip_total_length: 84
ip_ident: 0
ip_flags_plus_offset: 4000
ip_protocol: 1
ip_src: 64.233.161.147
ip_dest: 192.168.2.101
ip_options:
ip_payload_length: 64
ip_payload_data: \x00\x00l\xff$\x17\x00\x03AS\x9fG\x95H\x0e\x00\x08\t\n\x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !”#$%&\'()*+,-./01234567
————–
!!! FORGED: -1
ip_version: 4
ip_header_length: 5
ip_total_length: 84
ip_ident: 0
ip_flags_plus_offset: 4000
ip_protocol: 1
ip_src: 64.233.161.147
ip_dest: 192.168.2.101
ip_options:
ip_payload_length: 64
ip_payload_data: \x00\x00[\x0c$\x17\x00\x01?S\x9fG\xa9=\x0e\x00\x08\t\n\x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !”#$%&\'()*+,-./01234567
Here are the IP identification fields for forged and dropped packets:
list of inbound forged packets:
list of outbound forged packets: 0 0 0
list of inbound dropped packets:
list of outbound dropped packets:
————-
Packet counts
————-
inbound outbound
sent: 4 1
received: 4 4
forged: 0 3
dropped: 0 0
I’m not convinced the decision logic makes much sense here but it is noticing odd activity when I use the IPs as you suggested!