Three-way handshake in Scapy

Question:

I’m trying to build a three-way handshake in Scapy. Using the following code,

#!/usr/local/bin/python

from scapy.all import *

sport = random.randint(1024, 65535)

# SYN
ip = IP(src='172.16.120.5', dst='172.16.100.101')
SYN = TCP(sport=sport, dport=443, flags='S', seq=1000)
SYNACK = sr1(ip/SYN)

# ACK
my_ack = SYNACK.seq + 1
ACK = TCP(sport=sport, dport=443, flags='A', seq=1001, ack=my_ack)
send(ip/ACK)

However, on the server I see only SYN_RECV, even though the return SYN-ACK is sent and the ACK is sent in return. Here is a capture from the server (172.16.100.101),

08:10:19.455038 IP 172.16.120.5.58972 > 172.16.100.101.https: S 1000:1000(0) win 8192
08:10:19.455343 IP 172.16.100.101.https > 172.16.120.5.58972: S 2541678705:2541678705(0) ack 1001 win 18484 <mss 1460>
08:10:19.545808 IP 172.16.120.5.58972 > 172.16.100.101.https: . ack 1 win 8192
08:10:24.015204 IP 172.16.100.101.https > 172.16.120.5.58972: S 2541678705:2541678705(0) ack 1001 win 18484 <mss 1460>

As you can see, the SYN-ACK is being sent twice, so it looks like the server doesn’t like the final ACK. Any ideas?

I’ve also printed the output of each of the packets directly from Python. Note this was for a different connection.

>>> SYN
<TCP  sport=26193 dport=https seq=1000 flags=S |>
>>>
>>> SYNACK
<IP  version=4L ihl=5L tos=0x0 len=44 id=0 flags=DF frag=0L ttl=63 proto=tcp chksum=0x741 src=172.16.100.101 dst=172.16.120.5 options=[] |<TCP  sport=https dport=26193 seq=1023579974 ack=1001 dataofs=6L reserved=0L flags=SA window=18484 chksum=0xdb18 urgptr=0 options=[('MSS', 1460)] |<Padding  load='x00x00' |>>>
>>>
>>> ACK
<TCP  sport=26193 dport=https seq=1001 ack=1023579975 flags=A |>

Below shows a successful and unsuccessful connection.

Scapy

20:58:37.357056 IP 172.16.120.5.35957 > 172.16.100.101.https: S 10:10(0) win 8192
20:58:37.357369 IP 172.16.100.101.https > 172.16.120.5.35957: S 900629853:900629853(0) ack 11 win 18484 <mss 1460>
20:58:37.445888 IP 172.16.120.5.35957 > 172.16.100.101.https: . ack 900629854 win 8192

cURL

20:58:46.165413 IP 172.16.120.5.33241 > 172.16.100.101.https: S 2266708589:2266708589(0) win 5840 <mss 1460,sackOK,timestamp 17370497 0,nop,wscale 6>
20:58:46.166296 IP 172.16.100.101.https > 172.16.120.5.33241: S 2138155488:2138155488(0) ack 2266708590 win 18460 <mss 1460,sackOK,timestamp 107550664 17370497,nop,wscale 7>
20:58:46.169026 IP 172.16.120.5.33241 > 172.16.100.101.https: . ack 2138155489 win 92 <nop,nop,timestamp 17370497 107550664>
Asked By: felix001

||

Answers:

I managed to fix this in the end by incrementing the final SEQ number of the ACK.

from scapy.all import *

sport = random.randint(1024, 65535)

# SYN
ip = IP(src='172.16.120.5', dst='172.16.100.101')
SYN = TCP(sport=sport, dport=443, flags='S', seq=1000)
SYNACK = sr1(ip/SYN)

# SYN-ACK
ACK = TCP(sport=sport, dport=443, flags='A', seq=SYNACK.ack + 1, ack=SYNACK.seq + 1)
send(ip/ACK)

Here’s a tcpdump showing the behaviour…

20:47:54.226591 IP 172.16.120.5.55348 > 172.16.100.101.443: S 1000:1000(0) win 8192
20:47:54.227220 IP 172.16.100.101.443 > 172.16.120.5.55348: S 4265040634:4265040634(0) ack 1001 win 18484 <mss 1460>
20:47:54.317452 IP 172.16.120.5.55348 > 172.16.100.101.443: . ack 4265040635 win 8192
Answered By: felix001

This gist implements a simple Scapy three-way handshake class based on the example in scapy.layers.inet. For reference, this is the code:

#!/usr/bin/env python
# -*- coding: UTF-8 -*-
# Author: [email protected] <github.com/tintinweb>
'''
A simple TCP three-way handshake example

#> python scapy_tcp_handshake.py
DEBUG:__main__:init: ('oststrom.com', 80)
DEBUG:__main__:start
DEBUG:__main__:SND: SYN
DEBUG:__main__:RCV: SYN+ACK
DEBUG:__main__:SND: SYN+ACK -> ACK
DEBUG:__main__:RCV: None
DEBUG:__main__:RCV: None
None
DEBUG:__main__:SND: FIN
DEBUG:__main__:RCV: None

Note: Linux might send an RST for forged SYN packets. Disable it by executing:
#> iptables -A OUTPUT -p tcp --tcp-flags RST RST -s <src_ip> -j DROP
'''
from scapy.all import *
import logging
logger = logging.getLogger(__name__)

class TcpHandshake(object):

    def __init__(self, target):
        self.seq = 0
        self.seq_next = 0
        self.target = target
        self.dst = iter(Net(target[0])).next()
        self.dport = target[1]
        self.sport = random.randrange(0, 2**16)
        self.l4 = IP(dst=target[0])/TCP(sport=self.sport, dport=self.dport, flags=0,
                                        seq=random.randrange(0, 2**32))
        self.src = self.l4.src
        self.swin = self.l4[TCP].window
        self.dwin = 1
        logger.debug("init: %s"%repr(target))

    def start(self):
        logger.debug("start")
        return self.send_syn()

    def match_packet(self, pkt):
        if pkt.haslayer(IP) and pkt[IP].dst == self.l4[IP].src 
           and pkt.haslayer(TCP) and pkt[TCP].dport == self.sport 
           and pkt[TCP].ack == self.seq_next:
           return True
        return False

    def _sr1(self, pkt):
        send(pkt)
        ans = sniff(filter="tcp port %s"%self.target[1], lfilter=self.match_packet, count=1, timeout=1)
        return ans[0] if ans else None

    def handle_recv(self, pkt):
        if pkt and pkt.haslayer(IP) and pkt.haslayer(TCP):
            if pkt[TCP].flags & 0x3f == 0x12:   # SYN+ACK
                logger.debug("RCV: SYN+ACK")
                return self.send_synack_ack(pkt)
            elif  pkt[TCP].flags & 4 != 0:      # RST
                logger.debug("RCV: RST")
                raise Exception("RST")
            elif pkt[TCP].flags & 0x1 == 1:     # FIN
                logger.debug("RCV: FIN")
                return self.send_finack(pkt)
            elif pkt[TCP].flags & 0x3f == 0x10: # FIN+ACK
                logger.debug("RCV: FIN+ACK")
                return self.send_ack(pkt)

        logger.debug("RCV: %s"%repr(pkt))
        return None

    def send_syn(self):
        logger.debug("SND: SYN")
        self.l4[TCP].flags = "S"
        self.seq_next = self.l4[TCP].seq + 1
        response = self._sr1(self.l4)
        self.l4[TCP].seq += 1
        return self.handle_recv(response)

    def send_synack_ack(self, pkt):
        logger.debug("SND: SYN+ACK -> ACK")
        self.l4[TCP].ack = pkt[TCP].seq + 1
        self.l4[TCP].flags = "A"
        self.seq_next = self.l4[TCP].seq
        response = self._sr1(self.l4)
        return self.handle_recv(response)

    def send_data(self, d):
        self.l4[TCP].flags = "PA"
        response = self._sr1(self.l4/d)
        self.seq_next = self.l4[TCP].seq + len(d)
        self.l4[TCP].seq += len(d)
        return self.handle_recv(response)

    def send_fin(self):
        logger.debug("SND: FIN")
        self.l4[TCP].flags = "F"
        self.seq_next = self.l4[TCP].seq + 1
        response = self._sr1(self.l4)
        self.l4[TCP].seq += 1
        return self.handle_recv(response)

    def send_finack(self, pkt):
        logger.debug("SND: FIN+ACK")
        self.l4[TCP].flags = "FA"
        self.l4[TCP].ack = pkt[TCP].seq + 1
        self.seq_next = self.l4[TCP].seq + 1
        response = send(self.l4)
        self.l4[TCP].seq += 1
        raise Exception("FIN+ACK")

    def send_ack(self, pkt):
        logger.debug("SND: ACK")
        self.l4[TCP].flags = "A"
        self.l4[TCP].ack = pkt[TCP].seq + 1
        self.seq_next = self.l4[TCP].seq + 1
        response = self._sr1(self.l4)
        self.l4[TCP].seq += 1

if __name__=='__main__':
    logging.basicConfig(level=logging.DEBUG)
    logger.setLevel(logging.DEBUG)
    conf.verb = 0
    tcp_hs = TcpHandshake(("oststrom.com", 80))
    tcp_hs.start()
    print repr(tcp_hs.send_data("INTENTIONAL BAD REQUESTrnrnrn"))
    tcp_hs.send_fin()
Answered By: tintin
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.