Challenge

The challenge presents as a doc (BusinessPapers.doc) encrypted with some password. Also there is an executable called dudelocker.exe that might be our starting point. This is a CTF I did some years ago and I might miss some things, so I’ll try to keep this writeup as detailed as possible.

Solution

The solution is in front of you… better open your disassembler!

The sample performs many tasks before encrypting content:

  • Get files from a specific folder inside Desktop (Briefcase folder)
  • Checks the volume number against a constant value (GetVolumeSerialNumber)
  • If ok, starts to encrypt all the files inside Briefcase

The APIs used for encryption are the cryptdll Win32API ones. MSDN reference is shown for each function. These operations are used for establishing the cryptographic context required to encrypt the data.

  1. CryptAcquireContext

The CryptAcquireContext function is used to acquire a handle to a particular key container within a particular cryptographic service provider (CSP). This returned handle is used in calls to CryptoAPI functions that use the selected CSP.

This function inits the cryptographic operations but is important to understand also the type of encryption used. If we stop and analyze the DV_PROV param, we find that it is equal to PROV_RSA_AES. So we are on symmetric cryptography and the same secret is used for encryption and decryption.

  1. CryptCreateHash

The CryptCreateHash function initiates the hashing of a stream of data. It creates and returns to the calling application a handle to a cryptographic service provider (CSP) hash object. This handle is used in subsequent calls to CryptHashData and CryptHashSessionKey to hash session keys and other streams of data.

Here the type of hash is defined by the ALG_ID param and it is equal to CALG_SHA1.

  1. CryptHashData

The CryptHashData function adds data to a specified hash object. This function and CryptHashSessionKey can be called multiple times to compute the hash of long or discontinuous data streams. Before calling this function, CryptCreateHash must be called to create a handle of a hash object.

Here the pbData param is equal to the string thosefilesreallytiedthefoldertogether

  1. CryptDeriveKey

The CryptDeriveKey function generates cryptographic session keys derived from a base data value. This function guarantees that when the same cryptographic service provider (CSP) and algorithms are used, the keys generated from the same base data are identical. The base data can be a password or any other user data. This function is the same as CryptGenKey, except that the generated session keys are derived from base data instead of being random. CryptDeriveKey can only be used to generate session keys. It cannot generate public/private key pairs.

Here CALG_AES_256 type is used and hBaseData contains the hash object created in the previous step.

  1. CryptSetKeyParam

The CryptSetKeyParam function customizes various aspects of a session key’s operations. The values set by this function are not persisted to memory and can only be used with in a single session.

Here the crypt mode is set to CRYPT_MODE_CBC.

Then the sample iterates over the target directory and calculates the hash starting from each filename (lowercase). The hash is used as the IV (initialization vector) for the encryption. Here are shown all the functions.

  1. CryptCreateHash

MD5 type is now used.

  1. CryptHashData

Lowercase filename + extension is used.

  1. CryptSetKeyParam

IV param is used as the param of the key to set. As data, the resulting of CryptCreateHash is used.

Finally, the program calls CryptEncrypt for each file, modifying the content and making it unreadable.

There are two main solutions:

  1. Replace the CryptEncrypt call: the specular call of CryptEncrypt is CryptDecrypt that has identical parameters. So we can just replace the address of the call instruction to the one pointing to CryptDecrypt and we get the flag
  2. Re-implementing in some language all the cryptographic operations of the sample and then invoke CryptDecrypt instead of CryptEncrypt. I’ve done in this way in c++, I love wasting time :)

So, a starting point for the long solution is this MSDN code: https://learn.microsoft.com/it-it/windows/win32/seccrypto/example-c-program-encrypting-a-file?redirectedfrom=MSDN

It provides the base code for doing crypto using Win32API, I’ve slightly modified to adapt to this challenge. You can find it on Github: https://github.com/Caflo/flare-on-dudelocker-decryptor The solution code is all dec.cpp. I didn’t care on the code, the important thing was that it worked. Feel free to try it :)

I lost A LOT OF TIME because I could get the file full decrypted. This was due a BLOCK length for the Win32 crypto APIs when decrypting. For convenience here’s the thing (you can find also the comment inside the code):

// Important:
    // with the original dwBlockLen line the code won't work for the file businesspapers.doc (gives error 0x800005 NTE_BAD_DATA).
    // So, by increasing the total block len the decryptor works well in all cases.
    // Many people had this problem: in order to solve it you have to make sure 
    // that the buffer length is large enough during the decryption process. Same thing is for the encryptor, 
    // but I didn't modify it since it's not used for the challenge.
 
    // We can see a nice effect: with this line
    //dwBlockLen = 100000 - 1000 % ENCRYPT_BLOCK_SIZE;  <-- ORIGINAL LINE
    // we obtain an image with partial loss of data (bottom half of the .jpg is green but we can still see the flag)
    // if we increase more, like this
    dwBlockLen = 10000000 - 1000 % ENCRYPT_BLOCK_SIZE; 
    // we obtain the full image without any loss of data. So make sure to adjust this parameter to make the code work also for larger files (:

Then we run it and the sample correctly decrypts the BusinessPapers.doc file that is actually a JPG:


See you in the next research!