В 2021 году VPN протокол WireGuard стал настолько популярен в Египте, что удостоился чести пополнить список заблокированных, несказанно “обрадовав” не только клиентов Cloudflare Warp+, Mullvad Wireguard и других коммерческих VPN-провайдеров, но и некоторых пользователей корпоративных VPN. Предварительные исследования показали, что по всей видимости DPI нацелен на WireGuard Handshake Initiate пакеты, которые имеют фиксированный размер (148 байт) и узнаваемую структуру (первые четыре байта UDP пакета [0x01, 0x00, 0x00, 0x00]).

WireGuard Handshake Initiation
WireGuard Handshake Initiation

Чтобы понять, как DPI может обнаружить и заблокировать WireGuard нужно привнести немного теории. Сам по себе WireGuard протокол предельно простой, трафик упаковывается в совершенно типовой UDP с добавлением небольшого заголовка. Для согласования WireGuard туннеля, как правило, достаточно двух (трех, если считать Keepalive) пакетов:

  • Сторона желающая установить туннель (клиент) отправляет Hadshake Initiation другой стороне (сервер).

  • Если сервер в данный момент готов к подключению, то он отвечает клиенту пакетом Handshake Response.

  • Клиент получает Handshake Response и отвечает Keepalive пакетом, который представляет собой UDP пакет с WireGuard заголовком типа "данные" нулевого размера. В дальнейшем ключи обновляются повторяя описанную процедуру примерно каждые две минуты.

Таким образом, кажется, что для блокировки WireGuard DPI достаточно отслеживать UDP пакеты размером в 148 байт и проверять в них первые четыре байта на соответствие сигнатуре [0x01, 0x00, 0x00, 0x00]. Однако, стоит упомянуть, что корпоративные реализации WireGuard могут использовать зарезервированные три байта (поле Reserved) для собственных нужд (например, в Jamf Private Access они используются как идентификатор сессии). К тому же не исключено, что рано или поздно им найдется применение и в официальном клиенте. Так что для большей точности имеет смысл ограничиться только первым байтом UDP пакета. С другой стороны блокировка всех UDP пакетов размером в 148 байт с первым байтом 0x01 выглядит довольно рискованно. То же самое можно сказать и о Handshake Response пакете, который так же имеет фиксированный размер (92 байта) и схожую сигнатуру с тремя зарезервированными байтами [0x02, 0x00, 0x00, 0x00].

WireGuard Handshake Response
WireGuard Handshake Response

Как же тогда может выглядеть возможный критерий блокировки WireGuard с минимизированной вероятностью ложных срабатываний? По всей видимости DPI стоит пропустить пакет "похожий" на Handshake Initiate, запомнить состояние сессии (IP адреса и порты) и поставить ее на блокировку только после ответного пакета "похожего" на Handshake Response. Нельзя утверждать наверняка, но по косвенным признакам весьма вероятно, что с чем-то подобным мы и имеем дело.

С чего же началась эта история? Скорее всего я не стал бы специально заниматься этой темой, если бы меня неожиданно не заинтересовал пост на форуме, в котором пользователь описывал любопытный метод обхода блокировки WireGuard с помощью другого VPN. Кому интересно, можете почитать нашу переписку, но если в двух словах, то он использовал Windscribe VPN для того, чтобы активировать WireGuard туннель, затем выключал Windscribe VPN и продолжал пользоваться WireGuard (официальный клиент с конфигурацией от Cloudflare Warp+) как ни в чем ни бывало.

В ходе нашего общения постепенно прояснились и прочие подробности, включая государство, которое таким оригинальным способом "подталкивает" население к более глубокому изучению сетевых технологий. Последний раз я был в Египте в 2010 и не могу сказать, что сильно по нему скучаю. Однако в настоящее время это одна из немногих легкодоступных стран, и я в последнее время всерьёз задумывался не слетать ли туда на дайвинг. Надо ли говорить, что невозможность доступа к домашним сетям оказалась бы крайне неприятным сюрпризом.

Далее предстояло решить, что использовать для маскировки Handshake Initiate и Handshake Response вместо Windscribe VPN. Можно конечно построить самописный сервис для инкапсуляции и форвардинга этих пакетов, тем более что нагрузка на него была бы минимальной (два пакета раз в две минуты на туннель). Однако уже довольно давно существует и более подходящий кандидат на эту роль и имя ему SOCKS5. Пятая версия этого популярного протокола поддерживает UDP, что нам собственно от неё и нужно.

Ранее я уже писал о своём пет-проекте VPN клиента на основе Cloudflare реализации протокола WireGuard. В него то я и решил добавить поддержку SOCKS5. В процессе тестирования промежуточных сборок выяснились кое-какие особенности реализации DPI. Например, если первый Handshake Initiate пролетел мимо SOCKS и попал в DPI, то неважно что потом вы отправляете в рамках данной UDP сессии, DPI будет ее настойчиво блокировать. Что любопытно, ничтожная часть пакетов каким-то образом все таки прорывается (возможно их маршрут проходит мимо DPI). Так же похоже, что верно и обратное, если UDP сессия не началась с Handshake Initiate - Handshake Response, то претензий к данной сессии у DPI не будет, даже если эти пакеты покажутся там в дальнейшем. Это могло бы объяснить почему у пользователя WireGuard туннель не ломался через две минуты при следующем Handshake Initiate. Однако это только лишь неподтвержденная гипотеза.

Для поддержки SOCKS5 в Wiresock VPN Client были добавлены следующие дополнительные параметры:

  • Socks5Proxy – указывает адрес и порт сервера SOCKS5, например Socks5Proxy = socks5.sshvpn.me:1080 или Socks5Proxy = 13.134.12.31:1080

  • Socks5ProxyUsername – имя пользователя SOCKS5 (опционально)

  • Socks5ProxyPassword – пароль пользователя SOCKS5 (опционально)

Так же для тестирования нам понадобился SOCKS5 сервер с поддержкой UDP ASSOCIATE. Полагаю большинство читателей вполне способно сконфигурировать такой сервер самостоятельно, поэтому думаю не стоит раздувать размер статьи приводя здесь детальную инструкцию. Остальным же могу порекомендовать памятку, которую я составил для себя по установке Dante от Inferno Nettverk A/S на Ubuntu 20.04 в Oracle Cloud.

Мы протестировали как вариант с применением SOCKS только к Handshake Initiate/Response пакетам, так и полностью ко всем пакетам WireGuard туннеля. Оба варианта прекрасно справились с блокировкой, поэтому остановились на первом, поскольку он более выигрышный с точки зрения производительности и ресурсов. Из неожиданных побочных эффектов, отправка Hanshake Initiate через SOCKS5 прокси "починила" возможность построения вложенного WireGuard туннеля с использованием официального клиента WireGuard for Windows (в качестве внешнего туннеля) и Wiresock VPN Client (в качестве внутреннего), которая "поломалась" где-то между релизами 0.5 и 0.5.3. Официальный клиент внезапно перестал пропускать Handshake Initiate в туннель (возможно это связано с работой по поддержке собственных вложенных туннелей), но после оборачивания в SOCKS он перестал его узнавать и все снова заработало.

Так же хотел отметить, что не представляет большой сложности сделать отдельное Windows приложение для официального клиента, которое будет перехватывать исходящие Handshake Initiate и отправлять их через SOCKS5. Если подобное будет востребовано, то я постараюсь найти время для реализации.

На этом все, надеюсь эта информация будет кому-то полезной!

Комментарии (28)


  1. Zolg
    07.02.2022 11:05
    +3

    если UDP сессия не началась с Handshake Initiate - Handshake Response, то претензий к данной сессии у DPI не будет

    А зачем тогда хитрые телодвижения с соксом ? (я при беглом прочтении так и не понял, как именно вы его используете).

    Шлите первым пакетом со стороны клиента какой-нибудь мусор, сервер этот пакет проигнорирует, но на брандмауэре сессия создастся и она будет начинаться с этого мусора, а не с wireguard handshake


    1. SerpentFly Автор
      07.02.2022 11:14

      Если послать мусорный пакет, то сервер ничего не ответит, а DPI похоже ждет первую подходящую пару в обоих направлениях. Впрочем, наверняка сложно сказать, это только мои предположения.

      Через SOCKS пробрасывается Handshake Initiate и приходит ответный Hadnshake Response, остальные пакеты ходят напрямую между клиентом и сервером WireGuard. Другими словами SOCKS используется для маскировки handshake.


      1. 13werwolf13
        07.02.2022 11:25

        я что-то немного упустил.. почему хэндшейк не шифруется так же как остальные пакеты? вроде же это собирались добавить в мейнстримный wg??


        1. SerpentFly Автор
          07.02.2022 11:39

          Вопрос скорее к Джейсону, но вообще все кроме первого байта для DPI выглядит как произвольные бинарные данные. Основная проблема в фиксированном размере пакетов handshake и первом байте указывающем тип пакета, который 1 для initiate, 2 для response и 4 для пакетов с данными.


          1. 13werwolf13
            07.02.2022 11:56
            +2

            Мда, ну тут я тоже не вижу проблемы подмешивать в пакет рандома для изменения веса..


            1. SerpentFly Автор
              07.02.2022 12:24

              Согласен, можно было бы сделать Wireguard более стойким к детектированию. Но по всей видимости это вряд ли уже произойдёт. Никто не захочет ломать совместимость. А ее к слову ломает даже использование зарезервированных байтов.


              1. Revertis
                07.02.2022 21:39

                Насколько я знаю UDP, там всё равно вычитывается вся датаграмма, которая влезет в буфер. Неужели сервер создаёт буфер для чтения всего в 148 байт?


                1. SerpentFly Автор
                  07.02.2022 22:05

                  wireguard-go код проверяет размер полученного пакета и отбрасывает его если он не совпадает с ожидаемым. Другие реализации не проверял, но думаю там все аналогично. Поэтому дописывать в Handshake Initiate рандомный мусор в конец пакета не получится.

                  case MessageInitiationType:
                  			okay = len(packet) == MessageInitiationSize
                  
                  		case MessageResponseType:
                  			okay = len(packet) == MessageResponseSize
                  
                  		case MessageCookieReplyType:
                  			okay = len(packet) == MessageCookieReplySize
                  
                  		default:
                  			device.log.Verbosef("Received message with unknown type")
                  		}
                  
                  		if okay {
                  			select {
                  			case device.queue.handshake.c <- QueueHandshakeElement{
                  				msgType:  msgType,
                  				buffer:   buffer,
                  				packet:   packet,
                  				endpoint: endpoint,
                  			}:
                  				buffer = device.GetMessageBuffer()
                  			default:
                  			}
                  		}


                  1. Revertis
                    07.02.2022 22:58

                    Тогда придётся форкать. Проблема только в том, что теперь он аж в ядре линуха :(


                    1. SerpentFly Автор
                      08.02.2022 00:12

                      Можно использовать альтернативный BoringTun от Cloudflare. Не kernel mode, но производительность неплохая, существенно лучше чем wireguard-go.


  1. Uint32
    07.02.2022 11:06
    +1

    Так же похоже, что верно и обратное, если UDP сессия не началась с Handshake Initiate — Handshake Response, то претензий к данной сессии у DPI не будет, даже если эти пакеты покажутся там в дальнейшем.

    Может тогда будет достаточнл отправлять перед началом сессии WG произвольные UDP пакеты со случайными данными?


    1. SerpentFly Автор
      07.02.2022 11:19

      Возможно, здесь мы вступаем области предположений. Меня главным образом смущает, что у пользователя туннель не падал через две минуты. Так же вполне вероятно, что нужен еще и произвольный пакет в обратном направлении. А для этого придется еще и сервер модифицировать.

      Возможно SOCKS - это действительно overkill и может отработать и более простой вариант. Но главное, что работает.


  1. GritsanY
    07.02.2022 13:51
    +1

    Что-то похожее было в KZ в начале января, в самом начале блокировок интернета -

    у меня перестали подключаться клиенты к Wireguard VPN серверу в облаке Oracle, хотя доступ по SSH к нему оставался. Успел выяснить, что как раз Handshake Initiate до сервера доходил, и Handshake Response отправлялся, но до клиента не добирался - терялся на территории KZ провайдеров.

    Решил проблему, подняв на том же сервере рядом с Wireguard сервис Outline, который как раз прячется в shadowsocks. Но на заметку вашу статью возьму, спасибо.


  1. Sazonov
    07.02.2022 14:43
    +1

    WireGuard vpn (свой сервер, клиент iOS / Mac) отлично работал в начале года в Египте, в отличие от многих других сервисов. Может они ещё и адрес смотрят?


    1. SerpentFly Автор
      07.02.2022 15:02

      Возможно, у пользователя был конфиг от Warp+. С другой стороны здесь вообще корпоративный VPN заблокировали, в отличие от того же Warp вряд ли адреса были засвечены. Может быть еще не у всех провайдеров блокировка налажена, насколько я понимаю история довольно свежая.


      1. Sazonov
        07.02.2022 16:18

        Если что, провайдер Orange. Пользователь - я. Сервер от DigitalOcean в Германии.


  1. Earthsea
    07.02.2022 16:08
    +3

    государство, которое таким оригинальным способом "подталкивает" население к более глубокому изучению сетевых технологий

    Тем временем, граждане Египта более глубоко изучают сетевые технологии:


  1. GektorTM
    07.02.2022 20:29

    А OpenVPN там так же блокируют?


    1. SerpentFly Автор
      07.02.2022 20:31

      У меня не было возможности проверить, но судя по тому, что пишут, OpenVPN работает только в паре с shadowsocks.


      1. GektorTM
        07.02.2022 21:02

        Даже через TCP с TLS crypt и на 443 порту? Не знал, что DPI умеет распознавать такое...


        1. UserAd
          08.02.2022 14:11
          +1

          Да, сейчас нахожусь в египте. Работает только через obfsproxy, ssh tunnel. Shadowsock еще не пробовал. 443 tls ломается на фазе автоиизации


          1. GektorTM
            08.02.2022 15:58

            Еще в прошлом году у меня получалось подключаться, видимо что-то изменили совсем недавно.


            1. UserAd
              08.02.2022 17:38
              +1

              Неделю назад у нашего сотрудника тоже получалось подключиться просто по 443 TLS, но видимо когда я приехал что-то случилось. Очень похоже что ломается пакет ответа от VPN на стадии авторизации.

              Я пробовал как отельный wifi (Link Egypt), так и vodafone 4G.

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


  1. m0tral
    07.02.2022 22:59

    А откуда вообще проблема, был осенью там и не испытал никаких проблем с доступом ко всему включая домашний сервер.


    1. SerpentFly Автор
      07.02.2022 23:13

      Ссылки на пруфы разбросаны по статье. То с чего я начал этим заниматься: https://www.ntkernel.com/forums/topic/can-i-select-the-default-interface-when-using-wiresock-vpn-client-on-win10/

      Еще пара ссылок, которые я нашел пока, разбирался:

      https://www.reddit.com/r/Egypt/comments/lsnyk7/is_wireguard_blocked/
      https://twitter.com/6h4n3m/status/1459462360003919875

      Не исключено, что наличие блокировок зависит от конкретных провайдеров.


  1. splitfire
    08.02.2022 09:42
    +1

    Постепенно прихожу к мысли, что реализации VPN без качественной обфускации(а в перспективе и без маскировки под «легальный» сервис), нинужны. Всё больше и больше государств решают, что вахтёр на Интернет-границе — это отличная идея, которая избавит от всех проблем.


    1. SerpentFly Автор
      08.02.2022 10:25

      При этом маскировка под TLS возможно не лучший выбор. Как вариант можно было бы маскироваться под трафик популярных онлайн игр, в особенности построенных на основе UDP.


    1. dakuan
      08.02.2022 18:22
      +1

      У VPN все же немного иное предназначение - создание виртуальной сети поверх другой сети (даже шифрование не является обязательным), а использование VPN для обхода блокировок и сокрытия реального местоположения пользователя - это лишь частный случай. Поэтому подавляющее большинство реализаций VPN задачу сокрытия факта использования даже не пытается решать.