Creating ACK-GET packets with scapy

Categories Code, CTF

During the recent Defcon 19 CTF pre-qualifications, one of the challenges included to connect ‘quicker’ to a web server. While figuring out what the solution was for this challenge one of the things I tried was to send the HTTP GET request already in the TCP handshake stage. Sadly enough this had nothing to do with the real solution of this case, the real solution was making use of SPDY to connect to the server, this is also explained by one of the other teams in a write up here. However it was a nice exercise to create packets with scapy again and since I could not find a lot of information on the topic I decided to create this short write up on the topic.

 

Normal TCP Handshake

A normal TCP handshake contains three packets, a SYN packet which is being sent from the client to the server, on which the server replies with a SYN,ACK packet, on which the client on its turn replies with an ACK packet.  This handshake is shown in the schematic overview below. More information on the TCP handshake can be found on Wikipedia here.

Normal TCP handshake overview:

TCP_handshake

 

Normal TCP handshake in Wireshark

Wireshark_TCP_handshake

 

We can create our own packet for this TCP handshake, to do this we will use scapy. A normal TCP handshake in scapy:

# scapy
Welcome to Scapy (2.1.0)
>>> ip=IP(dst="www.google.com")
>>> port=RandNum(1024,65535)
>>> SYN=ip/TCP(sport=port, dport=80, flags="S", seq=42)
>>> SYNACK=sr1(SYN)
Begin emission:
.Finished to send 1 packets.
*
Received 2 packets, got 1 answers, remaining 0 packets
>>> ACK=ip/TCP(sport=SYNACK.dport, dport=80, flags="A", seq=SYNACK.ack, ack=SYNACK.seq + 1)
>>> send(ACK)
.
Sent 1 packets.
>>>

 

To make it a bit more easy to read and alter we can also build a Python script which makes use of scapy. Normal TCP handshake in a Python script making use of scapy:

#!/usr/bin/python

# Change log level to suppress annoying IPv6 error
import logging
logging.getLogger("scapy.runtime").setLevel(logging.ERROR)

# Import scapy
from scapy.all import *

# Print info header
print "[*] TCP Handshake example -- Thijs 'Thice' Bosschert, 06-06-2011"

# Set up target IP
ip=IP(dst="www.google.com")

# Generate random source port number
port=RandNum(1024,65535)

# Create SYN packet
SYN=ip/TCP(sport=port, dport=80, flags="S", seq=42)

# Send SYN and receive SYN,ACK
print "\n[*] Sending SYN packet"
SYNACK=sr1(SYN)
print "\n[*] Receiving SYN,ACK packet"

# Create ACK packet
ACK=ip/TCP(sport=SYNACK.dport, dport=80, flags="A", seq=SYNACK.ack, ack=SYNACK.seq + 1)

# SEND our ACK packet
print "\n[*] Sending ACK packet"
send(ACK)

print "\n[*] Done!"

 

The Python script above gives the following output:

python TCP_handshake.py
[*] TCP Handshake example -- Thijs 'Thice' Bosschert, 06-06-2011

[*] Sending SYN packet
Begin emission:
.Finished to send 1 packets.
*
Received 2 packets, got 1 answers, remaining 0 packets

[*] Receiving SYN,ACK packet

[*] Sending ACK packet
.
Sent 1 packets.

[*] Done!

 

ACK-GET TCP Handshake

Normally your system will send a GET request after the TCP handshake has been finished, which is shown in the schematic overview below.

 

TCP handshake ACK-GET

 

However, the GET request can actually be send together with the ACK request, without completing the handshake first. Which means the network traffic will look like this:

TCP handshake ACK-GET 2

 

Since the GET request will be send in the same packet as the ACK request, we will call this packet an ACK-GET packet (by lack of a better name).

After successfully sending the ACK-GET packet, the server will respond with the requested data as can be seen in the network traffic in Wireshark below.

Wireshark ACK-GET

 

And of course once again we can use scapy to create out own packets for the ACK-GET request. This is actually pretty simple by just placing a GET request after the normal ACK packet.

# scapy
Welcome to Scapy (2.1.0)
>>> get='GET / HTTP/1.0\n\n'
>>> ip=IP(dst="www.google.com")
>>> port=RandNum(1024,65535)
>>> SYN=ip/TCP(sport=port, dport=80, flags="S", seq=42)
>>> SYNACK=sr1(SYN)
.Begin emission:
.Finished to send 1 packets.
*
Received 3 packets, got 1 answers, remaining 0 packets
>>> ACK=ip/TCP(sport=SYNACK.dport, dport=80, flags="A", seq=SYNACK.ack, ack=SYNACK.seq + 1) / get
>>> reply,error=sr(ACK)
Begin emission:
Finished to send 1 packets.
*
Received 1 packets, got 1 answers, remaining 0 packets
>>> print reply.show()
0000 IP / TCP 192.168.1.20:52240 > 74.125.77.147:www A / Raw ==> IP / TCP 74.125.77.147:www > 192.168.1.20:52240 A / Padding
None

 

Since you are not completing the full TCP handshake your operating system might try to take control and can start sending RST (reset) packages, to avoid this we can use iptables:

iptables -A OUTPUT -p tcp --tcp-flags RST RST -s 192.168.1.20 -j DROP

The best way to see what is happening in the background is to have Wireshark running at the same time. You might want to set a filter in Wireshark to only show the packets involved in our request. Since my script uses the Google website I use the following filter:

ip.addr == 74.125.77.0/16

To make it a bit more easy to read and alter we can also build a Python script of the ACK-GET request.

TCP handshake with ACK-GET in a Python script making use of scapy:

#!/usr/bin/python

# Change log level to suppress annoying IPv6 error
import logging
logging.getLogger("scapy.runtime").setLevel(logging.ERROR)

# Import scapy
from scapy.all import *

# Print info header
print "[*] ACK-GET example -- Thijs 'Thice' Bosschert, 06-06-2011"

# Prepare GET statement
get='GET / HTTP/1.0\n\n'

# Set up target IP
ip=IP(dst="www.google.com")

# Generate random source port number
port=RandNum(1024,65535)

# Create SYN packet
SYN=ip/TCP(sport=port, dport=80, flags="S", seq=42)

# Send SYN and receive SYN,ACK
print "\n[*] Sending SYN packet"
SYNACK=sr1(SYN)

# Create ACK with GET request
ACK=ip/TCP(sport=SYNACK.dport, dport=80, flags="A", seq=SYNACK.ack, ack=SYNACK.seq + 1) / get

# SEND our ACK-GET request
print "\n[*] Sending ACK-GET packet"
reply,error=sr(ACK)

# print reply from server
print "\n[*] Reply from server:"
print reply.show()

print '\n[*] Done!'

 

The Python script above gives the following output:

# python ACK-GET.py
[*] ACK-GET example -- Thijs 'Thice' Bosschert, 06-06-2011

[*] Sending SYN packet
Begin emission:
.Finished to send 1 packets.
*
Received 2 packets, got 1 answers, remaining 0 packets

[*] Sending ACK-GET packet
Begin emission:
Finished to send 1 packets.
*
Received 1 packets, got 1 answers, remaining 0 packets

[*] Reply from server:
0000 IP / TCP 192.168.1.20:12511 > 74.125.79.99:www A / Raw ==> IP / TCP 74.125.79.99:www > 192.168.1.20:12511 A / Padding
None

[*] Done!

 

This is all it takes to create a HTTP GET request inside a TCP Handshake with scapy. Of course this is just a small proof of concept and it needs some more tweaking to use it to communicate correctly with a HTTP server.

For another example of the usage of scapy see here my write up on one of the Swiss Cyber Storm challenges.

24 Comments

  • Tosch
    27/01/2012

    Hello,
    First of all, thanks for this post really interesting.
    I tried you script and run it. The problem is that an RST packet is generated automatically after the SYN/ACK from the server.
    How is it possible to avoid it? Because I can’t get the answer for the HTTP request.

    Thanks,
    Tosch

  • Thice
    06/02/2012

    @Tosch
    To avoid the RST packet to be send use the iptables rule as shown in the article.

  • Dylanger
    02/04/2012

    Very helpful! Thanks heaps! 😀
    Is it possible to download a file?

  • Thice
    07/04/2012

    @Dylanger
    It probably is, but that takes some extra effort.

  • getz
    09/05/2012

    Hi,

    thanks for this great and helpful post;
    I do run into one issue, after it receives the syn/ack it sends the message and immediately after it sends the final ack that should of being to complete the handshake.
    e.g.
    syn ->

    ack ->

    Thanks

  • Thice
    10/05/2012

    @getz
    I am not sure if I understand you, you are sending the GET request inside the ACK request. What do you mean by that it is sending the ACK request?

  • obogan
    18/08/2012

    Hi,
    the way you doing your http request with Scapy is not normal, i am astonished that it worked.
    Normally the http header should be inserted in the PSH/ACK (Flags=”PA”) after the ACK who close the three-way handshake.
    you can see it clearly with Wireshark when you go to an url with your browser.
    It should be like that:

    the Hand check
    1> SYN
    2> SYN/ACK
    3> ACK (SPDY use this last ACK to already send the http header ? that possible, because this ACK is really a waste of bandwidth)

    the payload
    4> PSH/ACK [with the http header: GET / HTTP/1.1]
    5> PSH/ACK [Response from the server: HTTP/1.1 200 ok]

    The end of the request
    6> FIN/ACK [Normally, who need to close the connection at the end]

    with scapy you have:

    ACK=ip/TCP(sport=SYNACK.dport, dport=80, flags=”A”, seq=SYNACK.ack, ack=SYNACK.seq + 1)
    ACK=sr1(ACK)
    PSHACK=ip/TCP(sport=ACK.dport, dport=80, flags=”PA”, seq=ACK.ack, ack=ACK.seq + 1) / get
    win,fail=sr(PSHACK) # FIN/ACK can be discarded without big consequence, the server will close the connection anyway

  • Thice
    21/08/2012

    @obogan
    It seems that you missed the point of this article, the whole idea is that it does work to throw the GET request in to the ACK packet.

  • anne
    21/01/2013

    Tq for scapy code it works me…
    this thing can be use to test whether reverse proxy is used or not due to differ response header

  • gabrielle
    24/05/2013

    hi,

    Thanks a lot for your article
    However, I still have one question

    when you send the SYN packet with sr(), you get :

    [*] Sending SYN packet
    Begin emission:
    .Finished to send 1 packets.
    *
    Received 2 packets, got 1 answers, remaining 0 packets

    Since you received 2 packets, I was wondering if there’s a way to get the second packet (which is not considered an answer by scapy…) ?
    Because when we do ans,unans=sr(…), I can’t find it…

  • Thice
    04/06/2013

    @gabrielle
    Have you checked with a network sniffer (Wireshark) what kind of packets there are on the line?

  • Thice.nl » Post overview
    29/08/2014

    […] OUI (MAC address) lookup script Creating ACK-GET packets with scapy Soon: Meaningful MD5 Collisions: Creating […]

  • Rollander
    05/02/2015

    Is there a way to add delay between the packets, say I want to send a ACK for a segment from the server after say 30ms upon receiving.

  • Thice
    27/02/2015

    @Rollander
    Just add a sleep to the python code?

    http://www.tutorialspoint.com/python/time_sleep.htm

  • Zeta
    01/06/2015

    I think I have the same problem as Gabrielle, after receiveing the HTTP response from the server, I want to get the size of the HTTP response in order to send an ACK to the server, and avoid (actually, control) retransmission.
    As her, I get the response, and I see from wireshark that the server first send an empty ACK packet and then another TCP segment with the response.

    If I print the reply packet, I see the content of the HTTP server ACK (no payload) and not the subsequent packet with the actual HTTP response.

  • Lucian
    15/05/2016

    Hi! Very nice article! Good job!

    I try too see if work’s for me, and i have an error.
    ip=IP(dst=”www.google.com”)
    NameError: name ‘IP’ is not defined

    What is the problem?(i running python 2.7 on windows 10 with scapy,dnet and pcapy installed)

  • Thice
    21/05/2016

    @Lucian
    I never tried the code on Windows, so no idea if it would work there.
    Did you copy paste the full code or are you trying to use parts in your own code?

  • jon
    09/06/2016

    thank you!
    but i dont understand- where is the data ?
    i do GET req for get data from site…
    where the data passing??

  • hoogur
    22/10/2016

    gURL = “GET /index.html HTTP/1.0\r\n”.encode(‘utf-8′)
    gSport = RandNum(1024,65535)
    gSYN_ACK = sr1(IP(dst=”172.6.100.10″)/TCP(dport=80,sport=gSport,flags=’S’),timeout=5)
    gRep,ungRep = sr(IP(dst=”172.6.100.10″)/TCP(dport=80,sport=gSYN_ACK[TCP].dport,seq=gSYN_ACK[TCP].ack,ack=gSYN_ACK[TCP].seq+1,flags=’A’)/gURL,timeout=5)
    =====================
    Client 172.6.100.11
    10:32:35.585783 IP 172.6.100.11 > 172.6.100.10: Flags [S], seq 0, win 8192, length 0
    10:32:35.826322 IP 172.6.100.10 > 172.6.100.11: Flags [S.], seq 3747855505, ack 1, win 14600, options [mss 1460], length 0
    10:32:36.828615 IP 172.6.100.10 > 172.6.100.11: Flags [S.], seq 3747855505, ack 1, win 14600, options [mss 1460], length 0
    10:32:38.828786 IP 172.6.100.10 > 172.6.100.11: Flags [S.], seq 3747855505, ack 1, win 14600, options [mss 1460], length 0
    10:32:41.792058 IP 172.6.100.11 > 172.6.100.10: Flags [.], seq 1:27, ack 1, win 8192, length 26
    10:32:42.032468 IP 172.6.100.10 > 172.6.100.11: Flags [.], ack 27, win 14600, length 0
    10:33:42.093781 IP 172.6.100.10 > 172.6.100.11: Flags [F.], seq 1, ack 27, win 14600, length 0
    10:33:45.100599 IP 172.6.100.10 > 172.6.100.11: Flags [F.], seq 1, ack 27, win 14600, length 0
    10:33:51.116657 IP 172.6.100.10 > 172.6.100.11: Flags [F.], seq 1, ack 27, win 14600, length 0

    Server 172.6.100.10
    10:32:36.428930 IP 172.6.100.11 > 172.6.100.10: Flags [S], seq 0, win 8192, length 0
    10:32:36.429047 IP 172.6.100.10 > 172.6.100.11: Flags [S.], seq 3747855505, ack 1, win 14600, options [mss 1460], length 0
    10:32:37.431331 IP 172.6.100.10 > 172.6.100.11: Flags [S.], seq 3747855505, ack 1, win 14600, options [mss 1460], length 0
    10:32:39.431386 IP 172.6.100.10 > 172.6.100.11: Flags [S.], seq 3747855505, ack 1, win 14600, options [mss 1460], length 0
    10:32:42.635006 IP 172.6.100.11 > 172.6.100.10: Flags [.], seq 1:27, ack 1, win 8192, length 26
    10:32:42.635131 IP 172.6.100.10 > 172.6.100.11: Flags [.], ack 27, win 14600, length 0
    10:33:42.696241 IP 172.6.100.10 > 172.6.100.11: Flags [F.], seq 1, ack 27, win 14600, length 0
    10:33:45.703347 IP 172.6.100.10 > 172.6.100.11: Flags [F.], seq 1, ack 27, win 14600, length 0
    10:33:51.719372 IP 172.6.100.10 > 172.6.100.11: Flags [F.], seq 1, ack 27, win 14600, length 0

  • hoogur
    22/10/2016

    why no http response

  • venu
    25/04/2018

    what about ipv6 ?

  • venu
    03/05/2018

    Hi Thice,

    Can you please help me in generating ipv6 tcp 3 way handshake packet.

  • Thice
    19/07/2018

    This is an article from 2011, I have not looked at it since, so I do not have an example for ipv6 either.

  • yasin
    11/09/2019

    Is it possible to set ttl for the syn packet?

Leave a Reply

Your email address will not be published. Required fields are marked *