-------[ Problem 1: Fragmenting an IPv6 Echo Request ]------- Many of you use the fragment6() function in Scapy. It is useful but also somewhat restrictive, as you may have discovered while dealing with the "Provided fragment size value is too low." case. I suggest you read the code of the fragment6() and defragment6() functions in your Scapy's inet6.py Mine (on OSX Yosemite + Macports) is in /opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/scapy/layers/inet6.py but you should search for yours, as it may be quite different. See problem 3 for hints on packet reassembly. -------[ Problem 2: Crafted DNS request ]------- This is simple: u = IPv6(dst='2604:5f00:ffff:fe00::1:53')/UDP(dport=53)/DNS(id=9999, qdcount=1)/DNSQR(qname='sergey.bratus.test6.dartmouth.edu', qtype='AAAA') A handy illustrated guide to DNS and to Dan Kaminsky's famous attack on it can be found in http://unixwiz.net/techtips/iguide-kaminsky-dns-vuln.html Remember that DNS queries for IPv4 and IPv6 addresses are different types: A vs AAAA! Note: Your ISP's DNS server may not handle AAAA queries right. In that case, configure your machine to use Google's public DNS server, 8.8.8.8. You should see: $ dig aaaa test6.dartmouth.edu ; <<>> DiG 9.8.3-P1 <<>> aaaa test6.dartmouth.edu ;; global options: +cmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 1439 ;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0 ;; QUESTION SECTION: ;test6.dartmouth.edu. IN AAAA ;; ANSWER SECTION: test6.dartmouth.edu. 21599 IN AAAA 2604:5f00:ffff:fe00::1:53 ;; Query time: 443 msec ;; SERVER: 8.8.8.8#53(8.8.8.8) ;; WHEN: Tue Dec 8 23:28:01 2015 ;; MSG SIZE rcvd: 65 -------[ Problem 3: Breaking up a DNS UDP query across packets ]------- The easiest way to solve this problem is to use IPv6 fragmentation to break a DNS query with the word "awesome" across two packets, so that no packet contains the whole word. Fragmentation in IPv6 is controlled by the two parameters of the IPv6ExtHdrFragment: m, the "more fragments" flag, and "offset", the offset at which the payload bytes following the Fragment Header will be placed in the reassembly buffer at the target---in multiples of 8 bytes. So offset=1 means that the payload will be placed at the 8 byte mark in the buffer, offset=2 means 16 bytes, etc. Unlike IPv4 fragmentation, there is no way to fragment IPv6 packets not on an 8-byte boundary (unless you are willing to try overlapping fragments, and the target would actually accept them---which it might, to the benefit of attackers seeking to confuse the IDS/IPS systems in-between). On the first fragment, the "more fragments" flag m is typically set to 1 and offset to 0. This means that more packets with more fragments are coming. These packets are expected to have the same IPv6 header (varying only in plen, perhaps), and Fragment headers with the same id but different offsets (offsets are counted in multiples of 8 bytes, so offset=3 means that the previous packets contained 24 bytes of the original unfragmented packet, and this one starts from the 24th byte of that packet). All these packets except the last one with the largest offset will have m=1; the last one will have m=0. The receiving endpoint uses these m and offset fields to reassemble the packet, roughly as follows: it allocates a buffer and copies the fragments with the same id into it at the offsets that their Fragment headers indicate, until the fragment with m=0 is seen. Then it checks if the resulting packet has any gaps. If there are no gaps, then it prepends the IPv6 header of the first packet and checks the control sums for the packet so reassembled. Fragment headers themselves are discarded. If the control sums match, then the packet is considered received successfully, and is passed to the rest of the kernel for whatever response is needed. If not, it is discarded. When a new id is seen, a new buffer is started, and so on. Now what happens is the packet with m=0 never arrives, or there are gaps in the reassembly buffer? That is assumed to mean that the packets with missing fragments got lost. The buffer cannot tie up the memory forever hoping that they arrive; so it is associated with a timer, which causes the buffer and all packets in it to be discarded when it runs down. Then an ICMPv6 error report is sent, an ICMPv6 "Assembly Time Exceeded". It contains the start of the packet that could not be reassembled. You might wonder, what happens when a fragment header has offset=0 and m=0? Well, what does this say about how many more packets with more fragments of the same packet to be reassembled are coming? None. Are there any gaps in reassembly? No, the buffer starts at 0. So the packet is already "reassembled", and the Fragment header can be discarded. Now it's a single IPv6 packet, and the kernel responds accordingly---either gives it to the appropriate application if it's correct, or sends an ICMPv6 error report, or drops it. So, your fragments should reassemble to a what would be a valid payload without fragmentation. Together with the original IPv6 header, the concatenated payloads would form a valid IPv6 packet. For example, if you have packets that look like IPv6_header_1/Frag_header_1/Payload_1 IPv6_header_2/Frag_header_2/Payload_2 then the packet will be reassembled by the receiving stack as IPv6_header_1a/Payload_1/Payload_2 where IPv6_header_1a is almost like IPv6_header_1 but its payload length field plen is equal to the sum of Payload_1 and Payload_2 plen lengths (whereas IPv6_header_1's plen is only the length of Payload_1 and the Frag_header_1---but Scapy will compute it for you). Also note that the checksums in the combined payload must be correct for the reassembled packet! See scapy-ipv6-fragmentation-hints.txt . So, after all these explanations, the solution is simple: >>> uu = IPv6(dst='2604:5f00:ffff:fe00::5353')/UDP(dport=53)/DNS(qdcount=1)/DNSQR(qname='awesome.test6.dartmouth.edu', qtype='AAAA') >>> hexdump(uu) 0000 60 00 00 00 00 35 11 40 20 01 00 00 53 AA 06 4C `....5.@ ...S..L 0010 14 18 3E D1 B8 00 57 B7 26 04 5F 00 FF FF FE 00 ..>...W.&._..... 0020 00 00 00 00 00 00 53 53 00 35 00 35 00 35 C5 72 ......SS.5.5.5.r 0030 00 00 00 00 00 01 00 00 00 00 00 00 07 61 77 65 .............awe 0040 73 6F 6D 65 05 74 65 73 74 36 09 64 61 72 74 6D some.test6.dartm 0050 6F 75 74 68 03 65 64 75 00 00 1C 00 01 outh.edu..... >>> p1 = Raw( load = str(uu)[40:64] ) >>> p2 = Raw( load = str(uu)[64:] ) >>> hexdump(p2) 0000 73 6F 6D 65 05 74 65 73 74 36 09 64 61 72 74 6D some.test6.dartm 0010 6F 75 74 68 03 65 64 75 00 00 1C 00 01 outh.edu..... >>> u1 = IPv6(dst='2604:5f00:ffff:fe00::5353')/f1/p1 >>> u2 = IPv6(dst='2604:5f00:ffff:fe00::5353')/f2/p2 >>> hexdump(u1) 0000 60 00 00 00 00 20 2C 40 20 01 00 00 53 AA 06 4C `.... ,@ ...S..L 0010 14 18 3E D1 B8 00 57 B7 26 04 5F 00 FF FF FE 00 ..>...W.&._..... 0020 00 00 00 00 00 00 53 53 11 00 00 01 00 00 C0 01 ......SS........ 0030 00 35 00 35 00 35 C5 72 00 00 00 00 00 01 00 00 .5.5.5.r........ 0040 00 00 00 00 07 61 77 65 .....awe >>> hexdump(u2) 0000 60 00 00 00 00 25 2C 40 20 01 00 00 53 AA 06 4C `....%,@ ...S..L 0010 14 18 3E D1 B8 00 57 B7 26 04 5F 00 FF FF FE 00 ..>...W.&._..... 0020 00 00 00 00 00 00 53 53 11 00 00 18 00 00 C0 01 ......SS........ 0030 73 6F 6D 65 05 74 65 73 74 36 09 64 61 72 74 6D some.test6.dartm 0040 6F 75 74 68 03 65 64 75 00 00 1C 00 01 outh.edu..... We can test the fragmentation with Scapy's own defragment6 function: >>> defragment6([u1,u2]) an=None ns=None ar=None |>>> Since I was using miredo on MacOS Yosemite, I had the add the extra frame header fh. BTW, I found it out by sniffing the miredo-created tun0 interface; I knew beforehand that some tunnels use such extra headers. If you are using an HE tunnel or a direct IPv6 connection, you should rather use send() and no extra headers. >>> fh >>> sendp([ fh/dp, fh/u1, fh/u2 ], iface="tun0") -------[ Problem 4: Breaking up HTTP requests across TCP packets ]------- The 4th problem essentially requires you to implement the TCP handshake with the server, then send your HTTP request over that TCP connection in such a way that the word "awesome" is split between packets, then catch the TCP response. There are three separate challenges here: 1. Splitting up the "awesome"s in the URL. 2. Implementing TCP ACKs so that the handshake and the response work for the server to accept this connection as legitimate and to send the full response. 3. If not using the kernel's own socket, to suppress the TCP RST packets that the kernel will send when it sees ACK packets from the connection emulated by you (rather than the connection it has established itself and knows about). (3) is easy to do on Linux with ip6tables, and only marginally harder on MacOS X with its own built-in firewall, PF. In each case, it's a one-liner. ip6tables: ip6tables -A OUTPUT -p tcp -d --sport 80 --tcp-flags RST RST -j DROP mask ^^^ ^^^value to match MacOS X uses the OpenBSD-derived Packet Filter (PF). It is configured via /etc/pf.conf, where we put: block out quick on en0 inet proto tcp from any to any port 80 flags R/R block out quick on en0 inet6 proto tcp from any to any port 80 flags R/R and then we load and enable this PF policy with # pfctl -v # pfctl -e For more details: http://krypted.com/mac-security/a-cheat-sheet-for-using-pf-in-os-x-lion-and-up/ For the explanation of the PF syntax and mechanism: http://www.openbsd.org/faq/pf/index.html (2) can be done in various ways. The simplest but also longest is to implement a bit of TCP yourself in Scapy and use it with (3). This is what I did first :) Then the splitting-up of the URL can be done either via IPv6 fragmentation or simply via putting the parts into different TCP segments. The web server won't respond until it sees \r\n\r\n, which ends the HTTP request headers. I implemented this in send-tcp6.py . I first tried it for IPv4 in send-tcp.py, directed at a web server I controlled. Alternatively, you can use the kernel's own TCP implementation, and try to cause it to send the URL not in one piece but split. The problem is that normally TCP will aggregated segments before sending as it sees fit, so the URL will be sent in one packet and blocked. Thus you need either delays between packets or TCP_NODELAY flags. This worked for me, but the result is not guaranteed. More info on making TCP "send this buffer now": http://www.unixguide.net/network/socketfaq/2.11.shtml http://stackoverflow.com/questions/258883/i-need-a-tcp-option-ioctl-to-send-data-immediately (1) should be easy, once you understand how TCP/IP handshake and acks work: http://packetlife.net/blog/2010/jun/7/understanding-tcp-sequence-acknowledgment-numbers/ --------[ "How can I force a socket to send the data in its buffer?" ]---------- "There's no method that's guaranteed to work, because TCP is explicitly designed to provide no such guarantee. However, using the TCP_NODELAY flag with send(2) system call or its equivalent will most likely work". --- more discussion in http://www.unixguide.net/network/socketfaq/2.11.shtml Scapy does not provide TCP_NODELAY as a constant. However, all such constants and interfaces are described as .h files in /usr/include , so we'll find it there: bash-3.2# grep -r TCP_NODELAY /usr/include /usr/include/apache2/mpm_common.h:#include /* for TCP_NODELAY */ /usr/include/apache2/mpm_common.h:#if defined(TCP_NODELAY) /usr/include/apr-1/apr.h:/* Is the TCP_NODELAY socket option inherited from listening sockets? /usr/include/apr-1/apr.h:#define APR_TCP_NODELAY_INHERITED 1 /usr/include/apr-1/apr_network_io.h:#define APR_TCP_NODELAY 512 /**< For SCTP sockets, this is mapped /usr/include/apr-1/apr_network_io.h: * to STCP_NODELAY internally. /usr/include/apr-1/apr_network_io.h: * APR_TCP_NODELAY set to tell us that /usr/include/apr-1/apr_network_io.h: * APR_TCP_NODELAY should be turned on /usr/include/curl/curl.h: CINIT(TCP_NODELAY, LONG, 121), /usr/include/netinet/tcp.h:#define TCP_NODELAY 0x01 /* don't delay send to coalesce packets */ The last string gives us what we need. Instead of C, we can use Python's wrappers around the standard C library. See https://docs.python.org/2/howto/sockets.html for details on how these functions work, and compare with "man 2 socket", "man 2 send". Note that these manpages describe Unix system calls made from libc. Python does not need to take lengths of buffer payloads, and its sockets are objects rather than small integer file descriptors, but ultimately the same byte strings are given to the same system calls. ---------------------------[ BareTCP.py ]--------------------------- #!/usr/bin/env python import socket import sys TCP_IP = '2001:470:1f04:32::2' TCP_PORT = 80 BUFFER_SIZE = 1024 # s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) <-- use for IPv4 s = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) s.connect((TCP_IP, TCP_PORT)) # In TCP6, adding spaces at the end of the segments may be # necessary, because the last char is occasionally consumed (?!) s.send("GET /awe ", 0x01) # TCP_NODELAY s.send("somesauce.jpg\r\n\r\n ", 0x01) # \r\n\r\n ends the headers of an HTTP request payload = '' while 1: data = s.recv(BUFFER_SIZE) if data != "" : print len(data) payload += data else: break s.close() f = open("coolcatpic", "w") f.write(payload) f.close ---------------------------[ end file ]--------------------------- There's a mystery involved: the TCP segments so sent are occasionally cut short, losing the last character. This doesn't happen always, but frequently enough. Note the space in "GET /awe "---without it, you would occasionally get an HTTP 404 for "/awsomesauce.jpg" This is clearly against the TCP spec, but I could not find out so far if it was Miredo, Apache2, or TCPv6 stack itself at fault! More about interesting TCP features---read these, follow links, and you will learn a lot about TCP: http://www.stuartcheshire.org/papers/nagledelayedack/ Stuart Cheshire, "TCP Performance problems caused by interaction between Nagle’s Algorithm and Delayed ACK" (See also https://en.wikipedia.org/wiki/Nagle%27s_algorithm, https://en.wikipedia.org/wiki/TCP_delayed_acknowledgment)