How to hide a string in binary code?

2019-01-02 20:11发布

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.

20条回答
回忆,回不去的记忆
2楼-- · 2019-01-02 20:17

I wonder if after first obscuring it like others have mentioned, you could embed your string in an assembly block to try and make it look like instructions. You could then have an "if 0" or "goto just_past_string_assembly" to jump over the "code" which is really hiding your string. This would probably require a bit more work to retrieve the string in code (a one-time coding cost), but it might prove to be a bit more obscure.

查看更多
大哥的爱人
3楼-- · 2019-01-02 20:18

This is as secure as leaving your bike unlocked in Amsterdam, the Netherlands near Central Station. (Blink, and it's gone!)

If you're trying to add security to your application then you're doomed to fail from the start since any protection scheme will fail. All you can do is make it more complex for a hacker to find the information he needs. Still, a few tricks:

*) Make sure the string is stored as UTF-16 in your binary.

*) Add numbers and special characters to the string.

*) Use an array of 32-bits integers instead of a string! Convert each to a string and concatenate them all.

*) Use a GUID, store it as binary and convert it to a string to use.

And if you really need some pre-defined text, encrypt it and store the encrypted value in your binary. Decrypt it in runtime where the key to decrypt is one of the options I've mentioned before.

Do realize that hackers will tend to crack your application in other ways than this. Even an expert at cryptography will not be able to keep something safe. In general, the only thing that protects you is the profit a hacker can gain from hacking your code, compared to the cost of hacking it. (These costs would often be just a lot of time, but if it takes a week to hack your application and just 2 days to hack something else, something else is more likely to be attacked.)


Reply to comment: UTF-16 would be two bytes per character, thus harder to recognize for users who look at a dump of the binary, simply because there's an additional byte between every letter. You can still see the words, though. UTF-32 would even be better because it adds more space between letters. Then again, you could also compress the text a bit by changing to an 6-bit-per-character scheme. Every 4 characters would then compact to three numbers. But this would restrict you to 2x26 letters, 10 digits and perhaps the space and dot to get at 64 characters.

The use of a GUID is practical if you store the GUID in it's binary format, not it's textual format. A GUID is 16 bytes long and can be randomly generated. Thus it's difficult to guess the GUID that's used as password. But if you still need to send plain text over, a GUID could be converted to a string representation to be something like "3F2504E0-4F89-11D3-9A0C-0305E82C3301". (Or Base64-encoded as "7QDBkvCA1+B9K/U0vrQx1A==".) But users won't see any plain text in the code, just some apparently random data. Not all bytes in a GUID are random, though. There's a version number hidden in GUIDs. Using a GUID isn't the best option for cryptographic purposes, though. It's either calculated based on your MAC address or by a pseudo-random number, making it reasonable predictable. Still, it's easy to create and easy to store, convert and use. Creating something longer doesn't add more value since a hacker would just try to find other tricks to crack the security. It's just a question about how willing they are to invest more time into analyzing the binaries.

In general, the most important thing that keeps your applications safe is the number of people who are interested in it. If no one cares about your application then no one will bother to hack it either. When you're the top product with 500 million users, then your application is cracked within an hour.

查看更多
姐姐魅力值爆表
4楼-- · 2019-01-02 20:19

If you store the encryption key in reverse ("yek noitpyrcne gnorts yM") and then reverse it in your code (String.Reverse), this would prevent a simple search through the binary for the text of your encryption key.

To reiterate the point made by every other poster here, however, this will accomplish virtually nothing for you in terms of security.

查看更多
余生请多指教
5楼-- · 2019-01-02 20:21

It's a client-server application! Don't store it in the client itself, that's the place where hackers will obviously look. Instead, add (for your new client only) an extra server function (over HTTPS) to retrieve this password. Thus this password should never hit the client disk.

As a bonus, it becomes a lot easier to fix the server later. Just send a different, per-client time-limited password every time. Don't forget to allow for longer passwords in your new client.

查看更多
后来的你喜欢了谁
6楼-- · 2019-01-02 20:23

As noted in the comment to pavium's answer, you have two choices:

  • Secure the key
  • Secure the decryption algorithm

Unfortunately, if you must resort to embedding both the key and the algorithm within the code, neither is truly secret, so you're left with the (far weaker) alternative of security through obscurity. In other words, as you mentioned, you need a clever way to hide either or both of them inside your executable.

Here are some options, though you need to remember that none of these is truly secure according to any cryptographic best practices, and each has its drawbacks:

  1. Disguise your key as a string that would normally appear within the code. One example would be the format string of a printf() statement, which tends to have numbers, letters, and punctuation.
  2. Hash some or all of the code or data segments on startup, and use that as the key. (You'll need to be a bit clever about this to ensure the key doesn't change unexpectedly!) This has a potentially desirable side-effect of verifying the hashed portion of your code each time it runs.
  3. Generate the key at run-time from something that is unique to (and constant within) the system for example, by hashing the MAC address of a network adapter.
  4. Create the key by choosing bytes from other data. If you have static or global data, regardless of type (int, char, etc.), take a byte from somewhere within each variable after it's initialized (to a non-zero value, of course) and before it changes.

Please let us know how you solve the problem!

Edit: You commented that you're refactoring existing code, so I'll assume you can't necessarily choose the key yourself. In that case, follow a 2-step process: Use one of the above methods to encrypt the key itself, then use that key to decrypt the users' data.

查看更多
弹指情弦暗扣
7楼-- · 2019-01-02 20:24

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!

查看更多
登录 后发表回答