Как скрыть строку в двоичном коде? - PullRequest
61 голосов
/ 31 августа 2009

Иногда полезно скрыть строку из двоичного (исполняемого) файла. Например, имеет смысл скрыть ключи шифрования от двоичных файлов.

Когда я говорю «скрыть», я имею в виду усложнение поиска строк в скомпилированном двоичном файле.

Например, этот код:

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

после компиляции создает исполняемый файл со следующим в его разделе данных:

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        |

Вы видите, что наша секретная строка может быть легко найдена и / или изменена.

Я мог бы спрятать строку ...

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';

… но это не очень хороший метод. Есть идеи получше?

PS: я знаю, что простое сокрытие секретов не работает против решительного злоумышленника, но это намного лучше, чем ничего ...

Кроме того, я знаю об ассиметричном шифровании, но в данном случае это неприемлемо. Я выполняю рефакторинг существующего приложения, которое использует шифрование Blowfish и передает зашифрованные данные на сервер (сервер расшифровывает данные с помощью того же ключа).

Я не могу изменить алгоритм шифрования, потому что мне нужно обеспечить обратную совместимость. Я не могу даже изменить ключ шифрования.

Ответы [ 20 ]

51 голосов
/ 01 сентября 2009

Извините за длинный ответ.

Ваши ответы абсолютно верны, но вопрос был в том, как скрыть строку и сделать это красиво

Я сделал это так:

#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;\
}

Самая хитрая строка в HideString.h:

BOOST_PP_SEQ_ENUM(BOOST_PP_SEQ_FOR_EACH_I(CRYPT_MACRO, SEED, SEQ))

Позвольте мне объяснить строку. Для кода:

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

BOOST_PP_SEQ_FOR_EACH_I(CRYPT_MACRO, SEED, SEQ)
сгенерировать последовательность:
( '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))
генерировать:
'T' ^ ( 0x27 - 0 ), 'e' ^ ( 0x27 - 1 ), 's' ^ ( 0x27 - 2 ), 't' ^ ( 0x27 - 3 )

и, наконец,

DEFINE_HIDDEN_STRING(EncryptionKey2, 0x27, ('T')('e')('s')('t'))
сгенерировать:
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;
}

данные для "Мой надежный ключ шифрования" выглядят так:

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.........

Большое спасибо за ваши ответы!

46 голосов
/ 31 августа 2009

Как отмечается в комментарии к ответу павильона , у вас есть два варианта:

  • Зафиксируйте ключ
  • Безопасный алгоритм дешифрования

К сожалению, если вам нужно прибегнуть к встраиванию ключа и алгоритма в код, ни один из них не является по-настоящему секретным, поэтому у вас остается (гораздо более слабая) альтернатива защита через неизвестность . Другими словами, как вы упомянули, вам нужен умный способ скрыть один или оба из них внутри вашего исполняемого файла.

Вот несколько вариантов, хотя вы должны помнить, что ни один из них не является действительно безопасным в соответствии с любым методом криптографии, и у каждого есть свои недостатки:

  1. Замаскируйте свой ключ как строку, которая обычно появляется в коде. Одним из примеров может быть строка формата оператора printf(), который имеет тенденцию иметь цифры, буквы и знаки пунктуации.
  2. Хэш некоторые или весь код или сегменты данных при запуске и используйте его в качестве ключа. (Вы должны быть немного сообразительны в этом вопросе, чтобы гарантировать, что ключ не изменится неожиданно!) Это имеет потенциально желательный побочный эффект проверки хешированной части вашего кода каждый раз, когда он выполняется.
  3. Генерация ключа во время выполнения из чего-то, что является уникальным (и постоянным внутри) системы, например, путем хеширования MAC-адреса сетевого адаптера.
  4. Создайте ключ, выбрав байты из других данных. Если у вас есть статические или глобальные данные, независимо от типа (int, char, и т. Д. ), примите байт где-то внутри каждой переменной после ее инициализации (конечно, ненулевым значением) и до того, как она изменится.

Пожалуйста, дайте нам знать, как решить проблему!

Редактировать: Вы прокомментировали, что реорганизуете существующий код, поэтому я предполагаю, что вы не можете сами выбрать ключ. В этом случае выполните двухэтапный процесс: используйте один из вышеперечисленных методов для шифрования самого ключа, затем используйте ключ тот для расшифровки данных пользователей.

19 голосов
/ 01 сентября 2009
  1. Опубликуйте это как код проблемы с гольфом
  2. Ждите решения, написанного на J
  3. Вставить J интерпретатор в ваше приложение
11 голосов
/ 01 сентября 2009

Скрытие паролей в вашем коде - это безопасность от неясности. Это вредно, потому что заставляет вас думать, что у вас есть какой-то уровень защиты, а на самом деле у вас очень мало. Если что-то стоит обезопасить, оно стоит правильно.

PS: я знаю, что это не работает против настоящего хакера, но это намного лучше чем ничего ...

На самом деле, во многих ситуациях нет ничего лучше слабой безопасности. По крайней мере, вы точно знаете, где вы стоите. Вам не нужно быть "настоящим хакером", чтобы обойти встроенный пароль ...

РЕДАКТИРОВАТЬ: Ответ на этот комментарий:

Я знаю о парах клавиш, но это не так приемлемо в этом случае. Я рефакторинг существующее приложение, которое использует Blowfish шифрование. Зашифрованные данные передается на сервер и сервер расшифровывать данные. Я не могу изменить шифрование алгоритм, потому что я должен предоставить обратная совместимость.

Если вы заботитесь о безопасности вообще, поддержание обратной совместимости является ДЕЙСТВИТЕЛЬНО ПЛОХОЙ причиной, чтобы оставить себя уязвимым с помощью встроенных паролей. ХОРОШО сломать обратную совместимость с небезопасной схемой безопасности.

Это похоже на случай, когда уличные дети обнаруживают, что вы оставляете ключ от входной двери под ковриком, но вы продолжаете делать это, потому что дедушка ожидает найти его там.

8 голосов
/ 31 августа 2009

Ваш пример вообще не скрывает строку; строка все еще представляется как последовательность символов в выводе.

Существует множество способов запутать строки. Существует простой шифр замещения , или вы можете выполнить математическую операцию над каждым символом (например, XOR), когда результат передается в операцию следующего символа и т. Д. И т. Д.

Цель состоит в том, чтобы в итоге получить данные, которые не похожи на строку, поэтому, например, если вы работаете на большинстве западных языков, большинство значений ваших символов будут находиться в диапазоне 32-127 & mdash; поэтому ваша цель состоит в том, чтобы операция в основном выставляла их в основном из этого диапазона, чтобы они не привлекали внимание.

7 голосов
/ 31 августа 2009

Это так же безопасно, как оставить свой мотоцикл разблокированным в Амстердаме, Нидерланды, рядом с Центральным железнодорожным вокзалом. (Моргни, и все прошло!)

Если вы пытаетесь повысить безопасность своего приложения, вы обречены на провал с самого начала, так как любая схема защиты потерпит неудачу. Все, что вы можете сделать, это усложнить хакеру поиск нужной ему информации. Еще несколько хитростей:

*) Убедитесь, что в вашем двоичном файле строка хранится как UTF-16.

*) Добавление чисел и специальных символов в строку.

*) Используйте массив 32-битных целых вместо строки! Преобразуйте каждый в строку и объедините их все.

*) Используйте GUID, сохраните его как двоичный файл и преобразуйте в используемую строку.

И если вам действительно нужен какой-то заранее определенный текст, зашифруйте его и сохраните зашифрованное значение в вашем двоичном файле. Расшифруйте его во время выполнения, где ключ дешифрования является одним из вариантов, которые я упоминал ранее.

Поймите, что хакеры будут взламывать ваше приложение другими способами, кроме этого. Даже специалист по криптографии не сможет сохранить что-то в безопасности. В общем, единственная вещь, которая защищает вас, это прибыль, которую хакер может получить от взлома вашего кода, по сравнению со стоимостью взлома. (Эти затраты часто занимают много времени, но если для взлома вашего приложения потребуется неделя и всего 2 дня для взлома чего-то другого, скорее всего, что-то еще будет атаковано.)


Ответ на комментарий: UTF-16 будет составлять два байта на символ, поэтому его сложнее распознать пользователям, которые смотрят на дамп двоичного файла, просто потому, что между каждой буквой есть дополнительный байт. Вы все еще можете увидеть слова, хотя. UTF-32 будет даже лучше, потому что он добавляет больше места между буквами. Опять же, вы также можете немного сжать текст, изменив схему на 6 бит на символ. Каждые 4 символа будут затем сжаты до трех чисел. Но это ограничит вас 2х26 буквами, 10 цифрами и, возможно, пробелом и точкой, чтобы получить 64 символа.

Использование GUID целесообразно, если вы храните GUID в двоичном формате, а не в текстовом формате. GUID имеет длину 16 байтов и может генерироваться случайным образом. Таким образом, трудно угадать GUID, который используется в качестве пароля. Но если вам все еще нужно отправить простой текст, GUID может быть преобразован в строковое представление, например, «3F2504E0-4F89-11D3-9A0C-0305E82C3301». (Или Base64, закодированный как «7QDBkvCA1 + B9K / U0vrQx1A ==».) Но пользователи не увидят в коде простой текст, только некоторые явно случайные данные. Однако не все байты в GUID являются случайными. В GUID скрыт номер версии. Однако использование GUID - не лучший вариант для криптографических целей. Он либо рассчитывается на основе вашего MAC-адреса, либо по псевдослучайному числу, что делает его достаточно предсказуемым. Тем не менее, его легко создавать и хранить, конвертировать и использовать. Создание чего-то более длинного не добавляет ценности, так как хакер просто попытается найти другие приемы взлома безопасности. Вопрос лишь в том, насколько они готовы тратить больше времени на анализ двоичных файлов.

В общем, самое важное, что обеспечивает безопасность ваших приложений, - это количество людей, которые заинтересованы в этом. Если никто не заботится о вашем приложении, то никто не потрудится взломать его. Когда вы пользуетесь популярностью среди 500 миллионов пользователей, ваше приложение будет взломано в течение часа.

4 голосов
/ 01 сентября 2009

Я был когда-то в столь же неловком положении. У меня были данные, которые должны были быть в двоичном, но не в текстовом формате. Моим решением было зашифровать данные, используя очень простую схему, которая делала их похожими на остальную часть программы. Я зашифровал его, написав программу, которая взяла строку, преобразовал все символы в код ASCII (дополненный нулями, чтобы получить трехзначное число), а затем добавил случайную цифру в начало и конец трехзначного кода. , Таким образом, каждый символ строки был представлен 5 символами (все числа) в зашифрованной строке. Я вставил эту строку в приложение как константу, а затем, когда мне понадобилось использовать строку, я расшифровал и сохранил результат в переменной достаточно долго, чтобы сделать то, что мне нужно.

Итак, чтобы использовать ваш пример, «Мой надежный ключ шифрования» становится «207719121310329211541116181145111157110071030703283101101109309926114151216611289116161056811109110470321510787101511213». Затем, когда вам нужен ваш ключ шифрования, расшифруйте его, но отмените процесс.

Это, конечно, не пуленепробиваемый, но я не стремился к этому.

3 голосов
/ 31 августа 2009

Это клиент-серверное приложение! Не храните это в самом клиенте, это место, где хакеры, очевидно, будут искать. Вместо этого добавьте (только для вашего нового клиента) дополнительную серверную функцию (через HTTPS) для восстановления этого пароля. Таким образом, этот пароль никогда не должен попасть на клиентский диск.

В качестве бонуса становится намного проще исправить сервер позже. Просто каждый раз отправляйте другой, ограниченный по времени пароль для каждого клиента. Не забудьте разрешить использование более длинных паролей в вашем новом клиенте.

3 голосов
/ 20 августа 2017

Для C проверьте это: https://github.com/mafonya/c_hide_strings

Для C ++ это:

class Alpha : public std::string
{
public:
    Alpha(string str)
    {
        std::string phrase(str.c_str(), str.length());
        this->assign(phrase);
    }
    Alpha c(char c) {
        std::string phrase(this->c_str(), this->length());
        phrase += c;
        this->assign(phrase);

        return *this;
    }
};

Чтобы использовать это, просто включите Alpha и:

Alpha str("");
string myStr = str.c('T').c('e').c('s').c('t');

Таким образом, mystr теперь "Test" и строка скрыта из таблицы строк в двоичном виде.

3 голосов
/ 31 августа 2009

Технология шифрования достаточно сильна, чтобы защитить важные данные без сокрытия их в двоичном файле.

Или ваша идея использовать бинарный файл для маскировки того факта, что что-то скрыто?

Это будет называться стеганография .

...