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.
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.
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
.
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
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.
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.
CryptCreateHash
MD5
type is now used.
CryptHashData
Lowercase filename + extension is used.
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:
- Replace the
CryptEncrypt
call: the specular call ofCryptEncrypt
isCryptDecrypt
that has identical parameters. So we can just replace the address of thecall
instruction to the one pointing toCryptDecrypt
and we get the flag - Re-implementing in some language all the cryptographic operations of the sample and then invoke
CryptDecrypt
instead ofCryptEncrypt
. 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!