Предыстория, один клиент в компании попросил проверить пк сотрудника после упадка продаж, он реально думал что сливали заказы копированием или как то доступом через crm в общем упадок был, а понять как и отследить было сложно. Так для тех кто в теме, скрипт я переделал под exe прятался он под pdf прайсом. а в диспетчере скрыт. И пост не для программистов.
Попросили меня проверить, результат порадовал, но причастность доказать нереально кто из менеджеров запустил данный софт. Схема проста, покупается зарубежный виртуальный номер на который регистрируется аккаунт телеграмм и через которого создаётся бот.
Рекомендую всем проверять отправки на порты телеги возможно вы тот самый за кем шпионит жена, муж, начальник или конкурент.
Находку я немного переписал, потому что она была зашита под файлом но выполняла почто такой же функционал как на видео.
Кто такие “White hats - Белы шляпы” ?
“White hats - Белы шляпы” - это термин, используемый для обозначения этичных хакеров, которые используют свои навыки для улучшения кибербезопасности.
Они работают на благо общества, помогая организациям обнаруживать и устранять уязвимости в их системах безопасности.
Белые шляпы проводят тестирование на проникновение и другие проверки безопасности, чтобы обнаружить потенциальные угрозы.
В отличие от “Black hats”, белые шляпы действуют законно и с разрешения владельцев систем.
Работа белых шляп важна для поддержания безопасности в интернете и защиты данных пользователей от злоумышленников. Всё тестировалось в среде на виртуальных машинах и ведео не в коем образом не пропагандирует взлом или как то его рекламирует. Видео несёт информационный характер в сфере Pentest. Tg@Windall
Перед тем как ознакомиться с данным материалом настоятельно рекомендую ознакомиться с предыдущей статьей.
Один из столпов социального инжиниринга — это атака по сбору учетных данных. В ходе нее перехват учетной информации пользователя происходит за счет подмены оригинального сайта клонированной версией, где пользователь и вводит свои данные. Эта техника эффективна против организаций, которые предоставляют в интернете доступ к интерфейсу однофакторной аутентификации. Как только вы получили учетные данные пользователя, можете применять их для получения доступа к аккаунту на оригинальном сайте. Это зачастую приводит к прорыву сетевого периметра организации.
Go обеспечивает отличную платформу для выполнения подобных атак, потому что он быстро устанавливает новые серверы, позволяя также легко настраивать маршрутизацию и парсинг вводимой пользователем информации. В сборщик учетных данных можно добавлять множество настроек и возможностей, но в нашем примере будем придерживаться основ.
Для начала нужно сделать клон сайта, имеющего форму авторизации. Здесь можно рассмотреть множество вариантов. На практике вы будете делать копию сайта, используемого вашей мишенью. В своем примере мы будем клонировать ресурс Roundcube. Roundcube — это открытый клиент электронной почты, который применяется не так часто, как коммерческие решения наподобие Microsoft Exchange, но вполне годится для демонстрации принципа. Для запуска Roundcube мы задействуем Docker, так как он существенно упрощает процесс.
Вы можете запустить собственный сервер Roundcube, выполнив приведенный далее код. Делать это не обязательно, так как исходный код примера содержит клон данного сайта. Тем не менее для полноты информации мы включаем и этот вариант:
Эта команда запускает экземпляр Roundcube Docker. Перейдя по адресу http://127.0.0.1:80, вы увидите форму авторизации. Обычно для клонирования сайта и всех необходимых ему файлов используется wget, но задействованный в реализации Roundcube JavaScript лишает нас этой возможности. Вместо этого применим для сохранения Google Chrome. Структура каталога примера приведена в коде ниже.
Структура каталогов
Файлы в каталоге public представляют неизмененный сайт. Вам потребуется изменить исходную форму авторизации, чтобы перенаправлять вводимые данные, отправляя их своему серверу вместо действительного. Для начала откройте public/index.html и найдите элемент формы, используемый для POST-запроса авторизации. Он должен выглядеть так:
В этом теге нужно отредактировать атрибут action, направив его на свой сервер. Для этого измените action на /login и сохраните. Теперь эта строка должна выглядеть так:
Для корректного отображения формы авторизации и перехвата имени пользователя с паролем сначала потребуется разместить эти файлы в каталог public. Затем нужно будет написать для /login функцию HandleFunc, которая и будет выполнять перехват. Вам также потребуется сохранить полученные учетные данные в файле с помощью логирования.
Все это можно обработать буквально в нескольких строках кода, и в коде ниже вы увидите итоговую программу целиком.
Сервер сбора учетных данных
Первое, на что следует обратить внимание, — это импорт github.com/Sirupsen/logrus. Это структурированный пакет для логирования, который мы предпочитаем задействовать вместо стандартного пакета Go log. Он предоставляет более богатые возможности настройки логирования для лучшей обработки ошибок. Чтобы использовать этот пакет, нужно, как обычно, вначале выполнить go get.
Затем мы определяем функцию-обработчик login(). Надеемся, что данный паттерн вам знаком. Внутри этой функции запись перехваченных данных реализуется с помощью log.WithFields(). При этом отображаются текущее время, пользовательский агент и IP-адрес источника запроса. Помимо этого, выполняется вызов FormValue(string) для перехвата переданных значений имени пользователя (_user) и пароля (_pass). Эти значения мы получаем из index.html, также определив расположение элементов ввода формы для каждого имени пользователя и пароля. Ваш сервер должен явно соответствовать именам полей в том виде, в каком они присутствуют в форме авторизации.
Приведенный далее фрагмент, извлеченный из index.html, показывает соответствующие вводные элементы, чьи имена для наглядности выделены жирным:
В функции main() мы начинаем с открытия файла, в котором будут храниться перехваченные данные. Затем используем log.SetOutput(io.Writer), передавая ей только что созданный дескриптор файла для настройки пакета логирования, чтобы он производил запись в этот файл. Далее создаем новый маршрутизатор и добавляем функцию-обработчик login().
Перед запуском сервера нужно выполнить еще одно действие: сообщить маршрутизатору о необходимости предоставлять статические файлы из каталога. Таким образом, ваш сервер Go явно знает, где находятся все статические файлы — изображения, JavaScript, HTML. Go упрощает этот процесс и обеспечивает защиту против атак по обходу каталогов. Начиная изнутри, мы используем http.Dir(string) для определения каталога, из которого нужно предоставлять файлы. Результат передается в качестве ввода в http.FileServer(FileSystem), которая создает для данного каталога http.Handler. Все это прикрепляется к маршрутизатору с помощью PathPrefix(string). Использование / в качестве префикса пути будет соответствовать всем запросам, которые еще не нашли соответствия. Обратите внимание на то, что по умолчанию возвращаемый из FileServer обработчик поддерживает индексацию каталогов, что может спровоцировать утечку информации. Это можно отключить, но здесь мы данный вопрос рассматривать не будем.
В завершение, как и прежде, мы запускаем сервер. Собрав и выполнив код Сервера сбора учетных данных, откройте браузер и перейдите на http://localhost:8080. Попробуйте отправить через форму имя пользователя и пароль. Затем выйдите из программы и откройте credentials.txt:
Только взгляните на эти логи! Здесь видно, что были отправлены имя Oleg и пароль p@ssw0rd1!. Наш вредоносный сервер успешно обработал POST-запрос формы, перехватив введенные учетные данные и сохранив их в файл для просмотра офлайн. Будучи атакующим, вы могли бы затем использовать эти данные против целевой организации и продолжить внедрение в ее систему.
Далее мы проработаем вариацию этой техники по сбору учетных данных. Вместо ожидания отправки формы создадим кейлогер для перехвата нажатий клавиш в реальном времени.
Кейлогинг с помощью WebSocket API
WebSocket API (WebSockets) — это полнодуплексный протокол, чья популярность на протяжении последних лет возросла, поскольку теперь он поддерживается во многих браузерах. Этот протокол предоставляет веб-серверам и их клиентам способ эффективно взаимодействовать друг с другом. Что еще более важно, он позволяет серверу отправлять сообщения клиенту, не требуя опроса.
WebSockets применяются для создания приложений реального времени, таких как чаты и онлайн-игры. Но их можно задействовать и для вредоносных действий, например для внедрения кейлогера в приложение с целью перехвата всех нажимаемых пользователем клавиш. Для начала представьте, что нашли приложение, уязвимое для межсайтового выполнения сценариев (брешь, через которую сторонний агент может выполнять произвольный JS-код в браузере жертвы), или взломали сервер, получив возможность изменять исходный код этого приложения. При любом из этих вариантов вы сможете внедрить удаленный JS-файл. Мы с вами создадим инфраструктуру сервера для обработки WebSocket-соединения со стороны клиента и регистрации входящих нажатий клавиш.
В целях демонстрации для тестирования полезной нагрузки мы используем JS Bin(http://jsbin.com). JS Bin — это онлайн-песочница, где разработчики могут тестировать свой HTML- или JS-код. Перейдите на этот ресурс в браузере и вставьте следующий HTML в столбец слева, полностью заменив исходный код:
В правой части экрана отобразится форма. Вы могли заметить, что включили тег script с атрибутом src, установленным как http://localhost:8080/k.js. Это будет JS-код, реализующий создание WebSocket-соединения и отправку пользовательского ввода на сервер.
Нашему серверу потребуется выполнить два действия: обработать WebSocket и предоставить JS-файл. Давайте в первую очередь покончим с JavaScript, ведь книга, в конце концов, посвящена Go. (Инструкции по написанию JS-кода с помощью Go имеются в репозитории https://github.com/gopherjs/gopherjs/.)
Вот JS-код:
Он обрабатывает события нажатия клавиш. Каждое такое нажатие этот код отправляет через WebSocket на ресурс по адресу ws://{{.}}/ws. Напомним, что значение {{.}} является полем ввода шаблона Go, отражающего текущий контекст. Этот ресурс представляет WebSocket URL, который будет вносить информацию о местоположении сервера на основе переданной в шаблон строки. Мы вернемся к этому через минуту. Для этого примера сохраним JS в файл logger.js.
Вас может смутить то, что мы вроде собирались предоставлять его как k.js. HTML-код, который мы показали ранее, тоже явно использует k.js. Что это значит? Это значит, что на деле logger.js является не JS-файлом, а шаблоном Go. Мы будем применять k.js в маршрутизаторе в качестве паттерна для сопоставления. При его совпадении сервер будет отображать шаблон из файла logger.js, заполненный контекстными данными, представляющими хост, к которому подключается WebSocket. Код сервера, реализующий этот процесс, показан в коде ниже.
Сервер кейлогинга
Рассмотрим приведенный код подробнее. Прежде всего, обратите внимание на то, что мы используем еще одну стороннюю библиотеку, gorilla/websocket, с помощью которой обрабатываем коммуникации WebSocket. Это полноценный мощный пакет, упрощающий процесс разработки, наподобие уже знакомого вам gorilla/mux. Не забудьте сначала выполнить из терминала go get github.com/gorilla/websocket.
Затем переходим к определению нескольких переменных. Мы создаем экземпляр websocket.Upgrader, который будет добавлять в белый список каждый источник. Допуск всех источников обычно считается плохой практикой в плане безопасности, но здесь мы не придаем этому значения, поскольку работаем с тестовым экземпляром, который будем запускать на локальных рабочих станциях. Для использования в реальных вредоносных действиях источник нужно будет ограничить конкретным значением.
В функции init(), выполняющейся автоматически перед main(), мы определяем аргументы командной строки и пытаемся спарсить шаблон Go, расположенный в файле logger.js. Обратите внимание на то, что мы вызываем template.ParseFiles("logger.js"). Проверяем ответ, чтобы убедиться в успешном парсинге файла. Если все правильно, то спарсенный шаблон будет сохранен в переменной jsTemplate.
На данный момент мы еще не предоставляли контекстуальных данных шаблону и не выполняли его. Это произойдет чуть позже. Сначала идет определение функции serveWS(), которая будет использоваться для обработки WebSocket-коммуникаций. С помощью вызова upgrader.Upgrade(http.ResponseWriter, *http.Request, http.Header) мы создаем экземпляр websocket.Conn. Метод Upgrade() расширяет HTTP-соединение для использования протокола WebSocket. Это означает, что любой обрабатываемый данной функцией запрос будет расширен для использования WebSocket. Мы взаимодействуем с этим соединением внутри бесконечного цикла for, вызывая для чтения входящих сообщений conn.ReadMessage(). Если JS-код будет работать должным образом, то сообщения должны состоять из перехваченных символов нажатых клавиш. Эти сообщения и удаленный IP-адрес клиента записываются в stdout.
Мы разобрали самую сложную часть пазла создания обработчика WebSocket. Далее идет создание еще одной функции-обработчика serveFile(). Она будет извлекать и возвращать содержимое JS-шаблона, заполненного включенными контекстными данными. Для этого мы установим заголовок Content-Type как application/javascript. Это сообщит подключающимся браузерам, что содержимое тела HTTP-ответа должно рассматриваться как JavaScript. Во второй и последней строке обработчика выполняется вызов jsTemplate.Execute(w, wsAddr). Помните, как мы парсили logger.js в функции init() во время бутстрэппинга сервера? Результат был сохранен в переменной jsTemplate. Данная строка кода обрабатывает тот самый шаблон. Мы передаем ей io.Writer (в нашем случае используется w, http.ResponseWriter) и контекстные данные типа interface{}. Тип interface{} означает, что можно передать любой тип переменной, будь то строка, структура или что-то другое. В данном случае мы передаем строковую переменную wsAddr. Если вернуться назад к функции init(), то можно заметить, что эта переменная содержит адрес WebSocket-сервера и устанавливается через аргумент командной строки. Говоря кратко, она заполняет шаблон данными и записывает его как HTTP-ответ. Довольно хитро!
Мы реализовали функции-обработчики serveFile() и serveWS(). Теперь нужно только настроить маршрутизатор для сопоставления шаблонов, чтобы передавать выполнение правильному обработчику. Как и ранее, это делается в функции main(). Первый обработчик сопоставляется с URL-шаблоном /ws, выполняя функцию
serveWS для апгрейда и обработки WebSocket-соединений. Второй маршрут сопоставляется с шаблоном /k.js, выполняя функцию serveFile(). Таким образом сервер передает отрисованный JS-шаблон клиенту.
Теперь давайте этот сервер запустим. Если открыть HTML-файл, то мы увидим сообщение connection established. Оно регистрируется, так как JS-файл был успешно отрисован в браузере и запросил WebSocket-соединение. Если ввести учетные данные в элементы формы, то они будут выведены в stdout на сервере:
У нас все получилось! На выводе мы видим список всех нажатых при заполнении формы клавиш. В данном случае это набор пользовательских учетных данных. Если у вас возникли сложности, убедитесь, что передаете в качестве аргументов командной строки точные адреса. Кроме того, сам HTML-файл может нуждаться в доработке, если вы вызываете k.js с сервера, чей адрес отличен от localhost:8080.
Приведенный код можно улучшить несколькими способами. В одном из них можно логировать вывод не в терминал, а в файл или другое постоянное хранилище. Это снизит вероятность потери данных по причине закрытия окна терминала или перезапуска сервера. К тому же если ваш кейлогер регистрирует нажатия клавиш одновременно на нескольких клиентах, данные в выводе могут смешиваться, усложняя сбор и анализ учетных данных разных пользователей. Этого можно избежать, определив более эффективный формат представления, который, например, группирует нажатые клавиши по уникальному клиенту/порту.
На этом знакомство с техниками сбора учетных данных закончено. Последним мы рассмотрим мультиплексирование HTTP‑соединений C2.
Мультиплексирование C2-соединений:
В последнем разделе главы мы покажем, как мультиплексировать HTTP-соединения Meterpreter к различным бэкенд-серверам управления. Meterpreter — это популярный гибкий инструмент исполнения команд (C2), являющийся частью фреймворка Metasploit. Здесь мы не будем излишне углубляться в подробности Metasploit или Meterpreter. Если вы с ними прежде не сталкивались, рекомендуем ознакомиться с одним из множества руководств или сайтов документации.
В этом же разделе поговорим о создании обратного HTTP-прокси в Go, который позволит динамически перенаправлять входящие сессии Meterpreter на основе HTTP-заголовка Host. Именно так и работает виртуальный хостинг сайтов. Тем не менее вместо предоставления различных локальных файлов и каталогов мы будем проксировать соединение на разных слушателей Meterpreter. Это будет интересным случаем применения по нескольким причинам.
Во-первых, прокси-сервер выступает в роли переадресатора, позволяя раскрывать только имя домена и IP-адрес, но не слушателей Meterpreter. Если переадресатор вдруг попадет в черный список, можно будет легко переместить его, не перемещая C2-сервер. Во-вторых, вы можете расширить приведенные здесь концепции для выполнения доменного фронтирования, техники задействования доверенных сторонних доменов (зачастую от облачных провайдеров) для обхода ограничивающего контроля исходящего трафика. Мы не будем разбирать здесь пример детально, но вам рекомендуем изучить эту тему подробнее, поскольку это очень мощная техника. И наконец, приведенный пример показывает, как можно совместно использовать одну комбинацию «хост/порт» в команде союзников, потенциально атакуя разные целевые организации. Поскольку порты 80 и 443 — это наиболее вероятные допустимые точки выхода, можно применять прокси-сервер для их прослушивания и перенаправления соединений на правильного слушателя.
Вот наш план. Мы настроим два отдельных обратных HTTP-слушателя Meterpreter. В этом примере они будут размещаться на виртуальной машине с IP-адресом 10.0.1.20, также вполне допускается их размещение на разных хостах. Мы привяжем этих слушателей к портам 10080 и 20080 соответственно. В реальном сценарии они могут выполняться где угодно, при условии что прокси-сервер сможет связаться с их портами. Убедитесь, что у вас установлен Metasploit (в Kali Linux он установлен по умолчанию), затем запустите слушателей:
При запуске слушателя мы передаем прокси-данные как значения LHOST и LPORT. Тем не менее устанавливаем продвинутые опции ReverseListener, BindAddress и ReverseListenerBindPort на действительный IP-адрес и порт, где должен запускаться слушатель. Это дает некоторую гибкость при использовании портов, в то же время позволяя явно идентифицировать прокси-сервер, которым в случае, например, настройки фронтирования домена может быть имя хоста.
Во втором экземпляре Metasploit мы делаем то же самое для запуска дополнительного слушателя на порте 20080. Единственное отличие здесь в привязке к другому порту:
Теперь создадим обратный прокси, исчерпывающий код которого приведен в коде ниже.
Мультиплексирование Meterpreter
В первую очередь выполняется импорт пакета net/http/httputil, который содержит вспомогательную функциональность для создания обратного прокси. Это избавит вас от необходимости делать все с нуля.
После импорта пакетов определяются две переменные, которые являются картами. Первая, hostProxy, будет служить для сопоставления имен хостов с URL-адресом слушателя Metasploit, на который их нужно направлять. Вспомните, что переадресацию мы делаем на основе заголовка Host, который ваш прокси-сервер получает в HTTP-запросе. Поддержание этого сопоставления — простой способ определения мест назначения.
Вторая переменная, proxies, также будет использовать в качестве значения ключей имена хостов. Тем не менее соответствующие им значения в карте являются экземплярами *httputil.ReverseProxy. То есть эти значения будут не строковыми представлениями места назначения, а фактическими экземплярами прокси-сервера, на которые можно делать перенаправление.
Обратите внимание: данную информацию мы кодируем жестко, и это не самый удачный способ управления конфигурацией и проксирования данных. В более оптимальной реализации информация сохранялась бы во внешнем файле конфигурации, но это упражнение мы оставим вам для самостоятельной проработки.
С помощью функции init() мы определяем сопоставления между именами доменов и целевыми экземплярами Metasploit. В этом случае будем перенаправлять все запросы со значением заголовка Host, равным attacker1.com, на http://10.0.1.20:10080, а со значением attacker2.com — на http://10.0.1.20:20080. Конечно же, пока мы не делаем реальное перенаправление, а просто создаем зачаточную конфигурацию. Обратите внимание на то, что адреса назначения соответствуют значениям ReverseListenerBindAddress и ReverseListenerBindPort, которые мы использовали для слушателей Meterpreter ранее.
Далее все в той же функции init() мы перебираем карту hostProxy, делая парсинг целевых адресов для создания экземпляров net.URL. Полученный результат задействуется в качестве ввода в вызове функции httputil.NewSingleHostReverseProxy (net.URL), которая является вспомогательной функцией, создающей обратный прокси из URL. Более того, тип httputil.ReverseProxy удовлетворяет требованиям интерфейса http.Handler, то есть создаваемые экземпляры прокси можно использовать как обработчики для маршрутизатора. Делается это с помощью функции main(). Сначала создается маршрутизатор, после чего осуществляется перебор всех экземпляров прокси. Напомним, что ключ — это имя хоста, а значение имеет тип httputil.ReverseProxy. Для каждой пары «ключ/значение» карты мы добавляем в маршрутизатор функцию сопоставления. Тип Route из набора Gorilla MUX содержит такую функцию под названием Host, которая получает имя хоста для сопоставления со значениями заголовка Host входящих запросов. Для каждого имени хоста, которое нужно проверить, мы указываем маршрутизатору использовать соответствующий прокси. Это на удивление простое решение того, что в противном случае оказалось бы сложной задачей.
В завершение происходит запуск сервера и его привязка к порту 80. Сохранитесь и запустите программу. Это нужно будет сделать от имени привилегированного пользователя, поскольку привязка выполняется к привилегированному порту.
На данный момент у нас запущены два обратных HTTP-слушателя Meterpreter, а также должен работать обратный прокси-сервер. Последний шаг — генерирование тестовой полезной нагрузки для проверки его итоговой работоспособности. Для этого мы задействуем msfvenom — инструмент генерирования полезной нагрузки, который также поставляется вместе с Metasploit. С его помощью создадим два исполняемых файла Windows:
Эти команды создадут два файла с названиями payload1.exe и payload2.exe. Обратите внимание на то, что единственное различие между ними помимо самого имени заключается в значениях HttpHostHeader. Это гарантирует, что итоговая полезная нагрузка отправляет свои HTTP-запросы с конкретным значением заголовка Host. Также стоит заметить, что значения LHOST и LPORT соответствуют информации нашего обратного прокси-сервера, а не слушателей Meterpreter. Отправьте эти исполняемые файлы в систему Windows или на виртуальную машину. При их выполнении должны устанавливаться две сессии: одна в слушателе, привязанном к порту 10080, вторая в слушателе, привязанном к порту 20080. Выглядеть они должны так:
Если с помощью tcpdump или Wireshark вы проверите трафик, предназначенный для порта 10080 или 20080, то должны увидеть, что обратный прокси-сервер является единственным хостом, коммуницирующим со слушателем Metasploit. Вы также можете убедиться, что заголовок Host соответствующим образом устанавливается на attacker1.com для слушателя на порте 10080 и на attacker2.com для слушателя на порте 20080.
Вот и все. Вы справились! Теперь пора поднять планку. Я советую вам в качестве дополнительного упражнения доработать код для использования поэтапной полезной нагрузки. Это будет сопряжено с дополнительными трудностями, так как потребуется добиться того, чтобы обе стадии правильно перенаправлялись через прокси. Затем попробуйте реализовать это с помощью HTTPS вместо небезопасного HTTP. Так вы сможете глубже разобраться в проксировании трафика для вредоносных целей и повысить его эффективность.
sn0int — фреймворк для сбора и полуавтоматической обработки информации о поддоменах, IP-адресах, скомпрометированных учетных записях, телефонных номерах и профилях в социальных сетях.
Помимо прочего, sn0int в настоящее время может:
Собирать поддомены из журналов прозрачности сертификатов и пассивного DNS.
Массовое разрешение собранных поддоменов и сканирование служб http или https.
Обогатить IP-адреса информацией asn и geoip.
Собирать электронные письма с серверов ключей pgp и whois.
Обнаруживать скомпрометированные логины при нарушениях
Находит чьи-либо профили в Интернете
Перечисляет локальные сети с помощью уникальных методов, таких как пассивный ARP.
Сбор информации о номерах телефонов
Сбор активность и изображения из профилей социальных сетей.
Базовая обработка изображений
sn0int во многом вдохновлен recon-ng и maltego, но остается более гибким. и имеет полностью открытый исходный код.
На вопрос журналиста, правда ли, что его зовут Роберт Бэнкс, художник отвечает: «Робби». Теперь мы можем услышать голос одной из самых дорогих арт-персон современности.
Стало на одну тайну меньше.
Кто не знает, Бэнкси — известный художник, известный своими перформансами и остросоциальными работами с черным юмором. В октябре 2018 года на аукционе Sothebyʼs его картина «Девочка с воздушным шаром» самоуничтожилась сразу после того, как ее купили за £1 млн.
Как выяснилось потом, в раму картины был встроен высокоточный шредер (смотрите видео от 2018 года). Покупателю не пришлось за нее платить и потом он смог перепродать работу Бэнкси за £18,5 млн на другом аукционе.
Каждый день в своем блоге Telegram я публикую интересные новости об интернет-культуре, разработках, технологиях, полезных сервисах и нейросетях!
Мастер Йода говорил: «И как всегда двое их, не больше и не меньше». Он, конечно же, говорил об отношениях «клиент — сервер», и поскольку вы являетесь мастером клиентов, то пришло время стать мастером серверов. В этом разделе с помощью того же пакета Go DNS мы напишем простой сервер и прокси. DNS-серверы можно использовать для нескольких вредоносных задач, включая туннелирование сетей с ограниченным доступом и совершение спуфинг-атак с помощью поддельных беспроводных точек доступа.
Для начала нужно настроить лабораторную среду. Она позволит вам симулировать реалистичные сценарии, не требуя наличия действительных доменов и использования дорогостоящей инфраструктуры. Но при желании вы без проблем можете зарегистрировать домены и применять реальный сервер.
Настройка лаборатории и знакомство с сервером
Лаборатория состоит из двух виртуальных машин (VM): Microsoft Windows VM, выступающей в роли клиента, и Ubuntu VM, действующей в качестве сервера.
В этом примере для каждой машины используются VMWare Workstation и сетевой мост. Допустимо применение частной виртуальной сети, но при этом необходимо убедиться, что обе машины принадлежат одной сети. Сервер будет выполнять два экземпляра Cobalt Strike Docker, собранных из официального образа Java Docker (Java — необходимое условие для Cobalt Strike). Снизу показано, как будет выглядеть лаборатория.
Настройка лабораторного стенда для создания DNS-сервера
Сначала нужно создать виртуальную машину Ubuntu (Ubuntu VM). Для этого мы используем дистрибутив 16.04.1 LTS. Никаких особых требований здесь нет, но VM необходимо настроить на использование не менее 4 Гбайт ОЗУ и двух CPU. Если есть, можно задействовать существующую VM или хост. Закончив с операционной системой, необходимо установить среду разработки Go (см. главу 1).
После создания Ubuntu VM займитесь установкой утилиты контейнера виртуализации Docker. В разделе этой главы, посвященном прокси, мы будем использовать Docker для запуска нескольких экземпляров Cobalt Strike. Для установки Docker выполните в терминале:
После этого повторно войдите в систему и убедитесь, что Docker установлен, выполнив следующую команду:
После установки Docker с помощью следующей команды скачайте образ Java:
Эта команда получит базовый образ Java Docker, не создавая контейнеры. Таким образом мы подготавливаемся к скорому выполнению сборок Cobalt Strike.
В завершение необходимо убедиться в том, что dnsmasq не запущен, потому что он слушает порт 53. В противном случае ваши DNS-серверы не смогут работать, так как они должны использовать именно этот порт. Если процесс dnsmasq запущен, завершите его по ID:
Теперь нужно создать виртуальную машину Windows (Windows VM). Опять же можно использовать существующую машину. Никаких особых настроек делать не требуется, достаточно минимальных. Когда система заработает, установите для DNS-сервера IP-адрес системы Ubuntu.
Чтобы протестировать настройку лабораторного стенда и перейти к написанию DNS-серверов, мы начнем с создания простого сервера, который возвращает только А-записи. В GOPATH системы Ubuntu создайте каталог github.com/blackhat-go/bhg/ch-5/a_server и файл для хранения кода main.go. Снизу будет показан весь код для создания простого DNS-сервера.
Написание DNS-сервера (/ch-5/a_server/main.go)
Код начинается с вызова HandleFunc(), он во многом напоминает пакет net/http. Первый аргумент функции является шаблоном запроса для сопоставления. Он станет применяться для указания DNS-серверам, какие запросы будут обрабатываться переданной функцией. Используя точку, мы сообщаем серверу, что предоставляемая во втором аргументе функция будет обрабатывать все запросы.
Следующий передаваемый в Handlefunc() аргумент — это функция, содержащая логику обработчика. Она получает два аргумента: ResponseWriter и сам запрос. Внутри обработчика сначала создается новое сообщение и устанавливается ответ. Затем создается ответ на каждый вопрос с помощью А-записи, которая реализует интерфейс RR. Эта часть будет различаться в зависимости от типа искомого вами ответа. Указатель на А-запись добавляется в поле Answer ответа с помощью append(). По завершении ответа его сообщение записывается вызывающему клиенту с помощью w.WriteMsg(). В конце для запуска сервера вызывается ListenAndServe(). Этот код интерпретирует все запросы в IP-адрес 127.0.0.1.
Запустив сервер, можно протестировать его с помощью dig. Убедитесь, что имя хоста, для которого выполняются запросы, разрешается в 127.0.0.1. Это будет означать, что все работает как надо:
Обратите внимание на то, что сервер нужно будет запускать с помощью sudo или через корневую учетную запись (root), потому что он прослушивает привилегированный порт 53. Если сервер не запускается, может потребоваться завершить dnsmasq.
Создание DNS-сервера и прокси:
DNS-туннелирование — это техника извлечения данных, дающая возможность установить C2-канал из сетей с контролем исходящего трафика. Используя авторитетный DNS-сервер, злоумышленник может проложить маршрут через внутренние DNS-серверы организации и выйти через интернет, причем ему не потребуется прямое подключение к собственной инфраструктуре. Несмотря на медлительность такой атаки, защититься от нее сложно. DNS-туннелирование выполняется с помощью ряда открытых и проприетарных полезных нагрузок, одной из которых является Beacon от Cobalt Strike. В текущем разделе мы напишем собственный DNS-сервер и прокси, а также научимся с помощью Cobalt Strike мультиплексировать полезные нагрузки C2 для DNS-туннелирования.
Настройка Cobalt Strike:
Если вам доводилось использовать этот инструмент, то вы наверняка замечали, что по умолчанию team-сервер прослушивает порт 53. В связи с этим и с тем, что советует документация, в системе должен быть запущен только один сервер, поддерживая соотношение один к одному. Это может стать проблемой для средних и больших команд. Например, если у вас есть 20 команд, реализующих наступательные мероприятия против 20 отдельных организаций, то поддержка 20 систем, способных выполнять team-сервер, может стать затруднительной. Эта проблема касается не только Cobalt Strike и DNS, но и других протоколов, включая полезные нагрузки HTTP, такие как Metasploit Meterpreter и Empire. Несмотря на то что можно установить слушатели на различные порты, есть большая вероятность выхода трафика через такие стандартные TCP-порты, как 80 и 443. Отсюда возникает логичный вопрос: как вы и другие команды можете совместно использовать один порт и делать перенаправление на разных слушателей? Ответом будет, конечно же, прокси-сервер. Пора вернуться в лабораторию.
ПРИМЕЧАНИЕ
В реальных сценариях противодействия вам потребуется иметь несколько уровней маневрирования, абстрагирования и переадресации для маскировки team-сервера. Это можно реализовать с помощью UDP- и TCP-переадресации через небольшие вспомогательные серверы, использующие разных хостинг-провайдеров. Основной team-сервер и прокси также могут работать на разных системах. В этом случае кластер коллективного сервера размещается в обширной системе с большим объемом ОЗУ и мощным CPU.
Давайте запустим два экземпляра коллективного сервера в двух контейнерах Docker. Это позволит им прослушивать порт 53, а также даст каждому серверу возможность использовать собственную систему и, следовательно, собственный стек IP. Для сопоставления UDP-портов с хостом из контейнера мы будем применять встроенный в Docker сетевой механизм. Для начала скачайте пробную версию Cobalt Strike с https://trial.cobaltstrike.com/1. Для этого нужно создать пробную учетную запись, получив возможность скачать tar-архив. Теперь можно запускать team-серверы.
Для запуска первого контейнера выполните в терминале следующий код:
Эта команда выполняет несколько действий. С ее помощью вы сообщаете Docker о необходимости удаления контейнера после выхода, а также о том, что после запуска будете с ним взаимодействовать. Далее идет сопоставление порта 2020 системы хоста с портом 53 в контейнере и порта 50051 с портом 50050. Затем каталог, содержащий архив Cobalt Strike, сопоставляется с каталогом данных в контейнере. Здесь можно указать любое имя каталога, и Docker без проблем его создаст. В завершение предоставляется образ, который нужно использовать (в данном случае Java), а также команда для выполнения при запуске.
Оказавшись внутри контейнера, запустите team-сервер с помощью следующих команд:
Указываемый IP-адрес должен соответствовать текущей виртуальной машине, а не адресу контейнера.
Далее откройте новое окно терминала в хосте Ubuntu и перейдите в каталог с архивом Cobalt Strike. Выполните следующие команды для установки Java и запуска клиента Cobalt Strike:
Должен запуститься Cobalt Strike GUI. После сообщения о пробной версии измените порт team-сервера на 50051, а также установите соответствующие имя пользователя и пароль.
Вы успешно подключились к серверу, полностью работающему в Docker-контейнере. Теперь повторим тот же процесс для запуска второго сервера. На этот раз будем сопоставлять другие порты. При этом вполне логичным будет увеличить значение порта на единицу. Выполните следующую команду в новом окне терминала, чтобы запустить новый контейнер и прослушивать порты 2021 и 50052:
Из клиента Cobalt Strike создайте новое подключение, выбрав Cobalt StrikeNew Connection, изменив порт на 50052 и нажав Connect. Подключившись, вы должны увидеть в нижней части консоли две вкладки, с помощью которых можно переключаться между серверами.
Успешно завершив подключение к двум коллективным серверам, пора запустить два DNS-слушателя. Для создания слушателя выберите в меню пункт Configure Listeners. Он обозначен значком с изображением наушников. Из этого меню выберите Add, чтобы вызвать окно NewListener. Введите в нем следующее:
Name: DNS 1;
Payload: windows/beacon_dns/reverse_dns_txt;
Host: <IP address of host>;
Port: 0.
В этом примере установлен порт 80, но наша полезная нагрузка DNS по-прежнему использует порт 53. Это нормально. Порт 80 специально задействуется для гибридных полезных нагрузок. На скрине снизу показаны окно New Listener и информация, которую необходимо ввести.
Добавление слушателя
Далее, как показано на скрине снизу, вам будет предложено указать домены, которые будут использоваться для установки маячков.
Добавление домена DNS-маячка
Введите в качестве DNS-маячка домен attacker1.com. Он должен соответствовать имени домена, куда полезная нагрузка будет отправлять сигналы. Далее отобразится сообщение о запуске нового слушателя. Повторите этот процесс на другом team-сервере, используя значения DNS2 и attacker2.com. Прежде чем задействовать этих двух слушателей, нужно написать промежуточный сервер, который будет проверять DNS-сообщения и соответствующим образом их перенаправлять. Это и будет ваш прокси.
Создание DNS-прокси:
Используемый вами на протяжении этой главы DNS-пакет облегчает написание функции-посредника — вы уже работали с некоторыми такими функциями в предыдущих разделах. Наш прокси должен уметь:
создавать функцию-обработчик для приема входящего запроса;
проверять в этом запросе вопрос и извлекать имя домена;
определять вышестоящий DNS-сервер, соответствующий этому имени домена;
обмениваться вопросом с этим вышестоящим DNS-сервером и писать ответ клиенту.
В функции можно прописать обработку attacker1.com и attacker2.com как статических значений, но такой вариант не удастся поддерживать. Вместо этого следует искать записи во внешнем для программы источнике, например в базе данных или файле конфигурации. В приведенном далее коде это реализуется с помощью формата domain.server, который перечисляет входящие домен и вышестоящий сервер через точку. Чтобы запустить программу, создайте функцию для парсинга файла, содержащего записи в этом формате. Запишите код снизу в новый файл main.go.
Написание DNS-прокси (/ch-5/dns_proxy/main.go)
В этом коде сначала мы определяем функцию, которая парсит файл с информацией о конфигурации и возвращает map[string]string. Эта карта будет использоваться для поиска входящего домена и извлечения вышестоящего сервера.
Введите в терминале первую команду приведенного далее кода, чтобы записать следующую за echo строку в файл proxy.config. Затем нужно скомпилировать и запустить dns_proxy.go.
Что мы здесь видим? Вывод представляет сопоставление между именами доменов team-серверов и портом, который прослушивает DNS-сервер Cobalt Strike. Напомним, что в двух отдельных контейнерах Docker мы сопоставили порты 2020 и 2021 с портом 53. Здесь же использовали быстрый и грязный путь создания основной конфигурации для инструмента, чтобы вам не пришлось хранить его в базе данных или другом постоянном хранилище.
Определив карты записей, можно написать обработчик. Давайте уточним код, добавив в функцию main() приведенный далее фрагмент, который должен следовать за парсингом файла конфигурации:
Код начинается с вызова HandleFunc() с точкой для обработки всех входящих запросов, а также определения анонимной функции, то есть функции, которую мы не собираемся использовать повторно (у нее нет имени). Это удобная структура на случай, когда вам не нужно повторно задействовать некий блок кода. Если же ее применение в нескольких местах все же подразумевается, то необходимо объявлять и вызывать ее как именованную функцию. Далее идет проверка среза входящих вопросов, гарантирующая, что все вопросы переданы. Если же нет, происходит вызов HandleFailed() и возврат для раннего выхода из функции. Такой шаблон используется во всем обработчике. Если присутствует хотя бы один вопрос, можно безопасно получить запрашиваемое имя из первого вопроса. Разделять имя точкой нужно для извлечения имени домена. В результате этого не должно получаться значение меньше 1, но на всякий случай стоит проверить. Хвост среза — элементы в его конце — можно получить, применив в срезе оператор slice. Теперь нужно извлечь вышестоящий сервер из карты записей.
При извлечении значения из карты могут возвращаться одна или две переменные. Если ключ (в нашем случае имя домена) в карте присутствует, будет возвращено соответствующее значение. Если же домен отсутствует, возвращается пустая строка. Можно проверять, является ли возвращенное значение пустой строкой, но это окажется неэффективным, когда вы начнете работать с более сложными типами. Вместо этого мы задаем две переменные: первая — это значение ключа, а вторая — логическое значение, возвращающее true, если ключ найден. Убедившись в совпадении, мы обмениваемся запросом с вышестоящим сервером. Здесь мы просто подтверждаем, что имя домена, для которого получен запрос, настроено в постоянном хранилище. Далее записывается ответ вышестоящего сервера клиенту. Определив функцию-обработчик, мы запускаем сервер. В завершение можно собирать и запускать прокси.
После запуска мы протестируем его с помощью двух слушателей Cobalt Strike. Для этого сначала нужно создать два самостоятельных (stageless) исполняемых файла. В верхнем меню CobaltStrike нажмите значок с изображением шестеренки и измените формат вывода на Windows Exe. Повторите процесс из каждого team-сервера. Скопируйте эти исполняемые файлы в Windows VM и запустите. DNS-сервер Windows VM должен иметь IP-адрес вашего Linux-хоста. В противном случае тест не сработает.
На это уйдет какое-то время, но в итоге вы должны увидеть, что в каждом team-сервере установлен маячок. Миссия выполнена!
Финальные штрихи:
Все отлично, но когда вам нужно изменить IP-адрес team-сервера или переадресатора, а также в случаях добавления записи, потребуется перезапускать сервер. Маячки, скорее всего, переживут этот процесс, но зачем рисковать, если есть лучшее решение? Можно использовать сигналы процесса, сообщая выполняющейся программе о необходимости перезагрузки файла конфигурации. Об этом трюке я впервые узнал от Мэтта Холта (Matt Holt), который реализовал его на прекрасном Caddy Server. На скрине снизу показана вся программа с уже добавленной логикой отправки сигнала процесса.
Здесь есть несколько дополнений. Поскольку программа будет изменять карту, которая может в это время использоваться параллельными горутинами, необходимо применить мьютекс для контроля доступа. Мьютекс предотвращает одновременное выполнение чувствительных блоков кода, позволяя закрывать и открывать доступ. В этом случае мы применяем RWMutex, давая любой горутине возможность производить чтение, не блокируя другие горутины, но запрещая им доступ в процессе записи. Если же реализовать горутины без мьютекса на используемых ресурсах, то возникнет чередование, что может привести к состоянию гонки и даже худшим последствиям.
Перед обращением к карте в обработчике происходит вызов RLock для считывания значения в match. По завершении чтения вызывается RUnlock, освобождая карту для следующей горутины. В анонимной функции, выполняющейся в новой горутине, мы начинаем процесс прослушивания сигнала. Это делается с помощью канала типа os.Signal, передаваемого в вызове к signal.Notify() вместе с фактическим сигналом, получаемым каналом SIGUSR1, который сам является сигналом, зарезервированным для различных целей. В цикле перебора этих сигналов с помощью инструкции match определяется тип полученного сигнала.
Мы настроили мониторинг только одного сигнала, но в дальнейшем это можно изменить, так что данный шаблон окажется универсальным. В завершение перед перезагрузкой текущей конфигурации используется Lock() для блокирования всех горутин, которые могут попробовать произвести чтение из записей карты. Для продолжения выполнения применяется Unlock().
Давайте протестируем программу, запустив прокси и создав новый слушатель в существующем team-сервере. Используйте домен attacker3.com. При запущенном прокси измените файл proxy.config, добавив новую строку, направляющую домен на слушатель. Сигнализировать процессу о необходимости перезагрузки конфигурации можно с помощью kill, но сначала используйте ps и grep для определения его ID процесса:
Прокси должен перезагрузиться. Проверьте это, создав и выполнив новый самостоятельный исполняемый файл. Теперь прокси должен быть работоспособен и готов к использованию.
Прежде чем начать знакомство с более сложными программами, рассмотрим ряд опций, доступных для клиентских операций. Встроенный в Go пакет net предлагает обширную функциональность и поддерживает большинство, если не все типы записей. Преимущество этого пакета — в простоте его API. Например, LookupAddr(addr string) возвращает список имен хостов для заданного IP-адреса. Недостаток же его заключается в невозможности указывать целевой сервер. Вместо этого пакет использует настроенный в операционной системе механизм распознавания. К недостаткам можно отнести также отсутствие возможности выполнения углубленного анализа результатов.
Для обхода этих недочетов мы задействуем отличный сторонний пакет Go DNS, написанный Миком Гибеном (Miek Gieben). Предпочесть этот DNS-пакет всем прочим стоит из-за его высокой модульности и грамотно написанного и протестированного кода. Вот команда для его установки:
Установив пакет, вы будете готовы к проработке последующих примеров кода. Начнем с выполнения поиска А-записей для получения IP-адресов из имен хостов.
Извлечение А-записей:
Сперва познакомимся с поиском для полностью уточненного имени домена (fully qualified domain name, FQDN), которое указывает точное расположение хоста в иерархии DNS. Затем попробуем интерпретировать это FQDN в IP-адрес с помощью DNS-записи А. Эта запись связывает имя домена с IP-адресом. (Все листинги кода находятся в корневом каталоге /exist репозитория GitHub https://github.com/blackhat-go/bhg/.)
Код на языке Golang
Сначала создается msg , после чего идет вызов fqdn(string) для преобразования этого домена в FQDN, которым можно обменяться с DNS-сервером . Далее нужно изменить внутреннее состояние Msg на вызов SetQuestion(string, uint16)
с помощью значения TypeA, указывающего, что нужно искать А-запись. (В пакете она определена как const. Другие поддерживаемые значения можно найти в документации.) В завершение мы помещаем вызов Exchange(*Msg, string) , чтобы отправить сообщение на предоставленный адрес сервера, в данном случае являющегося DNS-сервером, обслуживаемым Google.
Нетрудно заметить, что данный код не особо полезен. Несмотря на то что мы отправляем запрос к DNS-серверу и запрашиваем А-запись, ответ мы не обрабатываем, то есть с результатом ничего не делаем. Но прежде чем реализовать нужную функциональность в Go, давайте рассмотрим, как выглядит ответ DNS, чтобы лучше понять этот протокол и различные типы запросов.
Перед выполнением программы которая написана выше запустите анализатор пакетов, например Wireshark или tcpdump, чтобы просмотреть трафик. Вот пример возможного использования tcpdump на хосте Linux:
$ sudo tcpdump -i eth0 -n udp port 53
В отдельном окне терминала скомпилируйте и выполните программу:
$ go run main.go
После выполнения кода в выходных данных перехвата пакетов должны отобразиться подключение к 8.8.8.8 через UDP 53, а также детали DNS-протокола:
Две из получаемых при перехвате пакетов строчек нуждаются в дополнительном пояснении. Сначала запрос отправляется с 192.168.7.51 к 8.8.8.8 с помощью UDP 53, при этом происходит запрос А-записи. В ответе от DNS-сервера Google 8.8.8.8 содержится интерпретированный из имени домена IP-адрес 104.131.56.170.
С помощью анализатора пакетов можно преобразовать имя домена stacktitan.comв IP-адрес. Теперь посмотрим, как извлечь эту информацию, используя Go.
Отработка ответов от структуры Msg:
В качестве значения Exchange(*Msg, string) возвращает (*Msg, error). Возврат типа error имеет смысл и является стандартным для идиом Go, но почему в ответе приходит также изначально отправленная *Msg? Чтобы это понять, нужно взглянуть на определение этой struct в исходном коде:
Код на языке Golang
Как видите, Msgstruct содержит как вопросы (question), так и ответы (answer). Это позволяет объединять все DNS-вопросы и ответы на них в единую унифицированную структуру. Тип Msg располагает различными методами, упрощающими работу с данными. Например, срез Question изменяется с помощью метода setQuestion(). Это срез можно изменять напрямую, используя append(), и получать тот же результат. Срез Answer содержит ответ на запросы и имеет тип RR. Ниже будет показано, как эти ответы обрабатывать.
Обработка DNS-ответов:
Пример начинается с сохранения возвращенных от Exchange значений и их проверки на наличие ошибок. Если ошибка обнаружена, вызывается panic() для остановки программы. Функция panic() позволяет быстро просмотреть трассировку стека и определить место возникновения ошибки. Далее проверяется длина среза Answer. Если она меньше 1, это означает, что записей нет, и происходит возврат — бывают случаи, когда имя домена не может быть интерпретировано.
Тип RR является интерфейсом, имеющим всего два определенных метода, ни один из которых не дает доступа к IP-адресу, хранящемуся в ответе. Для доступа к этим адресам нужно применить утверждение типа, чтобы создать экземпляр данных в качестве нужного типа.
Сначала выполняем перебор ответов. Далее применяем в ответе утверждение типа, чтобы гарантировать работу с типом *dns.A. При выполнении этого действия можно извлечь два значения: данные в виде утвержденного типа и bool, отражающее успешность утверждения. После проверки успешности утверждения происходит вывод IP, сохраненного в a.A. Несмотря на тип net.IP, он реализует метод String(), поэтому его можно легко вывести на экран.
Поработайте с этим кодом, изменяя DNS-запрос и обмен (exchange) для поиска дополнительных записей. Утверждение типа может оказаться для вас незнакомым, но по своему принципу оно аналогично приведению типов в других языках.
Перечисление поддоменов:
Теперь, научившись использовать Go в качестве DNS-клиента, вы можете создавать полезные инструменты. В этом разделе мы создадим утилиту подбора поддоменов. Подбор поддоменов цели и других DNS-записей — основополагающий шаг в процессе разведки, так как чем больше поддоменов вам известно, тем обширнее поле атаки. Наша утилита будет угадывать их на основе передаваемого списка слов (файла словаря).
Используя DNS, можно отправлять запросы настолько быстро, насколько быстро система сможет обрабатывать пакеты данных. Узким местом здесь станут не язык или среда выполнения, а сервер назначения. При этом, как и в предыдущих главах, будет важно управление многопоточностью программы.
Сначала нужно создать в GOPATH каталог под названием subdomain_guesser, а затем файл main.go. После этого в начале создания нового инструмента необходимо решить, какие аргументы эта программа будет получать. В данном случае это будет несколько аргументов, включая целевой домен, имя файла, содержащего поддомены для подбора, используемый DNS-сервер, а также количество запускаемых воркеров. В Go для парсинга опций командной строки есть полезный пакет flag, который мы будем применять для обработки аргументов командной строки. Несмотря на то что мы используем этот пакет не во всех примерах кода, в данном случае он служит для демонстрации более надежного и изящного парсинга аргументов. Код этого процесса будет приведен ниже.
Создание программы подбора поддоменов на языке Golang
В начале строка кода, объявляющая переменную flDomain, получает аргумент String и объявляет пустое строковое значение для того, что будет парситься как опция domain. Следующая связанная строка — это объявление переменной flWorkerCount. Здесь в качестве опции командной строки c нужно предоставить значение Integer. В данном случае мы устанавливаем 100 воркеров. Но это значение можно счесть консервативным, так что в процессе тестирования смело экспериментируйте с увеличением их числа. В завершение вызов flag.Parse() заполняет переменные, задействуя предоставленный пользователем ввод.
ПРИМЕЧАНИЕ:
Вы могли обратить внимание на то, что этот пример идет вразрез с правилами Unix в том, что определяет необязательные аргументы, которые на деле являются обязательными. Можете свободно использовать здесь os.Args. Просто нам быстрее и удобнее поручить всю работу пакету flag.
При сборке данной программы должна возникнуть ошибка, указывающая на неиспользованные переменные. Добавьте приведенный далее код сразу после вызова flag.Parse(). Это дополнение выводит в stdout переменные наряду с кодом, гарантируя передачу пользователем -domain и -wordlist:
Чтобы ваш инструмент сообщал, какие имена оказались интерпретируемыми, указывая при этом соответствующие им IP-адреса, нужно создать для хранения этой информации тип struct. Определите его над функцией main():
Для этого инструмента вы будете запрашивать два основных типа записей — А и CNAME. Каждый запрос будет выполняться в отдельной функции. Стоит создавать эти функции максимально небольшими и поручать каждой выполнение только одной задачи. Такой стиль разработки позволит в дальнейшем писать менее объемные тесты.
Запрос записей A и CNAME:
Для выполнения запросов мы создадим две функции: одну для А-записей, вторую для записей CNAME. Они обе будут получать FQDN в качестве первого аргумента и адрес DNS-сервера в качестве второго. Каждая из них должна возвращать срез строк и ошибку. Добавьте эти функции в код, который начали определять, расположив вне области main():
Этот код должен показаться вам знакомым, так как он практически идентичен коду, который мы писали в самом начале главы. Первая функция, lookupA, возвращает список IP-адресов, а lookupCNAME возвращает список имен хостов.
Записи CNAME (канонические имена) сопоставляют одно FQDN с другим, которое служит псевдонимом для первого. Предположим, что владелец организации example.com хочет разместить WordPress-сайт с помощью сервиса хостинга WordPress. У этого сервиса могут быть сотни IP-адресов для балансировки всех пользовательских сайтов, в связи с чем предоставить IP для отдельного сайта просто невозможно. Вместо этого данный хостинг может предоставить каноническое имя (CNAME), на которое и будет ссылаться example.com. В итоге адрес www.example.com получит CNAME, указывающее на somewhere.hostingcompany.org, которое, в свою очередь, будет иметь А-запись, указывающую на IP-адрес. Это позволит владельцу example.com разместить свой сайт на сервере, для которого у него нет IP-данных.
Зачастую это означает, что вам нужно проследить целый хвост из канонических имен, чтобы в итоге добраться до действительной А-записи. Мы говорим хвост, потому что из подобных имен может выстраиваться бесконечная цепочка. Добавьте приведенный далее код функции в область за пределами функции main(), чтобы понаблюдать, как использовать череду CNAMES для нахождения А-записи:
Сначала определяется срез для хранения результатов. Далее создается копия FQDN, переданного в качестве первого аргумента. В итоге вы не только не теряете исходный угаданный FQDN, но и можете задействовать его в первой попытке запроса. Начав бесконечный цикл, мы пробуем получить CNAME для этого FQDN. В случае отсутствия ошибок и возвращения не менее одного CNAME устанавливаем cfqdn равным этому возвращенному CNAME, используя continue для возврата к началу цикла. Данный процесс позволяет проследить череду CNAME до возникновения сбоя. Последний будет означать, что конец цепочки достигнут и можно искать А-записи. Но если возникнет ошибка, означающая, что при поиске записи возникли проблемы, то выход из цикла произойдет раньше. В случае обнаружения действительных А-записей каждый возвращенный IP-адрес добавляется в срез результатов, а цикл прерывается. В завершение results возвращается вызывающему.
Наша связанная с интерпретацией имен логика выглядит гладко, однако мы не учли производительность. Давайте совместим этот пример с горутинами, добавив в него многопоточность.
Переход к воркер-функции:
Мы создадим пул горутин, которые будут передавать работу воркер-функции, выполняющей единицу работы. Для распределения работы и сбора ее результатов задействуем каналы. Напомним, что нечто подобное мы уже делали в главе 2, когда создавали многопоточный сканер портов.
Продолжим расширять код Создание программы подбора поддоменов. Сначала создадим функцию worker(), разместив ее вне области функции main(). Она будет получать три аргумента каналов: канал для воркера, чтобы он сигнализировал о своем закрытии, канал доменов, в которых нужно получать работу, и канал для отправки результатов. Этой функции потребуется заключительный строковый аргумент для указания используемого DNS-сервера. Далее приведен пример кода для функции worker():
Прежде чем вводить функцию worker(), определим тип empty для отслеживания завершения выполнения воркера. Это будет структура без полей. Мы задействуем пустую struct, так как она имеет размер 0 байт и практически не создаст нагрузку при использовании. Далее в функции worker() происходит перебор канала доменов, используемый для передачи FQDN. После получения ответа от функции lookup() и проверки наличия не менее одного результата мы отправляем его в канал gather, который собирает все результаты обратно в main(). После того как канал закрывается и цикл совершает выход, структура empty отправляет в канал tracker сигнал вызывающему о завершении всей работы. Отправка пустой struct в канал отслеживания — это важный последний шаг. Если этого не сделать, возникнет состояние гонки, так как вызывающий компонент может выйти до получения каналом gather результатов.
Поскольку вся необходимая структура теперь настроена, можно переключиться обратно на main() и закончить программу, которую мы начали писать в Создание программы подбора поддоменов.
Определите переменные, которые будут содержать результаты и каналы, передаваемые в worker(), после чего добавьте в main() следующий код:
Создайте канал fqdns как буферизованный на основе предоставленного пользователем количества воркеров. Это позволит воркерам запускаться быстрее, поскольку канал сможет вместить больше одного сообщения до блокировки отправителя.
Создание сканера с помощью bufio:
Далее откройте файл, предоставленный пользователем в качестве списка слов, и создайте в нем новый scanner с помощью пакета bufio. Добавьте в main() код
Если возвращаемая ошибка не равна nil, используется встроенная функция panic(). При написании пакета или программы для применения другими людьми следует постараться представить эту информацию более ясно.
Мы будем применять новый scanner для захвата строки текста из переданного списка слов и создания FQDN путем совмещения этого текста с предоставленным пользователем доменом. Результат будет отправляться в канал fqdns. Но сначала нужно запустить воркеры, так как порядок важен. Если отправить работу в канал fqdns, не запустив их, этот буферизованный канал в итоге заполнится и функции-производители будут заблокированы. В main() нужно добавить приведенный далее код, чья задача — запускать горутины воркеров, читать вводный файл и отправлять работу в канал fqdns.
Создание воркеров с помощью этого паттерна похоже на то, что мы уже делали при построении многопоточного сканера портов: задействовали цикл for до момента достижения числа, переданного пользователем. Для захвата каждой строки в цикле используется scanner.Scan(). Этот цикл заканчивается, когда в файле не остается строк для считывания. Для получения строкового представления текста из отсканированной строки мы применяем scanner.Text().
Работа запущена! Отвлекитесь на секунду и ощутите свое величие. Прежде чем читать следующий код, подумайте, где вы находитесь в программе и что уже успели сделать за время чтения книги. Попробуйте самостоятельно закончить эту программу и затем перейти к следующему разделу, где мы поясним ее оставшуюся часть.
Сбор и отображение результатов
Проработку последней части мы начнем с запуска анонимной горутины, которая будет собирать результаты воркеров. Добавьте в main() следующее:
Перебирая канал gather, мы добавляем полученные результаты в срез results. Поскольку мы добавляем срез в другой срез, нужно использовать синтаксис … . После закрытия канала gather и завершения перебора, как и прежде, происходит отправка пустой struct в канал отслеживания. Это делается для предотвращения состояния гонки на случай, если append() не завершится к моменту итогового предоставления результатов пользователю.
Остается только закрыть каналы и представить результаты. Для этого добавьте следующий код в конец main():
Первым можно закрыть канал fqdns, так как мы уже отправили по нему всю работу. Далее нужно выполнить получение результатов в канале tracker по одному разу для каждого воркера, что позволит им обозначить свое полное завершение. После этого можно закрыть канал gather, потому что результатов для получения не остается. В завершение нужно выполнить еще одно получение результатов на канале tracker, чтобы позволить горутине окончательно завершиться.
Эти результаты пользователю еще не представлены. Нужно это исправить. При желании можно просто перебрать срез results и вывести поля Hostname и IPAddress, используя fmt.Printf(). Тем не менее мы предпочитаем задействовать для представления данных один из нескольких прекрасных пакетов Go, а именно tabwriter. Он позволяет выводить данные в красивых ровных столбцах, разбитых на вкладки. Для его применения добавьте в конец main() следующий код:
На этом наша программа для подбора поддоменов готова. Теперь вы можете собрать и запустить этот инструмент. Опробуйте его на списках слов или словарях из открытых репозиториев (можете найти множество через Google). Поэкспериментируйте с количеством воркеров. Вы можете заметить, что при слишком быстрой обработке результаты получаются неоднозначные. Вот пример выполнения с использованием ста воркеров:
Вы увидите, что вывод показывает несколько FQDN и их IP-адреса. Мы смогли угадать значения поддоменов для каждого результата на основе списка слов, переданного в качестве вводного файла.
Теперь, когда вы создали собственный инструмент для подбора поддоменов и научились интерпретировать имена хостов в IP-адреса для перечисления разных DNS-записей, можно переходить к написанию собственного DNS-сервера и прокси.
По состоянию на начало 2023 года в интернете существует более двух миллиардов сайтов. Если ранжировать эти сайты в соответствии с их трафиком, то можно получить представленную ниже картину.
На первом месте в списке самых посещаемых сайтов в мире находится Google. Выполняя более 3,5 миллиардов поисковых запросов в день, Google укрепил свои позиции в качестве основного источника информации в интернете. Но на этом доминирование Google не заканчивается. Компании также принадлежит YouTube, второй по популярности сайт в мире. Вместе Google и YouTube имеют больше трафика, чем следующие 48 сайтов вместе взятые.
Но Google и YouTube — не единственные крупные игроки в интернете. Другие сайты в топ-50 рейтинга включают гигантов социальных сетей Facebook, Instagram и TikTok. В частности, в последние годы популярность TikTok резко возросла, и сейчас это одна из самых популярных социальных сетей в мире.
Также стоит отметить, что в топ-10 вошли поисковики Китая и России — Baidu и Yandex.
Всем привет, суть такая две сети пытаюсь объединить в одну, везде статические айпи адреса от провайдеров. Роутер сервер имеет сеть вида 192.168.1.0, роутер клиент 192.168.0.0. Сервер работает, клиент подключается, со стороны сервера вижу только зашифрованные виртуальные клиенты, которые ни как не определяются. Со стороны клиента вообще ничего не вижу.
Настройки сервера и клиента такие + лог со стороны клиента. Подскажите пожалуйста, что я сделал не правильно?)