Что ответит соединение в состоянии ESTABLISHED, когда оно получит SYN?

задняя часть TCP/IP

Первоначально этот вопрос был задан читателем в комментарии к разделу «Атака TCP RST и как отключить TCP-соединение» моего буклета TCP Nuggets: «Соединение УСТАНОВЛЕНО, зачем отвечать на пакет SYN?», в этой статье пойдет речь о содержании этой части.

Прочитав эту статью, вы освоите эти знания

  • Что ответит соединение, находящееся в состоянии ESTABLISHED, когда оно получит неупорядоченный пакет?
  • Концепция вызова ACK
  • Каков предел скорости пакетов ACK?
  • Использование инструмента SystemTap для трассировки ядра Linux
  • Использование артефакта внедрения пакета scapy
  • Как работают RST-атаки
  • Принцип, согласно которому такие инструменты, как killcx, используют RST-атаку для уничтожения соединения.

Начнем с содержания статьи.

scapy эксперимент воспроизводит явление

Шаги эксперимента следующие:

Используйте nc для запуска сервисной программы на машине A (10.211.55.10), прослушивающей порт 9090, как показано ниже.

nc -4 -l 9090

Используйте tcpdump для синхронного захвата пакетов на машине A, где -S означает отображение абсолютного серийного номера.

sudo tcpdump -i any port 9090 -nn  -S

Используйте команду nc на машине B, чтобы подключиться к серверу nc на машине A, и введите «hello».

nc 10.211.55.10 9090

Используйте netstat для просмотра информации об этом соединении.

Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name
tcp        0      0 10.211.55.10:9090       10.211.55.20:50718      ESTABLISHED 9029/nc

Используйте scapy на машине B для имитации отправки пакета SYN, сценарий scapy выглядит следующим образом.

send(IP(dst="10.211.55.10")/TCP(sport=50718, dport=9090, seq=10, flags='S'))

Спорт с номером исходного порта использует временный номер порта 50718 этого соединения, а порядковый номер записывается случайным образом, где seq равно 10.

Выполните scapy, чтобы выполнить приведенный выше код, и результаты пакета, отображаемые в tcpdump, будут следующими.

// nc 终端中 hello 请求包
18:41:51.956735 IP 10.211.55.20.50718 > 10.211.55.10.9090: Flags [P.], seq 3219267420:3219267426, ack 2848436085, win 229, options [nop,nop,TS val 1094540820 ecr 12823113], length 6
18:41:51.956787 IP 10.211.55.10.9090 > 10.211.55.20.50718: Flags [.], ack 3219267426, win 227, options [nop,nop,TS val 12827910 ecr 1094540820], length 0

// scapy 的 SYN 包
18:44:32.373331 IP 10.211.55.20.50718 > 10.211.55.10.9090: Flags [S], seq 10, win 8192, length 0
18:44:32.373366 IP 10.211.55.10.9090 > 10.211.55.20.50718: Flags [.], ack 3219267426, win 227, options [nop,nop,TS val 12988327 ecr 1094540820], length 0

Видно, что на пакет SYN со случайным SEQ TCP отвечает правильным пакетом ACK с номером подтверждения 3219267426.

Также видно из документации rfc793:

На полученное сообщение SYN с нарушением порядка ядро ​​Linux ответит сообщением ACK с правильным порядковым номером и номером подтверждения.

Этот ACK называется Challenge ACK.

На этом основан принцип уничтожения инструмента подключения killcx, который мы представим позже.

Анализ причин

Для удобства объяснения мы записываем конец, который отправляет SYN-пакет, как A, а конец, который получает SYN-пакет в состоянии ESTABLISHED, как B. Причина, по которой B отвечает ACK на полученный SYN-пакет, состоит в том, чтобы позволить партнеру A подтвердите, было ли установлено предыдущее соединение. Недействительно, чтобы выполнить некоторую обработку.

Только для A, если предыдущее соединение все еще существует, полученный пакет ACK может быть обработан в обычном режиме и больше не будет обсуждаться.

Если предыдущее соединение А больше не существует, пакет SYN должен инициировать новое соединение.Для полученного пакета ACK немедленно будет отправлен ответ RST, а порядковый номер пакета RST равен порядковому номеру Пакет ACK, B После получения этого действительного пакета RST соединение будет разорвано. Если A хочет продолжить устанавливать соединение с B в это время, он может снова отправить пакет SYN, чтобы восстановить соединение, как показано на следующем рисунке.

estab_syn

Далее рассмотрим обработку исходного кода ядра,

Анализ исходного кода ядра

Перед этим нам нужно понять, как использовать инструмент SystemTap. SystemTap — очень мощный инструмент отладки в Linux. Он похож на инструмент javaagent в java. Он может получать входные параметры, возвращаемое значение, стек вызовов функции ядра во время ее работы и даже напрямую изменять значение Переменная. Подробное использование этого инструмента здесь не раскрывается, и заинтересованные студенты могут самостоятельно найти его в Google.

Далее воспользуемся инструментом SystemTap для вставки зондов в ядро ​​На примере ядра 3.10.0 функция ответа ack в ядре реализована в tcp_send_ack в net/ipv4/tcp_output.c. Мы вставляем в эту функцию call probe и печатаем стек вызовов на порту 9090. Создайте новый файл ack_test.stp, часть кода показана ниже.

%{
#include <net/sock.h>
#include <linux/tcp.h>
#include <linux/skbuff.h>
#include <net/route.h>
%}

function tcp_src_port:long(sk:long)
{
	return __tcp_sock_sport(sk)
}
function tcp_dst_port:long(sk:long)
{
	return __tcp_sock_dport(sk)
}
function tcp_src_addr:long(sk:long)
{
	return ntohl(__ip_sock_saddr(sk))
}
function tcp_dst_addr:long(sk:long)
{
	return ntohl(__ip_sock_daddr(sk))
}
function str_addr:string(addr, port) {
        return sprintf("%d.%d.%d.%d:%d",
                       (addr & 0xff000000) >> 24,
                       (addr & 0x00ff0000) >> 16,
                       (addr & 0x0000ff00) >> 8,
                       (addr & 0x000000ff),
                       port
                )
}

probe kernel.function("tcp_send_ack@net/ipv4/tcp_output.c")
{
       src_addr = tcp_src_addr($sk);
       src_port = tcp_src_port($sk);
       dst_addr = tcp_dst_addr($sk);
       dst_port = tcp_dst_port($sk);
       if (dst_port == 9090 || src_port == 9090)
       {
              printf("send ack : %s:->%s\n",
                     str_addr(src_addr, src_port),
                     str_addr(dst_addr, dst_port));
              print_backtrace();
       }
}

Выполните приведенный выше скрипт с помощью команды stap.

sudo stap -g ack_test.stp

Снова используйте scapy для отправки пакета синхронизации, ядро ​​также ответит ACK, и вывод stap будет следующим.

send ack : 10.211.55.10:9090:->10.211.55.20:50718
 0xffffffff815d0940 : tcp_send_ack+0x0/0x170 [kernel]
 0xffffffff815cb1d2 : tcp_validate_incoming+0x212/0x2d0 [kernel]
 0xffffffff815cb44d : tcp_rcv_established+0x1bd/0x760 [kernel]
 0xffffffff815d5f8a : tcp_v4_do_rcv+0x10a/0x340 [kernel]
 0xffffffff815d76d9 : tcp_v4_rcv+0x799/0x9a0 [kernel]
 0xffffffff815b1094 : ip_local_deliver_finish+0xb4/0x1f0 [kernel]
 0xffffffff815b1379 : ip_local_deliver+0x59/0xd0 [kernel]
 0xffffffff815b0d1a : ip_rcv_finish+0x8a/0x350 [kernel]
 0xffffffff815b16a6 : ip_rcv+0x2b6/0x410 [kernel]

Вы можете видеть, что этот ACK проходит через следующие вызовы функций.

tcp_v4_rcv
  -> tcp_v4_do_rcv
    -> tcp_rcv_established
      -> tcp_validate_incoming
        -> tcp_send_ack

Упрощенная часть кода функции tcp_validate_incoming показана ниже.

static bool tcp_validate_incoming(struct sock *sk, struct sk_buff *skb,
				  const struct tcphdr *th)
{	
	// seq 不在窗口内
	/* Step 1: check sequence number */
	if (!tcp_sequence(tp, TCP_SKB_CB(skb)->seq, TCP_SKB_CB(skb)->end_seq)) {
		// RST 标记没有设置
		if (!th->rst) {
			if (th->syn)
				goto syn_challenge;
		}
		goto discard;
	}
	
	/* step 4: Check for a SYN。 RFC 5961 4.2 : Send a challenge ack */
	if (th->syn) {
syn_challenge: // 处理 SYN Challenge 的情况
		tcp_send_challenge_ack(sk, skb); // 
		goto discard;
	}

Функция tcp_send_challenge_ack фактически вызывает функцию tcp_send_ack. В примечании здесь упоминаетсяRFC 5961 4.2, что в точности соответствует содержимому, связанному с вызовом ACK.

Если злоумышленник отчаянно отправляет поддельные неупорядоченные пакеты, получатель также отвечает вызовом ACK, который потребляет много ресурсов ЦП и полосы пропускания. Так RFC 5961 предложил схему ACK Throttling, которая ограничивает количество пакетов Challenge ACK, отправляемых в секунду.Это значение определяется системной переменной net.ipv4.tcp_challenge_ack_limit. Значение по умолчанию 1000, что означает, что максимум 1000 Challenge ACK пакеты разрешены в пределах 1с.ст.

Затем используйте sysctl, чтобы уменьшить это значение до 1, как показано ниже.

sudo sysctl -w net.ipv4.tcp_challenge_ack_limit="1"

Таким образом, теоретически пакет Challenge ACK отправляется несколько раз в одну секунду, а затем scapy используется для отправки 5 пакетов SYN за короткий промежуток времени, чтобы увидеть, будет ли ядро ​​отвечать только на один пакет ACK. scapy выглядит следующим образом.

send(IP(dst="10.211.55.10")/TCP(sport=50718,dport=9090,seq=10,flags='S'), loop=0, count=5)

Результаты захвата пакетов tcpdump следующие.

03:40:30.970682 IP 10.211.55.20.50718 > 10.211.55.10.9090: Flags [S], seq 10, win 8192, length 0
03:40:30.970771 IP 10.211.55.10.9090 > 10.211.55.20.50718: Flags [.], ack 3219267426, win 227, options [nop,nop,TS val 45146923 ecr 1094540820], length 0
03:40:30.974889 IP 10.211.55.20.50718 > 10.211.55.10.9090: Flags [S], seq 10, win 8192, length 0
03:40:30.975004 IP 10.211.55.20.50718 > 10.211.55.10.9090: Flags [S], seq 10, win 8192, length 0
03:40:30.978643 IP 10.211.55.20.50718 > 10.211.55.10.9090: Flags [S], seq 10, win 8192, length 0
03:40:30.981987 IP 10.211.55.20.50718 > 10.211.55.10.9090: Flags [S], seq 10, win 8192, length 0

Видно, что на первый SYN-пакет возвращается только один пакет ACK, а остальные четыре SYN не ответили на ACK.

RST-атака

Атаки RST также называются атаками поддельных пакетов сброса TCP, которые закрывают обычное соединение путем подделки пакетов RST.

Исходный IP-адрес очень легко подделать, а серийный номер подделать непросто.Самый важный момент RST-атаки заключается в том, что серийный номер построенного пакета должен попадать в скользящее окно другой стороны, иначе пакет RST будет проигнорирован и не сможет достичь эффекта атаки.

Ниже мы используем эксперимент, чтобы продемонстрировать, что пакет RST, который не находится в скользящем окне, будет игнорироваться Полный код см.: rst_out_of_window.pktGitHub.com/Артур-Станция…

+0 < S 0:0(0) win 32792 <mss 1460> 
+0 > S. 0:0(0) ack 1 <...>
+.1 < . 1:1(0) ack 1 win 65535 
+0 accept(3, ..., ...) = 4

// 不在窗口内的 RST
+.010 < R. 29202:29202(0) ack 1 win 65535

// 如果上面的 RST 包落在窗口内,连接会被重置,下面的写入不会成功
+.010 write(4, ..., 1000) = 1000 

// 断言服务端会发出下面的数据包
+0 > P. 1:1001(1000) ack 1 <...>

Выполните приведенный выше скрипт, результаты захвата пакета следующие, см. полный пакет: rst_out_of_window.pcapGitHub.com/Артур-Станция…

Пятый пакет в файле захвата пакетов показывает, что вызов записи выполнен успешно, 1000 байтов отправлены успешно, и пакет RST не влияет на вызов записи.

Давайте представим два инструмента, которые используют атаку RST для разрыва соединения.

Инструмент 1: Знакомство с использованием и принципом работы инструмента tcpkill

Шаги по установке команды tcpkill под Centos следующие:

yum install epel-release -y
yum install dsniff -y

Экспериментальные шаги: 1. Машина c2 (10.211.55.10) запускает команду nc для прослушивания порта 8080 и действует как сервер, обозначенный как B

nc -l 8080

2. Машина c2 запускает tcpdump для захвата пакетов.

sudo tcpdump -i any port 8080 -nn -U -vvv -w test.pcap

3. Терминал локальной машины (10.211.55.2, обозначенный как A) использует nc для установления TCP-соединения с портом 8080 B.

nc c2 8080

Это TCP-соединение можно увидеть на сервере B.

netstat -nat | grep -i 8080
tcp        0      0 10.211.55.10:8080       10.211.55.2:60086       ESTABLISHED

4. Запустите tcpkill

sudo tcpkill -i eth0 port 8080

Обратите внимание, что в настоящее время TCP-соединение все еще не повреждено и не было прервано.

5. Введите что-нибудь в командной строке nc локального машинного терминала, введите здесьhello, обнаружил, что процесс nc сервера и клиента в это время завершился

Давайте проанализируем файл захвата, который можно скачать с моего github.tcpkill.pcap

Видно, что tcpkill подделал IP-адреса A и B и отправил RST-пакеты обеим сторонам связи.Проблема в том, что поддельный IP-адрес очень прост.Как он узнает серийный номер текущей сессии?

Принцип tcpkill аналогичен принципу tcpdump, и он будет захватывать подходящие пакеты через библиотеку libpcap. Следовательно, только при наличии tcp-соединения с передачей данных он может получить серийный номер текущего сеанса и использовать этот серийный номер для подделки IP для отправки квалифицированных RST-пакетов.

Принцип показан на рисунке ниже

Видно, что tcpkill отправляет по 3 RST-пакета на каждый конец, так как при подключении высокоскоростной передачи данных порядковый номер, рассчитанный по текущему захваченному пакету, может больше не находиться в окне TCP-соединения. в этом случае пакеты RST игнорируются, поэтому по умолчанию tcpkill заранее вычисляет несколько порядковых номеров. Вы также можете указать параметры-nУкажите дополнительные пакеты RST, такие какtcpkill -9

Согласно приведенному выше анализу, ограничения tcpkill по-прежнему очевидны, и он не может уничтожить мертвое соединение.Давайте представим новый инструмент killcx, чтобы посмотреть, как он справляется с этой ситуацией.

Инструмент 2: killcx

killcx — это скрипт, написанный на Perl под Linux, который может закрыть TCP-соединение, независимо от того, в каком состоянии находится TCP-соединение.

Давайте проведем эксперимент Первые несколько шагов эксперимента точно такие же, как и в первом примере.

1. Машина c2 (10.211.55.10) запускает команду nc для прослушивания порта 8080 и действует как сервер, обозначенный как B

nc -l 8080

2. Машина c2 запускает tcpdump для захвата пакетов.

sudo tcpdump -i any port 8080 -nn -U -vvv -w test.pcap

3. Терминал локальной машины (10.211.55.2, обозначенный как A) использует nc для установления TCP-соединения с портом 8080 B.

nc c2 8080

Это TCP-соединение можно увидеть на сервере B.

netstat -nat | grep -i 8080
tcp        0      0 10.211.55.10:8080       10.211.55.2:61632       ESTABLISHED

4. Введите все, что хотите, в командной строке клиента A nc.Этот шаг можно полностью пропустить.Введите здесь "hello\n".

5. Выполните команду killcx, обратите внимание, что killcx выполняется после шага 4.

sudo ./killcx 10.211.55.2:61632

Вы можете видеть, что процессы nc сервера и клиента завершились.

Результаты захвата пакета следующие

Первые 5 пакетов нормальные, три рукопожатия плюс одна передача данных, самое интересное начинается с 6-го пакета

  • Шестой пакет — это SYN-пакет, отправленный поддельным IP-адресом killcx на сервер B.
  • Седьмой пакет — это пакет ACK, на который ответил сервер B и который содержит номера SEQ и ACK.
  • Восьмой пакет — это RST-пакет, отправленный поддельным IP-адресом killcx на сервер B.
  • Девятый пакет — это пакет RST, отправленный поддельным IP-адресом killcx клиенту А.

Весь процесс показан на рисунке ниже

резюме

В этой статье рассказывается, почему соединение в состоянии ESTABLISHED должно отвечать на пакеты SYN, что такое Challenge ACK, воспроизводится явление с использованием scapy, демонстрируется использование инструмента отладки зонда ядра SystemTap и, наконец, воспроизводится ACK путем изменения ограничения скорости системных переменных.

В конце статьи представлены два инструмента для уничтожения TCP-соединений, tcpkill и killcx:

  • tcpkill использует консервативный метод: при поступлении нового пакета, например при захвате трафика, он получает номер SEQ/ACK, этот метод может только разорвать соединение с передачей данных.
  • killcx использует более активный метод, активно отправляя SYN-пакеты для получения номеров SEQ/ACK, таким образом, как активные, так и неактивные соединения могут быть уничтожены.

Если у вас есть какие-либо вопросы, вы можете отсканировать QR-код ниже и подписаться на мою официальную учетную запись, чтобы связаться со мной.