String encryption using macro and cryptor
Self-decrypting strings in C/C++
29 June 2014 21:17 5 messages
License : Copyright Emeric Nasi, some rights reserved
This work is licensed under a Creative Commons Attribution 4.0 International License.
I Presentation.
I.1 Strings encryption
I’ve found out a lot of people want to be able to encrypt strings in a C or C++ software. There are a lot of methods available, some of them not very user friendly. I present here my own proof of concept code for encrypting string and have them decrypted at runtime.
I.2 The requirements
We want the process to be user friendly, which means:
- We are able to tell which string is going to be encrypted and which is not
- All strings must be stored in clear-text in the source code (not in encrypted unreadable form).
- Encryption/Decryption password is randomly generated for each build without manual intervention
We want the process to be easy:
- A single MACRO function is used to mark the secured strings and to decrypt them at runtime.
- Encryption is done on the binary code, not on the source code.
Memory must be protected against strings listing:
- Strings must be kept encrypted in memory
- Limited number of decrypted string at the same time
II The method.
II.1 Mark Strings
In our case we want to be sure we encrypt only strings, and we also want to choose which strings are going to be encrypted. For that we will mark the strings in a way they are recognized after compilation.
The way I choose to mark a string is to append a ’.’ and a NULL character at the end of the string. It is pretty easy to do with a MACRO and during encryption phase cryptor can find the pattern.
Example for the ASCII string "wyziwig":
Normal string -> 'w','y','z','i','w','i','g','\0'
Marked string -> 'w','y','z','i','w','i','g','\0','.','\0'
In both case printf(%s), strlen, strcmp and other string manipulation functions will stop at the first ’\0’. That means when you need it, the string is decrypted then you can manipulate it like a regular ACSII char *.
II.2 Encryption phase
We want the encryption to be done on the binary code. For that we have to build a cryptor application which is able to recognized the strings which must be secured on the binary executable. This means we are going to have two steps generation. First, the application executable is generated without encryption, next the encryption is done by the cryptor software.
So the first thing is, how are we going to recognize our strings in the binary code?
First, it is important to know there is no way to give an exact and precise list of string from a binary code. There are tools like Strings from Sysinternal which can display a list of strings. It basically returns all series of 3 or more string characters found in string followed 00 character (NULL) which ends all strings in C/C++. The problem with Strings tool is it will return a lot of strings which are in fact just code instruction happening to match the pattern.
We are going to be sure we can find all the strings to secured without false positives by applying two filters:
- First we use a method similar to the one used by strings listing programs.
- Second we scan the results of operation one for marked strings (characters ’\0’, ’.’, ’\0’ ending the string).
II.3 Decryption phase
Decryption is done one string at a time at runtime. When a char * which is encrypted must be used, it is automatically decrypted and stored in temporary buffer. This is easy to do with a MACRO surrounding declared strings.
In the method described here, only one string at the time is in clear text in the program. This is more secured but leads to limitations in the application source code (see section V for more about that).
III Implementation.
III.1 Encrypted string target application
First we build a XOR string decryption function. Strings are decrypted one at a time and stored in global variable buffer.
/* secStrKey is string decryption password, its value will be replaced at encryption phase by random password generated by cryptor */ static const char * secStrKey = "secStrKeyLoc"; /* This buffer contains the decrypted string */ char mainStorage[BIGBUFFER]={0}; /** * Decrypt string (without using CRT) * @param str Null terminated char array * @return Pointer to mainStorage global variable containing Null terminated char array */ char * decryptStr(const char * str) { size_t i=0; char tmpChar; int cpt = 0; char * tmpDecryptedString=NULL; tmpDecryptedString = mainStorage; while(str[i] != '\0') { tmpChar=str[i]^secStrKey[cpt]; if((tmpChar==0x1A)||(tmpChar==0x00)) // Skip invalid txt EOF char tmpDecryptedString[i]=str[i]; else tmpDecryptedString[i]=tmpChar; cpt++; if(secStrKey[cpt] == '\0') cpt = 0; i++; } tmpDecryptedString[i]='\0'; /* Finish to form te string */ return tmpDecryptedString; }
Next we declare a macro to wrap up strings which should be tagged and decrypted.
/** * string decryption macro: * - Appends NULL and . char to end of string * - Decrypt the string * - __CRYPTED_STRINGS to enable or disable feature */ #ifdef __CRYPTED_STRINGS #define SEC_STR(A) (decryptStr(A ## "\0\.")) #else #define SEC_STR(A) A #endif
III.2 Cryptor application
The cryptor application will:
- Generate random key
- Read target file in memory
- Search for marked strings
- Encrypted using key
- Replace secStrKey value by random key
- Replace target by new code
/** * Encrypt given segment ASCII string in the given file */ #define CHARSET "A-Za-z0-9_-:@$&#().&~!, %\\'/\"" void cryptStrings(char* fileName, char * strKey) { FILE* fptr; BYTE *buffer; DWORD nItems; DWORD n; size_t fileSize; int cpt = 0; BOOL isString=0; char tmpbuf[128]={0}; long position= 0; char * keySignature = "secStrKeyLoc"; BYTE * keyAddress = NULL; /* Oen binary file */ if(fptr == NULL) { return; } /* Get size of file */ /* allocate buffer to store file in memory */ if(buffer == NULL) { return; } /* Load target file in memory */ if(nItems <fileSize) { return; } // Go back to begining of file while(1) { /* Scan using charset as in string listing applications */ { isString= 0; n=0; position=0; { if(n < 4 && isString == 0) break; // length < 4 is not string else isString = 1; } if(isString != 0) { // Verify string is ending with NULL, '.', and NULL if((buffer[position+1]=='.')&&(buffer[position+2]=='\0'))// custom string are often followed by to NULL { xorCrypt(strKey,buffer + position-n,n,buffer + position-n); } } } { break; } { break; } } /* Patch key in code to put randomly generated one */ keyAddress = binStrstr(buffer,keySignature,fileSize); if(keyAddress != NULL) { /* Change generated key by calculated value */ // Go back to begining of file if(nItems <fileSize) { return; } } else { } return; }
Here is the implementation of the binStrstr() function which is used to find the key signature in the binary.
/** * Layer over 'strstr()' compatible with binary data * data and strToFind must be zero-terminated * @data Binary buffer data to search in, can contain NULL chars * @strToFind String to search in data * @dataSize Size of data buffer */ char *binStrstr(BYTE *data, const char *strToFind, size_t dataSize) { char *result = 0; DWORD cpt = 0; { while((data[0]==00)&&(cpt < dataSize)) //skip following null bytes { data++; cpt++; } if(cpt >= dataSize) break; } return result; }
We also need a XOR encryption feature in cryptor.
/** * @brief Xor encryption * @param[in] key Encoding key (null terminated string) * @param[in] data Data to crypt (null terminated string) * @param[in] dataSize Number of byte to crypt * @param[out] encryptedData will contain crypted data, must be allocated to same size as data */ void xorCrypt(char *key, const char *data, size_t dataSize, char* encryptedData) { int i; char tmpChar; int cpt = 0; for( i = 0 ; i < dataSize ; i++ ) { if(data[i] != '\0') // Skip null char { tmpChar=data[i]^key[cpt]; if((tmpChar==0x1A)||(tmpChar==0x00)) // Skip invalid txt EOF char encryptedData[i]=data[i]; else encryptedData[i]=tmpChar; cpt++; if(cpt == keyLength) cpt = 0; } else encryptedData[i]=data[i]; } }
III.3 Usage
Using this method is pretty simple. There are only two additional steps for the developer:
- Use SEC_STR on strings which must be encrypted
- Run cryptor on generated binary before it is started/distributed.
Here are some example on how to use the code in the target application:
IV Going further.
One limitation in the above code is it does not support multi-threading (one decryption store shared by all the threads). There are several possibilities regarding that issue.
Personally I use a dedicated thread storage for strings which must be encrypted by a given thread (one storage per thread). This means the number of decrypted string at runtime is equal to the number of threads.
The current method also brings some limitation in the usage of source code, for example in the same thread, it is not possible to call a function with two parameters being encrypted strings. If you don’t want to worry about that, you can increase the number of strings which can be decrypted at the same time. You could also decide it is OK for you to have all strings decrypted at runtime, in this case you don’t need string storage.
Another possibility for the C++ users is to modify the decryptStr function to make it manipulate String C++ objects. With that method you don’t need explicit string storage as memory management is done dynamically behind the scene.
Also in this section
2 February 2022 – MSDT DLL Hijack UAC bypass
15 July 2021 – Hide HTA window for RedTeam
24 February 2019 – Bypass Windows Defender Attack Surface Reduction
23 January 2019 – Yet another sdclt UAC bypass
23 June 2018 – Advanced USB key phishing
3 Forum posts
Have you seen this encryptor and decryptor generator http://stringencrypt.com/c-cpp-encryption/ ?
1. String encryption using macro and cryptor, 30 June 2014, 10:37, by Emeric Nasi
Source code string encryption is another process. In your link the encryption must be written in the source file which is very heavy to maintain in my opinion. That is why I describe how to encrypt strings directly by patching the binary. What is interresting is the unicode feature that is missing in my poc. I must not be very difficult to adapt my code to unicode of wide char strings.
Hey mate,
there is a line:
keyAddress = binStrstr(buffer,keySignature,fileSize);
is that function name right? (binStrstr).
thanks
1. String encryption using macro and cryptor, 15 August 2014, 16:07, by Emeric Nasi
Yes. It is a layer over strstr() which is compatible with searching in binaries (with null bytes inside data you want to search). I forgot to add implementation, I will correct that right away!
Great idea, but... I tried to use it, but it does not work for all strings. For example urls are not decrypted correctly.