В мире, где информационная безопасность играет ключевую роль, мобильные прокси стали настоящим спасением для многих. Вот почему они так хороши и почему использование прокси компании Proxys - это настоящий шаг вперед!
Безопасность в интернете: Мобильные прокси обеспечивают вашу анонимность, скрывая ваш IP-адрес. Это важно для защиты данных и предотвращения отслеживания ваших онлайн-активностей.
Обход географических ограничений: Многие веб-ресурсы ограничивают доступ к контенту по региону. Мобильные прокси помогут вам обойти эти ограничения, предоставляя доступ к контенту из любой точки мира.
Улучшенная скорость и производительность: Мобильные прокси, такие как те, что предоставляет Proxys, обеспечивают высокую скорость интернет-соединения и эффективно снижают задержки, что особенно важно для пользователей, ценящих быстродействие.
Защита от вредоносных атак: Прокси служат дополнительным слоем защиты, предотвращая атаки на вашу сеть и улучшая безопасность ваших устройств.
Онлайн-маркетинг и тестирование: Мобильные прокси идеально подходят для тестирования веб-ресурсов, а также для онлайн-маркетинга, позволяя эффективно адаптировать контент для различных регионов.
Блокировка рекламы и трекинга: Прокси помогут вам избавиться от навязчивой рекламы и онлайн-трекинга, обеспечивая приятный и комфортный онлайн-опыт.
Управление социальными сетями: Мобильные прокси полезны для управления несколькими аккаунтами в социальных сетях, обеспечивая безопасность и удобство.
Интернет-безопасность в общественных местах: Используя мобильные прокси, вы можете защитить свои данные в общественных Wi-Fi сетях, минимизируя риски несанкционированного доступа.
Прокси компании Proxys стали моим надежным союзником в мире интернет-безопасности. Их мобильные прокси не только обеспечивают защиту, но и открывают новые возможности в использовании интернета. Попробуйте, и вы убедитесь во всех преимуществах, которые они могут предложить! 🚀
Вы можете реализовать все 𝗧𝗖𝗣-взаимодействия, используя встроенный в 𝗚𝗼 пакет 𝗻𝗲𝘁. В предыдущем разделе мы сосредоточились главным образом на его применении с позиции клиента. В этом же разделе задействуем его для создания 𝗧𝗖𝗣-серверов и передачи данных. Изучение этого процесса начнется с создания эхо-сервера — сервера, который просто возвращает запрос обратно клиенту. Затем мы создадим две более универсальные в применении программы: переадресатор 𝗧𝗖𝗣-портов и 𝗡𝗲𝘁𝗰𝗮𝘁-функцию «зияющая дыра в безопасности», применяемую для удаленного выполнения команд.
Использование io.Reader и io.Writer
При создании примеров этого раздела вам потребуется задействовать два значимых типа: 𝗶𝗼.𝗥𝗲𝗮𝗱𝗲𝗿 и 𝗶𝗼.𝗪𝗿𝗶𝘁𝗲𝗿. Они необходимы для всех задач ввода/вывода (𝗜/𝗢) вне зависимости от того, задействуете вы 𝗧𝗖𝗣, 𝗛𝗧𝗧𝗣, файловую систему или любые другие средства. Будучи частью встроенного в 𝗚𝗼 пакета 𝗶𝗼, эти типы являются краеугольным камнем любой передачи данных, как локальной, так и сетевой. В документации они определены так:
Оба типа определяются как интерфейсы, то есть напрямую их создать нельзя. Каждый тип содержит определение одной экспортируемой функции: 𝗥𝗲𝗮𝗱 или 𝗪𝗿𝗶𝘁𝗲. Можно рассматривать эти функции как абстрактные методы, которые должны быть реализованы в типе, чтобы он считался 𝗥𝗲𝗮𝗱𝗲𝗿 или 𝗪𝗿𝗶𝘁𝗲𝗿. Например, следующий искусственный тип выполняет это соглашение и может использоваться там, где приемлем 𝗥𝗲𝗮𝗱𝗲𝗿:
Давайте с помощью них создадим что-нибудь полуготовое: настраиваемый 𝗥𝗲𝗮𝗱𝗲𝗿 и 𝗪𝗿𝗶𝘁𝗲𝗿, обертывающий 𝘀𝘁𝗱𝗶𝗻 и 𝘀𝘁𝗱𝗼𝘂𝘁. Код для этого тоже будет несколько искусственным, так как типы 𝗚𝗼 𝗼𝘀.𝗦𝘁𝗱𝗶𝗻 и 𝗼𝘀.𝗦𝘁𝗱𝗼𝘂𝘁 уже действуют как 𝗥𝗲𝗮𝗱𝗲𝗿 и 𝗪𝗿𝗶𝘁𝗲𝗿. Но если не пытаться изобрести колесо, то ничему и не научишься, ведь так?
Ниже показана полная реализация, а далее дано пояснение.
Реализация reader и writer /io-example/main.go
Этот код определяет два пользовательских типа: 𝗙𝗼𝗼𝗥𝗲𝗮𝗱𝗲𝗿 и 𝗙𝗼𝗼𝗪𝗿𝗶𝘁𝗲𝗿. В каждом типе вы определяете конкретную реализацию функции 𝗥𝗲𝗮𝗱([]𝗯𝘆𝘁𝗲) для 𝗙𝗼𝗼𝗥𝗲𝗮𝗱𝗲𝗿 и функции 𝗪𝗿𝗶𝘁𝗲([]𝗯𝘆𝘁𝗲) для 𝗙𝗼𝗼𝗪𝗿𝗶𝘁𝗲𝗿. В этом случае обе функции считывают из 𝘀𝘁𝗱𝗶𝗻 и записывают в 𝘀𝘁𝗱𝗼𝘂𝘁.
Обратите внимание на то, что функции 𝗥𝗲𝗮𝗱 и в 𝗙𝗼𝗼𝗥𝗲𝗮𝗱𝗲𝗿, и в 𝗼𝘀.𝗦𝘁𝗱𝗶𝗻 возвращают длину данных и все ошибки. Сами эти данные копируются в срез 𝗯𝘆𝘁𝗲, передаваемый этой функции. Это согласуется с начальным определением интерфейса 𝗥𝗲𝗮𝗱𝗲𝗿, приведенным в данном разделе ранее. Функция 𝗺𝗮𝗶𝗻() создает этот срез с названием 𝗶𝗻𝗽𝘂𝘁 и затем использует его в вызовах к 𝗙𝗼𝗼𝗥𝗲𝗮𝗱𝗲𝗿.𝗥𝗲𝗮𝗱([]𝗯𝘆𝘁𝗲) и 𝗙𝗼𝗼𝗥𝗲𝗮𝗱𝗲𝗿.𝗪𝗿𝗶𝘁𝗲([]𝗯𝘆𝘁𝗲).
При пробном запуске программы мы получим следующий вывод:
Копирование данных из 𝗥𝗲𝗮𝗱𝗲𝗿 в 𝗪𝗿𝗶𝘁𝗲𝗿 — это настолько распространенный шаблон, что пакет 𝗶𝗼 содержит специальную функцию 𝗖𝗼𝗽𝘆(), которую можно задействовать для упрощения функции 𝗺𝗮𝗶𝗻(). Вот ее прототип:
Эта удобная функция позволяет реализовывать то же поведение программы, что и ранее, заменив 𝗺𝗮𝗶𝗻() кодом, показанным ниже.
Применение io.Copy /ch-2/copy-example/main.go
Обратите внимание, что явные вызовы 𝗿𝗲𝗮𝗱𝗲𝗿.𝗥𝗲𝗮𝗱([ ]𝗯𝘆𝘁𝗲) и 𝘄𝗿𝗶𝘁𝗲𝗿.𝗪𝗿𝗶𝘁𝗲([ ] 𝗯𝘆𝘁𝗲) были замещены одним вызовом 𝗶𝗼.𝗖𝗼𝗽𝘆(𝘄𝗿𝗶𝘁𝗲𝗿, 𝗿𝗲𝗮𝗱𝗲𝗿). Внутренне 𝗶𝗼.𝗖𝗼𝗽𝘆(𝘄𝗿𝗶𝘁𝗲𝗿, 𝗿𝗲𝗮𝗱𝗲𝗿) вызывает в переданном ридере функцию 𝗥𝗲𝗮𝗱([ ]𝗯𝘆𝘁𝗲), в результате чего 𝗙𝗼𝗼𝗥𝗲𝗮𝗱𝗲𝗿 выполняет считывание из 𝘀𝘁𝗱𝗶𝗻. Далее 𝗶𝗼.𝗖𝗼𝗽𝘆(𝘄𝗿𝗶𝘁𝗲𝗿, 𝗿𝗲𝗮𝗱𝗲𝗿) вызывает в переданном райтере функцию 𝗪𝗿𝗶𝘁𝗲([ ]𝗯𝘆𝘁𝗲), что приводит к вызову 𝗙𝗼𝗼𝗪𝗿𝗶𝘁𝗲𝗿, записывающего данные в 𝘀𝘁𝗱𝗼𝘂𝘁. По сути, 𝗶𝗼.𝗖𝗼𝗽𝘆(𝘄𝗿𝗶𝘁𝗲𝗿, 𝗿𝗲𝗮𝗱𝗲𝗿) обрабатывает последовательный процесс чтения/записи без лишних деталей.
Этот вводный раздел никак нельзя считать подробным рассмотрением системы 𝗜/𝗢 и интерфейсов в 𝗚𝗼. Многие вспомогательные функции и пользовательские ридеры/райтеры существуют как часть стандартных пакетов 𝗚𝗼. В большинстве случаев эти стандартные пакеты содержат все основные реализации, необходимые для реализации большинства распространенных задач. В следующем разделе мы рассмотрим применение всех этих основ к 𝗧𝗖𝗣-коммуникациям и в итоге применим полученные навыки для разработки реальных рабочих инструментов.
Создание эхо-сервера:
Как и во многих языках, изучение процесса чтения/записи данных с сокета мы начнем с построения эхо-сервера. Для этого будем использовать 𝗻𝗲𝘁.𝗖𝗼𝗻𝗻 — потокоориентированное соединение 𝗚𝗼, с которым вы уже познакомились при создании сканера портов. Как указано в документации для этого типа данных, 𝗖𝗼𝗻𝗻 реализует функции 𝗥𝗲𝗮𝗱([ ]𝗯𝘆𝘁𝗲) и 𝗪𝗿𝗶𝘁𝗲([ ]𝗯𝘆𝘁𝗲) согласно определению для интерфейсов 𝗥𝗲𝗮𝗱𝗲𝗿 и 𝗪𝗿𝗶𝘁𝗲𝗿. Следовательно, 𝗖𝗼𝗻𝗻 одновременно является и 𝗥𝗲𝗮𝗱𝗲𝗿, и 𝗪𝗿𝗶𝘁𝗲𝗿 (да, такое возможно). Это вполне логично, так как 𝗧𝗖𝗣-соединения двунаправленные и могут использоваться для отправки (записи) и получения (чтения) данных.
После создания экземпляра conn вы сможете отправлять и получать данные через TCP-сокет. Тем не менее TCP-сервер не может просто создать соединение, его должен установить клиент. В 𝗚𝗼 для начального открытия 𝗧𝗖𝗣-слушателя на конкретном порте можно использовать 𝗻𝗲𝘁.𝗟𝗶𝘀𝘁𝗲𝗻(𝗻𝗲𝘁𝘄𝗼𝗿𝗸, 𝗮𝗱𝗱𝗿𝗲𝘀𝘀 𝘀𝘁𝗿𝗶𝗻𝗴). После подключения клиента метод 𝗔𝗰𝗰𝗲𝗽𝘁() создает и возвращает объект 𝗖𝗼𝗻𝗻, который вы можете применять для получения и отправки данных.
Ниже показан полноценный пример реализации сервера. Для большей ясности мы добавили в код комментарии. Не стремитесь сразу понять весь код, так как позже мы подробно его поясним.
Базовый эхо-сервер /ch-2/echo-server/main.go
Базовый эхо-сервер начинается с определения функции 𝗲𝗰𝗵𝗼(𝗻𝗲𝘁.𝗖𝗼𝗻𝗻), которая принимает в качестве параметра экземпляр 𝗖𝗼𝗻𝗻. Он выступает в роли обработчика соединения, выполняя все необходимые операции 𝗜/𝗢. Эта функция повторяется бесконечно, используя буфер для считывания данных из соединения и их записи в него. Данные считываются в переменную 𝗯, после чего записываются обратно в соединение.
Теперь нужно настроить слушатель, который будет вызывать обработчик. Как ранее говорилось, сервер не может сам создать соединение и должен прослушивать подключение клиента. Следовательно, слушатель, определенный как 𝘁𝗰𝗽, привязанный к порту 𝟮𝟬𝟬𝟴𝟬, запускается во всех интерфейсах посредством функции 𝗻𝗲𝘁.𝗟𝗶𝘀𝘁𝗲𝗻(𝗻𝗲𝘁𝘄𝗼𝗿𝗸, 𝗮𝗱𝗱𝗿𝗲𝘀𝘀 𝘀𝘁𝗿𝗶𝗻𝗴).
Далее бесконечный цикл обеспечивает, чтобы сервер продолжал прослушивание соединений даже после того, как оно было установлено. В этом цикле происходит вызов 𝗹𝗶𝘀𝘁𝗲𝗻𝗲𝗿.𝗔𝗰𝗰𝗲𝗽𝘁() ❻ — функции, блокирующей выполнение при ожидании подключений. Когда клиент подключается, эта функция возвращает экземпляр 𝗖𝗼𝗻𝗻. Напомним, что 𝗖𝗼𝗻𝗻 является и 𝗥𝗲𝗮𝗱𝗲𝗿, и 𝗪𝗿𝗶𝘁𝗲𝗿 (реализует методы интерфейса 𝗥𝗲𝗮𝗱([ ]𝗯𝘆𝘁𝗲) и 𝗪𝗿𝗶𝘁𝗲([ ]𝗯𝘆𝘁𝗲)).
После этого экземпляр 𝗖𝗼𝗻𝗻 передается в функцию обработки 𝗲𝗰𝗵𝗼(𝗻𝗲𝘁.𝗖𝗼𝗻𝗻). Перед ее вызовом указано ключевое слово 𝗴𝗼, делающее этот вызов многопоточным, в результате чего другие подключения в ожидании завершения функции-обработчика не блокируются. Это может показаться излишним для столь простого сервера, но мы добавили эту функциональность для демонстрации простоты паттерна многопоточности 𝗚𝗼 на случай, если вы еще не до конца его поняли. В данный момент у вас есть два легковесных параллельно выполняющихся потока.
Основной поток зацикливается и блокируется функцией listener.Accept() на время ожидания ею следующего подключения.
Горутина обработки, чье выполнение было передано в функцию echo(net.Conn), возобновляется и обрабатывает данные.
Далее показан пример использования 𝗧𝗲𝗹𝗻𝗲𝘁 в качестве подключающегося клиента:
Сервер производит следующий стандартный вывод:
Революционно, не правда ли? Сервер, возвращающий клиенту в точности то, что клиент ему отправил. Очень полезный и сильный пример!
Создание буферизованного слушателя для улучшения кода:
Пример в коде Базового эхо-сервера работает прекрасно, но он опирается на чисто низкоуровневые вызовы функции, отслеживание буфера и повторяющиеся циклы чтения/записи. Это довольно утомительный и подверженный ошибкам процесс. К счастью, в 𝗚𝗼 есть и другие пакеты, которые могут его упростить и уменьшить сложность кода. Говоря конкретнее, пакет 𝗯𝘂𝗳𝗶𝗼 обертывает 𝗥𝗲𝗮𝗱𝗲𝗿 и 𝗪𝗿𝗶𝘁𝗲𝗿 для создания буферизованного механизма 𝗜/𝗢. Далее приведена обновленная функция 𝗲𝗰𝗵𝗼(𝗻𝗲𝘁.𝗖𝗼𝗻𝗻) с сопутствующим описанием изменений:
В экземпляре 𝗖𝗼𝗻𝗻 больше не происходит прямого вызова функций 𝗥𝗲𝗮𝗱([]𝗯𝘆𝘁𝗲) и 𝗪𝗿𝗶𝘁𝗲([]𝗯𝘆𝘁𝗲). Вместо этого вы инициализируете новые буферизованные 𝗥𝗲𝗮𝗱𝗲𝗿 и 𝗪𝗿𝗶𝘁𝗲𝗿 через 𝗡𝗲𝘄𝗥𝗲𝗮𝗱𝗲𝗿(𝗶𝗼.𝗥𝗲𝗮𝗱𝗲𝗿) и 𝗡𝗲𝘄𝗪𝗿𝗶𝘁𝗲𝗿(𝗶𝗼.𝗪𝗿𝗶𝘁𝗲𝗿). Оба вызова в качестве параметра получают существующие 𝗥𝗲𝗮𝗱𝗲𝗿 и 𝗪𝗿𝗶𝘁𝗲𝗿 (помните, что тип 𝗖𝗼𝗻𝗻 реализует необходимые функции, чтобы считаться 𝗥𝗲𝗮𝗱𝗲𝗿 и 𝗪𝗿𝗶𝘁𝗲𝗿).
Оба буферизованных экземпляра содержат вспомогательные функции для чтения и сохранения данных. 𝗥𝗲𝗮𝗱𝗦𝘁𝗿𝗶𝗻𝗴(𝗯𝘆𝘁𝗲) получает символ-разграничитель, обозначая, до какой точки выполнять считывание, а 𝗪𝗿𝗶𝘁𝗲𝗦𝘁𝗿𝗶𝗻𝗴(𝗯𝘆𝘁𝗲) записывает строку в сокет. При записи данных вам нужно явно вызывать 𝘄𝗿𝗶𝘁𝗲𝗿.𝗙𝗹𝘂𝘀𝗵() для сброса всех данных внутреннему райтеру (в данном случае экземпляру 𝗖𝗼𝗻𝗻).
Несмотря на то что предыдущий пример упрощает процесс, применяя буферизацию 𝗜/𝗢, вы можете переработать его под использование вспомогательной функции 𝗖𝗼𝗽𝘆(𝗪𝗿𝗶𝘁𝗲𝗿, 𝗥𝗲𝗮𝗱𝗲𝗿). Напомним, что функция получает в качестве ввода целевой 𝗪𝗿𝗶𝘁𝗲𝗿 и исходный 𝗥𝗲𝗮𝗱𝗲𝗿, просто выполняя копирование из источника в место назначения.
В этом примере вы передаете переменную 𝗰𝗼𝗻𝗻 и как источник, и как место назначения, так как в итоге будете отражать содержимое обратно в установленное соединение:
Вот вы и познакомились с основами системы 𝗜/𝗢, попутно применив ее к 𝗧𝗖𝗣-серверам. Пришло время перейти к более полезным и представляющим для вас интерес примерам.
Проксирование TCP-клиента:
Теперь, когда у вас под ногами есть твердая почва, можете применить полученные навыки для создания простого переадресатора портов для проксирования соединения через промежуточный сервис или хост. Как уже говорилось, это пригождается для обхода ограничивающего контроля исходящего трафика или использования системы с целью обхода сегментации сети. Прежде чем перейти к коду, рассмотрите вымышленную, но вполне реалистичную задачу: Андрей является малоэффективным сотрудником компании 𝗔𝗖𝗠𝗘 𝗜𝗻𝗰., работая на должности бизнес-аналитика и получая приличную зарплату просто потому, что слегка приукрасил данные своего резюме. (Неужели он реально учился в школе Лиги плюща? Андрей, такой обман неэтичен.) Недостаток мотивации Андрея может по силе сравниться разве что с его любовью к кошкам — такой сильной, что он даже установил дома специальные видеокамеры и создал сайт 𝗷𝗼𝗲𝘀𝗰𝗮𝘁𝗰𝗮𝗺.𝘄𝗲𝗯𝘀𝗶𝘁𝗲, через который удаленно следил за своими мохнатыми питомцами. Тем не менее здесь была одна сложность: 𝗔𝗖𝗠𝗘 следит за Андреем. Им не нравится, что он круглые сутки передает потоковое видео своих кошек в ультравысоком разрешении 𝟰𝗞, занимая ценный пропускной канал сети. Компания даже заблокировала своим сотрудникам возможность посещать его кошачий сайт.
Но у хитрого Андрея и здесь возник план: «А что, если я настрою переадресатор портов в подконтрольной мне интернет-системе и буду перенаправлять весь трафик с этого хоста на 𝗷𝗼𝗲𝘀𝗰𝗮𝘁𝗰𝗮𝗺.𝘄𝗲𝗯𝘀𝗶𝘁𝗲?» На следующий день Андрей отмечается на работе и убеждается в возможности доступа к личному сайту, размещенному на домене 𝗷𝗼𝗲𝘀𝗽𝗿𝗼𝘅𝘆.𝗰𝗼𝗺. Он пропускает все встречи после обеда и отправляется в кафетерий, где быстро пишет код для своей задачи, подразумевающей перенаправление на 𝗵𝘁𝘁𝗽://𝗷𝗼𝗲𝘀𝗰𝗮𝘁𝗰𝗮𝗺.𝘄𝗲𝗯𝘀𝗶𝘁𝗲 всего входящего на 𝗵𝘁𝘁𝗽://𝗷𝗼𝗲𝘀𝗽𝗿𝗼𝘅𝘆.𝗰𝗼𝗺 трафика.
Вот код Андрея, который он запускает на сервере 𝗷𝗼𝗲𝘀𝗽𝗿𝗼𝘅𝘆.𝗰𝗼𝗺:
Начнем с рассмотрения функции 𝗵𝗮𝗻𝗱𝗹𝗲(𝗻𝗲𝘁.𝗖𝗼𝗻𝗻). Андрей подключается к 𝗷𝗼𝗲𝘀𝗰𝗮𝘁𝗰𝗮𝗺.𝘄𝗲𝗯𝘀𝗶𝘁𝗲 (вспомните, что этот хост недоступен напрямую с его рабочего места). Затем он использует 𝗖𝗼𝗽𝘆(𝗪𝗿𝗶𝘁𝗲𝗿, 𝗥𝗲𝗮𝗱𝗲𝗿) в двух разных местах. Первый экземпляр обеспечивает копирование данных из входящего соединения в соединение 𝗷𝗼𝗲𝘀𝗰𝗮𝘁𝗰𝗮𝗺.𝘄𝗲𝗯𝘀𝗶𝘁𝗲. Второй же обеспечивает, чтобы считанные из 𝗷𝗼𝗲𝘀𝗰𝗮𝘁𝗰𝗮𝗺.𝘄𝗲𝗯𝘀𝗶𝘁𝗲 данные записывались обратно в соединение подключающегося клиента. Так как 𝗖𝗼𝗽𝘆(𝗪𝗿𝗶𝘁𝗲𝗿, 𝗥𝗲𝗮𝗱𝗲𝗿) является блокирующей функцией и будет продолжать блокировать выполнение, пока сетевое соединение открыто, Андрей предусмотрительно обертывает первый вызов 𝗖𝗼𝗽𝘆(𝗪𝗿𝗶𝘁𝗲𝗿, 𝗥𝗲𝗮𝗱𝗲𝗿) в новую горутину. Это гарантирует продолжение выполнения в функции 𝗵𝗮𝗻𝗱𝗹𝗲(𝗻𝗲𝘁.𝗖𝗼𝗻𝗻) и дает возможность выполнить второй вызов 𝗖𝗼𝗽𝘆(𝗪𝗿𝗶𝘁𝗲𝗿, 𝗥𝗲𝗮𝗱𝗲𝗿).
Прокси-сервер Андрей прослушивает порт 𝟴𝟬 и ретранслирует весь трафик, получаемый через это соединение, на порт 𝟴𝟬 сайта 𝗷𝗼𝗲𝘀𝗰𝗮𝘁𝗰𝗮𝗺.𝘄𝗲𝗯𝘀𝗶𝘁𝗲 и обратно. Этот безумный и расточительный парень убеждается, что может подключаться к 𝗷𝗼𝗲𝘀𝗰𝗮𝘁𝗰𝗮𝗺.𝘄𝗲𝗯𝘀𝗶𝘁𝗲 через 𝗷𝗼𝗲𝘀𝗽𝗿𝗼𝘅𝘆.𝗰𝗼𝗺 с помощью 𝗰𝘂𝗿𝗹:
Так Андрей успешно реализует коварный замысел. Он прекрасно устроился, получив возможность в оплачиваемое 𝗔𝗖𝗠𝗘 время использовать их же канал связи для наблюдения за жизнью своих питомцев.
Воспроизведение функции Netcat для выполнения команд:
В этом разделе мы воспроизведем одну из наиболее интересных функций 𝗡𝗲𝘁𝗰𝗮𝘁 — «зияющую дыру в безопасности».
𝗡𝗲𝘁𝗰𝗮𝘁 — это как швейцарский армейский нож для 𝗧𝗖𝗣/𝗜𝗣, который представляет собой более гибкую версию 𝗧𝗲𝗹𝗻𝗲𝘁 с поддержкой сценариев. Эта утилита имеет возможность перенаправлять 𝘀𝘁𝗱𝗶𝗻 и 𝘀𝘁𝗱𝗼𝘂𝘁 любой произвольной программы через 𝗧𝗖𝗣, позволяя атакующему, например, превратить уязвимость к выполнению одной команды в доступ к оболочке операционной системы. Взгляните:
Эта команда создает прослушивающий сервер на порте 𝟭𝟯𝟯𝟯𝟳. Любой подключающийся, возможно, через 𝗧𝗲𝗹𝗻𝗲𝘁, клиент сможет выполнить любые команды 𝗯𝗮𝘀𝗵 — вот почему данную функцию и называют зияющей дырой в безопасности. 𝗡𝗲𝘁𝗰𝗮𝘁 позволяет при желании включить такую возможность в процессе компиляции программы. (По понятным причинам большинство исполняемых файлов 𝗡𝗲𝘁𝗰𝗮𝘁 в стандартных сборках 𝗟𝗶𝗻𝘂𝘅 ее не включают.) Эта функция настолько потенциально опасна, что мы покажем, как воссоздать ее в 𝗚𝗼.
Для начала загляните в пакет 𝗚𝗼 𝗼𝘀/𝗲𝘅𝗲𝗰. Он будет использоваться для выполнения команд операционной системы. Этот пакет определяет тип 𝗖𝗺𝗱, который содержит необходимые методы и свойства для выполнения команд и управления 𝘀𝘁𝗱𝗶𝗻 и 𝘀𝘁𝗱𝗼𝘂𝘁. Вы будете перенаправлять 𝘀𝘁𝗱𝗶𝗻 (𝗥𝗲𝗮𝗱𝗲𝗿) и 𝘀𝘁𝗱𝗼𝘂𝘁 (𝗪𝗿𝗶𝘁𝗲𝗿) в экземпляр 𝗖𝗼𝗻𝗻, представляющий и 𝗥𝗲𝗮𝗱𝗲𝗿, и 𝗪𝗿𝗶𝘁𝗲𝗿.
При получении нового подключения создать экземпляр 𝗖𝗺𝗱 можно с помощью функции 𝗖𝗼𝗺𝗺𝗮𝗻𝗱(𝗻𝗮𝗺𝗲 𝘀𝘁𝗿𝗶𝗻𝗴, 𝗮𝗿𝗴 ...𝘀𝘁𝗿𝗶𝗻𝗴) из 𝗼𝘀/𝗲𝘅𝗲𝗰. Эта функция получает в качестве параметров команды ОС и любые аргументы. В данном примере нужно жестко закодировать в качестве команды /𝗯𝗶𝗻/𝘀𝗵 и передать в качестве аргумента -𝗶, чтобы перейти в интерактивный режим, из которого можно будет управлять потоками 𝘀𝘁𝗱𝗶𝗻 и 𝘀𝘁𝗱𝗼𝘂𝘁 более уверенно:
Эта инструкция создает экземпляр 𝗖𝗺𝗱, но команду еще не выполняет. Здесь для управления 𝘀𝘁𝗱𝗶𝗻 и 𝘀𝘁𝗱𝗼𝘂𝘁 есть два варианта: использовать 𝗖𝗼𝗽𝘆(𝗪𝗿𝗶𝘁𝗲𝗿, 𝗥𝗲𝗮𝗱𝗲𝗿), как говорилось ранее, или напрямую присвоить 𝗥𝗲𝗮𝗱𝗲𝗿 и 𝗪𝗿𝗶𝘁𝗲𝗿 экземпляру 𝗖𝗺𝗱. Давайте непосредственно присвоим объект 𝗖𝗼𝗻𝗻 экземплярам 𝗰𝗺𝗱.𝗦𝘁𝗱𝗶𝗻 и 𝗰𝗺𝗱.𝗦𝘁𝗱𝗼𝘂𝘁:
После настройки команды и потоков запустить ее можно с помощью 𝗰𝗺𝗱.𝗥𝘂𝗻():
Такая логика прекрасно работает для систем 𝗟𝗶𝗻𝘂𝘅. Тем не менее при настройке и запуске этой программы под 𝗪𝗶𝗻𝗱𝗼𝘄𝘀 с помощью 𝗰𝗺𝗱.𝗲𝘅𝗲, а не /𝗯𝗶𝗻/𝗯𝗮𝘀𝗵, подключающийся клиент не получает вывод команды из-за специфичной для 𝗪𝗶𝗻𝗱𝗼𝘄𝘀 обработки анонимных каналов. Далее описаны два решения этой проблемы.
Во-первых, можно настроить код для принудительного сброса 𝘀𝘁𝗱𝗼𝘂𝘁. Вместо непосредственного присваивания 𝗖𝗼𝗻𝗻 экземпляру 𝗰𝗺𝗱.𝗦𝘁𝗱𝗼𝘂𝘁 нужно реализовать собственный 𝗪𝗿𝗶𝘁𝗲𝗿, который обертывает 𝗯𝘂𝗳𝗶𝗼.𝗪𝗿𝗶𝘁𝗲𝗿 (буферизованный райтер) и явно вызывает его метод 𝗙𝗹𝘂𝘀𝗵 для принудительного сброса буфера. Пример использования 𝗯𝘂𝗳𝗶𝗼.𝗪𝗿𝗶𝘁𝗲𝗿 можно найти в разделе «Создание эхо-сервера» ранее в этой главе. Вот определение пользовательского райтера, 𝗙𝗹𝘂𝘀𝗵𝗲𝗿:
Тип 𝗙𝗹𝘂𝘀𝗵𝗲𝗿 реализует функцию 𝗪𝗿𝗶𝘁𝗲([]𝗯𝘆𝘁𝗲), которая записывает данные во внутренний буферизованный райтер, а затем сбрасывает вывод.
С помощью этой реализации пользовательского райтера можно настроить обработчик подключений на создание экземпляра и применение типа 𝗙𝗹𝘂𝘀𝗵𝗲𝗿 для 𝗰𝗺𝗱.𝗦𝘁𝗱𝗼𝘂𝘁:
Продолжение кода выше
Это решение хотя и вполне пригодно, но не очень элегантно. Несмотря на то что рабочий код для нас важнее, чем аккуратный, мы используем эту проблему как возможность рассказать о функции 𝗶𝗼.𝗣𝗶𝗽𝗲(). Она представляет собой синхронный канал в памяти 𝗚𝗼, который можно задействовать для подключения 𝗥𝗲𝗮𝗱𝗲𝗿 и 𝗪𝗿𝗶𝘁𝗲𝗿:
Применение 𝗣𝗶𝗽𝗲𝗥𝗲𝗮𝗱𝗲𝗿 и 𝗣𝗶𝗽𝗲𝗪𝗿𝗶𝘁𝗲𝗿 позволяет избежать необходимости явного сброса райтера и синхронного подключения 𝘀𝘁𝗱𝗼𝘂𝘁 и 𝗧𝗖𝗣-соединения. Опять же понадобится переписать функцию обработчика:
Вызов 𝗶𝗼.𝗣𝗶𝗽𝗲 создает ридер и райтер, подключаемые синхронно, — любые данные, записываемые в райтер (в данном примере 𝘄𝗽), будут считаны ридером (𝗿𝗽). Поэтому сначала происходит присваивание райтера экземпляру 𝗰𝗺𝗱.𝗦𝘁𝗱𝗼𝘂𝘁, после чего используется 𝗶𝗼.𝗖𝗼𝗽𝘆(𝗰𝗼𝗻𝗻, 𝗿𝗽) для присоединения 𝗣𝗶𝗽𝗲𝗥𝗲𝗮𝗱𝗲𝗿 к 𝗧𝗖𝗣-соединению. Это делается с помощью горутины, предотвращающей блокирование кода. Любой стандартный вывод команды отправляется райтеру, после чего передается ридеру и далее через 𝗧𝗖𝗣-соединение. Как вам такая элегантность?
Таким образом, мы успешно реализовали «зияющую дыру безопасности» 𝗡𝗲𝘁𝗰𝗮𝘁 с позиции 𝗧𝗖𝗣-слушателя, ожидающего подключения. По тому же принципу можно реализовать эту функцию с позиции подключающегося клиента, перенаправляющего 𝘀𝘁𝗱𝗼𝘂𝘁 и 𝘀𝘁𝗱𝗶𝗻 локального исполняемого файла удаленному слушателю. Детали этого процесса мы оставим вам для самостоятельной реализации, но в общем они будут включать следующее:
установку подключения к удаленному слушателю через net.Dial(network, address string);
инициализацию Cmd через exec.Command(name string, arg ...string);
перенаправление свойств Stdin и Stdout для использования объекта net.Conn;
выполнение команды.
На этом этапе слушатель должен получить подключение. Любые передаваемые клиенту данные должны интерпретироваться на клиенте как 𝘀𝘁𝗱𝗶𝗻, а данные, получаемые слушателем, — как 𝘀𝘁𝗱𝗼𝘂𝘁.
На этом всё, ждите в ближайшее время больше статей.
В роли атакующего вы должны понимать принцип работы 𝗧𝗖𝗣, чтобы справляться с разработкой пригодных вариантов его конструкций, позволяющих определять открытые/закрытые порты, распознавать такие потенциально ошибочные результаты, как ложные срабатывания — например, 𝗦𝗬𝗡-флуд защиты, — и обходить ограничения на исходящий трафик посредством переадресации портов. В этой главе вы изучите основы 𝗧𝗖𝗣-коммуникаций в 𝗚𝗼, реализуете многопоточный правильно отрегулированный сканер портов, создадите 𝗧𝗖𝗣-прокси, который можно использовать для переадресации портов, а также воссоздадите 𝗡𝗲𝘁𝗰𝗮𝘁-функцию «зияющая дыра в безопасности».
В интернете куча информации которые раскрывают каждый нюанс 𝗧𝗖𝗣, включая такие темы, как потоки и структура пакетов, надежность, повторная сборка сегментов и многие другие. Настолько подробная детализация выходит за рамки этой темы, поэтому я рекомендую глубже изучить эту тему, после прочтения статьи.
TCP Handshaking:
В качестве напоминания мы начнем с основ. снизу будет показано, как 𝗧𝗖𝗣 при запросе порта использует процесс рукопожатия (𝗵𝗮𝗻𝗱𝘀𝗵𝗮𝗸𝗶𝗻𝗴), определяя, открыт порт, закрыт или фильтруется.
Основы рукопожатия в TCP
Если порт открыт, рукопожатие осуществляется в три этапа. Сначала клиент отправляет пакет 𝘀𝘆𝗻, определяющий начало сеанса связи. В ответ на это сервер отправляет 𝘀𝘆𝗻-𝗮𝗰𝗸, иначе говоря, подтверждение получения пакета 𝘀𝘆𝗻, предлагая клиенту завершить сеанс установки связи отправкой сигнала 𝗮𝗰𝗸, то есть встречного подтверждения получения ответа сервера. После этого может начаться обмен данными. Если же порт будет закрыт, сервер ответит пакетом 𝗿𝘀𝘁, а не 𝘀𝘆𝗻-𝗮𝗰𝗸. В случае, когда трафик фильтруется межсетевым экраном (брандмауэром), клиент обычно не получает от сервера ответа.
При написании сетевых протоколов важно понимать принцип работы этих пакетов. Соответствие выходных данных создаваемых вами инструментов этим низкоуровневым потокам пакетов поможет убедиться в правильной установке сетевого соединения и устранить потенциальные проблемы. Как вы увидите чуть позже, в коде можно легко допустить ошибки, не реализовав полный цикл рукопожатия при соединении «клиент — сервер», что приведет к неточным или вводящим в заблуждение результатам.
Обход брандмауэра с помощью переадресации портов:
Иногда в системах с целью ограничения доступа клиента к конкретным серверам и портам устанавливаются брандмауэры. В некоторых случаях эти ограничения можно обойти, используя промежуточную систему для проксирования в обход или через такой брандмауэр. Эта техника называется переадресацией портов.
Многие корпоративные сети ограничивают возможность подключения своих внутренних ресурсов к вредоносным сайтам. В качестве примера представьте такой сайт под названием 𝗲𝘃𝗶𝗹.𝗰𝗼𝗺. Если сотрудник компании попытается подключиться к нему напрямую, брандмауэр заблокирует его запрос. Но если у сотрудника есть собственная внешняя система, доступная через брандмауэр (например, 𝘀𝘁𝗮𝗰𝗸𝘁𝗶𝘁𝗮𝗻.𝗰𝗼𝗺), то он может задействовать ее для установки связи с 𝗲𝘃𝗶𝗹.𝗰𝗼𝗺. Этот принцип отражен снизу.
TCP-проски
Клиент подключается к удаленному хосту 𝘀𝘁𝗮𝗰𝗸𝘁𝗶𝘁𝗮𝗻.𝗰𝗼𝗺 через брандмауэр. Этот хост настроен на перенаправление соединений к хосту 𝗲𝘃𝗶𝗹.𝗰𝗼𝗺. Несмотря на то что брандмауэр запрещает прямые подключения к 𝗲𝘃𝗶𝗹.𝗰𝗼𝗺, описанная конфигурация позволяет клиенту обойти этот механизм защиты.
Переадресацию портов можно использовать для эксплуатации нескольких запрещающих сетевых конфигураций. Например, можно перенаправить трафик через инсталляционный сервер для доступа к сегментированной сети или обращения к портам, привязанным к ограниченным интерфейсам.
Написание TCP-сканера:
Один из эффективных способов концептуализировать понимание взаимодействия 𝗧𝗖𝗣-портов — это реализация их сканера. В процессе его создания вы увидите все шаги обмена рукопожатиями в 𝗧𝗖𝗣, а также эффекты от возникающих изменений состояний, которые позволяют определить, является ли порт доступным, закрытым или отфильтровывается.
Написав базовый сканер портов, вы перейдете к созданию его ускоренной версии. Базово эта программа способна сканировать множество портов, используя один непрерывный метод, но если вам потребуется выполнить сканирование всех 𝟲𝟱 𝟱𝟯𝟱 портов, это может занять слишком много времени. Поэтому мы научим вас с помощью многопоточности делать малоэффективный сканер более подходящим для выполнения задач расширенного сканирования. Освоенные в этой статье шаблоны параллельности вы сможете применять и во многих других сценариях.
Тестирование портов на доступность:
Первый шаг в создании сканера портов — понять процесс инициирования соединения от клиента к серверу. В рассматриваемом примере вы будете подключаться к 𝘀𝗰𝗮𝗻𝗺𝗲.𝗻𝗺𝗮𝗽.𝗼𝗿𝗴 — сервису проекта 𝗡𝗺𝗮𝗽𝟭 и сканировать его. Для этого мы с вами задействуем пакет 𝗚𝗼 𝗻𝗲𝘁: 𝗻𝗲𝘁.𝗗𝗶𝗮𝗹(𝗻𝗲𝘁𝘄𝗼𝗿𝗸, 𝗮𝗱𝗱𝗿𝗲𝘀𝘀 𝘀𝘁𝗿𝗶𝗻𝗴).
Первый аргумент — это строка, определяющая тип инициируемого соединения. Дело в том, что 𝗗𝗶𝗮𝗹 используется не только для 𝗧𝗖𝗣, но и для создания соединений, задействующих сокеты 𝗨𝗻𝗶𝘅, 𝗨𝗗𝗣 и протоколы 𝟰-го уровня, которые мы оставим в стороне, так как на основе всего нашего опыта будет достаточно просто сказать, что 𝗧𝗖𝗣 очень хорош. В этот аргумент можно передать несколько вариантов строк, но для краткости будем использовать строку 𝘁𝗰𝗽.
Второй аргумент указывает 𝗗𝗶𝗮𝗹(𝗻𝗲𝘁𝘄𝗼𝗿𝗸, 𝗮𝗱𝗱𝗿𝗲𝘀𝘀 𝘀𝘁𝗿𝗶𝗻𝗴) на хост, к которому вы хотите подключиться. Обратите внимание на то, что это одна строка, а не 𝘀𝘁𝗿𝗶𝗻𝗴 и 𝗶𝗻𝘁. Для соединений 𝗜𝗣𝘃𝟰/𝗧𝗖𝗣 она будет принимать форму 𝗵𝗼𝘀𝘁:𝗽𝗼𝗿𝘁. Например, если вам нужно подключиться к 𝘀𝗰𝗮𝗻𝗺𝗲.𝗻𝗺𝗮𝗽.𝗼𝗿𝗴 через 𝗧𝗖𝗣-порт 𝟴𝟬, то нужно указать 𝘀𝗰𝗮𝗻𝗺𝗲.𝗻𝗺𝗮𝗽.𝗼𝗿𝗴:𝟴𝟬.
Теперь вы знаете, как создать соединение, но как понять, что оно было успешным? Для этого выполняется проверка на ошибки: 𝗗𝗶𝗮𝗹(𝗻𝗲𝘁𝘄𝗼𝗿𝗸, 𝗮𝗱𝗱𝗿𝗲𝘀𝘀 𝘀𝘁𝗿𝗶𝗻𝗴) возвращает Conn и error. При этом error будет nil, если соединение установлено успешно. Так что для проверки вам просто нужно убедиться, что error равна nil. Вот теперь у вас есть все необходимые элементы для построения сканера портов, хотя и не особо корректного. В Основах рукопожатия в TCP показано, как все это объединить.
Простой сканер портов, сканирующий только один порт /dial/main.go
Выполнив этот код, вы должны увидеть сообщение 𝗖𝗼𝗻𝗻𝗲𝗰𝘁𝗶𝗼𝗻 𝘀𝘂𝗰𝗰𝗲𝘀𝘀𝗳𝘂𝗹 при условии наличия у вас доступа к великой информационной супермагистрали
Выполнение однопоточного сканирования:
Сканирование по одному порту за раз не особо полезно и малоэффективно, так как диапазон 𝗧𝗖𝗣-портов — от 𝟭 до 𝟲𝟱 𝟱𝟯𝟱. В целях же тестирования давайте пока просканируем порты от 𝟭 до 𝟭𝟬𝟮𝟰. Для этого можно использовать цикл 𝗳𝗼𝗿:
Теперь у вас есть 𝗶𝗻𝘁, но нужно помнить, что в качестве второго аргумента для 𝗗𝗶𝗮𝗹(𝗻𝗲𝘁𝘄𝗼𝗿𝗸, 𝗮𝗱𝗱𝗿𝗲𝘀𝘀 𝘀𝘁𝗿𝗶𝗻𝗴) требуется строка. Есть по меньшей мере два способа преобразования целого числа в строку. Первый — использовать пакет преобразования строк 𝘀𝘁𝗿𝗰𝗼𝗻𝘃. Второй — применить функцию 𝗦𝗽𝗿𝗶𝗻𝘁𝗳(𝗳𝗼𝗿𝗺𝗮𝘁 𝘀𝘁𝗿𝗶𝗻𝗴, 𝗮 ...𝗶𝗻𝘁𝗲𝗿𝗳𝗮𝗰𝗲{}) из пакета 𝗳𝗺𝘁, которая (аналогично своему собрату в 𝗖) возвращает 𝘀𝘁𝗿𝗶𝗻𝗴, сгенерированную из строки формата (𝗳𝗼𝗿𝗺𝗮𝘁 𝘀𝘁𝗿𝗶𝗻𝗴).
Создайте файл с кодом из листинга 𝟮.𝟮 и убедитесь в работоспособности цикла и функции генерации строки. Выполнение этого кода должно вывести 𝟭𝟬𝟮𝟰 строки, но утруждать себя их подсчетом не обязательно.
Теперь осталось только подставить переменную адреса из предыдущего кода в 𝗗𝗶𝗮𝗹(𝗻𝗲𝘁𝘄𝗼𝗿𝗸, 𝗮𝗱𝗱𝗿𝗲𝘀𝘀 𝘀𝘁𝗿𝗶𝗻𝗴) и протестировать доступность портов, реализовав такую же проверку ошибок, как в предыдущем разделе. Помимо этого, чтобы не оставлять успешные соединения открытыми, следует добавить логику их закрытия. Завершение соединений — это жест вежливости. Для этого вам нужно выполнить в 𝗖𝗼𝗻𝗻 вызов 𝗖𝗹𝗼𝘀𝗲(). Снизу показана полноценная реализация сканера портов.
Скомпилируйте и выполните этот код для выполнения легкого сканирования цели. Вы должны обнаружить пару открытых портов.
Параллельное сканирование:
Предыдущий сканер сканировал серию портов в один заход. Но вашей целью является проверка множества портов параллельно, что существенно ускорит его работу. Для этого мы воспользуемся горутинами. 𝗚𝗼 позволяет создавать столько горутин, сколько способна обработать ваша система, ограничиваясь только объемом доступной памяти.
Слишком быстрая версия сканера:
Самым прямолинейным способом создания параллельного сканера будет обернуть вызов 𝗗𝗶𝗮𝗹(𝗻𝗲𝘁𝘄𝗼𝗿𝗸, 𝗮𝗱𝗱𝗿𝗲𝘀𝘀 𝘀𝘁𝗿𝗶𝗻𝗴) в горутину. Чтобы собственными глазами увидеть последствия этого, создайте файл 𝘀𝗰𝗮𝗻-𝘁𝗼𝗼-𝗳𝗮𝘀𝘁.𝗴𝗼 с кодом который снизу.
Сканер, работающий слишком быстро /tcp-scanner-too-fast/main.go
При выполнении кода вы заметите, что программа завершается практически мгновенно:
Этот код запускает по одной горутине для каждого соединения, а основная горутина не знает, что нужно ждать окончания его установки. В связи с этим выполнение кода завершается, как только цикл 𝗳𝗼𝗿 заканчивает перебор, что происходит быстрее, чем сеть успевает осуществить обмен пакетами между кодом и всеми целевыми портами. Поэтому вы не получите точных результатов для портов, чьи пакеты находились в процессе обмена.
Исправить это можно несколькими способами. Первый — использовать 𝗪𝗮𝗶𝘁𝗚𝗿𝗼𝘂𝗽 из пакета 𝘀𝘆𝗻𝗰, предоставляющий потокобезопасный способ управления параллельным выполнением. 𝗪𝗮𝗶𝘁𝗚𝗿𝗼𝘂𝗽 — это тип структуры, который создается так:
Создав 𝗪𝗮𝗶𝘁𝗚𝗿𝗼𝘂𝗽, вы можете вызвать для этой структуры несколько методов. Первый метод — 𝗔𝗱𝗱(𝗶𝗻𝘁), увеличивающий внутренний счетчик согласно переданному числу. Следующий — метод 𝗗𝗼𝗻𝗲(), уменьшающий счетчик на 𝟭. И наконец, метод 𝗪𝗮𝗶𝘁(), блокирующий выполнение горутины, в которой вызывается, запрещая дальнейЭтот вариант кода по большому счету остался неизменным. Тем не менее здесь мы добавили код, явно отслеживающий оставшуюся работу. В этой версии программышее выполнение, пока внутренний счетчик не достигнет нуля. Эти вызовы можно совмещать, гарантируя, что основная горутина дождется завершения всех соединений.
Синхронизированное сканирование с помощью WaitGroup:
Этот вариант кода по большому счету остался неизменным. Тем не менее здесь мы добавили код, явно отслеживающий оставшуюся работу. В этой версии программы
создаем 𝘀𝘆𝗻𝗰.𝗪𝗮𝗶𝘁𝗚𝗿𝗼𝘂𝗽, выступающую в качестве синхронизированного счетчика. Мы увеличиваем этот счетчик через 𝘄𝗴.𝗔𝗱𝗱(𝟭) при каждом создании горутины для сканирования порта. При этом отложенный вызов 𝘄𝗴.𝗗𝗼𝗻𝗲() уменьшает этот счетчик при завершении каждой единицы работы. Функция 𝗺𝗮𝗶𝗻() вызывает 𝘄𝗴.𝗪𝗮𝗶𝘁(), который блокирует выполнение, пока не будет выполнена вся работа и счетчик не достигнет нуля.
Эта версия программы уже лучше, но по-прежнему имеет недостатки. Если запустить ее несколько раз для разных хостов, можно получить несогласованные результаты. Одновременное сканирование чрезмерного количества хостов или портов может привести к тому, что ограничения системы или сети исказят результаты. Попробуйте изменить в коде значение 𝟭𝟬𝟮𝟰 на 𝟲𝟱𝟱𝟯𝟱 и укажите адрес целевого сервера как 𝟭𝟮𝟳.𝟬.𝟬.𝟭. При желании можете использовать 𝗪𝗶𝗿𝗲𝘀𝗵𝗮𝗿𝗸 или 𝘁𝗰𝗽𝗱𝘂𝗺𝗽, чтобы увидеть, насколько быстро открываются эти соединения.
Сканирование портов с помощью пула воркеров:
Чтобы избежать несогласованности, можно задействовать для управления параллельным выполнением пул горутин. С помощью цикла 𝗳𝗼𝗿 вы создаете определенное количество воркеров горутин в качестве пула. Затем в потоке 𝗺𝗮𝗶𝗻() с помощью канала обеспечиваете работу.
Для начала создайте новую программу, которая использует канал 𝗶𝗻𝘁, содержит 𝟭𝟬𝟬 воркеров и выводит их результаты на экран. При этом задействуйте 𝗪𝗮𝗶𝘁𝗚𝗿𝗼𝘂𝗽 для блокирования выполнения.
Создайте начальную заглушку кода для функции 𝗺𝗮𝗶𝗻, а над ней напишите функцию,приведенную в коде ниже
Функция с воркером для выполнения задачи.
Функция 𝘄𝗼𝗿𝗸𝗲𝗿(𝗶𝗻𝘁, *𝘀𝘆𝗻𝗰.𝗪𝗮𝗶𝘁𝗚𝗿𝗼𝘂𝗽) получает два аргумента: канал типа 𝗶𝗻𝘁 и указатель на 𝗪𝗮𝗶𝘁𝗚𝗿𝗼𝘂𝗽. Канал будет использоваться для получения работы, а 𝗪𝗮𝗶𝘁𝗚𝗿𝗼𝘂𝗽 — для отслеживания завершения одной ее единицы.
Далее добавьте функцию 𝗺𝗮𝗶𝗻(), приведенную в коде ниже, которая будет управлять рабочей нагрузкой и обеспечивать работу функции 𝘄𝗼𝗿𝗸𝗲𝗿(𝗶𝗻𝘁, *𝘀𝘆𝗻𝗰.𝗪𝗮𝗶𝘁𝗚𝗿𝗼𝘂𝗽).
Базовый пул воркеров /tcp-sync-scanner/main.go
Сначала создается канал с помощью 𝗺𝗮𝗸𝗲(). В 𝗺𝗮𝗸𝗲() в качестве второго параметра передается значение 𝟭𝟬𝟬. Это добавляет каналу буферизацию, то есть в него можно будет отправлять элемент и не ждать, пока получатель этот элемент прочтет. Буферизованные каналы идеально подходят для поддержания и отслеживания работы нескольких производителей и потребителей. Емкость канала определяется как 𝟭𝟬𝟬. Значит, он может вместить 𝟭𝟬𝟬 элементов, до того как отправитель будет заблокирован. Это дает небольшой прирост производительности, поскольку все воркеры смогут запускаться сразу.
Далее с помощью цикла 𝗳𝗼𝗿 запускается заданное число воркеров — в данном случае 𝟭𝟬𝟬. В функции 𝘄𝗼𝗿𝗸𝗲𝗿(𝗶𝗻𝘁, *𝘀𝘆𝗻𝗰.𝗪𝗮𝗶𝘁𝗚𝗿𝗼𝘂𝗽) с помощью 𝗿𝗮𝗻𝗴𝗲 происходит непрерывное циклическое получение данных из канала 𝗽𝗼𝗿𝘁𝘀, завершающееся только при закрытии канала. Обратите внимание: пока воркер никакой работы не выполняет — это произойдет чуть позже. Последовательно перебирая порты в функции 𝗺𝗮𝗶𝗻(), вы отправляете порт через канал 𝗽𝗼𝗿𝘁𝘀 воркеру. По завершении всей работы закрываете канал.
Запустив эту программу, вы увидите, как на экран выводятся числа. Здесь можно заметить кое-что интересное, а именно то, что выводятся они в конкретном порядке. Добро пожаловать в прекрасный мир многопоточности!
Многоканальная связь:
Чтобы завершить создание сканера, можно вставить код, использованный ранее в этом разделе, и это вполне сработает. Но в таком случае выводимые порты будут не отсортированы, так как сканер станет проверять их не по порядку. Решить эту проблему можно, реализовав упорядоченную передачу результатов сканирования в основной поток через дополнительный. Это изменение к тому же позволит полностью устранить зависимость от 𝗪𝗮𝗶𝘁𝗚𝗿𝗼𝘂𝗽, так как теперь у вас будет другой метод для отслеживания завершения. Например, если вы сканируете 𝟭𝟬𝟮𝟰 порта, то делаете по каналу воркера 𝟭𝟬𝟮𝟰 передачи, после чего снова выполняете 𝟭𝟬𝟮𝟰 передачи с результатами работы обратно в основной поток. Поскольку количество отправленных единиц работы и полученных результатов совпадает, программа понимает, когда нужно закрывать каналы и, следовательно, отключать воркеры.
Эта модификация кода представлена в коде ниже, которым завершается создание сканера.
Сканирование портов через несколько каналов /tcp-scanner-final/main.go
Функция 𝘄𝗼𝗿𝗸𝗲𝗿(𝗽𝗼𝗿𝘁𝘀, 𝗿𝗲𝘀𝘂𝗹𝘁𝘀 𝗰𝗵𝗮𝗻 𝗶𝗻𝘁) была изменена для получения двух каналов. Остальная логика почти полностью осталась прежней, за исключением того, что в случае закрытого порта вы отправляете ноль, а в случае открытого — значение этого порта. Кроме того, здесь вы создаете отдельный канал для передачи результатов от воркера в основной поток. Затем результаты сохраняются в срез, что позволяет выполнить их сортировку. Далее вам нужно реализовать отправку данных воркера в отдельной горутине, потому что цикл сбора результатов должен начаться до того, как сможет продолжиться выполнение более 𝟭𝟬𝟬 единиц работы.
Этот цикл получает по каналу 𝗿𝗲𝘀𝘂𝗹𝘁𝘀 𝟭𝟬𝟮𝟰 передачи. Если порт не равен 𝟬, он добавляется в срез. После закрытия каналов вы используете сортировку для упорядочивания среза открытых портов. Далее остается лишь перебрать срез и вывести открытые порты на экран.
Вот мы и написали высокопроизводительный сканер портов. Уделите время экспериментированию с кодом — в частности, с количеством воркеров. Чем их больше, тем быстрее должна выполняться программа. Но если их окажется слишком много, результаты станут ненадежными. При написании инструментов, которые будут применять другие люди, вам нужно использовать грамотное предустановленное значение, которое ориентировано на надежность, а не на скорость. При этом также следует предоставлять пользователям опцию самостоятельного выбора количества воркеров.
В полученную программу можно внести пару улучшений. Во-первых, вы отправляете по каналу 𝗿𝗲𝘀𝘂𝗹𝘁𝘀 результат сканирования каждого порта, что необязательно. Альтернативное решение потребует написания более сложного кода, который будет использовать дополнительный канал не только для отслеживания воркеро. Вам может потребоваться, чтобы сканер умел парсить строки с портами, например 𝟴𝟬,𝟰𝟰𝟯,𝟴𝟬𝟴𝟬,𝟮𝟭-𝟮𝟱, наподобие тех, что могут быть переданы в 𝗡𝗺𝗮𝗽. Я предлагаю вам освоить этот прием самостоятельно.
P.S: в следующей статье мы рассмотрим создание TCP-прокси.
Когда-то одна английская царственная дура начала выдавать капитанам кораблей разрешение грабить корабли других стран. Так появились корсары...дурной пример заразителен, пираты быстро повылезали из всех щелей и моря-океаны быстро заполнились этими отбросами, сделав морские пути опасными. Долго потом эту заразу не могли вывести чтобы спокойно торговать между странами.
Недавно с удивлением узнаю что ГД рассматривает проект легального хакерства, то есть разрешить группе лиц взламывать компьютерные сети в случае стратегической необходимости. Могу только представить чем это обернется. Интернет станет сточной канавой, онлайн сервисы загнутся...из-за моря прилетит ответочка, там свои хакеры и их в тысячи раз больше,так что страна у которой нет ничего своего, даже своей операционки, вряд ли выдержит напор цифровой атаки, управляемый искусственным интеллектом.
К чему это я. Надо думать о последствиях. Легко издать новый закон, но это как спустить охотничьих собак с поводка...а добычей будем мы с вами. Белые и черные хакеры, да без разницы. Пираты захватывали чужие корабли, а эти захватывают ваши компьютеры, ноутбуки, смартфоны. Любой взлом это преступление, даже если он сделан по заказу партии и правительства. На этом все.
В фильме кибер-террористическая группировка массово взламывает компьютеры которые связаны с важной гражданской инфраструктурой, в следствие чего падают самолеты, отключается электроэнергия и интернет, GPS тоже перестает работать. В это время пролетают беспилотники которые разбрасывают листовки на арабском, со словами в духе "Смерть Америке!". Герои узнают что в других регионах были сброшены другие листовки, на китайском (мандаринском) языке.
Однако в фильме также есть подсказки что все не так просто как кажется и возможно тут замешана операция под фальшивым флагом и на самом деле это госпереворот/глубинное государство. При этом всем на Нью-Йорк летит ядерка.
Higher Ground Productions которая продюсировала фильм основана Мишель и Бараком Обамой в 2018 году. Фильм называется Оставить мир позади "Leave the World Behind"
Ранее в США обвинили Иранскую анти-израильскую группировку во взломе водоочистительной инфраструктуры в Пенсильвании
Способ 1) Для этого нужно использовать русско английскую клавиатуру, на ней каждой русской букве соответствует английская практически, есть буквы у которых английской буквы нет потому что русских букв 33 а английских 26.
Так вот придумываем или используется самое привычное для вас слово, своё имя, отчество, фамилию свою или девичью матери или девичью бабушки то есть то что вы хрен уже забудете находясь в добром здравии и доброй памяти.
Например имени Александр будут соответствовать следующие английские буквы русско английской клавиатуры FKTRCFYLH.
Владимир будет русским буквам на клавиатуре соответствовать следующие ангельские DKFLBVBH и так далее
Способ 2) Потребуется десять символов первый вариант это строка из английского алфавита ABCDEFGHIJKL его многие знают наизусть.
Вторая строка это снова клавиатура, первый ряд символов русско английских их ровно десять сколько нам и надо QWERTYUIOP
Теперь любое число мы можем привести в набор символов двумя способами. За нулевой символ разумно использовать последний десятый, иначе если первый то будет менее удобно, всё окажется сдвинутым на единицу, 1 будет соответствовать второй символ и так далее что менее удобно чем если 1 соответствует первый символ буква.
Например
Дата рождения человека предположим 22.11.1987 переводим её в символы по строке алфавита английского BBAAAKJI
по строке клавиатуры первой, которая содержит 10 русско английских букв и находится сразу под цифрами WWQQQOIU
также можно перевести номер своего телефона или телефонов которые вы хорошо знаете.
Способ 3) Каждому символу, букве соответствует номер в алфавите, то есть переводим буквы в их порядковые номера в алфавите, числа, и записываем последовательность это и будет паролем. Алфавиты большинство хорошо помнят, но если даже и подзабыли подучить и вспомнить их совершенно не помешает, или можно иметь картинку с алфавитами английским и русскими по рукой, знать стоит наизусть как английский алфавит так и русский.
Например имя Катя 12(К) 1(А) 20(Т) 33(Я). И можно совместить Катя1212033
и пусть подбирают хакеры эту последовательность.
Способ 4) Написание хорошо известного, любимого вами слова, имени, фамилии, задом наперёд. Например Иванов будет Вонави, Петров будет Вортеп, Сидоров будет Вородис. К получившимся словам неочевидным но легко восстанавливаемым, можно добавить какое нибудь любимое вами число например год вашего рождения и тоже написанное задом наперёд.
Способ 5) Комбинация и букв и чисел. Например придумали вышеприведенным способом себе пароль из букв и добавляем к нему еще и циферки например год своего рождения написанный к примеру тремя способами, обычным 1987 или задом наперед 7891 или перенос пары последних цифр в перёд или двух первых назад даст тот же результат 8719 то есть зная что вы так оперируете своим цифрами года рождения у вас всего три варианта их написания, можно сделать больше при желании. И добавляем к паролю из букв год своего или даты рождения записанного обычно или необычно по приведенным выше или вашим собственным правилам.
Мне кажется такие пароли получаются непростыми для подбора при переборе наиболее распространенных слов например, но в тоже время простыми для восстановления и запоминания. Конечно никакие пароли не защитят от серьезных хакеров поэтому компрометирующую вас информацию лучше не хранить на устройствах подключенных к сети интернет, спиздят и ни здрасть ни до свидания не скажут, в легкую.
ЗЫ Более устойчивы пароли если вы используете больше вариантов символов в пароле, например если мы используем только цифры то каждый символ имеет 10 вариантов, если буквы английские и числа то уже 10+26 = 36 вариантов может иметь каждый символ, если буквы большие, мелкие и цифры то вариантов символа в пароле уже будет 10+26+26 = 62. Поэтому многие сайты просят нас использовать и маленькие буквы и большие и числа и даже специальные знаки чтобы пароль был максимально сложным для подбора. Чем большее число символов используется при составлении (цифры, буквы заглавные и мелкие, спецсимволы) пароля тем сложнее его будет взломать хакерам говнякерам методом перебора.
ЗЫ2 Чтобы проще переводить цифры в буквы и буквы в цифры разумно иметь под рукой, на компьютере алфавиты русский и английский с приписанными каждой букве её порядковым номером, картинкой с клавиатурой русско английской если например её не окажется под рукой, иметь возможность глянуть как шифровать слова и цифры. Можно и написать последовательности две из десяти символов из QWERTYUIOP и ABCDEFGHIJKL с подписанными порядковыми номерами для того чтобы не считать, какой букве соответствует десятизначное число, а глянул и сразу узнал какой букве какой номер соответствует, это упростит шифрацию и дешифрацию букв и цифр.