You are here : Home » Learning security » Operating Systems » Windows » String encryption using macro and cryptor

String encryption using macro and cryptor

Self-decrypting strings in C/C++

D 29 June 2014     H 21:17     A Emeric Nasi     C 5 messages


agrandir


License : Copyright Emeric Nasi, some rights reserved
This work is licensed under a Creative Commons Attribution 4.0 International License.
Creative Commons 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).

Note : Protecting the decryption password is not really the subject here but for more security the access to password and decryption algorithm could be obfuscated or protected by anti-debug/anti-disassembly trick.

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.

  1. /* secStrKey is string decryption password, its value will be replaced at encryption phase by random password generated by cryptor */
  2. static const char * secStrKey = "secStrKeyLoc";
  3.  
  4. /* This buffer contains the decrypted string */
  5. char mainStorage[BIGBUFFER]={0};
  6.  
  7. /**
  8.  * Decrypt string (without using CRT)
  9.  * @param str Null terminated char array
  10.  * @return Pointer to mainStorage global variable containing Null terminated char array
  11.  */
  12. char * decryptStr(const char * str)
  13. {
  14. size_t i=0;
  15. char tmpChar;
  16. int cpt = 0;
  17. char * tmpDecryptedString=NULL;
  18.  
  19. tmpDecryptedString = mainStorage;
  20.  
  21. while(str[i] != '\0')
  22. {
  23. tmpChar=str[i]^secStrKey[cpt];
  24. if((tmpChar==0x1A)||(tmpChar==0x00)) // Skip invalid txt EOF char
  25. tmpDecryptedString[i]=str[i];
  26. else
  27. tmpDecryptedString[i]=tmpChar;
  28. cpt++;
  29. if(secStrKey[cpt] == '\0')
  30. cpt = 0;
  31. i++;
  32. }
  33. tmpDecryptedString[i]='\0'; /* Finish to form te string */
  34.  
  35. return tmpDecryptedString;
  36. }
  37.  

 

Next we declare a macro to wrap up strings which should be tagged and decrypted.

  1.  
  2. /**
  3.  * string decryption macro:
  4.  * - Appends NULL and . char to end of string
  5.  * - Decrypt the string
  6.  * - __CRYPTED_STRINGS to enable or disable feature
  7.  */
  8. #ifdef __CRYPTED_STRINGS
  9. #define SEC_STR(A) (decryptStr(A ## "\0\."))
  10. #else
  11. #define SEC_STR(A) A
  12. #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
  1. /**
  2.  * Encrypt given segment ASCII string in the given file
  3.  */
  4. #define CHARSET "A-Za-z0-9_-:@$&#().&~!, %\\'/\""
  5. void cryptStrings(char* fileName, char * strKey)
  6. {
  7. FILE* fptr;
  8. BYTE *buffer;
  9. DWORD nItems;
  10. DWORD n;
  11. size_t fileSize;
  12. int cpt = 0;
  13. BOOL isString=0;
  14. char tmpbuf[128]={0};
  15. long position= 0;
  16. char * keySignature = "secStrKeyLoc";
  17. BYTE * keyAddress = NULL;
  18.  
  19. /* Oen binary file */
  20. fptr = fopen(fileName,"r+b");
  21. if(fptr == NULL)
  22. {
  23. printf("cryptStrings: fopen error \n");
  24. return;
  25. }
  26.  
  27. /* Get size of file */
  28. fseek(fptr, 0L, SEEK_END);
  29. fileSize = ftell(fptr);
  30. fseek(fptr, 0L, SEEK_SET);
  31.  
  32. /* allocate buffer to store file in memory */
  33. buffer = (byte *)malloc(fileSize);
  34. if(buffer == NULL)
  35. {
  36. printf("cryptStrings: malloc error \n");
  37. return;
  38. }
  39.  
  40. memset(buffer,00,fileSize);
  41.  
  42. /* Load target file in memory */
  43. nItems = fread(buffer, sizeof(BYTE), fileSize, fptr);
  44. if(nItems <fileSize)
  45. {
  46. printf("ncryptStrings: Trouble reading nItems = %d \n",nItems);
  47. fclose(fptr);
  48. return;
  49. }
  50.  
  51. printf("\ncryptStrings: Browsing for strings... \n") ;
  52. // Go back to begining of file
  53. fseek(fptr, 0L, SEEK_SET);
  54. while(1)
  55. {
  56. /* Scan using charset as in string listing applications */
  57. if(fscanf(fptr, "%*[^" CHARSET "]") != EOF)
  58. {
  59. memset(tmpbuf,00,sizeof(tmpbuf));
  60. isString= 0;
  61. n=0;
  62. position=0;
  63. while(fscanf(fptr, "%127[" CHARSET "]%n", tmpbuf, &n) == 1)
  64. {
  65. if(n < 4 && isString == 0) break; // length < 4 is not string
  66. else isString = 1;
  67. }
  68. if(isString != 0)
  69. {
  70. position=ftell(fptr);
  71.  
  72. // Verify string is ending with NULL, '.', and NULL
  73. if((buffer[position+1]=='.')&&(buffer[position+2]=='\0'))// custom string are often followed by to NULL
  74. {
  75. printf(" \t Found -> %s \n",(char*)(buffer + position-n));
  76. xorCrypt(strKey,buffer + position-n,n,buffer + position-n);
  77. printf(" \t crypt -> %s \n",(char*)(buffer + position-n));
  78. }
  79. }
  80. }
  81. if(feof(fptr))
  82. {
  83. break;
  84. }
  85. if(ferror(fptr) )
  86. {
  87. break;
  88. }
  89. }
  90.  
  91. /* Patch key in code to put randomly generated one */
  92. keyAddress = binStrstr(buffer,keySignature,fileSize);
  93. if(keyAddress != NULL)
  94. {
  95. /* Change generated key by calculated value */
  96. memcpy(keyAddress,strKey,strlen(strKey));
  97. // Go back to begining of file
  98. fseek(fptr, 0L, SEEK_SET);
  99. nItems = fwrite(buffer, sizeof(BYTE), fileSize, fptr);
  100. if(nItems <fileSize)
  101. {
  102. printf("ncryptStrings: Trouble writing nItems = %d \n",nItems);
  103. fclose(fptr);
  104. free(buffer);
  105. return;
  106. }
  107. }
  108. else
  109. {
  110. printf("cryptStrings: Could not find key to patch in target binary\n");
  111. }
  112.  
  113. fclose(fptr);
  114. free(buffer);
  115. return;
  116. }
  117.  

 

Here is the implementation of the binStrstr() function which is used to find the key signature in the binary.

  1. /**
  2.  * Layer over 'strstr()' compatible with binary data
  3.  * data and strToFind must be zero-terminated
  4.  * @data Binary buffer data to search in, can contain NULL chars
  5.  * @strToFind String to search in data
  6.  * @dataSize Size of data buffer
  7.  */
  8. char *binStrstr(BYTE *data, const char *strToFind, size_t dataSize)
  9. {
  10. char *result = 0;
  11. DWORD cpt = 0;
  12.  
  13. while(!(result = strstr((char*)data, strToFind)))
  14. {
  15. cpt += strlen((char*)data) + 1;// skip NULL byte
  16. data += strlen((char*)data) + 1;// skip NULL byte
  17. while((data[0]==00)&&(cpt < dataSize)) //skip following null bytes
  18. {
  19. data++;
  20. cpt++;
  21. }
  22. if(cpt >= dataSize)
  23. break;
  24. }
  25.  
  26. return result;
  27. }

 

We also need a XOR encryption feature in cryptor.

  1. /**
  2.  * @brief Xor encryption
  3.  * @param[in] key Encoding key (null terminated string)
  4.  * @param[in] data Data to crypt (null terminated string)
  5.  * @param[in] dataSize Number of byte to crypt
  6.  * @param[out] encryptedData will contain crypted data, must be allocated to same size as data
  7.  */
  8. void xorCrypt(char *key, const char *data, size_t dataSize, char* encryptedData)
  9. {
  10. int i;
  11. char tmpChar;
  12. int cpt = 0;
  13.  
  14. int keyLength = strlen(key);
  15. for( i = 0 ; i < dataSize ; i++ )
  16. {
  17. if(data[i] != '\0') // Skip null char
  18. {
  19. tmpChar=data[i]^key[cpt];
  20. if((tmpChar==0x1A)||(tmpChar==0x00)) // Skip invalid txt EOF char
  21. encryptedData[i]=data[i];
  22. else
  23. encryptedData[i]=tmpChar;
  24. cpt++;
  25. if(cpt == keyLength)
  26. cpt = 0;
  27. }
  28. else
  29. encryptedData[i]=data[i];
  30.  
  31. }
  32. }

 

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:

  1. #define NAME "myName" // not protected
  2.  
  3. #define SECRET SEC_STR("mySecret") // protected
  4.  
  5. printf("A secret:%s", SECRET);
  6.  
  7. printf("Another one:%s",SEC_STR("another secret));
  8.  

 

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

Any message or comments?
pre-moderation

This forum is moderated before publication: your contribution will only appear after being validated by an administrator.

Who are you?
Your post