blog.xlab.qianxin.com Open in urlscan Pro
121.32.243.76  Public Scan

URL: https://blog.xlab.qianxin.com/a-deep-dive-into-the-zergeca-botnet/
Submission: On July 08 via api from IL — Scanned from IL

Form analysis 0 forms found in the DOM

Text Content

奇安信 X 实验室
 * Home
 * About
 * EN


DDoS


NEW THREAT: A DEEP DIVE INTO THE ZERGECA BOTNET

 * 
 * 

ALEX.TURING, ACEY9

2024年6月19日 • 13 min read
 1. Background
 2. Sample & C2 Detection
 3. Profile of 84.54.51.82
    1. Scanner
    2. Mirai Downloader&C2
    3. Zergeca C2
    4. Exploits
    5. DDoS Statistics
 4. Reverse Analysis
    1. 0x00: String Decryption
    2. 0x01: Persistence Module
       1. Experiment A
    3. 0x2: Silivaccine Module
       1. Experiment B
    4. 0x3: Zombie Module
       1. Communication Protocol
       2. Experiment C
 5. Summary
 6. IOC
    1. Sample
    2. Domain
    3. IP
 7. Appendix
    1. IdaPython Script


BACKGROUND

On May 20, 2024, while everyone was happily celebrating the holiday, the
tireless XLab CTIA(Cyber Threat Insight Analysis) system captured a suspicious
ELF file around 2 PM, located at /usr/bin/geomi. This file was packed with a
modified UPX, had a magic number of 0x30219101, and was uploaded from Russia to
VirusTotal, where it was not detected as malicious by any antivirus engine.

Later that evening at 10 PM, another geomi file using the same UPX magic was
uploaded to VT from Germany. The suspicious file path, modified UPX, and
multi-country uploads caught our attention. After analysis, we confirmed that
this is a botnet implemented in Golang. Given that its C2 used the string
"ootheca," reminiscent of the swarming Zerg in StarCraft, we named it Zergeca.

Functionally, Zergeca is not just a typical DDoS botnet; besides supporting six
different attack methods, it also has capabilities for proxying, scanning,
self-upgrading, persistence, file transfer, reverse shell, and collecting
sensitive device information. From a network communication perspective, Zergeca
also has the following unique features:

 * Supports multiple DNS resolution methods, prioritizing DOH for C2 resolution.
 * Uses the uncommon Smux library for C2 communication protocol, encrypted via
   XOR.

During the investigation of Zergeca's infrastructure, we found that its C2 IP
address, 84.54.51.82, has been serving at least two Mirai botnets since
September 2023. We speculate that the author behind Zergeca accumulated
experience operating the Mirai botnets before creating Zergeca.

On June 10, XLab command tracking system captured a vector 7 DDoS command that
the current samples did not support, indicating that Zergeca's author is
actively developing and updating, with new samples yet to be discovered. Our
persistence paid off when we captured a new sample on the 19th that supports the
vector 7. Currently, the detection rates for Zergeca samples and C2 are very
low. Considering Zergeca's potential threat in DDoS attacks, we have decided to
release this article to share our findings with the community.


SAMPLE & C2 DETECTION

From the sample perspective, we captured a total of 5 Zergeca samples. While
their functions are nearly identical, there is a significant discrepancy in
their detection rates. How can this anomaly be explained? Most antivirus vendors
have categorized the sample 23ca4ab1518ff76f5037ea12f367a469 as Generic Malware.
We speculate that the detection of Zergeca by antivirus software is based on
file hash. Therefore, as long as the hash changes, the detection effectiveness
diminishes.

MD5 Detection First Seen Telemetry 23ca4ab1518ff76f5037ea12f367a469 28/64
2024.05.20 Russian 9d96646d4fa35b6f7c19a3b5d3846777 0/67 2024.05.20 Germany
d78d1c57fb6e818eb1b52417e262ce59 1/67 2024.05.22 China
604397198f291fa5eb2c363f7c93c9bf 1/66 2024.06.11 France
60f23acebf0ddb51a3176d0750055cf8 0/67 2024.06.18 France

To verify our hypothesis, we appended the 4-byte string "Xlab" to the end of the
file 23ca4ab1518ff76f5037ea12f367a469 and re-uploaded it to VirusTotal. The
detection rate changed to 9/67, partially confirming our speculation.



Additionally, the current detection is based on the packed samples, after
unpacking, the detection rate drops to 0.



From the Domain Perspective, the four samples share two C2 domains that were
created on the same day. The samples prioritize using DOH (DNS over HTTPS) for
C2 resolution, which obscures the relationship between the samples and the C2
domains to some extent. Because of this, VirusTotal couldn't even associate the
C2 domains with the samples, resulting in a naturally low detection rate.

Domain Detection Create date ootheca.pw 1/93 2024.04.28 ootheca.top 1/93
2024.04.28


PROFILE OF 84.54.51.82

The two C2 servers of Zergeca point to the same IP address, 84.54.51.82.
According to our data, this IP has been in use since September 2023, serving a
variety of roles. During this period, it has acted as a Scanner, Downloader,
Mirai botnet C2, and Zergeca botnet C2.


SCANNER

Starting from September 18, 2023, scanning activities commenced, primarily
targeting protocols such as Telnet, HTTP, and socks4. The main ports scanned
include23, 8080, 3128, 80, and 8888.




MIRAI DOWNLOADER&C2

From September and October 2023 to April 2024, 84.54.51.82 was primarily used as
the Loader IP and Downloader IP for the Mirai botnet.

 * 2023.09 - 2023.10, it was used as the Loader and Downloader IP to implant the
   following related samples.
   
    #Downloader
   
   http://84.54[.51.82/jaws
   http://84.54[.51.82/bin
   http://84.54[.51.82/596a96cc7bf9108cd896f33c44aedc8a/db0fa4b8db0333367e9bda3ab68b8042.x86
   http://84.54[.51.82/596a96cc7bf9108cd896f33c44aedc8a/db0fa4b8db0333367e9bda3ab68b8042.spc
   http://84.54[.51.82/596a96cc7bf9108cd896f33c44aedc8a/db0fa4b8db0333367e9bda3ab68b8042.sh4
   http://84.54[.51.82/596a96cc7bf9108cd896f33c44aedc8a/db0fa4b8db0333367e9bda3ab68b8042.ppc
   http://84.54[.51.82/596a96cc7bf9108cd896f33c44aedc8a/db0fa4b8db0333367e9bda3ab68b8042.mpsl
   http://84.54[.51.82/596a96cc7bf9108cd896f33c44aedc8a/db0fa4b8db0333367e9bda3ab68b8042.mips
   http://84.54[.51.82/596a96cc7bf9108cd896f33c44aedc8a/db0fa4b8db0333367e9bda3ab68b8042.m68k
   http://84.54[.51.82/596a96cc7bf9108cd896f33c44aedc8a/db0fa4b8db0333367e9bda3ab68b8042.i686
   http://84.54[.51.82/596a96cc7bf9108cd896f33c44aedc8a/db0fa4b8db0333367e9bda3ab68b8042.arm7
   http://84.54[.51.82/596a96cc7bf9108cd896f33c44aedc8a/db0fa4b8db0333367e9bda3ab68b8042.arm6
   http://84.54[.51.82/596a96cc7bf9108cd896f33c44aedc8a/db0fa4b8db0333367e9bda3ab68b8042.arm5
   http://84.54[.51.82/596a96cc7bf9108cd896f33c44aedc8a/db0fa4b8db0333367e9bda3ab68b8042.arm
   http://84.54[.51.82/596a96cc7bf9108cd896f33c44aedc8a/db0fa4b8db0333367e9bda3ab68b8042.arc
   
   #CC
   
   mirai://bot.hamsterrace.space:59666
   

 * 2024.04, it was used as the Loader IP to implant the following related
   samples.
   
   #Downloader
   http://145.239[.108.150/Fantazy.sh
   http://145.239[.108.150/Fantazy/Fantazy.arm5
   http://145.239[.108.150/Fantazy/Fantazy.arm6
   http://145.239[.108.150/Fantazy/Fantazy.mpsl
   http://145.239[.108.150/Fantazy/Fantazy.sh4
   http://145.239[.108.150/Please-Subscribe-To-My-YT-Channel-VegaSec/1isequal9.x86
   http://145.239[.108.150/cache
   
   # CC
   
   mirai://145.239.108.150:63645
   


ZERGECA C2

Starting from April 29, 2024, 84.54.51.82 began being used as the C2 server for
Zergeca. The relevant C2 domains and their resolution records are as follows:




EXPLOITS

In our observation, the primary methods used by 84.54.51.82 to propagate samples
are Telnet weak passwords and certain known vulnerabilities. The relevant
vulnerability identifiers are as follows:

Telnet Weak Password
CVE-2022-35733
CVE-2018-10562
CVE-2018-10561
CVE-2017-17215
CVE-2016-20016



DDOS STATISTICS

From early to mid-June 2024, the Zergeca botnet primarily targeted regions such
as Canada, the United States, and Germany. The main type of attack was ackFlood
(atk_4), with victims distributed across multiple countries and different ASNs.




REVERSE ANALYSIS

The four Zergeca samples in our observation are all designed for the x86-64 CPU
architecture and target the Linux platform. The presence of strings like
"android," "darwin," and "windows" in the samples, along with Golang's inherent
cross-platform capabilities, suggests that the author may eventually aim for
full platform support.

This article focuses on the earliest captured sample for detailed analysis. The
sample is packed with UPX and has a magic number of 0x30219101. For this type of
modified UPX packer, simply changing the magic back to the standard "UPX!"
allows for unpacking with the command upx -d.

MD5:23ca4ab1518ff76f5037ea12f367a469
Mgaic:ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, corrupted section header size
Packer: UPX
Version:0.0.01c


After unpacking, it becomes evident that Zergeca is a botnet implemented in Go
language. The symbols are not obfuscated, making reverse analysis relatively
straightforward.



The figure above shows a code snippet of the main_main function. Functionally,
it can be broken down into four distinct modules. The persistence and proxy
modules are self-explanatory, with the former ensuring persistence and the
latter handling proxying. The silivaccine module is used to remove competing
malware, ensuring exclusive control over the device. The most crucial module is
zombie, which implements the full botnet functionality. It reports sensitive
information from the compromised device to the C2 and awaits commands from the
C2, supporting six types of DDoS attacks, scanning, reverse shell, and other
functions.


0X00: STRING DECRYPTION

Zergeca uses XOR encryption for many sensitive strings. Using IDA, we found that
the XOR key is referenced 240 times across various functions. Each decryption
involves two uses of the XOR key: one for initialization and one for decryption.
So there are 120 decryption operations needed.


The XOR key is initially set to EC 22 2B A9 F3 DD DF 1C CD 46 AC 1E, but only
the first six bytes (EC 22 2B A9 F3 DD) are used.



Manually decrypting 120 times is impractical. Although the decryption process
isn't confined to a single function, CFG analysis revealed a specific pattern in
most decryption-related code blocks:

 1. The XOR block has one predecessor and one successor.
 2. The predecessor block's first instruction is mov, with the first operand
    being an address pointing to the original length of the XOR key.
 3. The successor block's first instruction is cmp, with the first operand being
    a number indicating the ciphertext's length.
 4. The predecessor block's predecessor's first instruction is lea, with the
    first operand being an address pointing to the ciphertext's starting
    address.



By identifying these patterns, we can automate the decryption process and
restore all encrypted strings efficiently.We implemented IdaPython decryption
script in the Appendix with the following results: 111 successful decryptions
and 9 mismatches.



The 9 mismatched codes are distributed across six functions. Among them, the
packets__Cursor Read/WriteString functions handle network packet
encryption/decryption and can be ignored.

gomi_bot_zombie__Zombie_Connect
geomi_common_utils_init_0_func1, 
geomi_bot_discovery_Run, 
geomi_common_packets__Cursor_WriteString,
geomi_common_packets__Cursor_ReadString, 
geomi_common_utils_RandomUserAgent


For the remaining four functions, the issue was that the ciphertexts were arrays
rather than single entries, causing the pattern match to fail. For example, in
the RandomUserAgent function, the user_agent_list contains 1000 encrypted user
agents.



For such cases, we can use the manual_decode function, where the first parameter
is the starting address of the ciphertext array and the second parameter is the
number of array elements.

ey=b"\xEC\x22\x2B\xA9\xF3\xDD"

def manual_decode(base,cnt):
    for i in range(cnt):
        start=idc.get_qword(base)
        addr=idc.get_qword(start+i*16)
        size=idc.get_qword(start+8+i*16)
        buff=idc.get_bytes(addr,size)
        out=bytearray()
        for k,v in enumerate(buff):
            out.append(v ^ key[k%6])
        print(out.decode())

manual_decode(0x000000000C56FA0,1000)  #user agent
manual_decode(0x0000000000C56F80,0xc)  #opennic dns
manual_decode(0x000000000C56C40,2) # c2


Decrypted examples include various user agents, OpenNIC DNS server, and C2s.



With all strings successfully decrypted, we can now begin reverse-engineering
Zergeca's various functionalities.


0X01: PERSISTENCE MODULE

Zergeca achieves persistence on compromised devices by adding a system service
geomi.service. This service ensures that the Zergeca sample automatically
generates a new geomi process if the device restarts or the process is
terminated.

[Unit]
Description=
Requires=network.target
After=network.target
[Service]
PIDFile=/run/geomi.pid
ExecStartPre=/bin/rm -f /run/geomi.pid
ExecStart=/usr/bin/geomi
Restart=always
[Install]
WantedBy=multi-user.target



EXPERIMENT A

When running the Zergeca sample on a virtual machine and restarting the device,
geomi.service automatically launches the Zergeca sample. The resulting process
named geomi had a PID of 897. Terminating this process with kill -9 897
immediately spawned a new geomi process with PID 8460.



When network administrators discover a geomi process and suspicious traffic on a
device, they can attempt the following cleanup steps:

 1. Delete /etc/systemd/system/geomi.service
 2. Delete the sample file referenced by the ExecStart parameter
 3. Terminate the geomi process


0X2: SILIVACCINE MODULE

To monopolize the device, Zergeca includes a list of competitor threats,
covering miners, backdoor trojans, botnets, and more. Some familiar names on the
list include mozi, kinsing, and various mining pools. Zergeca continuously
monitors the system and terminates any process whose name or runtime parameters
match those on the list, deleting the corresponding binary files.

Mozi.a com.ufo.miner kinsing kthreaddi kaiten srv00 meminitsrv .javae solr.sh
monerohash minexmr c3pool crypto-pool.fr f2pool.com xmrpool.eu .........


EXPERIMENT B

We renamed the system program /bin/sleep to Mozi.a and ran it. The Mozi.a
process was killed, and the corresponding binary file was deleted.




0X3: ZOMBIE MODULE

Zergeca resolves the C2 IP address using the geomi_common_utils_Resolve
function, which supports four resolvers: Public DNS, Local DNS, DoH (DNS over
HTTPS), and OpenNIC.



Zergeca prioritizes two DoH resolvers, masking C2 domain resolution in DNS
traffic.

https://cloudflare-dns.com/dns-query
https://dns.google/resolve




After obtaining the C2 IP, the bot reports device sensitive information
encapsulated in a DeviceInfo structure, including details like "country, public
IP, OS, user groups, runtime directory, and reachability".

struct DeviceInfo
{
Country string
PlucAddress byte[]
MAC string
OS string
ARCH string
Name string
MachineId string
Numcpu uint32
CPUMODEL string
username string
uid string
gid string
Users []string
Uptime time.Duration
PID	uitn32
Path string
checksum []uint8
version string
Reachable bool
}


The bot then awaits commands from the C2, processing them with different
handlers.



The supported functions are as follows:

ID Task 0x01 Proxy 0x02 Reverse Shell 0x03 FileTransfer 0x05 Self-update 0xa0
DDoS 0xb0 Stop Discovery 0xb1 Start Discovery

The DDoS functionality supports the following seven attack vectors:

Sub-ID Attack Vector 1 minecraft 2 httpPPS 3 synFlood 4 ackFlood 5 pushFlood 6
rstFlood 7 pushOVHFlood


COMMUNICATION PROTOCOL

Zergeca uses smux for Bot-C2 communication. Smux(Simple MUltipleXing) is a
Golang multiplexing library that relies on underlying connections like TCP or
KCP for reliability and ordering, providing stream-oriented multiplexing. Smux
packets feature an 8-byte header: VERSION(1B) | CMD(1B) | LENGTH(2B) |
STREAMID(4B) | DATA(LENGTH).

From an analysis perspective, only the LENGTH and DATA fields are of primary
concern. The captured traffic includes various messages such as online status,
device information reporting, command 0xb0, and heartbeat messages.



Online Message:

 * Length: 0x04 bytes
 * Content: Hardcoded 13 3a 12 79

Device Info Report:

 * Length: 0xd5 bytes (varies by device)
 * Content (excluding IP): XOR encrypted with key EC 22 2B A9 F3 DD
 * Decrypted DeviceInfo as follows
   
   pos: 0x4 len: 0x2 <----> b'JP'
   pos 0x7 len: 4 <----> 45.14.XX.XX
   pos: 0xc len: 0x11 <----> b'72:ba:29:e9:b8:08'
   pos: 0x1f len: 0x5 <----> b'linux'
   pos: 0x26 len: 0x5 <----> b'amd64'
   pos: 0x2d len: 0x6 <----> b's22262'
   pos: 0x35 len: 0x20 <----> b'b19642a3c672d4f20cbdb5b1569bf98f'
   pos: 0x5b len: 0x29 <----> b'Intel(R) Xeon(R) CPU E5-2678 v3 @ 2.50GHz'
   pos: 0x86 len: 0x4 <----> b'root'
   pos: 0x86 len: 0x4 <----> b'root'
   pos: 0xa2 len: 0x2 <----> b'\x92\xf1'
   pos: 0xa6 len: 0xe <----> b'/usr/bin/geomi'
   pos: 0xb6 len: 0x14 <----> b'r\xbd>\xcfY\x15[\xd9]\xa4\xe7m\x86\x9f\xbf\x895\xaa\x19\xe8'
   pos: 0xcc len: 0x7 <----> b'0.0.01c'
   

Command 0xb0 Message:

 * Length: 0x08 bytes
 * Function: Stop scanning

Heartbeat Message:

 * Length: 0x03 bytes
 * Content: ff 00 00

Let's take a look at the DDoS-related packets. The format is cmd (1 byte) +
length (2 bytes) + sub_cmd (1 byte) + target_info (length-1), where cmd is 0xa0,
indicating a DDoS command, and sub_cmd is 0x4, indicating an ACK flood attack.
The target_info field focuses on the first 4 bytes, which represent the target
IP. For example, 1f 06 10 21 corresponds to the IP address 31.6.16.33.



When the Bot receives the aforementioned command, the resulting attack traffic
aligns perfectly with our analysis.




EXPERIMENT C

Based on our network protocol analysis, we implemented a fake C2 to control the
Bot and observe its behavior upon receiving different commands. In this
experiment, we sent the Bot a 0xb1 command, which is to "start scanning."



Upon receiving this command, the Bot immediately began scanning 16 ports on
randomly generated IP addresses.




SUMMARY

Through reverse analysis, we gained initial insights into Zergeca's author. The
built-in competitor list shows familiarity with common Linux threats. Techniques
like modified UPX packing, XOR encryption for sensitive strings, and using DoH
to hide C2 resolution demonstrate a strong understanding of evasion tactics.
Implementing the network protocol with Smux showcases their development skills.
Given this combination of operational knowledge, evasion tactics, and
development expertise, encountering more of their work in the future would not
be surprising.

This is our basic intelligence of Zergeca. We welcome unique insights from other
companies, such as Init Access. And readers can contact us on Twitter for more
details.


IOC


SAMPLE

23ca4ab1518ff76f5037ea12f367a469
9d96646d4fa35b6f7c19a3b5d3846777
d78d1c57fb6e818eb1b52417e262ce59
604397198f291fa5eb2c363f7c93c9bf

f68139904e127b95249ffd40dfeedd21
d7b5d45628aa22726fd09d452a9e5717
6ac8958d3f542274596bd5206ae8fa96

pathced with "xlab" at the end of file
980cad4be8bf20fea5c34c5195013200

sample captured on 2024.06.19, support ddos vector 7
60f23acebf0ddb51a3176d0750055cf8




DOMAIN

ootheca.pw
ootheca.top
bot.hamsterrace.space



IP

84.54.51.82	The Netherlands|None|None	AS202685|Aggros Operations Ltd.



APPENDIX


IDAPYTHON SCRIPT

# Test script, only for 23ca4ab1518ff76f5037ea12f367a469
# Modidy keyaddr,sizeaddr in your case

def decode(buf):
    key=b"\xEC\x22\x2B\xA9\xF3\xDD"
    out=bytearray()
    for i in range(len(buf)):
        out.append(buf[i]^key[i%6])
    return out
    
count=0
notcount=0
failedfunc=[]
successedfunc=[]

keyaddr=0x0000000000C56FC0
sizeaddr=0x0000000000C56FC8

refs=XrefsTo(keyaddr, flags=0)
for ref in refs:
    f_blocks = idaapi.FlowChart(idaapi.get_func(ref.frm), flags=idaapi.FC_PREDS)
    for blk in f_blocks:
        if blk.start_ea!=ref.frm:
            continue
        if len(list(blk.preds()))!=1 and len(list(blk.succs()))!=1:
            continue
        predblk=list(blk.preds())[0]
        succsblk=list(blk.succs())[0]
        
        if idc.get_operand_value(predblk.start_ea,1)!=sizeaddr:
            
            continue
        if idc.get_operand_type(succsblk.start_ea,1)!=0x5:
            print(idc.get_func_name(ref.frm),hex(ref.frm),"not matched")
            notcount+=1
            failedfunc.append(idc.get_func_name(ref.frm))
            continue
        ppredblk=list(predblk.preds())
        if len(ppredblk)!=1:
            continue
        addr=idc.get_operand_value(ppredblk[0].start_ea,1)
        size=idc.get_operand_value(succsblk.start_ea,1)
        buf=idc.get_bytes(addr,size)
        out=decode(buf)
        count+=1
        print(idc.get_func_name(ref.frm),hex(ppredblk[0].start_ea),"matched, ciphertext at", hex(addr), "<---->",bytes(out))
        successedfunc.append(idc.get_func_name(ref.frm))

print("\n--------------------Statistic--------------------")
print(f'Success:{count},Failed:{notcount}\n')
print("---------Success Function---------")
print(set(successedfunc),'\n')
print("---------Failed Function---------")
print(set(failedfunc),'\n')




Please enable JavaScript to view the comments powered by Disqus.
奇安信 X 实验室 © 2024
 * RSS

Powered by Ghost