Smargaft harnesses EtherHiding for stealthy C2 hosting

By on 1 Apr 2024

Category: Tech matters

Tags: , , ,

Blog home

Botnet image created with Copilot.

At XLab, we see a lot of botnets every day, mainly tweaks of old Mirai and Gafgyt codes. These are common and usually don’t grab our attention. But recently, we found something different.

This new botnet uses some of Gafgyt’s attack style but has been built differently from the ground up. Interestingly, it uses the Binance Smart Chain to host command and control servers (C2). It also infects shell scripts like a virus to achieve persistence.

When looking closer, we noticed that some antivirus vendors flagged this botnet as Mirai, which isn’t correct. Because of its smart use of contracts and Gafgyt’s methods, we’ve decided to call it Smargaft. It mainly does Distributed Denial-of-Service (DDoS) attacks, runs system commands, and lets users connect anonymously using the socks5 proxy.

Figure 1 — Smargaft overview.
Figure 1 — Smargaft overview.

The biggest highlight of Smargaft is its use of smart contracts for hosting its C2 infrastructure, a technique first disclosed in October 2023 and known in the industry as EtherHiding. It fully leverages the public and immutable nature of blockchain technology, making the ‘on-chain’ C2 irremovable, which is a highly advanced and rare form of bulletproof hosting.

This is the first time we’ve seen such technology applied in the botnet field. Another advantage of using smart contracts for cloud-based C2 configuration is flexibility. Malware authors can even design monitoring code to interact with smart contracts, enabling automatic updates of C2 when specific conditions are met or adjusting attack strategies based on environmental changes.

Given that once abused, contracts can quickly become powerful tools for cybercrime, significantly increasing the difficulty of monitoring and management, we decided to write this article to share our latest findings with the community, hoping to help everyone more effectively identify and prevent these new types of cyber threats.

What is BSC? What is Smart Contract?

Binance Smart Chain (BSC) is a blockchain platform developed and maintained by Binance, launched in 2020. It’s designed to support decentralized applications (DApps) and smart contracts, similar to Ethereum.

Smart contracts are automated protocols or programs executed on a blockchain. They consist of prewritten code designed to perform actions automatically or contractual terms under specific conditions. Smart contracts enable trustworthy transactions and interactions without intermediaries. When certain conditions are met, smart contracts can trigger various operations, such as transferring digital assets, distributing rewards, or creating tokens.

In the scenario where “Smargaft utilizes smart contracts for C2 management,” it essentially means that the developers of Smargaft configure and manage the C2 infrastructure through smart contracts, with the C2-related information ultimately stored on the blockchain. Once the C2 configuration is complete, Smargaft’s malicious software (bot samples) communicates with the blockchain network via JSON RPC to retrieve C2 information.

XLab notes.

Specifically, the bot samples send a request to a Remote Procedure Call (RPC) server, which includes the Smargaft smart contract address and the contract function to be called. Upon receiving this request, the RPC server forwards it to the blockchain network. Then, the blockchain nodes receive the request and use the Ethereum Virtual Machine (EVM) to load and execute the specified function within the smart contract. After execution, the results are sent back along the same path to the bot sample.

Figure 2 — How the bot works.
Figure 2 — How the bot works.

Considering the methods of querying data on the blockchain and the inherent characteristics of the blockchain itself, the advantages of the ‘smart contract-hosted C2’ technology are mainly reflected in three aspects of unblockable:

  1. Unblockable access channels: Due to the diversity of RPC nodes, including official nodes, public nodes, and privately built nodes, it’s difficult to cover all possible access paths with a blacklist. Taking Smargaft as an example, even if Binance’s official nodes are blocked in China, there are still multiple public nodes available for use. Smargaft’s bot samples are embedded with 14 different RPC server addresses, ensuring that at least some nodes are accessible.
  2. Unblockable configuration mechanism: Malicious smart contracts deployed on the blockchain are difficult to directly ban or delete. For example, the smart contract address of Smargaft (for example, 0xdf2208d4902aa1ec9a0957132ca86a4e1d40455b) is not subject to direct regulation or blocking by Binance or other blockchain operators.
  3. Unblockable C2 information: Once C2 information is stored on the blockchain via a smart contract, it cannot be deleted or altered. In the case of Smargaft, C2 information is permanently recorded on a specific block of the Binance Smart Chain (such as block number 34731229), ensuring its persistence and immutability.

Smargaft contracts

For analysing smart contracts, the platform provided by Binance, BscScan, can be used for exploration and analysis. The contract address used in the Smargaft sample is 0xdf2208d4902aa1ec9a0957132ca86a4e1d40455b. Based on the timeline we’ve compiled, it appears highly likely that the author of Smargaft began experimenting with the technology of smart contract-hosted C2 on 15 September 2023, completed testing by 20 September 2023, and officially deployed it on 27 December 2023.

+ 2023-09-15:
	+ Transfer
	+ Src Wallet: 0x7Be249AA69c631c7aa5De4F3aDbFb8A9db8DfD09
	+ Dst Wallet: 0x16Cc46219d062257F384D85F84c7AbC7D9e34444
	+ Amount: 0.06094994 BNB ($17.75)
+ 2023-09-15 : 
	+ Create the first contract (unstripped)
	+ Wallet: 0x16Cc46219d062257F384D85F84c7AbC7D9e34444
	+ Contract: 0xe77c6a0E10F2A469fb2afa667C99180E186233a8
	+ Init:
		+ Addr: 45.95.146.93
	+ The comment is Russian
+ 2023-09-15:
	+ Set address: 1.1.1.1
	+ Wallet: 0x16Cc46219d062257F384D85F84c7AbC7D9e34444
	+ Contract: 0xe77c6a0E10F2A469fb2afa667C99180E186233a8
+ 2023-09-15:
	+ Create the second contract (same as the first one, but stripped)
	+ Wallet: 0x16Cc46219d062257F384D85F84c7AbC7D9e34444
	+ Contract: 0x862fbeb2456499a37e9146f1ef6eb5a57c2fb97a
	+ InitAddr: 
+ 2023-09-15:
	+ Change the IP addresses around
		+ 45.95.146.93 -> 10.202.30.40 -> 45.95.146.93 -> 1.22.33.44 -> 45.95.146.93
	+ Wallet: 0x16Cc46219d062257F384D85F84c7AbC7D9e34444
	+ Contract: 0x862fbeb2456499a37e9146f1ef6eb5a57c2fb97a
+ 2023-09-20:
	+ Create the third contract (add one variable and its set/get function, stipped)
	+ Wallet: 0x16Cc46219d062257F384D85F84c7AbC7D9e34444
	+ Contract: 0xdf2208d4902aa1ec9a0957132ca86a4e1d40455b
	+ Init:
		+ Addr: 45.95.146.93
		+ New Parameter: hello
+ 2023-12-27
	+ Set address: 45.95.146.93;94.103.188.167;185.132.125.193
	+ Wallet: 0x16Cc46219d062257F384D85F84c7AbC7D9e34444
	+ Contract: 0xdf2208d4902aa1ec9a0957132ca86a4e1d40455b

The original contract, 0xe77c6a0E10F2A469fb2afa667C99180E186233a8, has its source code available for viewing, with many comments in Russian. It mainly functions to read and write the ServerAddr on the blockchain through getServerAddr and setServerAddr.

pragma solidity ^0.8.0;

contract ControlEbanataContract {
    string private serverAddr;
    address public admin;

    // Событие для уведомления о смене адреса сервера
    event ServerAddrChanged(string newAddr);

    constructor(string memory initialServerAddr) {
        serverAddr = initialServerAddr;
        admin = msg.sender;
    }

    // Изменить адрес сервера (только администратор)
    function setServerAddr(string memory newAddr) public {
        require(msg.sender == admin, "Only the admin can change the server address");
        serverAddr = newAddr;
        emit ServerAddrChanged(newAddr);
    }

    // Получить текущий адрес сервера
    function getServerAddr() public view returns (string memory) {
        return serverAddr;
    }

    // Изменить администратора (только текущий администратор)
    function changeAdmin(address newAdmin) public {
        require(msg.sender == admin, "Only the admin can change the admin");
        admin = newAdmin;
    }
}

After several tests, the final contract for Smargaft is 0xdf2208d4902aa1ec9a0957132ca86a4e1d40455b (Figure 3). This version of the contract is compiled bytecode.

Figure 3 — The final contract for Smargaft is 0xdf2208d4902aa1ec9a0957132ca86a4e1d40455b.
Figure 3 — The final contract for Smargaft is 0xdf2208d4902aa1ec9a0957132ca86a4e1d40455b.

Although the source code of the Smargaft contract is not directly viewable, it can be decompiled by clicking ‘Decompile Bytecode’.

def storage:
  stor0 is array of struct at storage 0
  stor1 is array of struct at storage 1
  adminAddress is addr at storage 2

def unknown61695f0a(array _param1) payable:
{...}
 if _param1.length:
 	stor1[].field_0 = Array(len=_param1.length, data=_param1[all])
{...}

def unknownd7ec3ad7() payable: 
{...}
  return Array(len=stor1.length % 128, data=mem[128 len ceil32(stor1.length.field_1)], mem[(2 * ceil32(stor1.length.field_1)) + 192 len 2 * ceil32(stor1.length.field_1)]), 
{...}


def unknownebe759f5(array _param1) payable: 
{...}
if _param1.length:
	stor0[].field_0 = Array(len=_param1.length, data=_param1[all])
{...}
def unknownbaaeedb7() payable: 
{...}
  return Array(len=stor0.length % 128, data=mem[128 len ceil32(stor0.length.field_1)], mem[(2 * ceil32(stor0.length.field_1)) + 192 len 2 * ceil32(stor0.length.field_1)]), 
{...}

This is a straightforward contract application that uses storage to save variables stor0stor1, and adminAddress. The function with identifier 0x61695f0a saves the input _param1 byte by byte into stor1, while 0xd7ec3ad7 reads the data from stor1 and returns it as a string. The functions 0xebe759f5 and 0xbaaeedb7 operate similarly but target the variable stor0. Readers with programming experience will recognize these as a pair of set/get methods, allowing interaction with the contract to write or update data on the blockchain through this set/get mechanism.

For example, in actual operation, the author of Smargaft used the 0x61695f0a method on Dec-27-2023 09:55:26 PM +UTC to write C2 data onto the blockchain.

Figure 4 — Call method 0x61695f0a set to C2.
Figure 4 — Call method 0x61695f0a set to C2.

In Smargaft’s bot samples, the eth_call is used to invoke the smart contract’s method 0xd7ec3ad7 to obtain the C2 information.

Figure 5 — Call method 0xd7ec3ad7 used to get C2.
Figure 5 — Call method 0xd7ec3ad7 used to get C2.

Q: What is eth_call?

Attentive readers will have noticed a Transaction Fee in the ‘SET C2’ diagram, indicating that invoking the method 0x61695f0a costs money. In fact, calling functions of a smart contract usually incur a fee. This is not an issue in the ‘Set C2’ scenario, as it’s used infrequently.

However, in the ‘Get C2’ scenario, a problem arises. If the number of bots is extremely large, wouldn’t each retrieval of C2 information cost a significant amount of money? This would obviously be unacceptable. Fortunately, Binance’s SDK provides a method called eth_call, which allows users to read data from a smart contract without generating any blockchain transactions. Since it doesn’t alter the state of the blockchain, it doesn’t require any fees.

eth_call was originally designed for simulating contract execution to read data or for testing purposes, without generating any actual effects; it doesn’t even get recorded on the blockchain. Therefore, you can retrieve your data (malicious payload) for free, without leaving any traces, and in a robust manner, without leaving behind any evidence.

XLab notes.

DDoS attack statistics

Looking at the geographical locations of the targets, the Smargaft botnet attacks are global and not targeted at any specific region. The main areas affected include China, Poland, the USA, Germany, and France.

Figure 6 — Distribution, trends, and statistics for Smargaft botnet attacks.
Figure 6 — Distribution, trends, and statistics for Smargaft botnet attacks.

An interesting observation is that the attack targets of Smargaft are simultaneously assaulted by several different botnets. This leads us to speculate that there is a DDoS platform that aggregates many different botnets.

Figure 7 — The attack targets of Smargaft are simultaneously assaulted by several different botnets.
Figure 7 — The attack targets of Smargaft are simultaneously assaulted by several different botnets.

Sample propagation analysis

Based on our data, Smargaft spreads by exploiting known vulnerabilities to propagate a Downloader onto target devices. Once the Downloader is successfully implanted, it then downloads the Bot sample to spread further.

VulnerabilityAffected
CVE-2013-5948ASUS RT-AC68U other RT series routers
CVE-2020-8515DrayTek Vigor Router
LILIN_DVR_RCELILIN DVR
TVT_API_RCETVT DVR
Table 1 — Some of the vulnerabilities used by Smargaft

Bot analysis

We captured Smargaft bot samples for three different CPU architectures: ARM, MIPS, and X86/64, belonging to two distinct versions. The primary difference between these versions lies in their ability to propagate like a worm. This article focuses on analysing an older version of the X64 sample. Its basic information is as follows, with the sample utilizing standard UPX packing.

MD5: 7f741495f14c828c20db4de6251673fd
Magic: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, corrupted section header size
Packer: UPX
Version: V0

Smargaft operates relatively simply. When it runs on a device that has been compromised, it first checks the current user. If it’s root, it initiates additional scanning and propagation tasks. Then, it binds to a local port to ensure only one instance runs at a time and manipulates the watchdog to prevent the device from rebooting. Next, it initializes five tasks, including obtaining C2 through smart contracts, conducting DDoS attacks, and ensuring its persistence on the device. Finally, Smargaft runs in an infinite loop, cycling through these tasks at preset intervals.

  1. Propagation task
  2. GetG2 task
  3. DDoS task
  4. Forward task
  5. Persistence task
  6. Killer task

The following discussion will dissect the implementation of Smargaft’s functionalities especially centred around tasks 1, 2, 3, and 5.

Propagation task

Smargaft spreads by scanning for open ports on devices within the 10.0.0.0/8 IP range, targeting 22 specific ports. It counts the number of IPs checked with a variable v4. At v4=100, it tries to infect devices with open ports using the CVE_2021_41653 vulnerability. When v4 reaches 500, it switches to randomly scanning public IPs.

Figure 8 — Smargaft spreads by scanning for open ports on devices within the 10.0.0.0/8 IP range, targeting 22 specific ports.
Figure 8 — Smargaft spreads by scanning for open ports on devices within the 10.0.0.0/8 IP range, targeting 22 specific ports.

There’s a bug in the code that prevents Smargaft from scanning public networks as intended: v4 gets reset to 0 after reaching 100, so the code for v4=500 never runs, losing the ability for wider network scanning. This wasn’t fixed in Smargaft’s newer version; the feature was simply removed.

Figure 9 — The payload constructed for CVE_2021_45653 by Smargaft.
Figure 9 — The payload constructed for CVE_2021_45653 by Smargaft.

When crafting the SYN packet, Smargaft uses a source port of 55555. The actual sniffing traffic clearly shows a pattern of 1 IP: 22 Port, which aligns with the analysis mentioned above.

Figure 10 — When crafting the SYN packet, Smargaft uses a source port of 55555.
Figure 10 — When crafting the SYN packet, Smargaft uses a source port of 55555.

Common Tasks

0x00: Initialization

Figure 11 — Smargaft initializes tasks using a specific code snippet and inserts them into a task chain.
Figure 11 — Smargaft initializes tasks using a specific code snippet and inserts them into a task chain.

Through analysis, the structure of a task is identified, containing information such as task method, last task start time, task interval, task type, and next task.

struct Task
{
  _QWORD *task_proc;
  _QWORD last_time;
  _DWORD interval_time;
  _DWORD task_type;
  Task *task_next;
};
Figure 12 — After redefining relevant variables in IDA as Task * type, the clarity of the task structure is significantly improved.
Figure 12 — After redefining relevant variables in IDA as Task * type, the clarity of the task structure is significantly improved.

Smargaft supports five different tasks, each with its detailed attributes (Table 2).

TypeTaskInterval (seconds)
1GetC23,000
2DDoS8
3Ip-Forward360,000
4Persistence360,000
5Killer1
Table 2 — Smargaft tasks and attributes.

After all tasks are initialized, they are cycled through in an infinite loop, ensuring each task is executed according to its scheduled interval and type.

Figure 13 — Smargaft task polling.
Figure 13 — Smargaft task polling.

0x01: Get C2

This task runs every 3,000 seconds and begins by constructing a JSON object using the following code snippet.

Figure 14 — Code used to construct a JSON object.
Figure 14 — Code used to construct a JSON object.

The generated JSON appears as follows:

{
jsonrpc: "2.0",
method: "eth_call",
id: 1,
params: [
{
to: "0xdf2208d4902aa1ec9a0957132ca86a4e1d40455b",
data: "0xd7ec3ad7"
},
"latest"
]
}

This JSON object is for an RPC request to the Binance Smart Chain, with each field meaning:

  1. jsonrpc: The JSON RPC protocol version used here is ‘2.0’. JSON RPC is a lightweight protocol for remote procedure calls, allowing requests with specific commands to Ethereum nodes.
  2. method: The method being called here is eth_call. The eth_call method executes a function call of a smart contract without causing any state change on the blockchain.
  3. id: the unique identifier for the request used is 1. This ID distinguishes between different requests and responses, usually a number or string.
  4. params: An array containing the parameters needed for the method.
    • The first parameter is an object with two fields:
      a. to: The address of the smart contract being called here is 0xdf2208d4902aa1ec9a0957132ca86a4e1d40455b.
      b. data: Encoded data for the contract function call used here is 0xd7ec3ad7; it’s usually a hash of a specific function signature.
    • The second parameter specifies the block state; here “latest” is used to execute the call with the state of the latest block.

“latest” always retrieves the most current state in the block, offering a convenient way for automatic C2 updates. Imagine a scenario where all C2s in the IOC database are blocked. The author only needs to update the C2 via the contract, and the Bot, without any updates and leveraging the “latest” feature, can resend the RPC request to access the new C2.

XLab notes.

After generating the JSON, Smargaft randomly selects one from 14 hardcoded RPC nodes to send the request and parses the ‘result’ value from the returned JSON.

Figure 15 — 'Result' value from the returned JSON.
Figure 15 — ‘Result’ value from the returned JSON.

The equivalent script:

curl -X POST URL -d '{ "jsonrpc": "2.0", "method": "eth_call", "id": 1, "params": [ { "to": "0xdf2208d4902aa1ec9a0957132ca86a4e1d40455b", "data": "0xd7ec3ad7" }, "latest" ] }' | jq.result

Using https://rpc.ankr.com/bsc as an example, replace the URL in the script mentioned above with https://rpc.ankr.com/bsc. Running it afterwards will directly get the following result value.

0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002b34352e39352e3134362e39333b39342e3130332e3138382e3136373b3138352e3133322e3132352e313933000000000000000000000000000000000000000000

To read the C2 from the result, start at the 122nd byte and continue until hitting a non-zero digit. Then, decode the following data as a hex string, where the first byte indicates the C2 list’s length and the rest is the C2 list, separated by ;.

00000000  2b 34 35 2e 39 35 2e 31 34 36 2e 39 33 3b 39 34  |+45.95.146.93;94|
00000010  2e 31 30 33 2e 31 38 38 2e 31 36 37 3b 31 38 35  |.103.188.167;185|
00000020  2e 31 33 32 2e 31 32 35 2e 31 39 33              |.132.125.193|

The bot connects to the C2 on port 81, sends a ready\x00 packet, and checks if the response is 0 or more bytes to confirm C2 is live.

Figure 16 — The first live C2 found is used for DDoS tasks.
Figure 16 — The first live C2 found is used for DDoS tasks.

0x02: DDoS task

This task runs every eight seconds. It establishes a connection with the C2 on port 81, sends a 5-byte ‘ready’ packet, and receives commands from the C2. It supports executing system commands, conducting DDoS attacks, and providing socks5 proxy functionality.

Figure 17 — The DDoS task connects with C2 on port 81 and sends a ready packet.
Figure 17 — The DDoS task connects with C2 on port 81 and sends a ready packet.

By cross-referencing the variable v205, it’s evident that the communication protocol is text-based, and a total of 15 different commands are supported.

Figure 18 — 15 different commands are supported.
Figure 18 — 15 different commands are supported.
CommandFunction
ackDDoS Vector
synDDoS Vector
greDDoS Vector
tcphDDoS Vector
udpgDDoS Vector
udphDDoS Vector
httphDDoS Vector
stompDDoS Vector
spoof_vseDDoS Vector
spoof_synDDoS Vector
socketDDoS Vector
sockssocks5 proxy
killkill self
execexec system cmds
updatebot update
Table 3 — The commands and their respective functionalities.
Figure 19 — Example of a real captured attack.
Figure 19 — Example of a real captured attack.

When the bot receives the command, it launches a DDoS attack on port 17481 of the IP 43.249.192.173 using the ‘udph’ attack vector (Figure 22).

Figure 20 — udph traffic.
Figure 20 — udph traffic.

0x03: Persistence task

This task runs every 100 hours with two main objectives. Firstly, it uses the Interfere_proc function to mount one of tmpfsdevptsminix, or sysfs onto the /proc/pid directory associated with the bot, preventing tools that rely on the /proc filesystem from obtaining accurate information about the bot process.

Figure 21 — The persistence task prevents tools that rely on the /proc filesystem from obtaining accurate information about the bot process.
Figure 21 — The persistence task prevents tools that rely on the /proc filesystem from obtaining accurate information about the bot process.

Secondly, taking ‘netstat’ as an example, the effect is that it can no longer correctly display information like the process’s program ID (PID) and program name.

Figure 22 — The PID and program name won't be shown.
Figure 22 — The PID and program name won’t be shown.

The Infect_sh function implements a virus-like infection for files with the .sh suffix in a specified directory by appending \n\n\n to the bot absolute path and \n to the end. This ensures the bot gets executed every time such a script runs. The function’s first parameter is the directory, and the second is the maximum number of layers for infection.

Figure 23 — The infection.
Figure 23 — The infection.

To demonstrate the infection effect, we created a ‘goat’ directory under the /home directory

./goat/
├── 1.sh
└── 3layer
    ├── 2.sh
    └── 4layer
        └── 3.sh

In practice, the infection of many ‘goat’ and ‘/etc’ directories’.sh files have had the execution statement /home/kali/sample/main.x86.unp & added to their ends. The file /home/goat/3layer/4layer/3.sh was not infected because its directory depth exceeded 3.

Figure 24 — Infected /home and /etc directories.
Figure 24 — Infected /home and /etc directories.

Summary

Smargaft’s use of EtherHiding for ‘C2 hosting’ showcases significant advantages by harnessing blockchain’s decentralization, transparency, and immutability. This method, immune to interventions at the blockchain level, represents an advanced ‘bulletproof’ hosting technique. With smart contracts being relatively easy to learn, more malware creators are expected to turn to this technology. The new era of threats is upon us —stay vigilant, stay safe.

If you’re intrigued by our research, reach out to us on Twitter. Also, we have a favour to ask. Our DDoS data suggests coordinated attacks by various botnets, yet we’re not fully versed in the DDoS underground. If you have insights, we’d love to hear from you.

Rate this article

The views expressed by the authors of this blog are their own and do not necessarily reflect the views of APNIC. Please note a Code of Conduct applies to this blog.

Leave a Reply

Your email address will not be published. Required fields are marked *

Top