Enigma’s EP_CryptDecryptBuffer internals

kao

For one of my private projects, I needed to decrypt some data. Original executable uses Enigma's EP_CryptDecryptBuffer function but I needed to implement the same thing using .NET. Much to my surprise, there was no information about what encryption algorithm is used by Enigma or how it works internally. So, I started by compiling Enigma's sample project CryptBuffer and debugging the executable.

TL;DR - IDEA cipher in CBC mode, password = MD5(user_password), IV for CBC comes from IDEA.Encrypt(8-zeroes, password).

Research

After protecting my executable with Enigma, few assembly lines

004142FA   68 B85E4100      PUSH crypto_p.00415EB8                   ; ASCII "password"
004142FF   68 00100000      PUSH 1000
00414304   53               PUSH EBX
00414305   E8 EEF9FFFF      CALL 

got changed into something horrible that looked like an Enigma's VM:

004142FA   68 B85E4100      PUSH crypto_p.00415EB8                   ; ASCII "password"
004142FF   68 00100000      PUSH 1000
00414304   53               PUSH EBX
00414305   E8 EEF9FFFF      CALL crypto_p.00413CF8
..
00413CF8   FF25 44D44100    JMP DWORD PTR DS:[41D444]                ; crypto_p.004EEC6C
..
004EEC6C  /E9 C6A60800      JMP crypto_p.00579337
..
00579337   68 6D5133A1      PUSH A133516D
0057933C   E9 BBC1FEFF      JMP crypto_p.005654FC
..
005654FC   60               PUSHAD
005654FD   9C               PUSHFD
005654FE   B2 01            MOV DL,1
00565500   BE 00000000      MOV ESI,0
00565505   8DBE CC775600    LEA EDI,DWORD PTR DS:[ESI+5677CC]
0056550B   8D3F             LEA EDI,DWORD PTR DS:[EDI]
0056550D   B9 01000000      MOV ECX,1
00565512   31C0             XOR EAX,EAX
00565514   F0:0FB10F        LOCK CMPXCHG DWORD PTR DS:[EDI],ECX      ; LOCK prefix
00565518   74 0F            JE SHORT crypto_p.00565529
0056551A   3A15 C8775600    CMP DL,BYTE PTR DS:[5677C8]
...

I didn't feel like spending my time on analyzing that. Much easier solution was to put hardware breakpoints on my data and password and wait for them to trigger. In a few seconds I landed into code that looked like MD5 (notice the constants!):

...
00454E1C   89D7             MOV EDI,EDX
00454E1E   31CF             XOR EDI,ECX
00454E20   21DF             AND EDI,EBX
00454E22   31D7             XOR EDI,EDX
00454E24   01F0             ADD EAX,ESI
00454E26   8D8407 78A46AD7  LEA EAX,DWORD PTR DS:[EDI+EAX+D76AA478]
00454E2D   C1C8 19          ROR EAX,19
00454E30   01D8             ADD EAX,EBX
00454E32   8B75 04          MOV ESI,DWORD PTR SS:[EBP+4]
00454E35   89CF             MOV EDI,ECX
00454E37   31DF             XOR EDI,EBX
00454E39   21C7             AND EDI,EAX
00454E3B   31CF             XOR EDI,ECX
00454E3D   01F2             ADD EDX,ESI
00454E3F   8D9417 56B7C7E8  LEA EDX,DWORD PTR DS:[EDI+EDX+E8C7B756]
...

and few hits later I landed into code that I didn't recognize at first:

...
004DB61C   E8 DBFDFFFF      CALL crypto_p.004DB3FC
004DB621   66:8B446E 7E     MOV AX,WORD PTR DS:[ESI+EBP*2+7E]
004DB626   66:0147 02       ADD WORD PTR DS:[EDI+2],AX
004DB62A   66:8B846E 800000>MOV AX,WORD PTR DS:[ESI+EBP*2+80]
004DB632   66:0147 04       ADD WORD PTR DS:[EDI+4],AX
004DB636   66:8B946E 820000>MOV DX,WORD PTR DS:[ESI+EBP*2+82]
004DB63E   8D47 06          LEA EAX,DWORD PTR DS:[EDI+6]
004DB641   E8 B6FDFFFF      CALL crypto_p.004DB3FC
004DB646   66:8B47 04       MOV AX,WORD PTR DS:[EDI+4]
004DB64A   66:894424 0C     MOV WORD PTR SS:[ESP+C],AX
004DB64F   66:8B07          MOV AX,WORD PTR DS:[EDI]
004DB652   66:3147 04       XOR WORD PTR DS:[EDI+4],AX
...

Delphi compiler had helpfully included RTTI information telling me that this second piece of code belongs to a class called TIdeaCipher. Now I had all the information I needed, I just had to put it all together. 🙂

Implementing decryption in C#

There aren't many IDEA implementations in C#. In fact, there are 2: by BouncyCastle and by LexBritvin. Since I'm not a big fan of huge and complex libraries like BouncyCastle, so I took the simplest possible code: from Github and modified it a bit.

First, we need change password generation algorithm to use MD5 to generate real password:

private static byte[] generateUserKeyFromCharKey(String charKey)
{
   MD5 md5 = MD5.Create();
   return md5.ComputeHash(Encoding.ASCII.GetBytes(charKey));
}

Second, we need to add support for CBC mode. It requires both calculating correct IV value and pre/post-processing each block we're encrypting or decrypting.

Calculating IV is quite simple:

// IV for CBC mode
byte[] IV;

public Idea(String charKey, bool encrypt)
{
    byte[] key = generateUserKeyFromCharKey(charKey);
    // Expands a 16-byte user key to the internal encryption sub-keys.
    int[] tempSubKey = expandUserKey(key);

    // calculate IV
    subKey = tempSubKey;
    IV = new byte[8];
    crypt(IV, 0);

    if (encrypt)
    {
        subKey = tempSubKey;
    }
    else
    {
        subKey = invertSubKey(tempSubKey);
    }
}

Processing each block is slightly harder but not a rocket science either. As explained in image borrowed from Wikipedia:
.

Before decrypting the data block, we need to store the encrypted values as IV for the next block. After decrypting the block, we need to XOR each byte of data with a corresponding byte of IV. Unfortunately, during encryption the data flow is different, so the simple crypt function has to be replaced with 2 functions: encrypt and decrypt.

/**
 * Decrypts array
 *
 * @param data
 *    Buffer containing data bytes to be decrypted
 */
public void decrypt(byte[] data)
{
    for (int q = 0; q < data.Length; q += 8)
    {
        // save original bytes for next IV
        byte[] origBytes = new byte[8];
        Array.Copy(data, q, origBytes, 0, 8);

        // decrypt data
        crypt(data, q);

        // xor data with previous IV
        for (int w = 0; w < 8; w++)
           data[q + w] ^= IV[w];

        // copy original data to next IV
        Array.Copy(origBytes, IV, 8);
    }
}

Few more cosmetic changes and that's it! We got clean and nice implementation of EP_CryptDecryptBuffer in pure C#. 🙂
Download link: https://bitbucket.org/kao/ep_cryptdecryptbuffer/

Have fun!

P.S. My implementation only supports cases where data length is divisible by 8, adding support for other lengths is left as an exercise for the reader.