Orbit is a two-stage malware that appeared in July 2022, discovered by Intezer lab. Acting as a stealer and backdoor on 64-bit Linux systems, it consists of an executable acting as a dropper and a dynamic library.

In July 2022, Intezer's research teams published the first paper on the OrBit malware, with an evocative title: 'New Undetected Linux Threat Uses Unique Hijack of Execution Flow'. This paper has the modest intention of completing this analysis of the malware.

The OrBit dropper

Type
$ELF
Architecture
$x86-64
Obfuscation
$No
Debugging information
$Yes
Required privileges
$root (fails without superadmin rights)
SHA-256
$f1612924814ac73339f777b48b0de28b716d606e142d4d3f4308ec648e3f56c8
MD5
$67048a69a007c37f8be5d01a95f6a026

The goal of the dropper is to install a shared library on the target system.

Several command line arguments are supported:

  • Without argument, the malware is installed in the directory /lib/libntpVnQE6mk/
  • sh installs the malware in /dev/shm/ldx
  • shred removes the malware
  • newpath modify the linker to write the path passed in parameter
  • mov installs the malicious library in the chosen directory with name passed in parameter
  • -O ignore the version of the binary ld.so during installation
  • -o allows to rewrite the path written in the linker by /dev/shm/ldx/.l
  • -u reinstalls the malware

Files created

Utility
.backup_ld.so
$linker backup
libdl.so
$malicious shared library
.l
$contains the path to the malicious library
.profile
$script to be installed in a home directory
.bashrc
$symbolic link to .profile
escalator
$privilege elevation script
.bootsh
$file to execute when the cron daemon is activated
.logpam
$indicates whether ssh passwords should be saved
sshpass.txt
$PAM password list
sshpass2.txt
$sudo or ssh password list
.ports
$list of ports to filter in TCP

Persistent installation

Entry point of the malware

The main function vérifie checks for the presence of the directory /lib/libntpVnQE6mk, this will eventually contain all the files and subdirectories needed for the malware to work effectively, its absence means that the malware is not yet present.

Once the directory is created, the program changes the owner group ID to 920366.

/* main() - Creation of the directory */
if (stat("/lib/libntpVnQE6mk", ...) {
   puts("new hdd"); 
   system("mkdir /lib/libntpVnQE6mk");
   chown("/lib/libntpVnQE6mk", 0, 920366);
   backup_ld(); 
}

This identifier is very unlikely to belong to a group already present on the system and is used by the malware to differentiate malicious directories, files and processes from normal ones.

Backup of the linker

The program then calls the backup_ld function, as its name suggests, this function makes a backup of the dynamic linker present on the machine.

backup_ld() - Linker backup
readlink("/lib64/ld-linux-x86-64.so.2", dest);
/* ... */
sprintf(src, "cp %s /lib/libntpVnQE6mk/.backup_ld.so", dest);
return (system(src));

On a 64-bit Linux system, the symbolic link /lib64/ld-linux-x86-64.so.2 points to the dynamic linker binary.

The malware obtains the path to the linker through this symbolic link and copies it to the location /lib/libntpVnQE6mk/.backup_ld.so.

Creation of the malicious dynamic library

The malware then introduces a malicious shared library with the load_ld function which takes as parameter the destination path of this library.
A check is performed on the version of the linker, it has a name like ld-${LIBC_VERSION}.so, which means that each libc version brings a new linker.

If the version is lower than 2.4 (before September 2006), then the check fails unless you use the -O  option on the command line.

The path passed in parameter is then created.

load_ld() - Check of the linker version
stream = popen("ls -l /lib64/ld-linux-x86-64.so.2", "r");
fread(buffer, 1, 255, stream);
/* ... */
ld_version = strstr(buffer, "ld-2.");
if (ld_version) {
    if (ld_version[5] <= '4' && ld_version[6] == '.') {
        printf("ld error: %s\n", buffer);
        return override_version != 0;
    } else {
        return (1):
    }
} else {
    printf("ld: %s\n", buffer);
    return (0);
}

Two symbols are then used to fill the file:

  • rkld_so whose first 4 bytes (7F 45 4C 46) are the signature of an executable binary under Linux: this is where the malicious library of the malware is located.
  • rkld_so_len which contains the size of the malicious library.
load_ld() - Creation of the malicious library
unlink(a1);
fd = open(...);
write(fd, &rkld_so, rkld_so_len);
fchown(fd, 0, 920366);
close(fd);
return (build_root());

Being one of the files associated with the malware, it is assigned the group ID 920366.

Creation of utility files

Once the library is on the system, the malware will build a directory and file tree that will be useful when the malicious library is used.

The two files placed in /lib/libntpVnQE6mk/bin stand out:

  • python which is the copy of the python interpreter bcy adding a SUID bit to it.
  • A python script stored in base64 in the binary that is used to elevate the privileges of a user.

Finally, the .l is added and the malicious library path is written to it (/lib/libntpVnQE6mk/libdl.so).

Linker modification and replacement

The last function called to finish the installation of the malware is patch_ld  which will allow the malicious library to be used instead of the classic libraries.

In the same way as for the backup seen previously, the program, thanks to the symbolic link /lib64/ld-linux-x86-64.so.2, then copies the original linker to /lib/lib0UZ0LfvWZ.so.

This file is then opened and mapped in memory to search for the string /etc/ld.so.preload and replace it by /lib/libntpVnQE6mk/.l  (see precision part).

The program then changes the location of the copy of the linker to that of the original linker, so that every time the program is run, the modified copy of the linker is used.

Volatile installation

Entry point of the malware

Using sh on the command line, the program will install the shared library in the /dev/shm/ldx directory.

This directory is not really a directory, it is a tree representation of the shared memory system(SHared Memory) which is a way to communicate between different processes (e.g. during a fork).

The data present is erased as soon as the user disconnects from the machine.

Installation function

The rkload_shm  function is then called, which performs all the necessary steps to deploy the malware.

The temporary installation is very similar to the persistent installation:

  • The creation of the /dev/shm/ldx  directory avec 920366 as the group ID
  • The creation a backup of the linker  (/dev/shm/ldx/.backdup_ld.so)
  • The modification of the linker with the patch_ld patch_ld function
  • A call to load_ld which places the malicious library in /dev/shm/ldx/libdl.so
  • The creation of the file /dev/shm/ldx/.l which contains the path of the previously created library
rkload_shm() - Volatile installation
system("mkdir /dev/shm/ldx");
chown("/dev/shm/ldx", 0, 920366);
system("cp -p %s /dev/shm/ldx/.backup_ld.so"); //erreur
patch_ld(1, 1);
load_ld("/dev/shm/ldx/libdl.so");
fd = open(...);
write(fd, "/dev/shm/ldx/libdl.so\n", 22);
return (close(fd));

The line system("cp -p %s /dev/shm/ldx/.backup_ld.so"); is bound to fail because the system function does not support string formats ("%s") and the path to the original linker is never recovered.

The volatile installation therefore modifies the linker without being able to retrieve the original.

Manual modification of the linker

With newpath, the program offers the possibility to choose the file path to be modified in the linker via  swap_ldpath function.

The linker pointed by the symbolic link /lib64/ld-linux-x86-64.so.2 is copied to /lib/lib0UZ0LfvWZ.so and searches in the file for the string passed in the 1st argument of the program to replace it by the string passed in the 2nd argument.

This function is similar to  patch_ld function,  the process is the same if the following arguments are passed on the command line: /etc/ld.so.preload /lib/libntpVnQE6mk/.l

Two ways of using this capability can be distinguished:

  1. If the malware is already installed, the corrupted linker can be changed to point to another file.
  2. If the malware is not installed, the attacker may want to use another library and different directories or files than those proposed in the classic installation, the dropper is then only used to modify the linker.

Reset

With the -u argument passed on the command line, the program calls the rkld_update function.

This function retrieves the path to the current installation of the malicious library and reinstalls it with load_ld.

rkld_update() - Recovery of the installation path
if (stat("/lib/libntpVnQE6mk/libdl.so", v1)) {
    if (!stat("/dev/shm/ldx/libdl.so", v1))
        lib_path = "/dev/shm/ldx/libdl.so";
} else {
    lib_path = "/lib/libntpVnQE6mk/libdl.so";
}
return (load_ld(lib_path));

We can note an unmanaged case, summarized by the diagram below:

Deleting

To remove the corrupted linker, the program supports the shred hat causes a call to the unload_ld function.

In this function, the file /lib/libntpVnQE6mk/.l is deleted and the original linker backup replaces the modified linker at the location pointed to by the symbolic link /lib64/ld-linux-x86-64.so.2.

 

Clarification on the elevation of privilege script

The escalator file has theSUID bbit of the root user, so in theory executing the execv function should open a bash shell with root rights (0:0).

It is however necessary to add the setreuid function before execv.

escalator
import os
os.setreuid(0, 0)
os.execv("/bin/bash", ("/bin/bash", "-i"))

To understand why, we must first talk about the identifiers. In a Linux system, each user has an identifier, these are visible in the /etc/passwd file. This identifier is the real id (ruid). There is also an effective id which has the same value as the real id most of the time.

When running a program with the bit SUID set, a user will only have his effective id changed to the value of the file owner's, which means that the real id remains the same.

But when a shell is run, if the effective id is different from the real id then the shell takes the real id as reference and removes the privileges granted by the SUID bit.

Thus, in the case of the python script, the user would not be root once /bin/bash is launched. To remedy this problem, the setreuid function is called before the execution of the command. This function allows to change directly the real id if the effective id allows it. This way /bin/bash is run with a real id and an effective id of 0 (root).

Clarification on the dynamic linker

A binary under Linux can be compiled in a static or dynamic way.

In static mode, the program contains all the libraries necessary for it to function properly and can be executed directly.

In dynamic mode, the dependencies are not added to the binary but stored as symbols.

During its execution, the dynamic linker searches for symbols in a list of shared libraries and loads the necessary libraries into memory.

Finally, the dynamic linker matches the symbols of the program with the functions either before the execution of the program or when a function is called.

The order in which the libraries are loaded in memory is predefined but it is possible to load libraries in priority:

  • With the LD_PRELOAD environment variable
  • With the /etc/ld.so.preload file

The latter is only supposed to exist for testing purposes and is therefore absent by default on a production system. We find in the source code the definition of the string used to open this file.

Source code of the ld.so binary
1869 /* There usually is no ld.so.preload file, it should only be used
1870 for emergencies and testing. So the open call etc should usually
1871 fail. Using access() on a non-existing file is faster than using
1872 open(). So we do this first. If it succeeds we do almost twice
1873 the work but this does not matter, since it is not for production
1874 use. */
1875 static const char preload_file[] = "/etc/ld.so.preload";

As the variable is declared constant, its value is found in the compiled binary, in the .rodata section.

When the linker is executed, the program retrieves the value located at the location of this string.

If this string is modified, the new value will be used by the binary when initializing the preload_file variable and the location remains the same.

Thus, the malware can insert a string representing the path to a file containing its own list of shared libraries.

OrBit library

Type
$ELF
Architecture
$x86-64
Obfuscation
$XOR on string
Debugging information
$Yes
Required Privileges
$No
SHA-256
$40b5127c8cf9d6bec4dbeb61ba766a95c7b2d0cafafcb82ede5a3a679a3e3020
MD5
$ac89d638cb6912b58de47ac2a274b2fb

The library has several purposes, it allows the malware to remain discreet by modifying network captures and preventing users from manipulating malicious files.

It also allows capturing passwords and allowing SSH connections with a predefined username and password to bypass authentication.

Modification of system call interfaces

Instead of directly calling the functions that interface to system calls (writeopen, stat, etc.), the library uses syscall directly, which takes as a parameter the number of the desired system call followed by the arguments usually sent.

This method is used because the library itself defines its own interfaces with malicious effects for certain system calls and therefore cannot use them to obtain standard behavior.

Obfuscation

The library contains strings obfuscated with XOR encryption within the data section.

The decryption is done on the fly with a key measuring one byte and having the value 0xA2 (162).

xor cypher 
for (i = 0; i < len_string; ++i)
    string[i] = obfuscated_string[i] ^ 0xA2;
string[i] = 0;

Constructor

In a code compiled with GCC, it is possible to add attributes to the functions, these attributes allow to modify the compilation in order to change the behavior of the program during its execution.

Among them, we find the constructor and the destructor, allowing respectively to execute code before and after the main function of a program.

_do_global_ctors_aux

In the library, there is a function _do_global_ctors_aux, this is where the functions with the constructor attribute are called..

The program retrieves the fct_ptr array, created by the compiler and which contains the addresses of the functions to be executed.

If this array is not empty, a loop goes through each entry to call the functions.

_do_global_ctors_aux() 
fct = array_fct_constructor;
if (array_fct_constructor != -1) {
    fct_iterator = &array_fct_constructor;
    do {
        --fct_iterator;
        fct();
        fct = *fct_iterator;
    } while (fct_iterator != -1);
}
return (fct);

__libc_sym_init

This function with the constructor attribute is split into parts, the first executes a user command via an environment variable while the second executes a predefined file.

If the environment variable HTTP_X_MAGICAL_PONIES is present when a program is executed, its value will be executed as a command line before the variable is deleted.

__libc_sym_init() - Command execution
if (getenv("HTTP_X_MAGICAL_PONIES")) {
    command = getenv("HTTP_X_MAGICAL_PONIES");
    unsetenv("HTTP_XMAGICAL_PONIES");
    system(command);
}

In the second step, if the program name contains cron, the file /dev/shm/.lck is created and its owner group id is set to 920366 then the file is closed.

A new process is created to run the  .boot.sh, a group id 920366 is assigned to it to get the maximum permissions.

__libc_sym_init() - Execution of the .boot.sh file 
v0 = strstr(_progname, "cron");
if (v0) {
    v0 = syscall(2, "/dev/shm/.lck", 192, 420); // open()
    fd = v0;
    if (v0 >= 0) {
        syscall(93, fd, 0, 920366); // chown()
        sycall(3, fd); // close()
        v0 = fork();
        if (!v0) {
            syscall(106, 920366); // setgid()
            len_string = 27;
            for (i = 0; i < len_string; ++i)
                string[i] = obfuscated_string[i] ^ 0xA2; // /lib/libntpVnQE6mk/.boot.sh
            string[i] = 0;
            stream = popen(string, "r");
            pclose(stream);
            exit(0);
        }
    }
}

The .boot.sh is left empty when the dropper creates it, but it is easy to imagine that an attacker connected via SSH could add commands to exfiltrate the collected data.

By using a file rather than the  cron, service, the malware remains discreet and avoids being detected with the crontab -l command which lists the various tasks; on the other hand, the attacker does not control the recurrence of the execution of his script.

Password Capture

In order to recover the passwords entered by a user, the write and read functions are modified and used in a complementary way with the global variables  sshpass and sniff_ssh_session.

Sudo and ssh programs have in common that they display a sentence like [sudo] pass or 's password to tell the user to enter his password, which implies that the next calls to read will be used to recover the password.

If one of these strings is detected in the write function ,the sshpass variable takes the value 1.

With this value, the read function saves each entry in the file sshpass2.txt until it reads a newline (\n) sets sshpass to 0.

This mechanism allows to save only the user passwords without having to save each entry.

Once the password is entered, the read function checks the existence of the .sniff file. If it is present on the system, sniff_ssh_session takes the value 1. This variable is used in write, a value of 1 will save all the content of the ssh session in the file sniff.txt.

Hiding in the file system

To avoid that the files related to the malware can be listed, read, written or deleted by an ordinary user, the library redefines the stat ystem call which allows to get information about a file or a directory.

Thus, the library can retrieve the identifier of the group that owns a file in functions like openreaddir or opendir.

If this group identifier is 920366 and the user does not have this id, the library refuses access and the file or directory cannot be opened or read.

Verification of the group identifier
is_malicious = syscall(4, path, &info_file) >= 0 && info_file.st_gid == 920366; // stat()
if (is_malicious && syscall(104) != 920366) { // getgid()
    return (-1);
}

Open fonction

This function has the goal of making the malware as undetectable as possible.

The procfs is a file system that allows to get information about the running processes, several files that allow to detect the malware are located there.

  • /proc/net/tcp which contains the list of active TCP connections
  • /proc/*/maps, /proc/*/smaps and /proc/*/numa_maps which contain information about the memory representation of a process.
    Among this information are the name and address of the different segments of a program, so the dynamic libraries used are present.

If one of these files is passed as a parameter to the open function, the library creates a temporary file. Inside this file, the content of the original file is copied line by line, excluding those containing information about suspicious activity.

The /var/log/lastlog file which contains the list of users having connected in SSH is also targeted by the malicious library.

In order to avoid that the attacker's connections are listed, the library returns a file descriptor on /dev/null which results in writing the logs nowhere.

open() - Hiding suspicious SSH connections
if ( syscall(104) == 920366 ) // getgid()
{
    len = 4;
    for ( k = 0; k < len; ++k )
      sshd[k] = obfuscated_string[k] ^ 0xA2;
    sshd[len] = 0;
    if ( !strcmp(_progname, sshd) )
    {
      len = 7;
      for ( m = 0; m < len; ++m )
        lastlog[m] = obfsucated_string_2[m] ^ 0xA2;
      lastlog[len] = 0;
      if ( strstr(filename, lastlog) )
        haystack = "/dev/null";
    }
  }
/* ... */
return syscall(2, haystack, mode, flags);

Backdoor

To allow an attacker to get access to the infected machine, the library rewrites several functions of the PAM library which is used to centralize and configure authentications for different programs (sudosshdcron, etc...).

The pam_authenticate function is used to authenticate a user to a service, it is responsible for retrieving the username and password.

In the implementation of the library, its role is also to allow an attacker to connect with an identifier("2l8").

In case this username is entered, the port involved in the connection is added to the .ports file and the group ID for the user is given the value 920366.

The password is checked by the pam_get_password, which will return a success value if the password sent is ("c4ss0ul3tt3").

pam_get_password() - Hardcoded password
len = 3;
for (i = 0; i < len; ++i) {
    password_2l8[i] = obfuscated_string[i] ^ 0xA2;
password_2l8[len] = 0;
if (!strcmp(username, password_2l8)) {
    /* ... */
    len = 25;
    for (j = 0; j < len; ++j)
        ports_filename[j] = obfuscated_string_2[j] ^ 0xA2;
    ports_filename[len] = 0;
    fd = syscall(2, ports_filename, 1090, 420); // open()
    /* ... */
    syscall(1, fd, port_to_hide, len_port_to_hide); // write()
    syscall(3, fd); // close()
    syscall(106, 920366); // set_gid()
    if (pam_get_password(...))
        return (0); // Authentication done
    else
        return (6); // Authentication failed
}

In the other hand, if a lambda user connects, and the .logpam file exists, the original function is called, in case of success, the pam_log_password function writes the username and password in the sshpass.txt file.

Modifying network frames

To prevent the attacker's SSH connections or other network activities from being detected, bind et connect  as well as some functions of the pcap are reimplemented by the malware.

bind / connect

The bind function is used to assign a socket to an ip address and a port, this function is necessary when a program wants to take the role of a server to be able to accept new incoming connections.

If bind or connect are called by a program launched by the malicious user, the port used is written to the .ports file previously created by the dropper.

bind() - Retrieve the port involved in the attacker's connection 
if ( syscall(104) == 920366 ) // getgid()
{
    port = htons(serv_addr->sin_port);
    len = 25;
    for ( i = 0; len > i; ++i )
        ports_file[i] = obfuscated_string[i] ^ 0xA2;
    ports_file[len] = 0;
    fd = syscall(2, ports_file, 1090, 420); // open
    len_buffer = sprintf(buffer, "%d\n", port);
    syscall(1, fd, buffer, len_buffer); // write()
    fsync(fd);
    syscall(3, fd); // close()
}

libpcap

The pcap library is used on unix systems to capture packets on a network interface, it is notably used in tools like wiresharktcpdumpnmap etc... The goal of the malware is to remove packets containing ports from suspicious connections.

The pcap_loop function allows to start capturing packets on an interface and takes as argument a function, which will be called each time a packet is intercepted.

In its own implementation, the library saves the function passed as argument in the global variable orig_callback and calls pcap_loop with another function: pcap_packet_callback.

The header of each captured packet is analyzed to determine the protocol (TCP or UDP), in both cases, a function is called to check if the involved ports are found in the .ports or .udp files.

If no suspect ports are found, the original callback function is called.

execve

The execve function aims to further disguise the malware from the eyes of a normal user by removing specific content or content in configuration files from certain commands:

  • dmesg is stripped of lines containing promiscuouszappingtcpdumplibntpVnQE6mk and libdl.so, as well as the contents of the /lib/ibntpVnQE6mk/.sys/.dmesg file.
  • strace sis stripped of lines containing .l920366 and libntpVnQE6mk. Also, if the -o option is used, the output will be redirected to /tmp/.orbit.
  • ip and iptables have their output compafred with the contents of /lib/libntpVnQE6mk/.net and /lib/libntpVnQE6mk/.fw, the lines are also removed in case of a match.

If the variable LD_TRACE_LOADED_OBJECTS is present in the environment of the user running a program, the list of used dynamic libraries is displayed on the screen.

To stay hidden, the malware modifies the linker to use the backup present in its folder.

Finally, if the file /lib/libntpVnQE6mk/.nolog is present, each command executed will be written in /lib/libntpVnQE6mk/execlog.

OrBit IoC 

SHA-256
dropper
$f1612924814ac73339f777b48b0de28b716d606e142d4d3f4308ec648e3f56c8
libdl.so
$40b5127c8cf9d6bec4dbeb61ba766a95c7b2d0cafafcb82ede5a3a679a3e3020
.boot.sh
$e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
.profile
$025e776c51b23e83fe4b400c527902d59dffa65cb35b4a163298790b1990b49c
escalator
$d316e896ed4c9a737b2964e5aceaf25751383db13bd9cdcb346bb893eff7fc47
.l
$fbe72ad884c4bf7b874794c0d6fe99054cf06c9d23ac004be3a36142bbcaa728

Stormshield protections against OrBit

With the Stormshield Network Security solution, dedicated to network protection, the dropper and the OrBit library are detected and blocked thanks to the embedded antiviral engine, but also via the Breach Fighter cloud detonation option.

Share on

[juiz_sps buttons="facebook, twitter, linkedin, mail"]
Malicious code is designed to be less and less detectable by traditional protection systems. For this reason, Stormshield Network Security firewalls do not rely solely on a malware signature-based system but incorporate emulation mechanisms to proactively identify malicious code.
With our Breach Fighter option, enhance the functionality of your Stormshield appliances with sandboxing and analysis of your suspicious files. Add dynamic protection against unknown attacks to your cybersecurity solutions.