Mirai Botnet Analysis



Introduction

During a workshop I had the pleasure to have a look at the popular Mirai Botnet malware. Besides all the historical information we had to gather, there was also room for a small technical analysis on the sample. Said analysis has been copied over to the blog and is presented in the upcoming lines of this page. The analysis is by no means exhaustive and shall only present one approach one might take on analysing the malware.


General Behavior

Many variants of the botnet exist and new ones are still being deployed into the wilderness. Most if not all of them have some similarities in common in regards to their behavior.

The botnet generally scans its environment in which it was deployed. It then terminates any services on the target device which might interfere with the malware and it scans for any open telnet ports. In the case that open telnet ports were found on the device, the malware tries to gain access to the device by brute forcing a set of hardcoded default username and password combinations. If successfull, the malware drops a payload, which is used as a callback to the C2 server, which the attack controls. After successfully gaining access to the device, the malware now awaits orders from the C2 server to for example take part in a DDoS attack, spread to a new victim or to self-destruct itself when needed.


String Decryption Routine

So one possible approach is to search for the string decryption function. Using a reverse engineering tool of your liking (I used Ghidra) we can first have a look at the strings page.

Open the "Window -> Defined Strings" view inside of Ghidra.


  .shstrtab
  .init
  .text
  .fini
  .rodata
  .ctors
  .dtors
  .data
  .bss
  ELF
  POST /cdn-cgi/
  HTTP/1.1
  User-Agent:

  Host:
  Cookie:
  /proc/net/tcp
  /dev/watchdog
  /dev/misc/watchdog
  abcdefghijklmnopqrstuvw012345678
  RCQQUMPF
  QOACFOKL
  cFOKLKQVPCVMP
  OGKLQO
  QGPTKAG
  QWRGPTKQMP
  EWGQV
  CFOKLKQVPCVMP
  HT@XF
  Q[QVGO
  FPGCO@MZ
  PGCNVGI
  OMVJGP
  DWAIGP
  enter
  assword
  TKXZT
  CFOKL
  ZOJFKRA
  FGDCWNV
  QWRRMPV

  RPMA
  "

  GZG"

  QVCVWQ"
  jvvrdnmmf"
  nmnlmevdm"
  XMNNCPF"
  egvnmacnkr"
  QJGNN"
  GLC@NG"
  Q[QVGO"
  LAMPPGAV"
  AJWLIGF"
  /dev/null
    

We for example have the string "ZOJFKRA" which gets passed to the function `FUN_00010778` as parameter.


    00010b70 c0  1d  9f  e5    ldr        r1=>s_ZOJFKRA_0001618c ,[PTR_s_ZOJFKRA_00011938 ] = "ZOJFKRA"
                                                                                        = 0001618c
    00010b74 05  20  a0  e3    mov        r2,#0x5
    00010b78 fe  fe  ff  eb    bl         FUN_00010778                                     undefined FUN_00010778()
    

By looking at the references of the function we can see, that most if not all encrypted strings are passed by parameter to the following function.


      void FUN_00010778(undefined4 param_1,undefined4 param_2,uint param_3)

      {
        int iVar1;
        ushort uVar2;
        int iVar3;
        int iVar4;
        int iVar5;
        int iVar6;

        iVar3 = FUN_0001439c(DAT_0001ec28,DAT_0001ebfc * 0x10 + 0x10);
        iVar1 = DAT_0001ebfc;
        DAT_0001ec28 = iVar3;
        iVar4 = FUN_00012c90(param_1);
        iVar5 = FUN_00014100(iVar4 + 1);
        FUN_00012d0c(iVar5,param_1,iVar4 + 1);
        if (0 < iVar4) {
          iVar6 = 0;
          do {
            *(byte *)(iVar6 + iVar5) = *(byte *)(iVar6 + iVar5) ^ 0x22;
            iVar6 = iVar6 + 1;
          } while (iVar4 != iVar6);
        }
        *(char *)(DAT_0001ebfc * 0x10 + DAT_0001ec28 + 0xc) = (char)iVar4;
        *(int *)(iVar3 + iVar1 * 0x10) = iVar5;
        iVar3 = DAT_0001ec28;
        iVar1 = DAT_0001ebfc;
        iVar4 = FUN_00012c90(param_2);
        iVar5 = FUN_00014100(iVar4 + 1);
        FUN_00012d0c(iVar5,param_2,iVar4 + 1);


        if (0 < iVar4) {
          iVar6 = 0;
          do {
            *(byte *)(iVar6 + iVar5) = *(byte *)(iVar6 + iVar5) ^ 0x22;
            iVar6 = iVar6 + 1;
          } while (iVar4 != iVar6);
        }



        *(char *)(DAT_0001ebfc * 0x10 + DAT_0001ec28 + 0xd) = (char)iVar4;
        uVar2 = _DAT_0001ec2c;
        iVar4 = DAT_0001ebfc * 0x10 + DAT_0001ec28;
        *(char *)(iVar4 + 9) = (char)(_DAT_0001ec2c >> 8);
        *(char *)(iVar4 + 8) = (char)uVar2;
        iVar6 = (param_3 & 0xffff) + (uint)_DAT_0001ec2c;
        *(char *)(iVar4 + 0xb) = (char)((uint)iVar6 >> 8);
        *(char *)(iVar4 + 10) = (char)iVar6;
        _DAT_0001ec2c = (short)param_3 + _DAT_0001ec2c;
        *(int *)(iVar1 * 0x10 + iVar3 + 4) = iVar5;
        DAT_0001ebfc = DAT_0001ebfc + 1;
        return;
      }
    

So the part we are most interested in is the string decryption route, which is composed of a simple xor-ing of the strings using the value 0x22.


      str_len = calc_string_len(enc_text);
      ptr = memory_manipulation(str_len + 1U);
      str_copy(ptr,enc_text,str_len + 1U);
      if (0 < str_len) {
        idx = 0;
        do {
          *(byte *)(idx + (int)ptr) = *(byte *)(idx + (int)ptr) ^ 0x22;
          idx = idx + 1;
        } while (str_len != idx);
      }
    

Compared to the leaked source code, this slightly varies. However due to the many families of the Mirai botnet that exist, code parts may vary.


    static char *deobf(char *str, int *len)
    {
        int i;
        char *cpy;

        *len = util_strlen(str);
        cpy = malloc(*len + 1);

        util_memcpy(cpy, str, *len + 1);

        for (i = 0; i < *len; i++)
        {
            cpy[i] ^= 0xDE;
            cpy[i] ^= 0xAD;
            cpy[i] ^= 0xBE;
            cpy[i] ^= 0xEF;
        }

        return cpy;
    }
    

String Extraction

Using python we can write a small decryption script, which allows us to retrieve the strings in their plaintext form.


      strings = [
                  "RCQQUMPF",
                  "QOACFOKL",
                  "cFOKLKQVPCVMP",
                  "OGKLQO",
                  "QGPTKAG",
                  "QWRGPTKQMP",
                  "EWGQV",
                  "CFOKLKQVPCVMP",
                  "HT@XF",
                  "Q[QVGO",
                  "FPGCO@MZ",
                  "PGCNVGI",
                  "OMVJGP",
                  "DWAIGP",
                  "TKXZT",
                  "CFOKL",
                  "ZOJFKRA",
                  "FGDCWNV",
                  "QWRRMPV",
                  "QVCVWQ",
                  "jvvrdnmmf",
                  "nmnlmevdm",
                  "XMNNCPF",
                  "egvnmacnkr",
                  "QJGNN",
                  "GLC@NG",
                  "Q[QVGO",
                  "LAMPPGAV",
                  "AJWLIGF"
                  ]


      def str_xor(text):
          key = 0x22
          return "".join([chr(ord(c) ^ key) for c in text])


      def main():

          for i in strings:
            decoded_text = str_xor(i)
            print(decoded_text)


      if __name__ == "__main__":
          main()


    

In the end we get the following list of plaintext strings.

 
      password
      smcadmin
      Administrator
      meinsm
      s
      ervice
      supervisor
      guest
      administrator
      jvbzd
      system
      dreambox
      realtek
      mother
      fucker
      vizxv
      admin
      xmhdipc
      default
      support
      status
      HTTPFLOOD
      LOLNOGTFO
      zollard
      GETLOCALIP
      shell
      enable
      system
      ncorrect
      chunked
    

A quick search on the internet will tell us, that some of the strings correspond to default credentials for IoT devices in e.g. https://ipcamtalk.com/threads/escam-rt-ipc-telnet-login.6253/.


Future Work

This is just one aspect that can be analyzed from the malware. The malware is of course more complex and can be analyzed further if one so wishes. Thus if I get the chance in the future I will update this page with more information.


References