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.
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.
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:
- 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.
- 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.
- 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.
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 stor0
, stor1
, 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.
In Smargaft’s bot samples, the eth_call is used to invoke the smart contract’s method 0xd7ec3ad7 to obtain the C2 information.
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.
XLab notes.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.
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.
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.
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.
Vulnerability | Affected |
CVE-2013-5948 | ASUS RT-AC68U other RT series routers |
CVE-2020-8515 | DrayTek Vigor Router |
LILIN_DVR_RCE | LILIN DVR |
TVT_API_RCE | TVT DVR |
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.
- Propagation task
- GetG2 task
- DDoS task
- Forward task
- Persistence task
- 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.
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.
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.
Common Tasks
0x00: Initialization
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;
};
Smargaft supports five different tasks, each with its detailed attributes (Table 2).
Type | Task | Interval (seconds) |
1 | GetC2 | 3,000 |
2 | DDoS | 8 |
3 | Ip-Forward | 360,000 |
4 | Persistence | 360,000 |
5 | Killer | 1 |
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.
0x01: Get C2
This task runs every 3,000 seconds and begins by constructing a JSON object using the following code snippet.
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:
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.method
: The method being called here is eth_call. Theeth_call
method executes a function call of a smart contract without causing any state change on the blockchain.id
: the unique identifier for the request used is 1. This ID distinguishes between different requests and responses, usually a number or string.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.
- The first parameter is an object with two fields:
“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.
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.
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.
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.
Command | Function |
ack | DDoS Vector |
syn | DDoS Vector |
gre | DDoS Vector |
tcph | DDoS Vector |
udpg | DDoS Vector |
udph | DDoS Vector |
httph | DDoS Vector |
stomp | DDoS Vector |
spoof_vse | DDoS Vector |
spoof_syn | DDoS Vector |
socket | DDoS Vector |
socks | socks5 proxy |
kill | kill self |
exec | exec system cmds |
update | bot update |
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).
0x03: Persistence task
This task runs every 100 hours with two main objectives. Firstly, it uses the Interfere_proc
function to mount one of tmpfs
, devpts
, minix
, 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.
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.
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.
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.
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.
Alex Turing is a malware researcher at Xlab.
This post is adapted from the original at Xlab blog.
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.