静默之控:主动与被动双模后门MystRodX的隐匿渗透

背景介绍

2025年6月6日,Xlab大网威胁感知系统监测到 IP 139.84.156.79正在传播一个VT低检测 4/65,名为dst86.bin的可疑ELF文件。多引擎检测模块将该文件标识为MIRAI僵尸网络,但AI研判模块却没有给出相应的结果。这个“异常”引起了我们的兴趣,经过分析确认它是Dropper,最终会释放出一个全新的后门木马,和Mirai完全无关,多家杀软将其标记为Mirai是不准确的。基于其传僠中使用的文件名dst,释放样本中的类名cmy_,多种形式的Xor算法,我们将它命名为MystRodX。

MystRodX是一个由c++语言实现的典型后门木马,支持文件管理,端口转发,反弹SHELL,sockets管理等功能。相较于一般的后门,MystRodX在隐匿性,灵活性俩方面具有非常鲜明的特点。其中隐匿性体现在对于不同级别敏感信息采用了差异化加密策略:

  1. 虚拟机&调试器检测等相关敏感字符串使用单字节xor加密
  2. AES密钥,Payload,激活报文使用自定义的Transform算法加密
  3. 配置文件使用AES CBC模式加密

灵活性则是MystRodX会根据不同的配置动态开启不同的功能特性,比如网络协议使用TCP或HTTP,流量直接使用明文或AES加密等。其中最有意思的是支持被动唤醒的触发模式,即MystRodX可配置成被动式后门,在不使用开放端口的情况下由特定的DNS或ICMP网络报文激活

MystRodX的配置中存在一项用于设定后门生效时间的选项。在已捕获的样本中,该选项所设置的最早时间为2024年01月07日 23:10:20,表明该后门在真实网络中已潜伏超过20个月,且一直未被安全社区准确识别。此外,基于奇安信网络空间测绘鹰图平台的C2探测服务,发现了3个仍在活跃的C2服务器,并以技术手段确认在野还存在未被捕获的样本。再考虑到该后门所采用的被动通信机制所带来的高隐蔽特性,我们决定撰写本文,公开相关研究成果,以揭示这一长期存在的威胁,为增强网络安全防御能力提供支持。

被动后门模式

当配置中Backdoor Type选项的值为1时,MystRodX开启被动后门模式,它使用RAW SOCKET监听网络流量,可在不使用开放端口的情况下,被特定的DNS或ICMP网络报文激活

dst_rawsock.png

激活报文采用了Appendix章节中的Transform算法加密,解密后的格式为 Magic(4字节)+ Protocol(4字节)+ Port(4字节)+ C2。当 Magic 值比对通过后,MystRodX 便会根据报文中指定的协议类型与C2建立通信,等待接收攻击者的后续指令。

dst_connect.png

不同于知名的SYNfull Knock后门完全利用TCP协议内部字段以传递指令,MystRodX使用的是一种更为简单的方式,即激活指令隐藏在ICMP 载荷或DNS请求的域名中。

0x1: DNS激活报文

首先看一下DNS激活报文,有效的激活报文必须是www.DomainName.com这种格式。

dst_dnspacket_mask.png

DomainName {9-bytes mask}UBw98KzOQyRpoSgk5+ViISKmpC6ubi7vao= 使用base64解码后得到以下密文:

00000000: C5 E4 F2 A7 11 73 DD 40  70 F7 C2 B3 39 0C 91 A6  .....s.@p...9...
00000010: 84 A0 93 9F 95 88 84 8A  9A 90 BA B9 B8 BB BD AA  ................

使用Transform算法,magic参数为0x0d,magic2参数为密文的最后一字节 0xaa, key参数为key_for_backdoor进行解密,即可得到以下明文。
dst_dnspayload.png

按照激活报文格式对明文进行解析,可知

  • Magic值为CAT
  • 协议类型为TCP
  • 端口为0x1f4a,即8010
  • C2为149.28.137.254

当Magic通过比对之后,MystRodX就与C2 149.28.137.254:8010建立通信,等待执行其下发的指令。
dst_dnsc2.png

0x2: ICMP激活报文

接着看一下ICMP激活报文,这次我们从正向的角度,构造报文,观察样本的行为。

首先构造一个简单的ICMP ping请求 08 00 00 00 30 39 00 01, 接着构造PAYLOAD,指定C2为192.168.96.1,端口为443,协议使用HTTP。

00000000: 43 41 54 00 01 00 00 00  BB 01 00 00 31 39 32 2E  CAT.........192.
00000010: 31 36 38 2E 39 36 2E 31  00 00 00 00 00 00 00 00  168.96.1........
00000020: 00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  ................

然后使用Transform算法,magic2参数设为0x9f对Payload进行加密。最终将ICMP 与 Payload合并,形成以下的ICMP报文。

dst_icmppacket_mask.png

当MystRodX收到该ICMP报文后,就会与192.168.96.1:443建立通信连接,发送HTTP格式的上线报文。这和我们的预期完全一致,验证了分析的正确性。
dst_proof.png

深入挖掘

在目前捕获的俩个MystRodX样本中,其配置的C2服务器均未开放有效端口。完成逆向分析后,我们面临一个关键问题:MystRodX究竟是一个仍在活跃的威胁,还是已被彻底废弃?为回答这一问题,我们依托于奇安信网络空间测绘鹰图平台分别从BOT端和C2端进行了一些尝试。

0x1: 唤醒BOT

我们尝试在全网范围内发送DNS/ICMP激活报文,意图唤醒处于被动模式的MystRodX后门,从而定位潜在受害者。遗憾的是,除我们自己的测试IP外,并未收到任何有效上线响应。造成这一现象的原因可能包括:在野MystRodX样本并未启用被动后门模式,或者样本使用了新的密钥、Magic值等配置,导致我们发出的激活报文未能匹配生效。

0x2: 探测C2

借助活跃C2探测服务的支持,我们成功发现3个仍在活跃的在野C2服务器。这些服务器对上线报文做出了响应,向Bot回复7号指令,要求开启流量加密。它们从2024年活跃至今,证明了MystRodX威胁的持续存在。

dst_newc2.png

在MystRodX的配置项中包含一组RSA公钥,用于解密7号指令。攻击者通常会在不同活动中部署不同的公钥,目前已发现的两个公钥分别用于“neybquno”和“zoufkcfr”活动。在7号指令报文中,偏移0x110处长度为256字节的部分为MagicString的密文。只有当该密文经解密后得到的MagicString与样本中硬编码的字符串 0x68abut 完全一致时,MystRodX才会尝试开启流量加密。利用这一特性,可以判断某个C2是否用于已知的攻击活动。

dst_magicstring.png

在新捕获的三个活跃C2服务器中,仅149.28.137.254下发的7号指令能够被已知公钥成功解密。这一现象表明,另外两个C2(156.244.6.68与185.22.153.228)应归属于某次尚未知晓的攻击活动,意味着当前在野环境中肯定存在尚未被捕获的MystRodX样本

检测分析

在俩个月内MystRodX的样本检测率稍有提高,目前已升至6/65,主流标签依然是Mirai。

dst_vt.png

我们推测部分杀软使用Mirai这个标签,是因为样本使用了Mirai经典的单字节Xor的方式加密与虚拟机,调试器相关的字符串。

vmware vbox phoenix
innotek lldb strace

尝试俩种patch方式:一种是移除样本中虚拟机,调试器相关的加密字串;另一种是使用明文替换对应的密文。杀软对Patch之后文件的检测让人诧异,结果表明这俩种patch方式都有效的降低了检测率。然而这些字串实际上和样本的核心功能完全无关,这说明社区并未真正的识别MystRodX这一威胁

dst_patch.png

Dropper分析

0x1: 字串解密

MystRodX使用单字节Xor对敏感的字符串进行加密保护,解密方法很简单:密文最后一字节为xor密钥,将其与密文逐字节进行xor即可。如密文\x13\x08\x12\x04\x17\x00\x65,它的密钥为0x65,解密后为vmware\x00。为了分析的方便,可以使用Idapython脚本实现批量解密。

dst_decodestring.png

效果如下所示:

dst_strings.png

解密出的字符串可以分成3大类,分别用于虚拟机检测,调试检测,启动Launcher等功能。

  1. VM相关:检查/sys/class/dmi/id/bios_vendor的内容是否包含vmware,vbox,Phoneix,innotek来判断当前是否处于虚拟化环境
  2. Debugger相关:检查父进程名是否匹配常见调试工具gdb,lldb,ltrace,strace判断当前进程是否被调试
  3. Launcher相关:下一阶段Launcher文件名,pid文件,以及工作目录

0x2: Payload解密

解密Payload前需要预先设置一个keyinfo的结构体,其中key1的值为0x13,xorkey由样本硬编码,长度为32字节。

struct keyinfo
{
  uint8_t key1;
  uint8_t unknow[3];
  void *xorkey;
  uint16_t xorkey_len;
  uint8_t key2;
  uint8_t notused;
};

xorkey 
00000000  02 06 03 09 04 02 0e 0a 01 0f 08 0a 04 0d 0b 09  |................|
00000010  0a 09 01 03 06 05 6d 0c 01 02 0f 03 03 0a 05 00  |......m.........|

而key2则是对xorkey使用类似校验和的算法计算而来,此处它的值为0x90。

dst_checksum.png

获得key1,xorkey,key2 之后使用以下代码片段解密Payload,可以看出Payload的最后一个字节也是一个密钥。这个算法在MystRodX的多个场景中重复使用,如AES密钥的解密,激活报文的解密等,我们称之为MystRodX_Transform。

dst_payload.png

经过分析后,我们使用Python实现了对该算法的模拟,详情见Appendix章节的Transform Algorithm部分。实际使用只需提供magic,magic2,以及key即可。例如解密Payload,magic为上文所说的key1 0x13,magic2为Payload的最后一字节 0xab,key使用key_for_dropper。

解密后的Payload中包含3个关键文件chargen,busybox,daytime。其中daytime为Launcher组件,负责启动chargen;chargen为核心组件MystRodX后门。Payload 的校验机制依赖 C2 0A D7 A4 22 21 5A 这一 7 字节的校验值。Dropper会比对该值,仅在匹配时才会释放 LauncherMystRodX 后门。

dst_decryptpayload.png

Launcher分析

Launcher使用相同的字串加密算法,解密后的clog,dlog用于保存MystRodX,Launcher的pid。它的核心功能是持续监控MystRodX后门进程chargen的运行状态,若发现chargen未运行,重新启动该后门进程。

dst_launcher.png

MystRodX后门分析

MystRodX是一个C++实现的典型后门,样本中的类名清晰的揭示了它支持的功能,比如文件管理,反弹shell,socks代理,端口转发等。

dst_class.png

由于篇幅限制,本文不再对常见功能展开分析,而是从主机行为,网络协议俩个方面,对MystRodX 的特色功能进行剖析,包括:

  • 双进程守护机制
  • 配置信息的解密
  • 通信协议
  • 被动后门模式

0x1: 双进程守护

MystRodX会持续监控daytime进程的运行状态。如果发现daytime未运行,MystRodX会立即启动重Launcher进程。这样,Launcher和MystRodX就形成了双进程守护机制,俩者中任意一个进程终止,都会被对方重新拉起,确保长期稳定运行。

dst_proclaunch.png

0x2: 配置解密

MystRodX的配置使用AES加密,AES密钥和Payload一样使用Transform算法保护,只不过key1,xorkey,key2的值有所不同。

key1:0xd

xorkey
00000000  00 02 07 11 13 19 04 06 16 0e 18 0b 02 2d 0b 19  |.............-..|
00000010  a0 91 02 23 96 45 6c 1c b1 d2 7f e3 22 00 00 00  | ..#.El.±Ò.ã"...|

key2:0xf1

以下是AES相关的密文与解密后的明文,AES密钥从明文的0x08偏移处开始,长度为32字节。
dst_aeskey.png
使用上述 AES 密钥,配合硬编码的 IV 0D 0F 02 04 08 07 2D 1C 01 04 0D 01 02 07 06 02,采用CBC模式即可解密配置信息,感兴趣的读者可以参阅Appendix的CyberChef。

以下为样本72d377fa8ccf23998dd7c22c9647fc2a的配置:

dst_config.png

以下为样本a46f2c771fb580e2135ab898731be9a7的配置:

dst_config_cfr.png

配置中包含活动名、时间、C2,端口,公钥等信息。下表列出了各属性及其在配置中的偏移量(注:配置因样本而异):

Offset Field
0x00 Campaign
0x08 Backdoor Type
0x0c MainC2 Port
0x10 BackupC2 Port
0x1c Interval
0x24 Effective date
0x78 Main C2
0x178 Backup C2
0x278 Public Key

当Backdoor Type等于1时,MystRodx进入被动后门模式,等待激活报文;当Backdoor Type的值不为1时,MystRodX进入主动后门模式与配置中的C2建立通信,等待执行下发的指令。目前捕获的俩个样本中的Backdoor Type的值均为0。

dst_choice.png

0x3: 网络通信

MystRodX 后门支持 TCP 和 HTTP 两种通信模式,并可配置是否启用 AES 加密。当前捕获的样本均采用 TCP 模式,且未启用加密功能。网络报文格式为Packet Length (4bytes) + Main Code(4 bytes) + Sub Code(4 bytes) + Packet Direction(4 bytes) + Data

dst_packet.png

以报文10 00 00 00 01 00 00 00 01 00 00 00 01 00 00 00为例,它表示该报文由于BOT发往C2,长度为16字节,MainCode,SubCode分别为1,其实它正是MystRodX 的上线报文。

Little Endian
10 00 00 00  --->  Pakcet length, 0x10 bytes
01 00 00 00  --->  Main Code, 0x01
01 00 00 00  --->  Sub Code, 0x01 
01 00 00 00  --->  Direction, 0x01, bot_to_c2

协议中的MainCode的值可以是:1,2,5,7,8。其中2、5、7、8分别对应反弹shell、文件管理、端口转发和socks管理功能。

dst_maincode.png

而1则表示通用管理功能,主要用于C2对Bot的控制操作,例如更新配置文件、上传设备信息等。这些操作被分配了不同的SubCode,下表为SubCode以及它们对应的功能。

SubCode Function
1 Beacon
2 Uplaod DeviceInfo
4 Heartbeat
7 Enable Traffic encryption
14 Set a new interval
15 Update Configuration
16 Teardown
19 Upload TimeInfo

以实际捕获的流量为例,当bot收到MainCode为1,SubCode为2的指令后,就会将设备信息上报给C2。
dst_deviceinfo.png

另外值得一提的是,当启用流量加密后,网络报文格式有所变化,升级为CipherText Length(4bytes)+ PlainText Length(4bytes) + padding(8bytes) + CipherText

dst_ciphertxt.png

总结

至此,我们对 MystRodX 的分析暂告一段落。以上是目前所掌握的全部相关情报,网络管理员可参考技术分析中的各项细节,以判断自身系统是否已遭受该后门入侵。

由于视野所限,我们目前尚不清楚 MystRodX 的具体入侵途径、攻击目标与真实意图。我们诚挚欢迎掌握更多信息的业内伙伴向我们提供情报,共同助力网络安全防御。

如果您对我们的研究感兴趣,或了解与该后门相关的线索,欢迎通过X平台与我们联系。

IOC

Downloader

http://139.84.156[.]79/dst-x86.bin

C2 & Campaign

airtel.vpndns.net:443   neybquno
149.28.130.195:443    zoufkcfr

149.28.137.254:8010   neybquno
149.28.137.254:8443   zoufkcfr


156.244.6.68:443    unknown
185.22.153.228:443  unknown

Sample MD5

Dropper
5e3a2a0461c7888d0361dd75617051c6 *dst
72d377fa8ccf23998dd7c22c9647fc2a *chargen
5bf67ce1b245934965557de6d37f286f *daytime


fa3b4d5fd1f6c995395244f36c18ffec *dst
a46f2c771fb580e2135ab898731be9a7 *chargen
e8fcb7f3f0edfc7d1a99918dc14527d1 *daytime
1f003437e3d10e07f5ee5f51c61c548f *networkd

Patched By Xlab
4dc20d1177da7932be3d63efe939b320
2775d9eac1c4a5eb2c45453d63ea6379
4db35e708c2d0cabe4709fa0540bafb7

Public Key

neybquno

-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAs7/nw8KnB3Ow2uUR1bNW
60UQKOI7emuau8AyCK4KqK/iUGQJzOoopLgi2D4DWrK5Wi+qtgLPt7WSTFUnMGge
XRbXdHamEasF/8kNhuv7F/CKSc+sCy/TrtLeYAQH4nuT+PhMym0aOLEwSJIuDu+4
wgUzONdgpkZZnx2h8TQmzv3LmeQWx1iOk+L4SrwbG3Cs889eWlj2O66hyT5kz6s5
6HxRjZD4V1zuWzcuoNpdqaKKA4DaraF4onYNNctIiSdkaTKPeJaim+whljmuFn8Q
y9WKcT2yogoUaUd3fkx+MPaK80R6nIEN+ooreBkf2eXXJwuTRFl1eocaUENENo5h
QwIDAQAB
-----END PUBLIC KEY-----

zoufkcfr

-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5blT2R1XzP3T0Eu0vatg
u8h15ysd+TYQWYCrm1LT9bISVx9Jhzhbf3l5oFQD/TBstZQ6hjhUZuDCczdaYZJu
4HVzhkmVKsyjLV16aG5mCbDiF/bR879jSDJMZoqZJOitAA0xQQ2FqmuOxlFkN8Ab
Rd87xcDTF/SzWcV2nj6UlNHcFilxz48kai3/lcypnIoUtnEMtkMGsRX81LVniyUm
yrvvRAA7PQqHa1qFbJSt3xY+FAzC/Iy6QbSnrMoc8FVMCDUR/YKLCDU2c3SUshUs
Xkanh6odvXOBjEKoEbaBgc3Bb2uAPdiDkEGqDiZl0yitzopA9+f+606Q5UG9CVcW
OwIDAQAB
-----END PUBLIC KEY-----

Appendix

Transform Algorithm

key_for_dropper=bytes.fromhex('02 06 03 09 04 02 0e 0a 01 0f 08 0a 04 0d 0b 09
0a 09 01 03 06 05 6d 0c 01 02 0f 03 03 0a 05 00')


key_for_backdoor=bytes.fromhex('00 02 07 11 13 19 04 06 16 0E 18 0B 02 2D 0B 19
A0 91 02 23 96 45 6C 1C B1 D2 7F E3 22 00 00 00')


def calc_sum(buf):
    checksum = 0
    for i, v in enumerate(key):
        checksum ^= (v << (i&7)) & 0xFF  
    return checksum  
    

def transform(magic,magic2,buf,key):
    
    buf_len=len(buf)-1
    key_len=len(key)
    key1=magic ^ calc_sum(key)
    
    key2=(key[(key1^buf_len)%key_len]) ^ magic2 ^ buf_len
    out=bytearray()
    for i, value in enumerate(buf):
        out.append((key[(i^key1)%key_len] ^ key2 ^ value ^ i)&0xff)

    return out

CyberChef

https://gchq.github.io/CyberChef/#recipe=AES_Decrypt(%7B'option':'UTF8','string':'z7bcjTSKrFiHYUB63NZendVvtJ2RGfo8'%7D,%7B'option':'Hex','string':'0d%200f%2002%2004%2008%2007%202d%201c%2001%2004%200d%2001%2002%2007%2006%2002'%7D,'CBC','Raw','Raw',%7B'option':'Hex','string':''%7D,%7B'option':'Hex','string':''%7D)To_Hexdump(16,false,false,false)&input=PBpcVtR8VHCFl/oaqAGk0w7pCVMMe1xQ1Ma0FBs2X2P9vpD6GrVC/b6XqKDWb/eFS/g9najMHOO3r38h3V9lZvvSKl31GM2FJbIKw91n/r6Z9TgKmrlLdLroahpV91HmQPi2ttGAyXrF9SmsLMCBqA6X5fIPZ/v3cEI69nKREj1Fq%2BHffi6q4s3sfzgDmFDZL9XFff9g2AtSVm7MLBAhNjMXqifFPxIbo5L0McZ%2B%2B78Wrdv3VnsvZIZTBLLM%2BEY/EqiKf73OIvmRGrI3Q4ksiGyG8CTcau6hrlJUN91IWdSjRU0vNPxvwIU4qy9aTSKhTI2Be2Cc53B3aRu%2BGKMcFG0d/IioT0fb1MxeIMPKtPViBzHRRj0OGbK5OFpX0nfdTyAEW85fnXos14I6yO7%2B/JfsaF4YQv7OVCKgQnYXFrjMMajtE%2BoiGInB6d4ybrNNXA3u3p%2BQ3leErM8yuIpvzSre6wPsyJ4VZxyPQA4iRn1AnZO7QA5UG2IzqPCXZfAvLHhvMqMNI9D8bwInx1pnVdx2kwHQhxvyzLGFxkVrhvTcZSGSG708jkAYfzjItoPANBc0WMg0NWuSs53gVmuc/UJk%2BvEiZTjHayg4o1yLANpPtRTfmIMcLfSRIw2BCdthqmPLwx8L46rvUycpHvL3Nb7vl9Uwr12%2BqRjMQj5aaLg4veTaOnRCSJTjVq9aRrzjZutk1hb9CiS/JigJYJoxhCnZ82ZpTIzrjMR8RjES5UX9%2B9/3xkARRcAunSTQP8SZiPVuMOXTHyKB8nr7NsfoUeQ1ixisswp7E3FOqmel92N2CdmrifZzdD3KmnPubHhM5OIX7x0X9004py1zlMSZCAPPLvrCxLpBwqkmuxNjlW7Qz6FKMgXoiwucVrYUzj3rWPBpENrqBAkWvKR3c9Y6dKzTmOPT/CRsEvdbe/l5MWmg5n7vog0TOR3TQpaDRBVC6HMsCSIS/NG0lteKVEevZQDQvSqtWbjMneAUvQ8RLEl8e7CDAQN7xfmQpdRVbaqUtD7hzvz9pfsHgWCG5g0lMkYA6NQFtfyH98/DwB/Jo5fmUANQZGu8XJ6pDF7KSqbqKDxk2EzVNK7po3llMavonwq9JxHk8xoywqGhNoKLWbFlatzFx6fTqn%2BtLZBZcYzb/csSzWeq9t3ireaZw3EJGaxOX09YvlTZbCMIyIEed17qkdNbufSJ4hPWsLHzmnihehvy1xOPlmVX%2BjLnBHRX5mNgVdWednIL0duHduvvCOejxcn5QFTwaspWfeSXYXlNoMFrVfCvrv%2BscinLs2OvtUlBV3mijU2pnE0tmTn%2BZqpYCeht5cWA5VAPbz63mOEIAl9%2BNcYsrUAaZf51jiyzhBI%2BW0pH5kATshWsI0Ltl6rThYzCOYdgzaSRDEjOw/dNqJdK2k8Ut4t5uUvcgw4oryOrhQOJVaVcGX%2BvRVnwYzfeF5ryWFbfvlVp661XNlA7