Search code examples
c++securitymemoryaesdump

How to protect strings in a unmanaged application from process dump


First of all it's not a duplicate post!

Please read to the end,you clearly understand!

I'm developing some apps and the security is very important to me...

I searched a lot, I did a lot of ways, I tested every method I found but not so much difference !

◾️ I need to protect some really important strings in my application like the Key and IV Key of AES256 or Base64 encryption.

Two Fact We All Faced :

  1. We all know that the security of .NET is very low! In fact, A kid can crack it by a cellphone !!!

  2. There is nothing as 100% security, we only reduce the speed of access to resources by making it harder and harder ...

But what I'm facing right now is really a serious security problem and it can hurt lot of customers.

Okay, Time to Ask...

I need to know how can I keep my AES Key and AES IV safe from dumpers and memory extractors, Even if I can not be able to create a very high security I need to protect it of opening by one click!

My application is a Unmanaged C++

The things I did and tried :

A ) Using XOR Method :

I used xor method for creating my string but it can be extract by string2 dumper with one click!

Read More : https://github.com/Jyang772/XOR_Crypter

B ) Using Separated Strings :

Here's an example :

char Departed_String1[4];
int nmbr1 = 0;
char Departed_String1_dep[4];
int nmbr1_dep = 0;
Departed_String1[nmbr1++] = 'T';
Departed_String1_dep[nmbr1_dep++] = 'U';
Departed_String1[nmbr1++] = 'E';
Departed_String1_dep[nmbr1_dep++] = 'C';
Departed_String1[nmbr1++] = 'S';
Departed_String1_dep[nmbr1_dep++] = 'h';
Departed_String1_dep[nmbr1_dep++] = 'U';
Departed_String1[nmbr1++] = 'T';
Departed_String1_dep[nmbr1_dep++] = 'y';
Departed_String1_dep[nmbr1_dep++] = '8';

This one can also be easily opened with a debugger!

C ) My Own Method : LostChars

const char* teststring_src = "HeVthsNiNVrtTuODkhPgDkmCxQApD:feSCiQDDWePakOTtFcLzTSbKTaZwsUnpeYMlndoYXJyXBpSSSNGsWblpQhUKKCzWUfHnNxQtNsXXnFzXtSGzIBYjCIlSMbEoqwJfArwrqfeLRANEYgjdknHuzSIzgiglRBFEDmFqDBBbgUQD VvDjnQPdFKDYTSxnDXTqKdHtOCayMbACkmQLJqgHtBtTj CoiXxETJwiIkgMgaVaskZtLiWDotsTldTHdBiJiGIfCmjjjBdAbIFiJFFhXPeAjiKbPuktOmiIuhqDkIhMxFBGZevIIjoOuKfddWgUmdFbNfShAIhphPYKhpxtimPhmDatYlOWCXBXQbkFDY QaKyRMhHznNJClQjDmevKUSnfoCXfWplSDzVWxMOkGkntVMmijf QzbalAbRokBAXXfDvevyHbOmAaUIKMBivJVrTxALngQjGShZdzTsZJwIooYLIuqxcTjELFPRFAAzqE fnIznpwtUzEXFBm"; 
std::string teststring = (std::string(1,teststring_src[468])+std::string(1,teststring_src[4])+std::string(1,teststring_src[230])+std::string(1,teststring_src[249])+std::string(1,teststring_src[174])+std::string(1,teststring_src[343])+std::string(1,teststring_src[239])+std::string(1,teststring_src[365])+std::string(1,teststring_src[41])+std::string(1,teststring_src[417])+std::string(1,teststring_src[227])+std::string(1,teststring_src[62])+std::string(1,teststring_src[469])+std::string(1,teststring_src[248])+std::string(1,teststring_src[220])+std::string(1,teststring_src[329])+std::string(1,teststring_src[504])+std::string(1,teststring_src[453])+std::string(1,teststring_src[223])+std::string(1,teststring_src[66])+std::string(1,teststring_src[156])+std::string(1,teststring_src[496])+std::string(1,teststring_src[29])+std::string(1,teststring_src[20]));

Not works perfectly still dumps!

D ) Using Hex instead of String :

wchar_t string[7] = { 0x0118, 0x0154, 0x010C, 0x012C, 0x0154, 0x0084, 0x0000 };

for (unsigned int GUEvK = 0, GNsdj = 0; GUEvK < 7; GUEvK++)
{
        GNsdj = string[GUEvK];
        GNsdj = ((GNsdj << 14) | ( (GNsdj & 0xFFFF) >> 2)) & 0xFFFF;
        string[GUEvK] = GNsdj;
}

wprintf(string);

Okay :) This guy made a software to protect strings heavily... Wow Awesome! Good deal for 50EURO! But there's a little problem here!

It can be dump in just 2 Click and 1 Second by Process Explorer

It's an example :

unsigned char myKey[48] = { 0xCF, 0x34, 0xF8, 0x5F, 0x5C, 0x3D, 0x22, 0x13, 0xB4, 0xF3, 0x63, 0x7E, 0x6B, 0x34, 0x01, 0xB7, 0xDB, 0x89, 0x9A, 0xB5, 0x1B, 0x22, 0xD4, 0x29, 0xE6, 0x7C, 0x43, 0x0B, 0x27, 0x00, 0x91, 0x5F, 0x14, 0x39, 0xED, 0x74, 0x7D, 0x4B, 0x22, 0x04, 0x48, 0x49, 0xF1, 0x88, 0xBE, 0x29, 0x1F, 0x27 };

myKey[30] -= 0x18;
myKey[39] -= 0x8E;
myKey[3] += 0x16;
myKey[1] += 0x45;
myKey[0] ^= 0xA2;
myKey[24] += 0x8C;
myKey[44] ^= 0xDB;
myKey[15] ^= 0xC5;
myKey[7] += 0x60;
myKey[27] ^= 0x63;
myKey[37] += 0x23;
myKey[2] ^= 0x8B;
myKey[25] ^= 0x18;
myKey[12] ^= 0x18;
myKey[14] ^= 0x62;
myKey[11] ^= 0x0C;
myKey[13] += 0x31;
myKey[6] -= 0xB0;
myKey[22] ^= 0xA3;
myKey[43] += 0xED;
myKey[29] -= 0x8C;
myKey[38] ^= 0x47;
myKey[19] -= 0x54;
myKey[33] -= 0xC2;
myKey[40] += 0x1D;
myKey[20] -= 0xA8;
myKey[34] ^= 0x84;
myKey[8] += 0xC1;
myKey[28] -= 0xC6;
myKey[18] -= 0x2A;
myKey[17] -= 0x15;
myKey[4] ^= 0x2C;
myKey[9] -= 0x83;
myKey[26] += 0x31;
myKey[10] ^= 0x06;
myKey[16] += 0x8A;
myKey[42] += 0x76;
myKey[5] ^= 0x58;
myKey[23] ^= 0x46;
myKey[32] += 0x61;
myKey[41] ^= 0x3B;
myKey[31] ^= 0x30;
myKey[46] ^= 0x6C;
myKey[35] -= 0x08;
myKey[36] ^= 0x11;
myKey[45] -= 0xB6;
myKey[21] += 0x51;
myKey[47] += 0xD9;

You just need to run your app , Right click on your app in Process Explorer and Click Full Dump

... Boom!!! All Strings are just right there !

** I tried a lot of other methods but still everything is in Full Dump...**

Is there really no way to prevent this security gap? I appreciate any help!


Solution

  • First of all, remember that if an attacker can attach a debugger to your process and your process has to be the one in charge of the decryption, you have already lost by definition; the best you can get is "security through obscurity". More secure approaches generally require offloading part of the work to an actor that the attacker cannot access - be it an external, tamper-proof cryptographical device or a remote service.

    But most importantly, any method is vulnerable to being dumped, as sooner or later you'll need to access the plaintext, and it'll be there in memory ready for anyone with a debugger to be read.

    That being said, you can mitigate this last problem by decrypting your string only on demand, and wiping it away immediately after; this shrinks the window of opportunity for the attacker. So, you don't want a function that decrypts a static buffer, but a function that fills a client-provided buffer with the needed secrets (and make sure that the clients actually zero out the content of such buffer before deallocating it - use a memset-like which takes a volatile pointer to make sure that the wiping isn't optimized out).

    As for actually storing the keys into the executable, you can use a variety of methods to create confusion. Initializing a global with a plain initializer puts the relevant data in the .rodata section of the executable, which is the first place where I'd look; any string with high enough entropy would be a dead giveaway to investigate further where it is used (the IDA disassembler makes this particularly easy). A possibility that comes to mind is to actually initialize the buffer one byte at time from a function (possibly making the pointer to the buffer volatile, to make sure the compiler doesn't pull strange tricks); this should put your data straight into the code section, which is a bit less suspect, and where the entropy should be kept lower thanks to the interleaving with opcodes.

    This data could have been further encrypted by using some simple trick - say, XORed with the output of a simple XorShift PRNG; this again adds to the confusion, but a XorShift is implemented in a handful of instructions, so you don't have extra dependencies or "suspect" code.

    Another important point, if you are hiding decryption keys, is not to use cryptographic primitives provided by your operating system, but link statically your implementation, and possibly one that doesn't use AES-NI or other obvious giveaways. If I were trying to extract decryption keys first thing I'd hook into a debugger all the relevant CryptoAPIs, and look into the executable for cryptographic instructions to locate the most interesting zones.