cencryptionxor

XOR encryption/decryption of Mirai


I am doing a case study on Mirai, and I am stuck on the XOR encryption/decryption. From the source code, I copied the toggle_obf function and I tried several ways to reverse the "\x22\x35" into 23.

I also tried the method provided in What type of encryption is used with Mirai? But it doesn't seem to work even if table_key is 0xdeadbeef. If anybody can explain how it gets to the 23 in the entry, that would be great and would help me understand the XOR better.

Function:

  static uint32_t table_key = 0xdeadbeef;
  static void toggle_obf(uint8_t id){
  int i;
  struct table_value *val = &table[id];
  uint8_t k1 = table_key & 0xff,
        k2 = (table_key >> 8) & 0xff,
        k3 = (table_key >> 16) & 0xff,
        k4 = (table_key >> 24) & 0xff;

      for (i = 0; i < val->val_len; i++)
      {
        val->val[i] ^= k1;
        val->val[i] ^= k2;
        val->val[i] ^= k3;
        val->val[i] ^= k4;
      }

Entry:

add_entry(TABLE_CNC_PORT, "\x22\x35", 2);   // 23, TABLE_CNC_PORT 4(id)

Solution

  • Let's do a bit of reverse engineering.

    TCP and UDP allot 16 bits for port numbers, so port 23 (decimal) is conventionally stored internally as two bytes, 0x0017 (hex).

    Considering ...

    add_entry(TABLE_CNC_PORT, "\x22\x35", 2)
    

    ... we see two bytes (ignoring the string terminator). Let's look at some representations:

    decimal hex binary
    0 0x00 0b00000000
    23 0x17 0b00010111
    34 0x22 0b00100010
    53 0x35 0b00110101
    222 0xde 0b11011110
    173 0xad 0b10101101
    190 0xbe 0b10111110
    239 0xef 0b11101111

    Now consider what we would observe if the two bytes 0x00 and 0x17 were each encrypted by XORing it with the same 8-bit key (equivalent to XORing with the same combination of any number of separate 8-bit keys):

    Now note that the binary representations of 0x17 and 0x00 differ exactly where 0x17 has 1 bits, and note that these are also exactly the positions where the two cipherbytes differ. Under the circumstances, I think we can conclude that "\x22\x35" represents a two-byte cipherbyte sequence corresponding to a 16-bit integer representation of 23 (decimal).

    Note further that the result of XORing together the bytes of 0xdeadbeef is 0b00100010, or 0x22. That is the same as the first cipherbyte, so if we take 0xdeadbeef for the original key material then we can recognize that the first cipherbyte corresponds to the 0x00 of the supposed plain bytes. That means that the encoded representation is big-endian, also known as "network byte order". This is not particularly surprising for a port number.

    So, to get from the cipherbytes to the number 23, we might use something like this:

    #include <stdio.h>
    #include <stdint.h>
    #include <inttypes.h>
    
    const uint32_t table_key = 0xdeadbeef;
    const uint8_t key8 =
            table_key
            ^ (table_key >> 8)
            ^ (table_key >> 16)
            ^ (table_key >> 24);
    
    uint16_t decode16n(const uint8_t cipherbytes[]) {
        return ((cipherbytes[0] ^ key8) << 8) | (cipherbytes[1] ^ key8);
    }
    
    int main(void) {
        printf("Deciphered port number: %" PRIu16 "\n", decode16n("\x22\x35"));
    }