Search code examples
c++obfuscation

How to hide a string in binary code?


Sometimes, it is useful to hide a string from a binary (executable) file. For example, it makes sense to hide encryption keys from binaries.

When I say “hide”, I mean making strings harder to find in the compiled binary.

For example, this code:

const char* encryptionKey = "My strong encryption key";
// Using the key

after compilation produces an executable file with the following in its data section:

4D 79 20 73 74 72 6F 6E-67 20 65 6E 63 72 79 70   |My strong encryp|
74 69 6F 6E 20 6B 65 79                           |tion key        |

You can see that our secret string can be easily found and/or modified.

I could hide the string…

char encryptionKey[30];
int n = 0;
encryptionKey[n++] = 'M';
encryptionKey[n++] = 'y';
encryptionKey[n++] = ' ';
encryptionKey[n++] = 's';
encryptionKey[n++] = 't';
encryptionKey[n++] = 'r';
encryptionKey[n++] = 'o';
encryptionKey[n++] = 'n';
encryptionKey[n++] = 'g';
encryptionKey[n++] = ' ';
encryptionKey[n++] = 'e';
encryptionKey[n++] = 'n';
encryptionKey[n++] = 'c';
encryptionKey[n++] = 'r';
encryptionKey[n++] = 'y';
encryptionKey[n++] = 'p';
encryptionKey[n++] = 't';
encryptionKey[n++] = 'i';
encryptionKey[n++] = 'o';
encryptionKey[n++] = 'n';
encryptionKey[n++] = ' ';
encryptionKey[n++] = 'k';
encryptionKey[n++] = 'e';
encryptionKey[n++] = 'y';

…but it's not a nice method. Any better ideas?

PS: I know that merely hiding secrets doesn't work against a determined attacker, but it's much better than nothing…

Also, I know about assymetric encryption, but it's not acceptable in this case. I am refactoring an existing appication which uses Blowfish encryption and passes encrypted data to the server (the server decrypts the data with the same key).

I can't change the encryption algorithm because I need to provide backward compatibility. I can't even change the encryption key.


Solution

  • I'm sorry for long answer.

    Your answers are absolutely correct, but the question was how to hide string and do it nicely.

    I did it in such way:

    #include "HideString.h"
    
    DEFINE_HIDDEN_STRING(EncryptionKey, 0x7f, ('M')('y')(' ')('s')('t')('r')('o')('n')('g')(' ')('e')('n')('c')('r')('y')('p')('t')('i')('o')('n')(' ')('k')('e')('y'))
    DEFINE_HIDDEN_STRING(EncryptionKey2, 0x27, ('T')('e')('s')('t'))
    
    int main()
    {
        std::cout << GetEncryptionKey() << std::endl;
        std::cout << GetEncryptionKey2() << std::endl;
    
        return 0;
    }

    HideString.h:

    #include <boost/preprocessor/cat.hpp>
    #include <boost/preprocessor/seq/for_each_i.hpp>
    #include <boost/preprocessor/seq/enum.hpp>
    
    #define CRYPT_MACRO(r, d, i, elem) ( elem ^ ( d - i ) )
    
    #define DEFINE_HIDDEN_STRING(NAME, SEED, SEQ)\
    static const char* BOOST_PP_CAT(Get, NAME)()\
    {\
        static char data[] = {\
            BOOST_PP_SEQ_ENUM(BOOST_PP_SEQ_FOR_EACH_I(CRYPT_MACRO, SEED, SEQ)),\
            '\0'\
        };\
    \
        static bool isEncrypted = true;\
        if ( isEncrypted )\
        {\
            for (unsigned i = 0; i < ( sizeof(data) / sizeof(data[0]) ) - 1; ++i)\
            {\
                data[i] = CRYPT_MACRO(_, SEED, i, data[i]);\
            }\
    \
            isEncrypted = false;\
        }\
    \
        return data;\
    }

    Most tricky line in HideString.h is:

    BOOST_PP_SEQ_ENUM(BOOST_PP_SEQ_FOR_EACH_I(CRYPT_MACRO, SEED, SEQ))

    Lets me explane the line. For code:

    DEFINE_HIDDEN_STRING(EncryptionKey2, 0x27, ('T')('e')('s')('t'))

    BOOST_PP_SEQ_FOR_EACH_I(CRYPT_MACRO, SEED, SEQ)
    generate sequence:

    ( 'T'  ^ ( 0x27 - 0 ) ) ( 'e'  ^ ( 0x27 - 1 ) ) ( 's'  ^ ( 0x27 - 2 ) ) ( 't'  ^ ( 0x27 - 3 ) )

    BOOST_PP_SEQ_ENUM(BOOST_PP_SEQ_FOR_EACH_I(CRYPT_MACRO, SEED, SEQ))
    generate:

    'T' ^ ( 0x27 - 0 ), 'e' ^ ( 0x27 - 1 ), 's' ^ ( 0x27 - 2 ), 't' ^ ( 0x27 - 3 )

    and finally,

    DEFINE_HIDDEN_STRING(EncryptionKey2, 0x27, ('T')('e')('s')('t'))
    generate:

    static const char* GetEncryptionKey2()
    {
        static char data[] = {
            'T' ^ ( 0x27 - 0 ), 'e' ^ ( 0x27 - 1 ), 's' ^ ( 0x27 - 2 ), 't' ^ ( 0x27 - 3 ),
            '\0'
        };
        static bool isEncrypted = true;
        if ( isEncrypted )
        {
            for (unsigned i = 0; i < ( sizeof(data) / sizeof(data[0]) ) - 1; ++i)
            {
                data[i] = ( data[i] ^ ( 0x27 - i ) );
            }
            isEncrypted = false;
        }
        return data;
    }
    

    data for "My strong encryption key" looks like:

    0x00B0200C  32 07 5d 0f 0f 08 16 16 10 56 10 1a 10 00 08  2.]......V.....
    0x00B0201B  00 1b 07 02 02 4b 01 0c 11 00 00 00 00 00 00  .....K.........

    Thank you very much for your answers!