Post

Mirai Analysis

Mirai Analysis

Overview

Mirai is one of the first significant botnets targeting exposed networking devices running Linux. Found in August 2016 by MalwareMustDie, its name means “future” in Japanese. Nowadays it targets a wide range of networked embedded devices such as IP cameras, home routers (many vendors involved), and other IoT devices. Since the source code was published on “Hack Forums” many variants of the Mirai family appeared, infecting mostly home networks all around the world.

SAMPLE - sha256 382521c8aa4702fde32a4e8d1678515192a87b8ae838c47ae72eac67484993ee which is a unpacked binary of sha256 5d8488a2f82e88c95d8fbe7176e849f2e023c1c7db76fd80fbb67115fcfa27d1.

Static Analysis

I downloaded the sample and done a basic search over MalwareBazaar and virustotal to get some intel about the malware, it came out that it was marked malicious by 9 vendors in malwarebazaar and 36 by virustotal. Virustotal gave a overview what this malware does, it says it a IOT Botnet Agent that uses crontab modification for persistance.

virustotal Figure 1: Virustotal

bazaar Figure 2: Malware Bazaar

Since its a ELF malware so i quickly ran file to get some info about its architecture and if any debug data is present because that will help during the analysis. So its a x86 binary, statically linked and is stripped!! :)

file Figure 3: x86 malware with no debug data

Now I move further and checked if in case the binary is packed(although I have downloaded unpacked one but for a packed one follow these steps), some common packers are such as UPX, and it came out that it is not using any packer i confirmed it with running DIE on it. But one thing that, when i read this blog in its Vaniila UPX section it says If segment table contains only PT_LOAD and PT_GNU_STACK segments. This is an anomaly in the segment tables structure that might indicate the file is packed. and in our case we have section to segment mapping this confirms that this binary is not packed.

readlf Figure 3: Segment Tables has PT_LOAD and PT_GNU_STACK with section to segment mapping

Now doing strings on the binary revealed many things about malware that it is doing a FTP connection with username and password as anonymous, also making requests like POST, PATCH, HEAD with predefined User-Agents also different architectures like arm, misp, etc..

strings Figure 4: Strings shows HTTP request body

arch Figure 5: Different architectures

So its just first stage of malware that might downloads or somehow make reverse connections to the threat actor, also I got a username teamcw maybe they are the one who compiled this malware.

tactor Figure 6: Possible name of threat actor

mainf Figure 7: Main function

1
2
3
4
sub_805AE04((int)"/home/teamcw/debug", 0);    // sys_access
sub_a805D7B3(v29);                             // memset
sub_805D75F(v29, 2);
sub_805B3E8(0, v29, 0);                       // sys_rt_sigprocmask

For further analysis I opened it in IDA, since its statically linked so binary also contains code of C runtime library(libc) so sub_8054C70 is our main function. Then it calls sub_805AE04((int)"/home/teamcw/debug", 0); which is a wrapper on access system call and it checks presence of /home/teamcw/debug, so if this file exists it will run in debug mode. It then calls sub_805B3E8 with which does a syscall to sys_rt_sigprocmask which means when a user tries to terminate using Ctrl+C or system tries to terminate it, kernal sees the SIG_BLOCK(0) mask which won’t deliver the signal to malware.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 v4[0] = 16;
  *(_DWORD *)sub_805808B() = 0;
  fd = sub_805D734(2, 2, 0);                    // socket(AF_INET, SOCK_DGRAM, 0)
  result = 0;
  if ( fd != -1 )
  {
    v2[0] = 2;                                  // AF_INET
    v3 = 134744072;                             // 8.8.8.8
    v2[1] = 13568;                              // PORT-53(DNS)
    sub_8059883(fd, v2, 16);                    // Connect()
    sub_805D559(fd, v2, v4);                    // Getsockname()
    sub_8059ABE(fd);                            // close()
    return v3;
  }
 return result;

Then there is call to sub_8057DD0 which verifies connectivity by sending request to 8.8.8.8 and gets the current address to which the socket is bound which is a trick used by threat actors to get the address of victim machine. Afterwards back in main function it initialize similar things for connection to 13.30.242.151 at 12121 port. It then initilizes all the secret strings that mirai needs by allocating buffer in memory to hold the data, copies the encrypted data to that new buffer, stores its address and size.

1
2
3
4
v0 = sub_805D921(19);                // malloc
sub_8057C10(v0, &unk_80652F2, 19);   // copies enc data to new buffer
dword_8071288 = v0;                  // stores its address
word_807128C = 19;                   // len of data

Now it calls sub_8054920 inside which it first calls a decryption logic sub_8056700 which decryptes data with 0xDEADBEEF by doing a multi byte XOR operation on encrypted data but we can get a single key by XORing all 4 key bytes and we get 0x22 so we can XOR with single key and it is similar as doing multi byte XOR. \(0xEF \oplus 0xBE \oplus 0xAD \oplus 0xDE = \mathbf{0x22}\) then it calls a getter function which gets the data at specific entry from the table.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
v35 = (unsigned __int8 *)sub_805E095(1u, 8);  // calloc
v40 = &v45;
v3 = sub_8057BB0((_BYTE *)a1);                // strlen
for ( i = v3 + 1; i > 0; --i ){
  v8 = *v2;
  if ( *v2 != '.' && v8 ){                    // Read until not hits '.'
    *v4 = v8;
    ++v1;
    v7 = v4 + 1;
  }
  else{
    v9 = v1;
    v1 = 0;
    *v5 = v9;                                 // write len of segment
    v7 = v4 + 1;
    v5 = v4;
  }
  ++v2;                                       // reads next segment
  v4 = v7;
}

Next it calls sub_80561B0parameter as previously returned data from getter function, it then allocate memory for an array and stores length of a1 i.e. parameter in v3, then in a for loop it reads and increment v1 until it hits a . , and when it hits dot it writes length of that segment into *v5. So, it means this part of code is converting a IP or domain to \x06domain\x03com\x00 format. Afterwards, it generates a random data and stores it in array and then in while loop calls socket() so this part of code is crafting DNS query packet connecting and sending packet to DNS server and stores those alive IP in a list. So to conclude what this sub_8054920 does is, it takes array of IP’s/Domain’s stored and perform a DNS query by randomly selecting a IP/Domain to check if its alive or not and stores alive one in a list, this is done my this malware because if in case any C2 domain gets down, it can query others and give access to those server. Then it selects one of alive IP and a gets a PORT so it now has C2 IP and port to where it can communicate, it overwrite pre-initialised IP and PORT as we found above. One thing to note, it first decrypts the data, perform its operation then again encryptes the data.

Coming back to main function, then it checks if only one instance of malware is running by trying to bind same port as it used above, if it returns -1 means port is in use. Then it calls sub_8055FD0 which generate a unique value and its different for each affected system.

1
2
3
4
5
6
dword_806CF40 = sub_805B470(0);               // time()
process_id = sub_805AEF6();                   // getpid()
dword_806CF44 = sub_805AF1C() ^ process_id;   // getppid() ^ pid
dword_806CF48 = sub_805B752();                // clock()
result = dword_806CF44 ^ dword_806CF48;
return result;

It first XOR PPID(Parent Process ID) with PID(Process ID) and finally it XOR the value returned by system clock and recently XORed value \(S = (PID \oplus PPID) \oplus Clock\)

1
2
3
4
5
6
7
if ( argc == 2 && (int)strlen(argv[1]) <= 31 )
{
  strcpy(v32, argv[1]);
  strcpy(&unk_806CF20, argv[1]);
  v9 = strlen(argv[1]);
  sub_8057C40(argv[1], v9);                    // custom memset()
}

fake_name Figure 8: List of fake names

In main function, now it checks for argc and argv its to store if any custom name is provided for the bot and also stores it in a global variable unk_806CF20 and clears out argv to prevent itself from being seen by admin because ps aux can show command line arguments of running process which can reveal name of bot. It then uses process masquerading to select a name from list of names of processes(sshd, crond, nginx, etc.) to appear legitimate and evade defensive measures. Next as syscall wrapper of prctl sub_805B1BC(15, fake_process_name, v21, v22, arg5); // sys_prctl which tells kernal to ignore original name of process and shows the fake one.

1pers Figure 9: Using Custom systemd service

2pers Figure 10: Using Cronjob

Now, it calls deployment and persistance function which creates a copy of malware if running, somewhere in system and launch it as background process even if user log out. It first uses systemd as persistance, and implements itself as custom systemd service to start it automatically everytime the system reboots. Second, it uses crontab for persistence by adding @reboot (sleep 60 && mal_with_path) & >/dev/null 2>&1 in cron entry which executes the malware in every system reboot after 60 seconds, it also checks if any crontab entry is already present by reading line by line current crontab, if present than cleans out and return otherwise add a cronjob entry in crontab.

3pers Figure 11: Using SysV Init

4pers Figure 12: Using rc.local

Third, it uses SysV Init for persistance by manually writing inside /etc/init.d, because it is used to start service on older linux systems and embedded firmwares. Now the last persistence method used is rc.local which executes at very end of multi-user runlevel boot process and everything before exit 0 executes everytime the device starts. It performs this by first creating a temp file having content of original rc.local and injects its malware path into it and replaces it with original rc.local.

Back to main function, now there is call to sub_804BD90 which is a DDoS attack dispatcher which takes attack id from C2 server and start DDoS based on attack id, it can perform attacks like DNS, SYN, ACK, UDP floods and so on. Then there is call to sub_8054710 whose work is to detach the process from terminal session and with use of fork() it splits the process to run in background, it also make sure that no other botnets are running by checking /proc directory every 5 seconds with list of blacklisted botnets, if it finds any then it kills that process and takes the port on which that was running.

Then there is sub_80549D0 which is a watchdog killer or you can say anti reboot function, now what is a watchdog?? in simple words watchdog is responsible for forced restarts in case of system freeze or suspicious process used in IoT devices such as Modems, etc. So this function’s work is to find system’s watchdog file and disable it using ioctl by disabling the watchdog timer to prevent the system from restarts.

c2 Figure 13: Connecting to server

Now, it forks the process to create a child process and makes the child process the leader of new session and process group with setsid() its done so that new session has no ‘controlling terminal’ also closes stdin, stdout, stderr for silent execution. At last there is a loop which is main Command and Control communication loop of the malware it uses non-blocking sockets to make sure malware doesn’t freeze while waiting for response from server and select() to manager connection of C2 server, it also make sure obfuscation of protocol to evade NIDS and Buffer overflow by checking length less then 1024 bytes. Also in case of if server is not responding or gets down it doesn’t exit but waits for 60 seconds and try again.

worm Figure 14: Download malware to maybe newly infected system

encs Figure 15: Some encrypted strings

But but but, it didn’t stop here it may also try to infect other devices by downloading the malware according to system’s architecture so it means it also acts like a worm. And I have also found many encrypted strings and we know it used 0x22 to decrypt and after using this key on them it successfully decrypts those strings like /etc/resolv.conf, nameserver, TSource Engine Query, Israeli dupe found, /dev/watchdog, qtxbot, etc. also i found the bot name hakai after decrypting JCICK and here is the script i used:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
def xor_decrypt(data, key=0x22):
    decrypted = ""
    for char in data:
        decrypted_char = chr(ord(char) ^ key)
        if ord(decrypted_char) == 0:
            break
        decrypted.append(decrypted_char)
    return "".join(decrypted)

# encrypted strings
encrypted_strings = {
    "v4": "\rRPMA\r\"",
    "v5": "\rGZG\"",
    "v6": "\rDF\"",
    "v7": "\rOCRQ\"",
    "v8": "\rQVCVWQ\"",
    "v10": "\rAOFNKLG\"",
    "v11": "VOR\r\"",
    "v12": "FCVC\rNMACN\"",
    "v13": "SVZ@MV\"",
    "v23": "OKRQ\"",
    "v24": "ORQN\"",
    "v25": "RRA\"",
    "v26": "QFC\"",
    "v27": "OVF\"",
    "v29": "JCICK\"",
    "v35": "\rFGT\rUCVAJFME\"",
    "v44": "\rWQP\rNMACN\r@KL\rKLDKLKVF\"",
    "v45": "\rGVA\rQ[QVGOF\rQ[QVGO\r",
    "v48": "bPG@MMV\"",
    "v62": "AJWLIGF\""
}

print(f"{'Variable':<10} | {'Encrypted':<25} | {'Decrypted'}")
print("-" * 60)

for var, ciphertext in encrypted_strings.items():
    display_text = ciphertext.replace("\r", "\\r")
    plaintext = "".join([chr(ord(c) ^ 0x22) for c in ciphertext if ord(c) ^ 0x22 != 0])
    print(f"{var:<10} | {display_text:<25} | {plaintext}")

Dynamic Analysis

Now I ran the malware with strace and stored output in a file and after analysing the strace log file I saw that it starts under name mysqld so our analysis it correct its using process masquerading to hide itself as a legitimate name.

dyn1 Figure 16: Malware starts under name mysqld

And it also tries to connect initially to 151.242.30.13 at port 12121 and also tries to send a DNS request to 8.8.8.8 i.e. google.com, it tries to read different watchdogs like /dev/watchdog, /sbin/watchdog, etc. and trying to disable the timer and it also starts a listener at port 12121 at localhost via systemd-logind process.

dyn2 Figure 17: Tries to drop a binary infinitd

It also tries to drop a binary infinitd in /usr/local/bin/ with permission 755 which is likely being used by different persistence techniques saw above to execute malware to communicate to C2 server. Till now I have been running this malware a normal user, due to that most of persistence technique didn’t work out due to permission Now, I will run it as a root user and let’s see how it behaves.

dyn3 Figure 18: Writes infinitd binary and starts service infinitech

dyn4 Figure 19: Service configuration

This time malware starts with name php-cgi and it successfully able to write infinitd in /usr/local/bin and creates a systemd service named infinitech.service and it enables the service and the config shows that it starts the infinitd binary on startup and this binary is same malware which we ran but only with a different name.

IOCs

  • C2 Server IP: 151.242.30.13 -> Belongs to Istanbul, Turkey and flaged malicious by virustotal
  • C2 Port: 12121
  • Fake process name php-cgi, mysqld, systemd-logind
  • Strings: TSource Engine Query, Israeli dupe found
  • Persistence Binary: /usr/local/bin/infinitd
  • Systemd Service: infinitech
This post is licensed under CC BY 4.0 by the author.