Packer: WinLicense
Dates of compilation and SHA1 hashes:
- a0ea9b06f4cb548b7b2ea88713bd4316c5e89f32 (Mssv36.asi) - 13.09.2017 22:16:52
Description
Trojan.Belonard.10 is part of the Belonard trojan. It’s distributed with pirated Counter-Strike 1.6 builds. Trojan.Belonard.10 acts as a protector of the game. It also installs Trojan.Belonard.5 in the system.
Operating routine
The trojan checks if the WinDHCP service is running. If not, it unpacks and runs Mssv24.asi (Trojan.Belonard.5), and then changes the created/modified/accessed dates of the file to those of Mss32.dll, msmp3.asi, hw.dll or hl.exe. It then creates a thread in which it sends information to the C&C server, and initializes an array of structures by gathering information about the downloaded modules hl.exe, GameUI.dll, hw.dll, sw.dll, and client.dll.
That information is formed into structures:
struct st_mod
{
_DWORD ImageBase;
_DWORD SizeOfImage;
_DWORD pOverlay;
_DWORD pCode;
_DWORD SizeOfCode;
_DWORD pData;
_DWORD pPEHeader;
};
The trojan scans client.dll’s memory using the mask:
The mask corresponds to the code of the CHudSpectator::VidInit function:
pfnSPR_Load is the first pointer to the function in the cl_enginefunc_t structure, which contains pointers to various functions of the game client.
Upon receiving the cl_enginefunc_t structure’s address, the trojan calls the GetFirstCmdFunctionHandle() function (the address of this function is stored in the cl_enginefunc_t structure). The called function returns the structures’ list address cmd_function_t, which contains information about the client’s console commands. With the help of cmd_function_t, the trojan seeks the address of the “version” function. When it’s found, the trojan checks if the client is official.
After that, the trojan modifies the memory of the steamclient.dll module by writing its master server address, suysfvtm[.]valve-ms[.]ru:27012 (port 27013, if it’s an official client). The trojan also hooks one of the module’s functions.
The trojan scans the “sw.dll” module’s memory in order to find the CBaseUI::Shutdown() method, and inserts a call for its own method inside, which will end the trojan’s process if the global variable is set.
It then seeks a place in sw.dll’s memory where the “ScreenFade” line address is pushed to the stack and takes the address by offset 0x10 or 0x13 from that push.
The resulting address is a pointer to the structure cldll_func_t. By using that pointer, the trojan hooks the HUD_Frame function; and in a function called in its stead initializes the main part of its own hooks and memory modifiers.
In the GameUI.dll, the trojan hooks the call of the strstr function from the CL_ParseConsistencyInfo function in order to exclude the following CRC files and paths from a check:
../Steam.dll
../steamclient.dll
../rev.ini
../Mssv36.asi
../platform/resource/cs.tga
../voice_speex.dll
../Start Counter-Strike Game.bat
../Start Counter-Strike Server.bat
../Start Half-Life Game.bat
../Start Half-Life Server.bat
Afterwards, the trojan hooks the CL_ConnectionlessPacket function and the handlers of the playdemo and viewdemo commands. Inside those functions and handlers, the trojan monitors the executed commands and sends information to the C&C server about those not on the trojan’s list.
The trojan also hooks the Netchan_CopyFileFragments and CL_BatchResourceRequest functions. This allows the trojan to send the names of files sent to the client by another servers to the C&C server . Additionally, the trojan detects and filters potentially dangerous files following the criteria:
- If a path contains the “..” or '\', ':', '*', '?', '"', '<', '>', '|’ symbols;
- If a path doesn’t begin with “idema/” or “idema\”;
- If a file has one of the following extensions: “asi”, “bat”, “cfg”, “cmd”, “com”, “dll”, “exe”, “flt”, “ini”, “js”, “log”, “lst”, “m3d”, “mix”, “ocx”, “rc”, “scr”, “vbs”.
Other functions hooked by the trojan:
- CL_Parse_StuffText notifies the C&C server about commands executed;
- CL_Parse_Director notifies the C&C server about the executed commands of DRC_CMD_BANNER and DRC_CMD_STUFFTEXT types;
- CL_Parse_VoiceInit notifies the C&C server about the voice_speex and voice_miles commands;
- CL_Send_CvarValue2 notifies the C&C server about attempts to change the Cvar located on the trojan’s list;
- CL_Send_CvarValue2 notifies the C&C server about attempts to change the Cvar located on the trojan’s list;
The trojan hooks the MOTD event handler and sends the text that was supposed to be displayed using that function to the C&C server. Then it hooks the call of NET_StringToAdr, whose address it finds from the CL_CheckForResend function’s call. If either IP address or the server’s port to which the connection is made doesn’t match those that are known to the trojan, the C&C server receives a notification. After that, the trojan makes several modifications to the memory of modules: client.dll, sw.dll, ServerBrowser.dll, GameUI.dll.
CallHome
The trojan initializes an AES-CFB context with a block size of 128 bit. The key is created by calling EVP_BytesToKey(cipher, md, 0, &aeskey, 32, 5, key, iv). Wherein, the aeskey is embedded in the trojan:
The trojan then decrypts the names of the master servers: suysfvtm[.]valve-ms[.]ru:28447 and etmpyuuo[.]csgoogle[.]ru:28447.
Here’s the decryption algorithm:
def decrypt(data):
s = 'e'
for i in range(0,len(data)-1):
s += chr((ord(s[i]) + ord(data[i]))&0xff)
print s
All the packets sent to and received from the server are encrypted using AES. At the same time, zero byte is added to the beginning of the sent data, and the “padding” in the sent packets is a randomly formed array of bytes.
The server sends packets with a size of 0x10 bytes or less:
- The first byte is an ID of a command;
- The next N byte is the payload (may be absent);
- The remaining bytes (up to 12) are compared against the data with the same offset in the modified sent data, acting as a signature.
Heartbeat (client - 0x8b, server - 0xD9)
The heartbeat packet has a size of 0x10 bytes:
#pragma pack(push,1)
struct st_buf
{
_BYTE byte_8b;
_BYTE client_ver;
_DWORD counter;
_BYTE byte_05;
_BYTE padding[9];
};
#pragma pack(pop)
Where client_ver takes values within the range of 0 to 3 depending on:
- whether the client is official or pirated, or
- whether it is a Russian or English version of the client.
After encrypting the data, the 6th byte changes to 0x29 in the original buffer.
If the master server’s address has been changed in the memory, the global flag is set, activating the sending of game servers’ suspicious actions to the trojan developer’s server.
The DWORD acts as a payload, containing information about the delay between heartbeat packets. If the packet’s size is greater than 0x10 bytes, 0x20 bytes by an offset of 0x10 bytes is the SHA256 hash of data starting by an offset of 0x30 bytes. The data is shown in the game client console as a message.
CheckServer (client - 0x6F, server - 0xE1)
The packet is sent using the code that hooks the NET_StringToAdr function. The file structure is as follows:
#pragma pack(push, 1)
struct st_checksrv
{
_WORD word_56f;
_BYTE client_ver;
_BYTE byte_ab;
_BYTE srvaddr[6];
_BYTE padding[6];
};
#pragma pack(pop)
After the packet is encrypted, the first 4 bytes of the original data are replaced with 0x894DC6E1. If the packet successfully passed the checks, the game client’s “disconnect” function is called.
GameMenuAction (client - 0x80, server - 0x12)
The file structure is as follows:
#pragma pack(push, 1)
struct st_gamemenu_action
{
_BYTE byte_80;
_BYTE client_ver;
_WORD word_6005;
_BYTE padding[12];
};
#pragma pack(pop)
After the packet is encrypted, its original structure is changed to:
- byte_80 is replaced with 0x12;
- word_6005 is replaced with 0xDAB1;
- If the client is pirated, client_ver is replaced with 0x87; and if the client is official, it is replaced with 0x11.
In the resulting packet, the payload is the 6 byte, which set the IP:port. The trojan rearranges it into a line and executes the game client command connect IP:port.
GameSecurityViolation (client - 0x05)
It is sent from the code that hooks the functions of the game client. The packet’s structure:
#pragma pack(push, 1)
struct st_report
{
_BYTE byte_05;
_BYTE client_ver;
_BYTE padding[0x0e];
_BYTE hash[0x20];
_DWORD ip;
_WORD port;
char msg[];
};
#pragma pack(pop)
Unlike other packets, 0x02 is added to the beginning of this encrypted packet.






