Проведение весенних дней за созданием пакетов на NahamCon 2022

CTF-отчет о сетевых задачах на NahamCon 2022

NahamCon2022 закончился, и мы рады, что нам удалось финишировать в топ 5% с нашей командой Stack Labs (3049 очков, 195 среди 4034 команд). В этом году они придумали захватывающие сетевые задачи, которые создал @Kkevsterrr#7469. Хотя нам не удалось их решить, это было хорошее введение в анализ сети и манипулирование пакетами с помощью Scapy.

Вот что я узнал.

Задачи:

  • 1. Contemporaneous Open – трудная – 14 решений – 500 очков – впервые решена StaticFlow
  • 2. День фривольного флага – сложно – 9 решений – 500 очков – первым разгадал Maple Bacon

1. Contemporaneous Open

Автор: @Kkevsterrr#7469
Мы хотим отдать вам флаг, правда хотим. Просто дайте нам TCP HTTP сервер, на который его нужно отправить, и мы сделаем POST запрос со всеми данными, которые вам нужны! У нас просто проблема с брандмауэром на нашей стороне, и мы отбрасываем некоторые важные пакеты (в частности, любой входящий SYN+ACK пакет отбрасывается). Однако это не должно быть проблемой для сетевого профессионала вроде вас – просто сделайте TCP-сервер, которому не нужно отправлять эти пакеты!

Используемые инструменты:

  • scapy для работы с пакетами
  • tshark wireshark’s cli version

Традиционное трехстороннее рукопожатие

Давайте начнем с некоторых полезных напоминаний о протоколе TCP. В традиционном трехстороннем рукопожатии TCP:

  1. клиент посылает SYN (Synchronize Sequence Number), чтобы сообщить серверу, что он хочет начать обмен данными. SYN указывает, с каким порядковым номером он начнет сегменты.
  2. Сервер отвечает клиенту SYN-ACK. ACK означает ответ на SYN клиента. При этом SYN указывает, с какого порядкового номера он начнет сегменты.
  3. Наконец, клиент подтверждает (ACK) ответ сервера, и соединение переходит в состояние ESTABLISHED, чтобы они могли начать обмен данными.

SYN-ACK отбрасывается недоверчивым брандмауэром

В этой задаче флаг передается POST-запросом к серверу, который мы должны создать на порту 80. Дело в том, что брандмауэр клиента отбрасывает каждый входящий пакет SYN-ACK. Поэтому мы должны найти решение для установления соединения без необходимости посылать пакет с флагом SYN-ACK.

Пока я проводил часы, освежая в памяти внутреннее устройство TCP/IP, изучая основы scapy и исследуя множество способов решения проблемы (фрагментация ответа SYN/ACK, попытка установить соединение HTTP3 через QUIC), решение лежало в названии задачи.

TCP Simultaneous Open

Contemporaneous Open – это ссылка на переход состояния TCP Simultaneous Open. Также его называют “одновременное активное открытие с обеих сторон”, он относится к старой функции TCP, используемой для обработки побочных ситуаций в рукопожатиях TCP, например, когда сервер и клиент одновременно посылают друг другу SYN. Этот процесс делает возможным для двух приложений посылать SYN друг другу, чтобы начать TCP-соединение.

Когда оба конца одновременно посылают SYN, оба конца переходят в состояние SYN_SENT. Когда они получают SYN, их состояние меняется на SYN_RCVD, они повторно посылают SYN и подтверждают полученный SYN. Когда оба конца получают SYN и подтвержденный SYN, соединение переходит в состояние ESTABLISHED. В таком состоянии оба конца действуют как клиент и сервер.

Поэтому клиенту не нужен ответ SYN/ACK от сервера, чтобы завершить трехстороннее рукопожатие. Нам достаточно послать SYN и дождаться SYN/ACK клиента, чтобы установить соединение и начать обмен HTTP. Это может избавить нас от отправки пакета SYN/ACK, который будет отброшен клиентом. Мы будем использовать Scapy для имитации сервера с таким поведением.

Создание скриптов с помощью Scapy

Перед выполнением любых скриптов Scapy мы должны отключить ответ ядра Linux, чтобы избежать любых ответов с меткой RST. Scapy работает в пространстве пользователя, поэтому ядро не имеет представления о том, что делает Scapy.

Используя iptables, мы можем отбрасывать пакеты с меткой RST, которые ядро посылает с порта 80.

sudo iptables -A OUTPUT -p tcp --tcp-flags RST RST --sport 80 -j DROP
Вход в полноэкранный режим Выход из полноэкранного режима

Идея заключается в том, чтобы создать настраиваемый сервер, который делает следующее:

  • принимать SYN клиента (seq=n)
  • посылает SYN (seq=m)
  • посылает ACK (seq=m+1, ack=n+1), чтобы подтвердить SYN клиента.
  • клиент подтверждает наш SYN, получает наш ACK и устанавливает сессию

Мы будем использовать tshark в качестве дополнительного инструмента для отслеживания входящих пакетов.

1. Сначала получим SYN-пакет клиента.

Обратите внимание, что IP-адреса были изменены на “MY_IP” и “CLIENT_IP”.

# server.py
#! /usr/bin/python
from scapy import *

S_ADDR = "MY_IP"
S_PORT = 80

# 1. Listen for client's SYN and get IP and port
c_syn = sniff(
    filter="tcp and port 80",
    count=1,
    prn=lambda x:x.sprintf("Received SYN from {IP:%IP.src%:%TCP.dport%, seq=%TCP.seq}%")
)
Вход в полноэкранный режим Выход из полноэкранного режима

Мы подключаемся к вызову:

❯ nc challenge.nahamcon.com 31334
so glad you're here! i would love to give you the flag. just give me the IP address that's running an HTTP server, and I'll shoot you the flag immediately.
oh one snag, we've got some firewall issues on our side, and some important packets are getting dropped. shouldn't be a problem for you, though.
>>> MY_IP
here it comes!
hmm nope looks like you didn't get it...
Вход в полноэкранный режим Выход из полноэкранного режима
# server.py output
Received SYN from CLIENT_IP:http, seq=510141201
Войти в полноэкранный режим Выход из полноэкранного режима
sudo tshark -f "tcp port 80"
Running as user "root" and group "root". This could be dangerous.
Capturing on 'ens3'
    1 0.000000000 CLIENT_IP → MY_IP TCP 74 58356 → 80 [SYN] Seq=0 Win=42600 Len=0 MSS=1420 SACK_PERM=1 TSval=3977333110 TSecr=0 WS=128
    2 1.015633580 CLIENT_IP → MY_IP TCP 74 [TCP Retransmission] 58356 → 80 [SYN] Seq=0 Win=42600 Len=0 MSS=1420 SACK_PERM=1 TSval=3977334125 TSecr=0 WS=128
Войти в полноэкранный режим Выйти из полноэкранного режима

Отлично! Теперь мы можем использовать IP и порт для отправки наших пакетов.

2. Теперь давайте отправим наш SYN и подтвердим SYN клиента.

# server.py
# ...

# 2. Send Syn
C_ADDR = c_syn[0][IP].src
C_PORT = c_syn[0].sport
C_SEQ = c_syn[0].seq
S_SEQ = 1234 # random number

ip = IP(src=S_ADDR, dst=C_ADDR)
tcp_syn = TCP(
    sport=S_PORT,
    dport=C_PORT,
    flags="S",
    seq=S_SEQ,
)
s_syn = send(ip/tcp_syn)

print(f"Send SYN with seq={S_SEQ}")

# 3. Send Ack
S_SEQ+=1
C_SEQ+=1
tcp_ack = TCP(
    sport=S_PORT,
    dport=C_PORT,
    flags="A",
    seq=S_SEQ,
    ack=C_SEQ,
)
s_ack = send(ip/tcp_ack)

print(f"Send ACK with seq={S_SEQ} and ack={C_SEQ}")
Вход в полноэкранный режим Выход из полноэкранного режима
Received SYN from CLIENT_IP:http, seq=3800804531
Send SYN with seq=1234
Send ACK with seq=1235 and ack=3800804532
Войти в полноэкранный режим Выход из полноэкранного режима

Мы будем использовать флаг -z "follow,tcp,hex,0" для отображения содержимого первого потока TCP (CLIENT_IP → MY_IP).

sudo tshark -f "tcp port 80" -z "follow,tcp,hex,0"
Running as user "root" and group "root". This could be dangerous.
Capturing on 'ens3'
    1 0.000000000 CLIENT_IP → MY_IP TCP 74 34206 → 80 [SYN] Seq=0 Win=42600 Len=0 MSS=1420 SACK_PERM=1 TSval=3979143134 TSecr=0 WS=128
    2 0.009865804 MY_IP → CLIENT_IP TCP 54 80 → 34206 [SYN] Seq=0 Win=8192 Len=0
    3 0.012471996 MY_IP → CLIENT_IP TCP 54 80 → 34206 [ACK] Seq=1 Ack=1 Win=8192 Len=0
    4 0.114386572 CLIENT_IP → MY_IP TCP 74 [TCP Retransmission] 34206 → 80 [SYN, ACK] Seq=0 Ack=1 Win=42600 Len=0 MSS=1420 SACK_PERM=1 TSval=3979143248 TSecr=0 WS=128
    5 0.115899112 CLIENT_IP → MY_IP TCP 268 POST / HTTP/1.1  [TCP segment of a reassembled PDU]
    6 0.115899266 CLIENT_IP → MY_IP HTTP 104 POST / HTTP/1.1  (application/x-www-form-urlencoded)
    7 0.319132750 CLIENT_IP → MY_IP TCP 318 [TCP Retransmission] 34206 → 80 [PSH, ACK] Seq=1 Ack=1 Win=333 Len=264
    8 0.727116985 CLIENT_IP → MY_IP TCP 318 [TCP Retransmission] 34206 → 80 [PSH, ACK] Seq=1 Ack=1 Win=333 Len=264
    9 1.551128826 CLIENT_IP → MY_IP TCP 318 [TCP Retransmission] 34206 → 80 [PSH, ACK] Seq=1 Ack=1 Win=333 Len=264
   10 3.119495767 CLIENT_IP → MY_IP TCP 54 34206 → 80 [FIN, ACK] Seq=265 Ack=1 Win=333 Len=0
   11 3.215170188 CLIENT_IP → MY_IP TCP 318 [TCP Retransmission] 34206 → 80 [FIN, PSH, ACK] Seq=1 Ack=1 Win=333 Len=264
^C11 packets captured

===================================================================
Follow: tcp,hex
Filter: tcp.stream eq 0
Node 0: CLIENT_IP:34206
Node 1: MY_IP:80
00000000  50 4f 53 54 20 2f 20 48  54 54 50 2f 31 2e 31 0d  POST / H TTP/1.1.
00000010  0a 48 6f 73 74 3a 20 ■   ■  ■  ■  ■  ■  ■  ■  ■   .Host: ■ ■■■■■■■■
00000020  ■  ■  ■  ■  0d 0a 55 73  65 72 2d 41 67 65 6e 74  ■■■■..Us er-Agent
00000030  3a 20 70 79 74 68 6f 6e  2d 72 65 71 75 65 73 74  : python -request
00000040  73 2f 32 2e 32 37 2e 31  0d 0a 41 63 63 65 70 74  s/2.27.1 ..Accept
00000050  2d 45 6e 63 6f 64 69 6e  67 3a 20 67 7a 69 70 2c  -Encodin g: gzip,
00000060  20 64 65 66 6c 61 74 65  0d 0a 41 63 63 65 70 74   deflate ..Accept
00000070  3a 20 2a 2f 2a 0d 0a 43  6f 6e 6e 65 63 74 69 6f  : */*..C onnectio
00000080  6e 3a 20 6b 65 65 70 2d  61 6c 69 76 65 0d 0a 43  n: keep- alive..C
00000090  6f 6e 74 65 6e 74 2d 4c  65 6e 67 74 68 3a 20 35  ontent-L ength: 5
000000A0  30 0d 0a 43 6f 6e 74 65  6e 74 2d 54 79 70 65 3a  0..Conte nt-Type:
000000B0  20 61 70 70 6c 69 63 61  74 69 6f 6e 2f 78 2d 77   applica tion/x-w
000000C0  77 77 2d 66 6f 72 6d 2d  75 72 6c 65 6e 63 6f 64  ww-form- urlencod
000000D0  65 64 0d 0a 0d 0a                                 ed....
000000D6  66 6c 61 67 3d 66 6c 61  67 25 37 42 36 61 63 66  flag=fla g%7B6acf
000000E6  64 66 63 39 33 36 39 65  61 64 66 64 62 39 34 33  dfc9369e adfdb943
000000F6  39 62 30 61 63 33 39 36  39 37 31 31 25 37 44 25  9b0ac396 9711%7D%
00000106  30 41                                             0A
===================================================================
Вход в полноэкранный режим Выход из полноэкранного режима

И вот мы получаем этот замечательный флаг:

Улучшения

Гораздо более элегантным решением было бы действовать как настоящий HTTP-сервер, принимая каждый пакет клиента, отвечая на POST-запрос и затем изящно завершая соединение, когда клиент посылает FIN.

Посмотрите решение nneonneo, в котором реализована эта возможность в дополнение к использованию асинхронного сниффера, который помещает входящие пакеты в очередь, что облегчает итерацию клиентских пакетов. Также он использует множество вспомогательных функций для отслеживания, фильтрации входящих и исходящих пакетов. Я бы определенно использовал их в качестве шаблона для следующих задач. Чисто и гладко.

В следующей части мы рассмотрим задачу Freaky Flag Day. Надеюсь, вам понравится поэзия и флаги TCP.

2. День причудливого флага

Автор: @Kkevsterrr#7469

Наши TCP-флаги решили, что сегодня они хотели бы поменяться местами; все, что вам нужно сделать, это добраться до HTTP-сервера!

Розы стали красными, а SYN-ы стали FIN-ами.

RST+ACK теперь для вас SYN+ACK.

ACK теперь Es, и что еще мы сделали?

PSH+ACKs – это FIN+SYNs, просто для развлечения.

Подсказка: Если вы хотите выполнить это задание из дома или виртуальной машины, убедитесь, что вы не находитесь за NAT, который может съесть ваши неожиданные пакеты.

Взаимодействуйте с этой задачей на: http://SERVER_IP

Использованные инструменты:

  • scapy для работы с пакетами
  • tshark wireshark cli версия
  • curl для http-клиента
  • nfqueue для перехвата пакетов, поставленных в очередь пакетным фильтром ядра.

Итак, после победы над подозрительным клиентом, который отбрасывает каждый входящий SYN/ACK пакет, наш следующий соперник – сервер, который меняет флаги каждого запроса (как входящего, так и исходящего).

Хотите установить сессию? Сервер понимает, что вы хотите его завершить.
Хотите подтвердить, что пакет успешно получен? Сервер понимает, что у вас перегрузка сети. Глупости.

Цель – правильно связаться с сервером. Поэтому мы должны говорить на его языке. Сопоставление флагов выглядит следующим образом:

S ⇔ F

RA ⇔ SA

A ⇔ E

PA ⇔ FS

Предварительные тесты

Давайте попробуем подключиться к серверу с помощью curl.

❯ curl --local-port 44444 http://SERVER_IP
Вход в полноэкранный режим Выход из полноэкранного режима
sudo tshark -f "tcp port 80 and host SERVER_IP"
    1 0.000000000 MY_IP → SERVER_IP TCP 74 44444 → 80 [SYN] Seq=0 Win=64240 Len=0 MSS=1460 SACK_PERM=1 TSval=1756025715 TSecr=0 WS=128
    2 1.021102385 MY_IP → SERVER_IP TCP 74 [TCP Retransmission] 44444 → 80 [SYN] Seq=0 Win=64240 Len=0 MSS=1460 SACK_PERM=1 TSval=1756026736 TSecr=0 WS=128
Войти в полноэкранный режим Выход из полноэкранного режима

Как и ожидалось, ничего не происходит, так как флаг SYN интерпретируется как флаг FYN на стороне сервера.

Теперь воспользуемся scapy и отправим FIN-флаг с сегментом случайного порядкового номера.

#! /usr/bin/python
from scapy import *

C_ADDR = "MY_IP"
C_PORT = 44444
S_ADDR = "SERVER_IP"
S_PORT = 80
C_SEQ = 1234 # random

ip = IP(src=C_ADDR, dst=S_ADDR)
tcp = TCP(
    sport=C_PORT,
    dport=S_PORT,
    flags="F",
    seq=C_SEQ,
)
p = send(ip/tcp)
Вход в полноэкранный режим Выход из полноэкранного режима
sudo tshark -f "tcp port 80 and host SERVER_IP"
    1 0.000000000 MY_IP → SERVER_IP TCP 54 44444 → 80 [FIN] Seq=1 Win=8192 Len=0
    2 0.144079153 SERVER_IP → MY_IP TCP 58 80 → 44444 [RST, ACK] Seq=1 Ack=2 Win=65320 Len=0 MSS=1420
Войти в полноэкранный режим Выход из полноэкранного режима

Сервер понимает “SYN” и посылает нам RST-ACK (который является SYN-ACK). Теперь он должен ждать ECN (который является ACK)… И так далее. Вы поняли.

Перехват и модификация пакетов с помощью scapy и nfqueue

Мы могли бы переписать HTTP-клиент с помощью scapy, используя модифицированные флаги. Но это долгая и утомительная задача, поскольку мы должны обрабатывать каждый запрос с помощью scapy. Менее болезненным решением было бы иметь слой, который перехватывает каждый пакет и просто обновляет флаги перед отправкой/принятием. И в этом нам может помочь nfqueue.

Используя стандартный http-клиент, например curl, перехватываем все входящие и исходящие пакеты с помощью nfqueue, передаем их нашему скрипту scapy, который соответствующим образом изменит флаги и затем отправит измененные пакеты по назначению.

Netfilter Queue – это цель iptables, которая отдает решение о пакетах в пользовательское пространство. Он является частью проекта Netfilter, который также предоставляет iptables и nftables. Она обычно используется в качестве прокси-сервера или для атак типа “человек посередине”.

Чтобы перехватывать пакеты с помощью nfqueue, необходимо соответствующим образом настроить правила цепочки брандмауэра. В данной ситуации мы хотим перехватить:

  • входящие пакеты от SERVER_IP (цепочка INPUT)
  • исходящие пакеты на SERVER_IP (цепочка OUTPUT).

Давайте попробуем это сделать.

1. Установим nfqueue

#!/usr/bin/python3
from netfilterqueue import NetfilterQueue
from scapy import *
import os

S_ADDR = "SERVER_IP"

# Update iptables rules
output_rule = f"iptables -A OUTPUT --destination {S_ADDR} -j NFQUEUE"
input_rule = f"iptables -A INPUT --source {S_ADDR} -j NFQUEUE"
flush_rules = "iptables -F OUTPUT && iptables -F INPUT"
os.system(input_rule)
os.system(output_rule)

def callback(raw_pkt):
    # Get a scapy object from raw packet
    p = IP(raw_pkt.get_payload())
    print(p.show())
    # Tell nfqueue to accept the packet
    raw_pkt.accept()

# Init nfqueue
q = NetfilterQueue()
q.bind(0, callback)
try:
    q.run()
except KeyboardInterrupt:
    q.unbind()
    os.system(flush_rules)
Войти в полноэкранный режим Выйти из полноэкранного режима

Сначала мы обновим правила iptables, чтобы перехватывать интересные пакеты для nfqueue. Затем мы инициализируем nfqueue и привязываем очередь под номером 0 к нашей функции обратного вызова. Мы используем одну и ту же очередь для входящих и исходящих пакетов.

В нашей функции обратного вызова мы получаем необработанный пакет. Для работы с ним мы создаем IP-слой scapy с полезной нагрузкой пакета в качестве аргумента. Мы печатаем его и, наконец, принимаем пакет. Если мы прерываем процесс (с помощью Ctrl+C), то мы отвязываем очередь и смываем правила iptables.

Важно: проверьте правила цепочки INPUT и OUTPUT перед прошивкой, так как она прошивает все.

Давайте посмотрим, работает ли это.

❯ curl http://SERVER_IP --local-port 44444
Вход в полноэкранный режим Выход из полноэкранного режима
# Script output
###[ IP ]### 
  version= 4
  ihl= 5
  tos= 0x0
  len= 60
  id= 60025
  flags= DF
  frag= 0
  ttl= 64
  proto= tcp
  chksum= 0xa981
  src= SERVER_IP
  dst= CLIENT_IP
  options
###[ TCP ]### 
     sport= 44444
     dport= http
     seq= 1190907934
     ack= 0
     dataofs= 10
     reserved= 0
     flags= S
     window= 64240
     chksum= 0xfaf1
     urgptr= 0
     options= [('MSS', 1460), ('SAckOK', b''), ('Timestamp', (2004053730, 0)), ('NOP', None), ('WScale', 7)]
Войдите в полноэкранный режим Выход из полноэкранного режима
sudo tshark -f "tcp port 80 and host SERVER_IP"
    1 0.000000000 CLIENT_IP → SERVER_IP TCP 74 44444 → 80 [SYN] Seq=0 Win=64240 Len=0 MSS=1460 SACK_PERM=1 TSval=2004025682 TSecr=0 WS=128
Войти в полноэкранный режим Выйти из полноэкранного режима

Отлично! Curl пытается инициировать сессию с сервером с помощью пакета с флагом SYN. Когда пакет достигает инструкции raw_pkt.accept(), он отправляется клиенту, и мы можем заметить это в выводе tshark.

2. Изменение флага

В нашей функции обратного вызова давайте попробуем изменить флаг пакета на ‘F’ и посмотрим, что произойдет.

def callback(raw_pkt):
    # Get a scapy object from raw packet
    p = IP(raw_pkt.get_payload())
    print(p.show())                                                                  
    # Set S flag to F flag
    if p.haslayer(TCP):
        if p[TCP].flags = "S"
            p[TCP].flags = "F"

    # Update raw packet and then accept it
    raw_pkt.set_payload(bytes(p))
    raw_pkt.accept()
Вход в полноэкранный режим Выход из полноэкранного режима
sudo tshark -f "tcp port 80 and host SERVER_IP"
    1 0.000000000 CLIENT_IP → SERVER_IP TCP 74 44444 → 80 [FIN] Seq=1 Win=64240 Len=0 MSS=1460 SACK_PERM=1 TSval=2005103907 TSecr=0 WS=128
    2 1.005513362 CLIENT_IP → SERVER_IP TCP 74 [TCP Retransmission] 44444 → 80 [FIN] Seq=1 Win=8222720 Len=0 MSS=1460 SACK_PERM=1 TSval=2005104912 TSecr=0 WS=128
Войти в полноэкранный режим Выход из полноэкранного режима

Что-то не так, мы посылаем FIN, но не получаем ответа… Ах да! Конечно же, контрольные суммы. Давайте обновим контрольные суммы соответствующим образом.

3. Обновить контрольные суммы

# Set S flag to F flag
    if p.haslayer(TCP):
        if p[TCP].flags = "S"
            p[TCP].flags = "F"
        # Update checksums
        del p[IP].chksum     
        del p[TCP].chksum    
        p.show2()
Войти в полноэкранный режим Выйти из полноэкранного режима

Я использую функцию show2(), которая пересчитывает контрольные суммы, если их нет. Это функционально, но громко, так как также печатает пакет. Я не нашел другого решения, которое делает это тихо.

Давайте попробуем сейчас.

sudo tshark -f "tcp port 80 and host SERVER_IP"
    1 0.000000000 CLIENT_IP → SERVER_IP TCP 74 44444 → 80 [FIN] Seq=1 Win=64240 Len=0 MSS=1460 SACK_PERM=1 TSval=2005448037 TSecr=0 WS=128
    2 0.154677103 SERVER_IP → CLIENT_IP TCP 74 80 → 44444 [RST, ACK] Seq=1 Ack=2 Win=64768 Len=0 MSS=1420 SACK_PERM=1 TSval=1006897750 TSecr=2005448037 WS=128

Вход в полноэкранный режим Выйти из полноэкранного режима

Да! Сервер отвечает нам RST-ACK. Но наш клиент curl все еще не понимает, что он имеет в виду. Давайте теперь сопоставим все флаги соответствующим образом.

4. Сопоставление всех флагов (окончательный код)

#!/usr/bin/python3
from netfilterqueue import NetfilterQueue
from scapy import *
import os

S_ADDR = "SERVER_IP"

# Update iptables rules to intercept incoming and outcoming packets from/to S_ADDR
output_rule = f"iptables -A OUTPUT --destination {S_ADDR} -j NFQUEUE"
input_rule = f"iptables -A INPUT --source {S_ADDR} -j NFQUEUE"
flush_rules = "iptables -F OUTPUT && iptables -F INPUT"
os.system(input_rule)
os.system(output_rule)

# Dictionary with flag mapping
fd = {
    "S":"F",
    "RA":"SA",
    "A":"E",
    "PA":"FS"
}
# Append the inverted dictionnary
fd.update(dict((v,k) for k,v in fd.items()))

# Helper function to set the flags accordingly
def set_flags(flags):
    return fd[flags] if flags in fd else flags

# Callback function that modifies our packets before sending them
def callback(raw_pkt):
    # Get a scapy raw packet that we can modify
    p = IP(raw_pkt.get_payload())
    # Set flags accordingly
    if p.haslayer(TCP):
        p[TCP].flags = set_flags(str(p[TCP].flags))
        # Calculate new chksums
        del p[IP].chksum
        del p[TCP].chksum
        p.show2()
        # Update 
        raw_pkt.set_payload(bytes(p))

    # Now we tell nfqueue to accept the modified packet
    raw_pkt.accept()

# Init nfqueue
q = NetfilterQueue()
q.bind(0, callback)
try:
    q.run()
except KeyboardInterrupt:
    q.unbind()
    os.system(flush_rules)
Вход в полноэкранный режим Выход из полноэкранного режима

Мы используем словарь для отображения всех флагов. Затем мы используем метод Dict.update(), чтобы добавить те же кортежи, но поменяв местами ключи и значения. (например, "S": "F" добавляет "F": "S").

Давайте теперь попробуем наш новый код.

❯ curl http://SERVER_IP --local-port 44444
Вход в полноэкранный режим Выход из полноэкранного режима
<!doctype html>
<html>
  <head>
    <title>FreakyFlagday</title>
  </head>
  <body>
    <p>It's freakyflagday and you've made it so far! Well done. All you've gotta do is download the text file from '/gimmedafile.txt' and it'll send you a big important file with the flag at the very end.</p>
  </body>
</html>
Войти в полноэкранный режим Выход из полноэкранного режима
sudo tshark -f "tcp port 80 and host SERVER_IP"
    1 0.000000000 MY_IP → SERVER_IP TCP 74 44444 → 80 [FIN] Seq=1 Win=64240 Len=0 MSS=1460 SACK_PERM=1 TSval=1784676225 TSecr=0 WS=128
    2 0.145262609 SERVER_IP → MY_IP TCP 74 80 → 44444 [RST, ACK] Seq=1 Ack=2 Win=64768 Len=0 MSS=1420 SACK_PERM=1 TSval=786145437 TSecr=1784676225 WS=128
    3 0.160854317 MY_IP → SERVER_IP TCP 66 44444 → 80 [ECN] Seq=2 Win=64256 Len=0 TSval=1784676385 TSecr=786145437
    4 0.170014574 MY_IP → SERVER_IP HTTP 144 [TCP Port numbers reused] GET / HTTP/1.1 
    5 0.287621647 SERVER_IP → MY_IP TCP 66 80 → 44444 [ECN] Seq=1 Win=506 Len=0 TSval=786145607 TSecr=1784676385
    6 0.290354873 SERVER_IP → MY_IP HTTP 220 [TCP Port numbers reused] HTTP/1.1 200 OK 
    7 0.311377282 MY_IP → SERVER_IP TCP 66 [TCP Keep-Alive] 44444 → 80 [ECN] Seq=78 Win=501 Len=0 TSval=1784676535 TSecr=786145609
    8 0.426628596 SERVER_IP → MY_IP TCP 376 [TCP Port numbers reused] 80 → 44444 [FIN, SYN] Seq=0 Win=506 Len=310 TSval=786145748 TSecr=1784676535
    9 0.444996568 MY_IP → SERVER_IP TCP 66 [TCP Keep-Alive] 44444 → 80 [ECN] Seq=78 Win=501 Len=0 TSval=1784676668 TSecr=786145748
   10 0.449897000 MY_IP → SERVER_IP TCP 66 [TCP ACKed unseen segment] [TCP Retransmission] 44444 → 80 [FIN, ACK] Seq=78 Ack=465 Win=501 Len=0 TSval=1784676669 TSecr=786145748
   11 0.562439239 SERVER_IP → MY_IP TCP 66 [TCP Retransmission] 80 → 44444 [FIN, ACK] Seq=310 Ack=1 Win=506 Len=0 TSval=786145885 TSecr=1784676669
   12 0.583268378 MY_IP → SERVER_IP TCP 66 44444 → 80 [ECN] Seq=79 Win=501 Len=0 TSval=1784676807 TSecr=786145885
Войти в полноэкранный режим Выйти из полноэкранного режима

Похоже, что это сработало довольно хорошо. Похоже, что флаг находится в этом файле gimmedafile.txt. Давайте попробуем получить этот файл.

❯ curl http://SERVER_IP/gimmedafile.txt --local-port 44445
Voluptatem ipsum [...39404 characters...] dolor ut.

flag{e2960da061a85fbcabb0670e4ddb9e93}
Вход в полноэкранный режим Выход из полноэкранного режима
sudo tshark -f "tcp port 80 and host SERVER_IP"
    1 0.000000000 MY_IP → SERVER_IP TCP 74 44445 → 80 [FIN] Seq=1 Win=64240 Len=0 MSS=1460 SACK_PERM=1 TSval=1784824675 TSecr=0 WS=128
    2 0.142799620 SERVER_IP → MY_IP TCP 74 80 → 44445 [RST, ACK] Seq=1 Ack=2 Win=64768 Len=0 MSS=1420 SACK_PERM=1 TSval=786293873 TSecr=1784824675 WS=128
    3 0.159637964 MY_IP → SERVER_IP TCP 66 44445 → 80 [ECN] Seq=2 Win=64256 Len=0 TSval=1784824834 TSecr=786293873
    4 0.165712720 MY_IP → SERVER_IP HTTP 159 [TCP Port numbers reused] GET /gimmedafile.txt HTTP/1.1 
    5 0.278158205 SERVER_IP → MY_IP TCP 66 80 → 44445 [ECN] Seq=1 Win=506 Len=0 TSval=786294039 TSecr=1784824834
    6 0.281436371 SERVER_IP → MY_IP HTTP 227 [TCP Port numbers reused] HTTP/1.1 200 OK 
    7 0.286101671 SERVER_IP → MY_IP TCP 1474 80 → 44445 [ECN] Seq=161 Win=506 Len=1408 TSval=786294048 TSecr=1784824834
    8 0.290101742 SERVER_IP → MY_IP TCP 1474 80 → 44445 [ECN] Seq=1569 Win=506 Len=1408 TSval=786294052 TSecr=1784824834
    9 0.293428900 SERVER_IP → MY_IP TCP 1474 80 → 44445 [ECN] Seq=2977 Win=506 Len=1408 TSval=786294056 TSecr=1784824834
   10 0.300100620 SERVER_IP → MY_IP TCP 1474 80 → 44445 [ECN] Seq=4385 Win=506 Len=1408 TSval=786294060 TSecr=1784824834
   11 0.303058929 SERVER_IP → MY_IP TCP 1474 80 → 44445 [ECN] Seq=5793 Win=506 Len=1408 TSval=786294060 TSecr=1784824834
   12 0.305803295 SERVER_IP → MY_IP TCP 1474 80 → 44445 [ECN] Seq=7201 Win=506 Len=1408 TSval=786294061 TSecr=1784824834
   13 0.308359956 SERVER_IP → MY_IP TCP 1474 80 → 44445 [ECN] Seq=8609 Win=506 Len=1408 TSval=786294061 TSecr=1784824834
   14 0.311371415 SERVER_IP → MY_IP TCP 1474 80 → 44445 [ECN] Seq=10017 Win=506 Len=1408 TSval=786294067 TSecr=1784824834
   15 0.314342631 SERVER_IP → MY_IP TCP 1474 80 → 44445 [ECN] Seq=11425 Win=506 Len=1408 TSval=786294070 TSecr=1784824834
   16 0.317448713 MY_IP → SERVER_IP TCP 66 [TCP Keep-Alive] 44445 → 80 [ECN] Seq=93 Win=501 Len=0 TSval=1784824974 TSecr=786294043
   17 0.322497523 MY_IP → SERVER_IP TCP 66 [TCP Keep-Alive] 44445 → 80 [ECN] Seq=93 Win=497 Len=0 TSval=1784824980 TSecr=786294048
   18 0.345296346 MY_IP → SERVER_IP TCP 66 [TCP Keep-Alive] 44445 → 80 [ECN] Seq=93 Win=497 Len=0 TSval=1784824986 TSecr=786294052
   19 0.363030999 MY_IP → SERVER_IP TCP 66 [TCP Keep-Alive] 44445 → 80 [ECN] Seq=93 Win=497 Len=0 TSval=1784824992 TSecr=786294056
   20 0.374327694 MY_IP → SERVER_IP TCP 66 [TCP Keep-Alive] 44445 → 80 [ECN] Seq=93 Win=497 Len=0 TSval=1784825009 TSecr=786294060
   21 0.379388653 MY_IP → SERVER_IP TCP 66 [TCP Keep-Alive] 44445 → 80 [ECN] Seq=93 Win=497 Len=0 TSval=1784825014 TSecr=786294060
   22 0.384474051 MY_IP → SERVER_IP TCP 66 [TCP Keep-Alive] 44445 → 80 [ECN] Seq=93 Win=497 Len=0 TSval=1784825020 TSecr=786294061
   23 0.389563243 MY_IP → SERVER_IP TCP 66 [TCP Keep-Alive] 44445 → 80 [ECN] Seq=93 Win=497 Len=0 TSval=1784825031 TSecr=786294061
   24 0.395089952 MY_IP → SERVER_IP TCP 66 [TCP Keep-Alive] 44445 → 80 [ECN] Seq=93 Win=497 Len=0 TSval=1784825038 TSecr=786294067
   25 0.400325803 MY_IP → SERVER_IP TCP 66 [TCP Keep-Alive] 44445 → 80 [ECN] Seq=93 Win=497 Len=0 TSval=1784825049 TSecr=786294070
   26 0.429292547 SERVER_IP → MY_IP TCP 1474 80 → 44445 [ECN] Seq=12833 Win=506 Len=1408 TSval=786294190 TSecr=1784824974
   27 0.432396491 SERVER_IP → MY_IP TCP 1474 80 → 44445 [ECN] Seq=14241 Win=506 Len=1408 TSval=786294190 TSecr=1784824974
   28 0.435011224 SERVER_IP → MY_IP TCP 1474 80 → 44445 [ECN] Seq=15649 Win=506 Len=1408 TSval=786294195 TSecr=1784824980
   29 0.437501469 SERVER_IP → MY_IP TCP 1474 80 → 44445 [ECN] Seq=17057 Win=506 Len=1408 TSval=786294195 TSecr=1784824980
   30 0.455718674 SERVER_IP → MY_IP TCP 1276 [TCP Port numbers reused] 80 → 44445 [FIN, SYN] Seq=0 Win=506 Len=1210 TSval=786294218 TSecr=1784824986
   31 0.459562289 SERVER_IP → MY_IP TCP 1474 80 → 44445 [ECN] Seq=1210 Win=506 Len=1408 TSval=786294222 TSecr=1784824986
   32 0.473141161 MY_IP → SERVER_IP TCP 66 [TCP Keep-Alive] 44445 → 80 [ECN] Seq=93 Win=497 Len=0 TSval=1784825125 TSecr=786294190
   33 0.473160024 SERVER_IP → MY_IP TCP 1474 80 → 44445 [ECN] Seq=2618 Win=506 Len=1408 TSval=786294235 TSecr=1784824992
   34 0.476471278 SERVER_IP → MY_IP TCP 1474 80 → 44445 [ECN] Seq=4026 Win=506 Len=1408 TSval=786294235 TSecr=1784824992
   35 0.478176107 MY_IP → SERVER_IP TCP 66 [TCP Keep-Alive] 44445 → 80 [ECN] Seq=93 Win=497 Len=0 TSval=1784825133 TSecr=786294190
   36 0.483052912 SERVER_IP → MY_IP TCP 1474 80 → 44445 [ECN] Seq=5434 Win=506 Len=1408 TSval=786294246 TSecr=1784825009
   37 0.489734170 MY_IP → SERVER_IP TCP 66 [TCP Keep-Alive] 44445 → 80 [ECN] Seq=93 Win=497 Len=0 TSval=1784825140 TSecr=786294195
   38 0.490446940 SERVER_IP → MY_IP TCP 1474 80 → 44445 [ECN] Seq=6842 Win=506 Len=1408 TSval=786294246 TSecr=1784825009
   39 0.495628297 SERVER_IP → MY_IP TCP 1474 80 → 44445 [ECN] Seq=8250 Win=506 Len=1408 TSval=786294252 TSecr=1784825014
   40 0.498457646 SERVER_IP → MY_IP TCP 1474 80 → 44445 [ECN] Seq=9658 Win=506 Len=1408 TSval=786294252 TSecr=1784825014
   41 0.502477260 MY_IP → SERVER_IP TCP 66 [TCP Keep-Alive] 44445 → 80 [ECN] Seq=93 Win=497 Len=0 TSval=1784825148 TSecr=786294195
   42 0.503250048 SERVER_IP → MY_IP TCP 1474 80 → 44445 [ECN] Seq=11066 Win=506 Len=1408 TSval=786294256 TSecr=1784825020
   43 0.508097290 SERVER_IP → MY_IP TCP 1474 80 → 44445 [ECN] Seq=12474 Win=506 Len=1408 TSval=786294261 TSecr=1784825031
   44 0.529693000 MY_IP → SERVER_IP TCP 66 [TCP Keep-Alive] 44445 → 80 [ECN] Seq=93 Win=497 Len=0 TSval=1784825165 TSecr=786294218
   45 0.547629433 MY_IP → SERVER_IP TCP 66 [TCP Keep-Alive] 44445 → 80 [ECN] Seq=93 Win=497 Len=0 TSval=1784825177 TSecr=786294222
   46 0.569959311 MY_IP → SERVER_IP TCP 66 [TCP Keep-Alive] 44445 → 80 [ECN] Seq=93 Win=497 Len=0 TSval=1784825198 TSecr=786294235
   47 0.574834351 MY_IP → SERVER_IP TCP 66 [TCP Keep-Alive] 44445 → 80 [ECN] Seq=93 Win=497 Len=0 TSval=1784825216 TSecr=786294246
   48 0.582698364 MY_IP → SERVER_IP TCP 66 [TCP Keep-Alive] 44445 → 80 [ECN] Seq=93 Win=497 Len=0 TSval=1784825234 TSecr=786294252
   49 0.590039492 MY_IP → SERVER_IP TCP 66 [TCP Keep-Alive] 44445 → 80 [ECN] Seq=93 Win=497 Len=0 TSval=1784825245 TSecr=786294256
   50 0.640226149 SERVER_IP → MY_IP TCP 651 [TCP Port numbers reused] 80 → 44445 [FIN, SYN] Seq=0 Win=506 Len=585 TSval=786294402 TSecr=1784825165
   51 0.663333652 MY_IP → SERVER_IP TCP 66 [TCP Keep-Alive] 44445 → 80 [ECN] Seq=93 Win=501 Len=0 TSval=1784825334 TSecr=786294402
   52 0.669714648 MY_IP → SERVER_IP TCP 66 [TCP ACKed unseen segment] [TCP Retransmission] 44445 → 80 [FIN, ACK] Seq=93 Ack=32933 Win=501 Len=0 TSval=1784825335 TSecr=786294402
   53 0.776541498 SERVER_IP → MY_IP TCP 66 [TCP Retransmission] 80 → 44445 [FIN, ACK] Seq=585 Ack=1 Win=506 Len=0 TSval=786294541 TSecr=1784825335
   54 0.793113070 MY_IP → SERVER_IP TCP 66 44445 → 80 [ECN] Seq=94 Win=501 Len=0 TSval=1784825466 TSecr=786294541
Войти в полноэкранный режим Выйти из полноэкранного режима

И вуаля! Вот второй флаг: flag{e2960da061a85fbcabb0670e4ddb9e93}

Надеюсь, вам понравилось это введение в манипулирование пакетами. Я хотел бы поблагодарить nneonneo и Kkevsterrr за их объяснения. Присоединяйтесь к Nahamsec на Discord, чтобы связаться с ними.
До следующего раза!

Дальнейшие лекции

  • Переходы состояний TCP – T/TCP (Transaction TCP) для Linux
  • Диаграмма переходов состояний TCP/IP (RFC793)
  • RFC-793 на ietf.org
  • Руководство по TCP/IP
  • Использование nfqueue в python
  • Документация по Scapy
  • О вычислении контрольных сумм с помощью Scapy на Stack Overflow
  • python-netfilterqueue на Github
  • Проект Netfilter
  • Флаги TCP на KeyCDN

Оцените статью
Procodings.ru
Добавить комментарий