Первоначально этот вопрос был задан читателем в комментарии к разделу «Атака 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, чтобы восстановить соединение, как показано на следующем рисунке.
Далее рассмотрим обработку исходного кода ядра,
Анализ исходного кода ядра
Перед этим нам нужно понять, как использовать инструмент 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-код ниже и подписаться на мою официальную учетную запись, чтобы связаться со мной.