Reversing basic encrypted strings

Background

Strings can reveal a lot about what’s happening in a program. Whether it be through logs, GUI elements or error messages. They can also reveal certain technologies, or libraries used by a program. It’s no wonder why some program authors would want to hide this information from prying eyes. Okay, great, but how do actual users read the strings if they’re encrypted?

How it works

When you use a string encrypting library, generally, it looks something like this:

std::string str = xorstr("hello world");

The macro, xorstr, XOR encrypts the string’s bytes at compile time, and then calls a decryption function at runtime. What this means is that while the code is on disk, in the form of an executable file, all you see are encrypted bytes. When the executable file is running, however, the string gets decrypted as it’s needed.

Real World Example

Here were are reversing a real world example.

decryption routine has been inlined.

  char bytes[24];
  int key = 50;
  int index = 0;

  *(unsigned long long *)&bytes[8] = 0x555C5B55555D7E32LL;
  *(unsigned int *)&bytes[16] = 0x1C5C5B12;
  *(unsigned short *)&bytes[20] = 0x1C1C;

  bytes[22] = 0;
  
  while (true) {
    bytes[index + 9] ^= key;
    if ((unsigned int) ++index >= 13)
      break;

    key = bytes[8];
  }

Let’s break this code down.

*(unsigned long long *)&bytes[8] = 0x555C5B55555D7E32LL;
*(unsigned int *)&bytes[16] = 0x1C5C5B12;
*(unsigned short *)&bytes[20] = 0x1C1C;

We have bytes which is a char array that holds our encrypted bytes. Here we see that some numbers are being assigned to different parts of the array. These numbers are actually the encrypted bytes. For example, on the first line this large number, 0x555C5B55555D7E32LL, turns into [0x32, 0x7E, ...] because the number is stored in little endian. These three lines amount to the entirety of the encrypted string.

while (true) {
    bytes[index + 9] ^= key;
    if ((unsigned int) ++index >= 13)
      break;

    key = bytes[8];
  }

Here is where the string actually gets decrypted. The start of the string is at bytes[9], and on each iteration it goes through each byte, decrypting it with ^= key, until it reaches the length of the string. The XOR key is set to the first byte of the encrypted string, which isn’t uncommon. After the loop is completed the entire string is decrypted.

Decrypting with Python

def decrypt(s, key):
    # here we split the string into groups of 2, then reverse.
    # '01010101' turns into ['01', '01', '01', '01']
    encrypted_bytes = [s[i:i+2] for i in range(0, len(s), 2)][::-1]
    
    # here we turn each hex byte into an int, then we xor it with the key.
    return bytes(map(lambda x: int(x, 16) ^ key, encrypted_bytes))

# the bytes that we want to decrypt.    
string = "1C1C1C5C5B12555C5B55555D7E"

print(decrypt(string, 50))

output:

$ py decrypt.py
> b'Logging in...'
8 Likes

Well written short and simple post, good job and nice example. Amazing to think that such a trivial encryption routine is still frequently used.

4 Likes

Very nice post, straight to the point.

2 Likes