双头龙(RotaJakiro)，一个至少潜伏了3年的后门木马

版权


版权声明：本文为Netlab原创，依据 CC BY-SA 4.0 许可证进行授权，转载请附上出处链接及本声明。



概述


2021年3月25日，360 NETLAB的BotMon系统发现一个的VT 0检测的可疑ELF文件(MD5=64f6cfe44ba08b0babdd3904233c4857)，它会与4个业务类型截然不同的域名进行通信，端口均为TCP 443（HTTPS），但流量却并非TLS/SSL类型，这个异常行为引起了我们的兴趣，进一步分析发现它是一个针对Linux X64系统的后门木马，该家族至少已经存在3年但目前还是0检测。基于该家族使用rotate加密，并且运行后对root/non-root账户有不同的行为，犹如一只双头龙，一体双向，我们将它命名为RotaJakiro。


RotaJakiro隐蔽性较强，对加密算法使用比较多，包括：使用AES算法加密样本内的资源信息；C2通信综合使用了AES，XOR，ROTATE加密和ZLIB压缩算法。指令方面，RotaJakiro支持12种指令码，其中3种是和特定plugin相关的，遗憾的是目前我们并没有捕获到这类payload，因此并不知道它的真正目的。从广义的后门角度来看，RotaJakiro支持的功能可以归纳成以下4类：


 * 上报设备信息
 * 窃取敏感的信息
 * 文件/Plugin管理(查询，下载，删除)
 * 执行特定的Plugin


当所有分析结束后，我们尝试对RotaJakiro进行溯源，根据解密后的资源以及编码的风格的相似性，我们推测它是Torii Botnet作者的又一作品。



潜伏了多少？


我们从捕获的样本出发，寻找RotaJakiro同源者，最终发现了以下4个样本，它们在VT上都是0检测，从VT的First Seen时间来看，RotaJakiro至少已经存在了3年。





FileName
MD5
Detection
First Seen in VT




systemd-daemon
1d45cd2c1283f927940c099b8fab593b
0/61
2018-05-16 04:22:59


systemd-daemon
11ad1e9b74b144d564825d65d7fb37d6
0/58
2018-12-25 08:02:05


systemd-daemon
5c0f375e92f551e8f2321b141c15c48f
0/56
2020-05-08 05:50:06


gvfsd-helper
64f6cfe44ba08b0babdd3904233c4857
0/61
2021-01-18 13:13:19





这批样本都内嵌了以下4个C2，目前它们在VT上也是0检测。这4个C2域名有非常接近的Crteated Updated Expired时间，我们推测它们一直以来用于同一个业务，从这个角度来看，RotaJakiro背后的团伙至少已经活动了6年。





Domain
Detection
Created
Last Updated
Expired




news.thaprior.net
0/83
2015-12-09 06:24:13
2020-12-03 07:24:33
2021-12-09 06:24:13


blog.eduelects.com
0/83
2015-12-10 13:12:52
2020-12-03 07:24:33
2021-12-10 13:12:52


cdn.mirror-codes.net
0/83
2015-12-09 06:24:19
2020-12-03 07:24:32
2021-12-09 06:24:19


status.sublineover.net
0/83
2015-12-09 06:24:24
2020-12-03 07:24:32
2021-12-09 06:24:24






逆向分析


4个RotaJakiro样本，时间分布从2018到2021，它们的功能非常接近，本文选取2021年的样本为分析对象，它的基本信息如下:


MD5:64f6cfe44ba08b0babdd3904233c4857
ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, stripped
Packer:No



从编码层面来说，RotaJakiro采用了动态AES，双层加密的通信协议等技术来对抗安全人员的二进制&网络流量分析。


从功能层面来说，RotaJakiro运行时首先会判断当时用户是root，还是non-root，不同的账户有不同的执行策略，然后使用AES&ROTATE解密出相关的敏感资源供后续的持久化，进程守护和单一实例使用，最后和C2建立通信，等待执行C2下发的指令。


下文将从上述角度出发剖析RotaJakiro具体实现。



样本对抗技巧


 * 
   
   
   动态生成AES加密算法所需的常量表，防止算法被直接识别
   
   

 * 
   
   
   使用stack strings obfuscation技术存储加密的敏感资源信息
   
   

 * 
   
   
   使用双层加密的网络通信
   



加密算法


RotaJakiro中所有的敏感资源都是加密的，在IDA中我们可以看出解密方法dec_proc调用了60次，它是由AES，Rotate俩部分组成。



AES解密入口如下所示：



其中aes_dec的采用的是AES-256, CBC模式，key&iv都是硬编码。


 * 
   
   
   key
   
   
   14 BA EE 23 8F 72 1A A6 00 00 00 00 00 00 00 00 
   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
   
   

 * 
   
   
   iv
   
   
   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
   
   


Rotate解密入口如下所示：



所谓Rotate即循环移位，可以看出此处使用的循环左移，其中移位的次数由plain_len(明文长度)&7的值决定。


以解密以下C2密文为例：


ff ba a2 3b cd 5b 7b 24 8c 5f e3 4b fc 56 5b 99 
ac 91 cf e3 9a 27 d4 c9 6b 39 34 ce 69 ce 18 60



其与解密相关的各种参数如下图所示，密文长度为32字节，明文长度为26字节



首先使用AES解密后得到以下“次级密文”：



然后从次级密文中取出有效密文，其中有效密文从第8字节开始，长度为明文长度减8，此处即为26-8=18字节。


98 1B DB D9 8B 59 19 5D 59 1B 59 D8 1D DC 8B D8 
DB 5B



最后通过明文长度26可以计算26&7=2，得到移位的次数，将上述有效密文逐字节左移2位，就能得到C2明文。


blog.eduelects.com




持久化


RotaJakiro在实现持久功能时，对root/non-root用户做了区分，不同的账号采用了不同的技术。



针对root账号的持久化实现


 * 
   
   
   根据不同Linux系统发行版本，创建相应的自启动脚本/etc/init/systemd-agent.conf或者/lib/systemd/system/systemd-agent.service。
   
   
   Content of systemd-agent.conf
   -----------------------------
   #system-daemon - configure for system daemon
   #This service causes system have an associated
   #kernel object to be started on boot.
   description "system daemon"
   start on filesystem or runlevel [2345]
   exec /bin/systemd/systemd-daemon
   respawn 
   
   
   
   Content of systemd-agent.service
   -----------------------------
   [Unit]
   Description=System Daemon
   Wants=network-online.target
   After=network-online.target
   [Service]
   ExecStart=/usr/lib/systemd/systemd-daemon
   Restart=always
   [Install]
   
   

 * 
   
   
   用于伪装的文件名，俩者2选1
   
   
   /bin/systemd/systemd-daemon
   /usr/lib/systemd/systemd-daemon
   
   



针对non-root账号的持久化实现


 * 
   
   
   创建桌面环境的自启动脚本$HOME/$.config/autostart/gnomehelper.desktop
   
   
   [Desktop Entry]
   Type=Application
   Exec=$HOME/.gvfsd/.profile/gvfsd-helper
   
   

 * 
   
   
   修改.bashrc文件，创建shell环境的自启动脚本
   
   
   # Add GNOME's helper designed to work with the I/O abstraction of GIO
   # this environment variable is set, gvfsd will not start the fuse filesystem
   if [ -d ${HOME} ]; then
           ${HOME}/.gvfsd/.profile/gvfsd-helper
   fi
   
   

 * 
   
   
   用于用于伪装的文件名，俩者同时存在
   
   
   $HOME/.dbus/sessions/session-dbus
   $HOME/.gvfsd/.profile/gvfsd-helper
   
   



进程守护


RotaJakiro实现了进程守护以保护自身的运行，和持久化一样，对root/non-root用户有不同的实现方式。



针对root账号的进程守护实现


在root账号下运行时，根据不同Linux系统发行版本，通过向服务的配置文件中写入Restart=always或者respawn，当服务进程被结束时，会自动创建新进程。



实际效果如下图所示，可以看到systemd-daemon进程被结束后，立马就生成了新进程。




针对non-root账号的进程守护实现


在non-root账号下运行时，RotaJakiro生成session-dbus和gvfsd-helper俩个进程，它们监控着彼此的存活，当其中一方被结束时，另一方将其恢复，这是非常典型的双进程保护。


RotaJakiro的双进程保护是如何实现的呢？首先以shmget API创建一片共享内存，session-dbus和gvfsd-helper通过这片共享内存实现进程间通信，告诉对方的自己的PID。然后通过/proc/[PID]目录，动态地获取进程的存活情况。当发现对方进程死亡时，通过execvp创建进程，帮助死亡一方“复活”，大致流程如下图所示：



实际效果如下图所示，可以看到session-dbus和gvfsd-helper被kill -9结束后，新进程立马就创建了。




单一实例


RotaJakiro通过文件锁来实现单一实例，具体实现如下所示：



其中用到的lockfile在root/non-root账号下有所不同。


 * 
   
   
   root下的lockfile，2选1
   
   
   /usr/lib32/.X11/X0-lock
   /bin/lib32/.X11/X0-lock
   
   

 * 
   
   
   non-root下的lockfile，同时存在
   
   
   $HOME/.X11/X0-lock
   $HOME/.X11/.X11-lock
   
   


以实际中non-root账号为例，通过/proc/locks可以将进程以及文件锁对应起来，此时再执行对应的RotaJakiro的样本，可以看到并不会有对应的新进程创建。





网络通信


RotaJakiro通过以下代码片段和C2建立通信，等待执行后续指令：



这个过程可以分成2个阶段


 * Stage 1，初始化阶段：解密出C2列表，和C2建立连接，发送上线信息，接回并解密C2返回的信息。
 * Stage 2，业务阶段：验证C2的返回信息，若通过验证，执行C2后续下发的指令。



Stage 1：初始化


通过前文所述的解密算法解密出C2列表，目前样本中内置了以下4个C2：


news.thaprior.net
blog.eduelects.com
cdn.mirror-codes.net
status.sublineover.net



RotaJakiro首先会尝试和它们建立连接，然后通过以下代码片段构造上线信息，



接着将上线信息加密并发送给C2



最后接收C2的回包，解密并校验其合法性，若通过校验，进入Stage 2。




Stage 2：具体业务


通过以下代码片段接收并执行C2下发的指令：



目前RotaJakiro一共支持12条指令，指令码与功能的对应关系如下表所示：





CmdId
Function




0x138E3E6
Exit


0x208307A
Test


0x5CCA727
Heartbeat


0x17B1CC4
Set C2 timeout time


0x25360EA
Streal Senstive Info


0x18320e0
Upload Device Info


0x2E25992
Deliver File/Plugin


0x2CD9070
Query File/Plugin Status


0x12B3629
Delete File/Plugin Or Dir


0x1B25503
Run Plugin_0x39C93E


0x1532E65
Run Plugin_0x75A7A2


0x25D5082
Run Plugin_0x536D01





其中Run Plugin功能复用相同的代码，通过以下逻辑实现函数调用：




我们目前被没有捕获到这类payload，因此用Plugin_“参数”的形式来表示不同的任务。


RotaJakiro的网络通信包如下图所示，由head，key，payload三部分组成，其中header是必须的，长度为82字节，而body&payload部分是可选的。head&key采用的XOR&Rotate加密，payload采用AES&ZLIB加密压缩。



下面我们将通过BOT与C2的一轮交互，来说明网络流量head&key&payload的组成以及解密过程。



C2 -> BOT



读取前0x52字节，就是head的内容。head如何解密呢？方法很简单，逐字节左移3位，然后和0x1b异或即可，解密后得下以内容：


00000000  16 11 10 b9 03 b1 0c fb 04 20 00 00 00 08 00 e0  |...¹.±.û. .....à|
00000010  20 83 01 c2 20 64 20 01 e2 00 00 00 00 c2 0c 00  | ..Â d .â....Â..|
00000020  00 00 32 42 36 39 33 33 34 46 38 34 31 44 30 44  |..2B69334F841D0D|
00000030  39 46 41 30 36 35 38 45 43 33 45 32 39 46 41 44  |9FA0658EC3E29FAD|
00000040  34 39 c8 53 e6 9c 48 c4 8b 77 24 2e 02 1c 96 d9  |49ÈSæ.HÄ.w$....Ù|
00000050  81 28
------------filed parse------------------
offset 0x09, 4 bytes--->payload length
offset 0x0d, 2 bytes--->body length
offset ox0f, 4 bytes--->cmdid



通过字段解析，可知key的长度为0x8字节，payload的长度为0x20字节，要执行的指令码为0x18320e0，即上报设备信息。


从偏移0x52读取8字节就得到了keyea 9a 1a 18 18 44 26 a0，使用和head一样的解密方法，得到4c cf cb db db 39 2a 1e，它是作为AES的密钥来解密payload。


从偏移0x5a读取32字节，就得到了下面的payload：


54 c1 c3 69 00 18 31 e4 a2 5b 10 7f 67 ab d1 4b 
b2 7b 3d 3f b3 bc 66 6a 26 f6 f6 b3 f7 2e 66 6d



使用解密后的key做为AES-256的密钥，以CBC模式解密以上数据得下以下内容：


3b c7 f8 9b 73 2b d1 04 78 9c e3 60 60 60 d8 df d9 c1 71 56 f7 6f 00 00 13 80 04 28



第8字节起即为ZLIB压缩数据，解压得到如下内容：


08 00 00 00 bf 89 88 08 cd 2d fd 50
------------filed parse------------------
offset 0, 4 bytes--->length



解压后的payload有什么用呢？它是做为新的AES密钥，用来解密部分敏感资源信息。

例如Bot在收集设备信息时，有一项是当前操作系统发行版本的信息，它是通过cat /etc/*release | uniq命令实现的。


root@debian:~# cat /etc/*release | uniq
PRETTY_NAME="Debian GNU/Linux 9 (stretch)"
NAME="Debian GNU/Linux"
VERSION_ID="9"
VERSION="9 (stretch)"
ID=debian
HOME_URL="https://www.debian.org/"
SUPPORT_URL="https://www.debian.org/support"
BUG_REPORT_URL="https://bugs.debian.org/"



cat /etc/*release | uniq这条命令正是以下密文通过新的AES密钥配合下图中的参数解密而来。


cmd ciphertxt
---------------------------
74 00 dd 79 e6 1e aa bb 99 81 7e ca d9 21 6b 81 
6b d9 9d 14 45 73 6a 1c 61 cc 28 a3 0f 2b 41 5a 
6b 33 8c 37 25 89 47 05 44 7e f0 6b 17 70 d8 ca





Bot -> C2


当BOT接收到C2的“上报设备信息”指令后，会向C2发送以下数据，可以看出key部分的值依然是ea 9a 1a 18 18 44 26 a0。



从上文已知解密后key值为4c cf cb db db 39 2a 1e，通过这个值将Bot发往C2的payload解密解压后，得到如下数据，正是设备的各种信息，其中有前文提到的通过cat /etc/*release | uniq获取的信息，验证了我们的分析是正确的。




与Torii Botnet团伙的关系


Torii僵尸网络于2018年9月20日被友商Avast曝光，对比RotaJakiro，俩者的相似之处体现在以下3方面：



1：字符串相似性


RotaJakiro&Torii的敏感资源解密后，我们发现它们复用了大量相同的命令。


1：semanage fcontext -a -t bin_t '%s' && restorecon '%s'
2：which semanage
3：cat /etc/*release 
4：cat /etc/issue
5：systemctl enable
6：initctl start
...




2：流量相似性


在构造流量的过程中，大量使用常数，构造方式非常接近。




3：功能相似性


从安全研究人员进行逆向工程的角度来说，RotaJakiro&Torii有着相常相似的风格：使用加密算法隐藏敏感资源，都实现了相当old-school式的持久化，结构化的网络流量等。


基于这些考量，我们推测RotaJakiro和Torii出自同一个团伙之手。



冰山一角


至此RotaJakiro的逆向与溯源告一段落，但真正的工作远没结束，有许多问题依然没有答案：“RotaJakiro是怎么传播的,它的目的是什么？”，“RotaJakiro是否有特定的攻击目标，是不是APT？”，“RotaJakiro与Torii背后的黑手是谁？”......由于我们的视野有限，目前只能向安全社区分享这么多。如果社区有相关的线索，欢迎与我们联系，让我们一起Make Cyber Security Great Again。



联系我们


感兴趣的读者，可以在 twitter 或者通过邮件netlab[at]360.cn联系我们。



IOC


Sample MD5


1d45cd2c1283f927940c099b8fab593b
11ad1e9b74b144d564825d65d7fb37d6
5c0f375e92f551e8f2321b141c15c48f
64f6cfe44ba08b0babdd3904233c4857



C2


news.thaprior.net:443
blog.eduelects.com:443
cdn.mirror-codes.net:443
status.sublineover.net:443



IP


176.107.176.16 Ukraine|Kiev|Unknown 42331|PE_Freehost