{"id":3197,"date":"2020-04-08T09:57:51","date_gmt":"2020-04-08T14:57:51","guid":{"rendered":"http:\/\/osric.com\/chris\/accidental-developer\/?p=3197"},"modified":"2020-04-08T09:57:51","modified_gmt":"2020-04-08T14:57:51","slug":"modifying-a-packet-capture-with-scapy","status":"publish","type":"post","link":"https:\/\/osric.com\/chris\/accidental-developer\/2020\/04\/modifying-a-packet-capture-with-scapy\/","title":{"rendered":"Modifying a packet capture with Scapy"},"content":{"rendered":"<p>My motivation was to start from a known good packet capture, for example, a DNS request and reply, and modify that request to create something interesting: an example to examine in Wireshark, or positive and negative test cases for an IDS software (Snort, Suricata).<\/p>\n<p>I haven&#8217;t done much with <a href=\"https:\/\/scapy.net\/\">Scapy<\/a> before, but it seemed like the right tool for the task. My planned steps were as follows:<\/p>\n<ol>\n<li><a href=\"#take-pcap\">Take pcap (packet capture)<\/a><\/li>\n<li><a href=\"#import-pcap\">Import pcap via scapy<\/a><\/li>\n<li><a href=\"#modify-pcap\">Modify pcap<\/a><\/li>\n<li><a href=\"#export-pcap\">Export pcap<\/a><\/li>\n<li><a href=\"#view-pcap\">View pcap in Wireshark<\/a><\/li>\n<\/ol>\n<p><!--more--><\/p>\n<p>All the commands shown were run on an Ubuntu 18.04 LTS VM running on VirtualBox, but should work on any Linux host with Python3, Scapy, and tcpdump.<\/p>\n<h3 id=\"take-pcap\">1. Take pcap (packet capture)<\/h3>\n<p>In one terminal I ran <code>tcpdump<\/code>, capturing only port 53 traffic:<\/p>\n<pre><code>$ sudo tcpdump -i enp0s3 -w dns.pcap port 53 \r\ntcpdump: listening on enp0s3, link-type EN10MB (Ethernet), capture size 262144 bytes\r\n^C2 packets captured\r\n2 packets received by filter\r\n0 packets dropped by kernel<\/code><\/pre>\n<p>In another terminal I generated a DNS request. I limited it to A records to reduce the number of packets generated:<\/p>\n<pre><code>$ host -t a mediacropolis.osric.com\r\nmediacropolis.osric.com has address 216.154.220.53<\/code><\/pre>\n<p>I confirmed it worked:<\/p>\n<pre><code>$ tcpdump -n -r dns.pcap \r\nreading from file dns.pcap, link-type EN10MB (Ethernet)\r\n19:51:29.207334 IP 192.168.1.56.57241 &gt; 192.168.1.1.53: 58127+ A? mediacropolis.osric.com. (41)\r\n19:51:29.247780 IP 192.168.1.1.53 &gt; 192.168.1.56.57241: 58127 1\/0\/0 A 216.154.220.53 (57)<\/code><\/pre>\n<h3 id=\"import-pcap\">2. Import pcap via scapy<\/h3>\n<p>First I set up a virtual environment. This was probably unnecessary, but is a habit I have when starting any new Python project:<\/p>\n<pre><code>mkdir mod_pcap\r\ncd mod_pcap\r\npython3 -m venv venv\r\nsource venv\/bin\/activate\r\npip install scapy<\/code><\/pre>\n<p>Then I ran Scapy and imported the packet capture:<\/p>\n<pre><code>$ scapy\r\n&gt;&gt;&gt; packets = rdpcap(\"\/home\/chris\/dns.pcap\")<\/code><\/pre>\n<h3 id=\"modify-pcap\">3. Change pcap<\/h3>\n<p>First I looked at the packets in Scapy just to see what the objects looked like:<\/p>\n<pre><code>&gt;&gt;&gt; packets[0]\r\n&lt;Ether  dst=fc:ec:da:7b:02:cf src=08:00:27:f0:43:22 type=IPv4 |&lt;IP  version=4 ihl=5 tos=0x0 len=69 id=2453 flags=DF frag=0 ttl=64 proto=udp chksum=0xad89 src=192.168.1.56 dst=192.168.1.1 |&lt;UDP  sport=57241 dport=domain len=49 chksum=0x83cc |&lt;DNS  id=58127 qr=0 opcode=QUERY aa=0 tc=0 rd=1 ra=0 z=0 ad=0 cd=0 rcode=ok qdcount=1 ancount=0 nscount=0 arcount=0 qd=&lt;DNSQR  qname='mediacropolis.osric.com.' qtype=A qclass=IN |&gt; an=None ns=None ar=None |&gt;&gt;&gt;&gt;\r\n&gt;&gt;&gt; packets[1]\r\n&lt;Ether  dst=08:00:27:f0:43:22 src=fc:ec:da:7b:02:cf type=IPv4 |&lt;IP  version=4 ihl=5 tos=0x0 len=85 id=30039 flags=DF frag=0 ttl=64 proto=udp chksum=0x41b7 src=192.168.1.1 dst=192.168.1.56 |&lt;UDP  sport=domain dport=57241 len=65 chksum=0x1cff |&lt;DNS  id=58127 qr=1 opcode=QUERY aa=0 tc=0 rd=1 ra=1 z=0 ad=0 cd=0 rcode=ok qdcount=1 ancount=1 nscount=0 arcount=0 qd=&lt;DNSQR  qname='mediacropolis.osric.com.' qtype=A qclass=IN |&gt; an=&lt;DNSRR  rrname='mediacropolis.osric.com.' type=A rclass=IN ttl=600 rdlen=None rdata=216.154.220.53 |&gt; ns=None ar=None |&gt;&gt;&gt;&gt;<\/code><\/pre>\n<p>2 packets: one request, one reply.<\/p>\n<p>What if I wanted to change the packets so that the request and reply are instead for supercalifragilisticexpialidocious.osric.net? And the record data is 10.0.100.7?<\/p>\n<pre><code>&gt;&gt;&gt; request = packets[0]\r\n&gt;&gt;&gt; reply = packets[1]\r\n&gt;&gt;&gt; request[\"DNSQR\"].qname = b'supercalifragilisticexpialidocious.osric.net'\r\n&gt;&gt;&gt; reply[\"DNSRR\"].rrname = b'supercalifragilisticexpialidocious.osric.net'\r\n&gt;&gt;&gt; reply[\"DNSRR\"].rdata = b'10.0.100.7'\r\n&gt;&gt;&gt; packets = [request, reply]\r\n&gt;&gt;&gt; wrpcap(\"\/home\/chris\/dns-modified.pcap\", packets)\r\n&gt;&gt;&gt; exit()<\/code><\/pre>\n<p>Can <code>tcpdump<\/code> still read it?<\/p>\n<pre><code>$ tcpdump -n -r dns-modified.pcap\r\nreading from file dns-modified.pcap, link-type EN10MB (Ethernet)\r\n19:51:29.207334 IP 192.168.1.56.57241 &gt; 192.168.1.1.53: 58127+[|domain]\r\n19:51:29.247780 IP 192.168.1.1.53 &gt; 192.168.1.56.57241: 58127 1\/0\/0 (57)<\/code><\/pre>\n<p>That doesn&#8217;t look quite right. What about <code>tshark<\/code>?<\/p>\n<pre><code>$ tshark -r dns-modified.pcap \r\n    1   0.000000 192.168.1.56 \u2192 192.168.1.1  DNS 83 Standard query 0xe30f[Malformed Packet]\r\n    2   0.040446  192.168.1.1 \u2192 192.168.1.56 DNS 99 Standard query response 0xe30f A mediacropolis.osric.com[Malformed Packet]<\/code><\/pre>\n<p>It looks unhappy: <code>Malformed Packet<\/code>. What went wrong?<\/p>\n<p>Oh! The length and the checksum in both the IP header and the UDP header are incorrect.<\/p>\n<p>Searching around for how to address this led me to <a href=\"https:\/\/stackoverflow.com\/questions\/5953371\/how-to-calculate-a-packet-checksum-without-sending-it\">How to calculate a packet checksum without sending it?<\/a> on StackOverflow. It suggested deleting the checksums and rebuilding the packets, and Scapy would automatically calculate the checksums.<\/p>\n<p><em>A tangent on using<\/em> <code>__class__<\/code><em>:<\/em><\/p>\n<p>I thought it inadvisable to use &#8220;magic&#8221; objects (see https:\/\/www.python.org\/dev\/peps\/pep-0008\/#descriptive-naming-styles) like <code>__class__<\/code> as in the example on StackOverflow. But it does make sense:<\/p>\n<pre><code>&gt;&gt;&gt; type(reply)\r\n&lt;class 'scapy.layers.l2.Ether'&gt;\r\n&gt;&gt;&gt; reply.__class__\r\n&lt;class 'scapy.layers.l2.Ether'&gt;\r\n&gt;&gt;&gt; type(reply['IP'])\r\n&lt;class 'scapy.layers.inet.IP'&gt;\r\n&gt;&gt;&gt; reply['IP'].__class__\r\n&lt;class 'scapy.layers.inet.IP'&gt;<\/code><\/pre>\n<p>No matter what subclass of a packet layer you have, __class__ will give you the right subclass.<\/p>\n<p>Back to the task at hand. I ended up deleting the length (<code>len<\/code>) and checksum (<code>chksum<\/code>) from both the IP layer and the UDP layer and rebuilt the packets:<\/p>\n<pre><code>&gt;&gt;&gt; del request['IP'].len\r\n&gt;&gt;&gt; del request['IP'].chksum\r\n&gt;&gt;&gt; del request['UDP'].len\r\n&gt;&gt;&gt; del request['UDP'].chksum\r\n&gt;&gt;&gt; del reply['IP'].len\r\n&gt;&gt;&gt; del reply['IP'].chksum\r\n&gt;&gt;&gt; del reply['UDP'].len\r\n&gt;&gt;&gt; del reply['UDP'].chksum\r\n&gt;&gt;&gt; # rebuild packets\r\n&gt;&gt;&gt; request = Ether(request.build())\r\n&gt;&gt;&gt; reply = Ether(reply.build())<\/code><\/pre>\n<p>[Note: I also changed the host request from supercalifragilisticexpialidocious.osric.net to example.osric.net. I also included the trailing dot in the request and reply: <code>b'example.osric.net.'<\/code>]<\/p>\n<p>Then I re-built the <code>PacketList<\/code>:<\/p>\n<pre><code>&gt;&gt;&gt; packets = PacketList([request, reply])<\/code><\/pre>\n<h3>4. Export pcap<\/h3>\n<p>Scapy&#8217;s <code>wrpcap<\/code> function takes a destination filename and a <code>PacketList<\/code> object (<code>scapy.plist.PacketList<\/code>).<\/p>\n<pre><code>wrpcap(\"\/home\/chris\/mod_dns.pcap\", packets)<\/code><\/pre>\n<p>Did it work?<\/p>\n<pre><code>$ tcpdump -n -r mod_dns.pcap \r\nreading from file mod_dns.pcap, link-type EN10MB (Ethernet)\r\n14:16:40.005857 IP 192.168.1.56.57241 &gt; 192.168.1.1.53: 58127+ A? example.osric.net. (35)\r\n14:18:34.295393 IP 192.168.1.1.53 &gt; 192.168.1.56.57241: 58127 1\/0\/0 A 10.0.100.7 (68)\r\n\r\n$ tshark -r mod_dns.pcap \r\n    1   0.000000 192.168.1.56 \u2192 192.168.1.1  DNS 77 Standard query 0xe30f A example.osric.net\r\n    2 114.289536  192.168.1.1 \u2192 192.168.1.56 DNS 110 Standard query response 0xe30f A example.osric.net A 10.0.100.7<\/code><\/pre>\n<p>Success!<\/p>\n<h3 id=\"view-pcap\">5. View pcap in Wireshark<\/h3>\n<p>If it worked in tcpdump and tshark, I expected it to work in Wireshark, but I wanted to make sure:<\/p>\n<p><a href=\"https:\/\/osric.com\/chris\/accidental-developer\/2020\/04\/modifying-a-packet-capture-with-scapy\/screenshot-from-2020-04-06-14-29-36\/\" rel=\"attachment wp-att-3204\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/osric.com\/chris\/accidental-developer\/wp-content\/uploads\/2020\/04\/Screenshot-from-2020-04-06-14-29-36.png\" alt=\"Screenshot of Wireshark showing 2 DNS packets\" width=\"706\" height=\"591\" class=\"alignnone size-full wp-image-3204\" srcset=\"https:\/\/osric.com\/chris\/accidental-developer\/wp-content\/uploads\/2020\/04\/Screenshot-from-2020-04-06-14-29-36.png 706w, https:\/\/osric.com\/chris\/accidental-developer\/wp-content\/uploads\/2020\/04\/Screenshot-from-2020-04-06-14-29-36-300x251.png 300w\" sizes=\"auto, (max-width: 706px) 100vw, 706px\" \/><\/a><\/p>\n<h3>A note on packet timestamps:<\/h3>\n<p>I don&#8217;t see any timestamp data when I view the packets in Scapy, but tcpdump shows timestamps:<\/p>\n<pre><code>$ tcpdump -n -r mod_dns.pcap \r\nreading from file mod_dns.pcap, link-type EN10MB (Ethernet)\r\n14:16:40.005857 IP 192.168.1.56.57241 &gt; 192.168.1.1.53: 58127+ A? example.osric.net. (35)\r\n14:18:34.295393 IP 192.168.1.1.53 &gt; 192.168.1.56.57241: 58127 1\/0\/0 A 10.0.100.7 (68)<\/code><\/pre>\n<p>How can I modify the timestamps? The timestamps appear to be the time the packet was created\/rebuilt by Scapy. I would like to have better control of this, but I have not yet found a way to do that. Please leave a comment if you know of a way to do this!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>My motivation was to start from a known good packet capture, for example, a DNS request and reply, and modify that request to create something interesting: an example to examine in Wireshark, or positive and negative test cases for an IDS software (Snort, Suricata). I haven&#8217;t done much with Scapy before, but it seemed like &hellip; <a href=\"https:\/\/osric.com\/chris\/accidental-developer\/2020\/04\/modifying-a-packet-capture-with-scapy\/\" class=\"more-link\">Continue reading <span class=\"screen-reader-text\">Modifying a packet capture with Scapy<\/span><\/a><\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[86,48],"tags":[548,545,493,547,549,546],"class_list":["post-3197","post","type-post","status-publish","format-standard","hentry","category-python","category-security","tag-pcap","tag-scapy","tag-tcpdump","tag-tshark","tag-udp","tag-wireshark"],"_links":{"self":[{"href":"https:\/\/osric.com\/chris\/accidental-developer\/wp-json\/wp\/v2\/posts\/3197","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/osric.com\/chris\/accidental-developer\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/osric.com\/chris\/accidental-developer\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/osric.com\/chris\/accidental-developer\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/osric.com\/chris\/accidental-developer\/wp-json\/wp\/v2\/comments?post=3197"}],"version-history":[{"count":13,"href":"https:\/\/osric.com\/chris\/accidental-developer\/wp-json\/wp\/v2\/posts\/3197\/revisions"}],"predecessor-version":[{"id":3212,"href":"https:\/\/osric.com\/chris\/accidental-developer\/wp-json\/wp\/v2\/posts\/3197\/revisions\/3212"}],"wp:attachment":[{"href":"https:\/\/osric.com\/chris\/accidental-developer\/wp-json\/wp\/v2\/media?parent=3197"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/osric.com\/chris\/accidental-developer\/wp-json\/wp\/v2\/categories?post=3197"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/osric.com\/chris\/accidental-developer\/wp-json\/wp\/v2\/tags?post=3197"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}