Conti Ransomware malware leak WITH LOCKER
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.
 

258 lines
14 KiB

ПРАВИЛА РАЗРАБОТКИ МОДУЛЕЙ
Модуль представляет из себя DLL, которая будет работать в левом произвольном процессе.
На 64 битной системе будет 64-битный модуль, а на 32 битной системе 32-битный модуль.
Модуль – это не просто программа на С++. У модуля есть некоторые ограничения в связи с контекстом, в котором он работает:
1. Нужно минимизировать использование Visual C++ Runtime. Функции malloc, memcpy, str* - следует избегать. В WinAPI уже есть все необходимое – вместо malloc – HeapAlloc, вместо memcpy – CopyMemory, вместо strstr – StrStr (shlwapi.h/.lib).
1.1. STL в общем случае следует избегать. ТОЧНО не работает std::mutex. Остальное - на ваш страх и риск!
1.2. Некоторые фичи STL дают дополнительный пролог к коду. Для std::mutex он весит 200к. Его не видно в вашем коде, а он есть, и будет вести себя странно в контексте выполнения!
1.3. Несоблюдение пунктов 1 и 1.1 может быть причиной ОЧЕНЬ странного поведения.
2. Линковка рантайма и сторонних библиотек к модулю должна быть статической (флаг /MT).
3. Если используются сторонние библиотеки, они должны позволять заменять функции управления памятью malloc/realloc/free (вместо них вы туда подсунете свои обертки над VirtualAlloc). Также они и сами должны быть слинкованы статически со всем необходимым (чтобы не было попыток загрузить какую-то dll-ку, которой по умолчанию нету в Windows).
4. Модуль может работать как от имени текущего интерактивного пользователя, так и от имени локальной системы (nt authority\SYSTEM), как НЕИНТЕРАКТИВНЫЙ процесс (как служба). По поводу неинтерактивности, гуглите Session 0 isolation/Services isolation in session 0.
5. Код модуля не должен пытаться получить путь к самому себе, ибо в реальных условиях он будет запускаться в памяти без сохранения на диск.
Разработчик обязан это учитывать, и обеспечить работу модуля во всех режимах. Игнорирование или незнание этих пунктов обычно являются причиной того, что у вас локально модуль работает, а в production – нет.
Также, есть дополнительные требования, связанные с безопасностью:
1. Запрещено использование временных файлов. По умолчанию запрещена запись в любые файлы и реестр. Каждый случай такой записи должен оговариваться.
2. Модуль по возможности не должен обладать состоянием (т.е., чтобы ему не нужно было сохранять что-либо в реестр/файл при остановке, и потом читать оттуда при запуске).
3. Если модуль работает в нескольких процессах, наилучший способ коммуникации между ними (по убыванию – помним что временные файлы запрещены):
a. Прямое чтение/запись памяти (ReadProcessMemory/WriteProcessMemory)
b. Named pipes
ОФОРМЛЕНИЕ МОДУЛЯ
1. Должен иметься проект Microsoft Visual Studio версии не ниже 2015.
2. Проект Visual Studio должен быть настроен следующим образом:
* Для ВСЕХ профилей сборки:
- выходной каталог: $(SolutionDir)Bin\$(PlatformTarget)\$(Configuration)\
- Промежуточный каталог: $(SolutionDir)\obj\$(Platform)\$(Configuration)\$(ProjectName)\
- Многопроцессорная компиляция: да
* Профиль Release:
- Формат отладочной информации (С/С++ создание кода): нет
- Создавать отладочную информацию (компоновщик/отладка): нет
3. Строки обфусцировать библиотекой Andrivet (приложена, см.макрос _STR())
4. Системные вызовы обфусцировать библиотекой GetApi.h. Быть внимательным, обфускация сисвызовов может давать падения.
5. Модуль должен иметь две версии - x32- и x64-разрядную.
6. В боевой сборке должны быть обфусцированы по максимуму строки, отключен всяческий отладочный вывод.
7. Модуль должен иметь отладочную версию. Отладочный вывод должен выводиться в c:/temp/modulename.log (путь к логу настраивается в макросе).
Каждая запись лога должна содержать временнУю метку с точностью до секунды.
8. В проекте должен быть файл настроек config.h (название неважно, важна суть - здесь все глобальные настройки - пути, макросы-переключатели условной компиляции итд).
9. Модуль должен работать на всех современных версиях Windows.
Минимальная поддерживаемая версия Windows - Windows XP (если невозможно - Windows Vista).
10. Дополнительно к компоновке должен добавляться файл notelemetry.obj (https://stackoverflow.com/questions/37761768/how-to-prevent-visual-studio-2015-update-2-to-add-telemetry-main-invoke-trigger)
НАИМЕНОВАНИЕ СБОРОК
В сборке должна быть структура каталогов:
modulename/Release_logged/x86
modulename/Release_logged/x64
modulename/Release_nologs/x86
modulename/Release_nologs/x64
В Release_logged идет версия с логом и без обфускации (но это все равно профиль сборки Release!
т.е. там должны быть включены все оптимизации, выключены отладочные символы итд).
В release_nologs - версия без лога, с полной обфускацией.
В имени каталога со сборкой должно быть имя модуля и дата (например cookies.22.04.2019).
Если на указанную дату есть несколько сборок одного модуля, должен добавляться уникальный суффикс (например cookies.22.04.2019.2)
Это нужно для нормальной эксплуатации модуля - чтобы тестировщики и администраторы отличали версии сборок,
чтобы был возможно откат или апгрейд, а также для выдачи нужной сборки по её дате.
ТЕСТИРОВАНИЕ МОДУЛЯ
Тестировать модуль следует в следующих режимах:
1. На ОС Windows 7, 8.1, 10 как 32-, так и 64-й разрядности. В ОС НЕ должны быть установлены программы, ставящие пакеты MSVC++ runtime library (т.е., ставим голую ОС и по минимуму что нужно для тестирования).
2. От имени интерактивного юзера (того, под кем вы вошли)
3. От имени НЕ администратора
4. От имени Системы (качаем пакет pstools, запускаем модуль так:
psexec –d –s runmodule.exe
это запуск модуля как неинтерактивный юзер SYSTEM
Только после прохождения внутренних тестов можно передавать модуль тестировщику.
ВЗАИМОДЕЙСТВИЕ С ВЫШЕСТОЯЩЕЙ ЛОГИКОЙ
Модуль получает необходимую информацию от вышестоящей логики. Идентификатор клиента ClientID, тэг группы group, внешний IP-адрес будут переданы через функцию Start в параметре ParentInfo
Конфиги модуль получает через функцию Control (если конфигов несколько, каждому из них соответствует свое значение параметра CtlArg).
Тело конфигов и их длина будут переданы через параметры CtlArg и CtlArgLen. остальные параметры не используются.
Для автономных тестов, следует запилить демо-запускатор, который будет грузить скомпиленный модуль через loadLIbrary. Псевдокод очень примерно такой:
main() {
LoadLibrary(“module.dll”);
Start = GetProcAddress(module, “Start”);
Handle = Start(…);
If(!handle) return 1;
Control(“config”, “config body”);
While(true) sleep(1000);
Release(handle);
}
Таким образом, следущая схема:
1. демо-запускатор грузит к себе модуль (DLL) и вызывает функции start и control чтобы передать в неё конфиги, внешний ip-адрес, clientid и group.
2. код внутри модуля, который делает полезную работу.
Поток выполнения следующий:
* Функция Start() инициализирует работу, запускает основной рабочий поток в фоне, и возвращает дескриптор модуля (обычно, адрес функции Start)
* Основной поток может дождаться, пока не прилетят необходимые вызовы Control() с конфигами, и только потом начать работу.
Это зависит от назначения самого модуля - может и не требоваться ожидание.
* Вызовы Control() обычно используются, чтобы передать в модуль конфиги, либо управляющие воздействия (смена режимов работы).
Эти вызовы должны обрабатываться как прерывания. Эти вызовы происходят в отдельном потоке, потому следует обеспечить потокобезопасность для всего,
что разделяется с основным потоком выполнения.
API МОДУЛЯ
Модуль экспортирует следующие функции: Start, Control, Release, FreeBuffer [*1]
Все функции имеют конвенцию вызова stdcall и экспортируются по именам.
Функция Start имеет следующий прототип:
PVOID Start(
LPCSTR ModuleName,
LPCBYTE Arg,
SIZE_T ArgLen,
LPSTR ResultInfo,
const ParentInfo* pParentData,
PVOID EventCallback,
PVOID EventCallbackContext,
PVOID Reserved1);
Функция вызывается при запуске модуля вышестоящей логикой.
ModuleName - имя модуля
Arg - аргумент команды start
ArgLen - размер параметр CtlArg в байтах
ResultInfo -буфер для результирующей строки ctl. Буфер имеет фиксированную длину 1024 байта
pParentData - информация о вышестоящей логике, управляется конфигом модуля
EventCallback – указатель на функцию логирования (см.ниже)
EventCallbackContext – контекст логирования (см.ниже)
Функция в случае удачи возвращает описатель, который необходимо использовать при вызове функций Control и Release (можно передавать адрес самой функции Start – он достаточно уникален в пределах ОС). В случае неудачи функция возвращает ноль.
typedef struct ParentInfo {
CHAR ParentID[256];
CHAR ParentGroup[64];
CHAR SelfIP[64];
LPCWSTR ParentFiles;
} ;
ParentID - полный ID клиента вышестоящей логики
ParentGroup - группирующий тег вышестоящей логики
SelfIP - внешний IP-адрес
ParentFiles - не используется
6.2 Функция Control имеет следующий прототип
BOOL Control (
PVOID ModuleHandle,
LPCSTR Ctl,
LPCBYTE CtlArg,
SIZE_T CtlArgLen,
LPSTR CtlResultInfo,
PVOID* ppOutData,
PDWORD pOutDataSize,
LPCSTR pOutDataTag,
PVOID Reserved1);
ModuleHandle - описатель модуля, которое вернула функция Start.
Ctl - строка содержащая описание управляющего сигнала к модулю
CtlArg - аргумент ctl к модулю (тело сигнала)
CtlArgLen - размер параметра CtlArg в байтах
ResultInfo -буфер для результирующей строки ctl. Буфер имеет фиксированную длину 1024 байта
ppOutData - указатель на переменную в которую будет сохранён указатель на буфер с выходными данными ctl (ctl_OutData)
pOutDataSize - указатель на переменную в которую будет сохранён размер данных в буфере с выходными данными ctl
pOutDataTag - буфер для вспомогательного тега, который будет отправлен на сервер. Буфер имеет фиксированную длину 128 байт
Функция в случае удачи возвращает TRUE, в противном случае функция возвращает FALSE. В случае успеха если значение *ppOutData после вызова не равно нулю, то этот буфер должен быть освобождён через функцию FreeBuffer.
ОБЫЧНОЕ назначение этой функции – передача конфига модулю.
Функция Release имеет следующий прототип
VOID Release (
PVOID ModuleHandle);
Функция реализует полное завершение работы модуля. В её задачи входит удаление всех ресурсов используемых в ходе работы модуля.
Функция FreeBuffer имеет следующий прототип
VOID FreeBuffer (
PVOID pMemory);
Функция освобождает буфер выделенный внутри функции Control (параметр ppOutData).
* Изменения именования функций в экспорте
1) старый вариант, продолжает работать, но антивирусы часто палят модуль по этим функциям
2) Модуль экспортирует любые минимум 4 функции по именам, имена функции сообщаются администратору в виде
CheckFuncStr=Start, GetLength=Control, SetHeigth=Release, Reload=FreeBuffer.
Функции можно изменить при чистке, но опять же нужно будет сообщить об этом администратору.
3) Модуль экспортирует функции по ординалу, или по имени, но обязательно в следующем порядке: 1.Start, 2.Control, 3.FreeBuffer 4.Release
Пример def файла для экспорта по ординалам
EXPORTS
@1 = Start_
@2 = Control_
@3 = FreeBuffer_
@4 = Release_
Пример def файла для экспорта по именам:
CheckFuncStr = Start_
GetLength = Control_
Reload = FreeBuffer_
SetHeigth = Release_
ПРАВИЛА ЛОГИРОВАНИЯ СОБЫТИЙ В АДМИНКУ
Два предпоследних параметра функции Start предназначены для логирования событий в админку.
EventCallback - это указатель на функцию, определенную как:
typedef VOID (__stdcall *pEventCallback)(
PVOID ModuleHandle,
LPCSTR EventName,
LPCSTR EventInfo,
PVOID pOutData,
DWORD OutDataSize,
LPCSTR pOutDataTag,
PVOID Context);
где:
ModuleHandle - дескриптор модуля (обычно это указатель на функцию Start)
EventName - тип события
EventInfo - содержимое события
pOutData - дополнительные данные события
OutDataSize - длина доп.данных
pOutDataTag - тэг события
Context - передаем сюда EventCallbackContext, полученный в Start
Определяем у себя в коде функцию наподобие
void SendEvent(char* name, char* value) {
pEventCallback callback = (pEventCallback)EvCallback;
if (callback) {
debug_printf("SendEvent(%s, %s)\r\n", name, value);
callback(Start, name, value, NULL, 0, tag, EvCallbackContext);
}
}
где
tag - тег события (нужен для фильтрации в логах; обычно это имя модуля. Ну например, "module1")
name - источник события (множество событий можно сгруппировать по их источнику - подсистеме в коде, где они сгенерированы. Ну например, "sql")
value - содержание события (произвольная строка. Ну например, "module1 build 01 Jan, 20xx 11:22:25 started ok")
Источник события разработчик определяет сам.
Источник должен состоять из 4 символов. К примеру, можно определить типы DEBG для отладки, VERS для передачи собственной версии, PING для передачи heartbeat'а в админку, итд.
Источник, идентифицирующий подсистему твоего модуля, из которого происходит событие. К примеру, в модуле есть подсистема работы с файлами, и подсистема сети - для них используются теги "file" и "net".
Раньше нужно было вешать мютекс на логирование, т.к. в вышестоящей логике не было защиты и проверки, и это было потоконебезопасным. Потом мютекс поставили с той стороны, и все вроде бы работает.
Но - если что - если будут проблемы - смотри в первую очередь в сторону многопоточности, во вторую очередь в конвенцию вызовов (_stdcall).
!!!!!!!!!!!
!!!ВАЖНО!!!
!!!!!!!!!!!
Событий от модуля должно быть мало и они должны быть важными. Недопустимо флудить отладкой.
т.е. ты можешь для отладки расставить в модуле логирование, но потом при выдаче сборки, ты обязан отладочные события убрать.
Отправка события является блокирующей операцией.
Следует понимать, что отправка события родителю не является мгновенной. Родитель получает события путем периодического опроса
через разделяемую память. Поэтому, хотя событие может быть отправлено, оно может быть еще не принято родителем.
Это в особенности касается событий, отправляемых перед завершением процесса - они могут быть потеряны по описанной причине.
Если модуль нерезидентный (единократно отрабатывает и далее отключается), по завершению работы он обязан отправить событие
EventName = WantRelease
EventInfo = NULL
По получению данного события носитель модуля выгрузит его из памяти.
ТИПИЧНЫЕ ОШИБКИ
1) отправление события (сообщения), чаще всего с версией и датой модуля из функции Start.
Этого делать НЕЛЬЗЯ.
Правильно так:
- Start провел начальные проверки, инициализации, запустил основной поток и завершился
- основной поток уже начинает спамить телеметрией в событиях
2) в функцию Start передаются botid, group и ip.
эти строковые значения нужно СКОПИРОВАТЬ в функции Start, потому что при выходе из функции память строк освобождается и строки становятся невалидными.