Как скрыть строку в двоичном коде?

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

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

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

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 и передает зашифрованные данные на сервер (сервер расшифровывает данные одним и тем же ключом).

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

Ответ 1

Как отмечено в комментарии к pavium answer, у вас есть два варианта:

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

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

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

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

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

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

Ответ 2

Прошу прощения за длинный ответ.

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

Я сделал это таким образом:

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

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

Ответ 3

  • Опубликовать его в качестве проблемы с кодом.
  • Дождитесь решения, написанного на J
  • Вставить J-интерпретатор в ваше приложение.

Ответ 4

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

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

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

EDIT: ответ на этот комментарий:

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

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

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

Ответ 5

Ваш пример не скрывает строку; строка по-прежнему представлена ​​в виде последовательности символов на выходе.

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

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

Ответ 6

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

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

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

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

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

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

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

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


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

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

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

Ответ 7

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

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

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

Ответ 8

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

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

Это будет называться steganography.

Ответ 9

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

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

Ответ 10

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

Для С++ это:

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", и строка скрыта из таблицы строк в двоичном формате.

Ответ 11

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

Ответ 12

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

Когда у вас это работает в вашем приложении, я бы сделал "математику", я использовал немного сложнее

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

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

string Encrypted = EncryptMystring("AAbbBb");
string Decrypted = DecryptMystring(Encrypted);

string DecryptMystring(string RawStr)
    {
        string DecryptedStr = "";
        for (int i = 0; i < RawStr.Length; i++)
        {
            DecryptedStr += (char)((int)RawStr[i] - 80 + i);
        }

        return DecryptedStr;
    }

    string EncryptMystring(string RawStr)
    {
        string EncryptedStr = "";
        for (int i = 0; i < RawStr.Length; i++)
        {
            EncryptedStr += (char)((int)RawStr[i] + 80 - i);
        }

        return EncryptedStr;
    }

Ответ 13

Если вы сохраните ключ шифрования в обратном порядке ( "yek noitpyrcne gnorts yM" ), а затем измените его в своем коде (String.Reverse), это предотвратит простой поиск через двоичный код для текста вашего ключа шифрования.

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

Ответ 14

Вы можете использовать библиотеку c++, которую я разработал для этой цели. Еще одна статья, которую гораздо проще реализовать, была признана лучшей статьей c++ за сентябрь 2017 года. Более простой способ скрытия строк см. в TinyObfuscate.

Ответ 15

создайте функцию, которая назначает ваш пароль статическому массиву char и возвращает указатель на эту функцию. Затем запустите эту функцию через программу обфускации.

Если программа выполняет хорошую работу. для чтения двоичного файла программы должно быть невозможно прочитать свой текстовый пароль с помощью шестнадцатеричного редактора. (по крайней мере, не без обратного инжиниринга языка ассемблера). Это должно остановить всех детей из числа "w370", вооруженных "строками" или шестнадцатеричными редакторами, за исключением преступного безумного хакера, которому нечего терять время.)

Ответ 16

Я думаю, вы хотите сделать его похожим на инструкции, ваш пример

х [у ++] = 'M'; х [у ++] = 'у'; ...

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

Ответ 17

Интересно, если после первого затенения, как это упоминали другие, вы можете вставить свою строку в блок сборки, чтобы попытаться сделать ее похожим на инструкции. Тогда вы могли бы получить "if 0" или "goto just_past_string_assembly", чтобы перепрыгнуть через "код", который действительно скрывает вашу строку. Это, вероятно, потребует немного больше работы для извлечения строки в коде (одноразовая стоимость кодирования), но это может оказаться немного более неясным.

Ответ 18

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

Ответ 19

Я предлагаю m4.

  • Сохраните строку с макросами типа const string sPassword = _ENCRYPT("real password");

  • Перед сборкой разверните макросы в зашифрованную строку с помощью m4, поэтому ваш код выглядит как const string sPassword = "encrypted string";

  • Расшифровывать в среде выполнения.

Ответ 20

Здесь perl script, чтобы сгенерировать запутанный c-код, чтобы скрыть пароль открытого текста из программы "строки".

  obfuscate_password("myPassword123");

  sub obfuscate_password($) {

  my $string = shift;
  my @c = split(//, $string);
  push(@c, "skip"); # Skip Null Terminator
                    # using memset to clear this byte
  # Add Decoy Characters
  for($i=0; $i < 100; $i++) {
    $ch = rand(255);
    next if ($ch == 0);
    push(@c, chr($ch));
  }                     
  my $count1 = @c;
  print "  int x1, x2, x3, x4;\n";
  print "  char password[$count1];\n";
  print "  memset(password, 0, $count1);\n";
  my $count2 = 0;
  my %dict  = ();
  while(1) {
    my $x = int(rand($count1));
    $y = obfuscate_expr($count1, $x);
    next if (defined($dict{$x}));
    $dict{$x} = 1;
    last if ($count2+1 == $count1);
    if ($c[$x] ne "skip") {
      #print "  $y\n";
      print "  $y password[x4] = (char)" . ord($c[$x]) . ";\n";
    }
    $count2++;
  }
  }

  sub obfuscate_expr($$) {
    my $count  = shift;
    my $target = shift;
    #return $target;

    while(1) {

       my $a = int(rand($count*2));
       my $b = int(rand($count*2));
       my $c = int(rand($count*2));
       next if (($a == 0) || ($b == 0) || ($c == 0));
       my $y = $a - $b;
       #print "$target: $y : $a - $b\n";
       if ($y == $target) {
          #return "$a - $b + $c";
          return "x1=$a; x2=$b; x3=$c; x4=x1-x2+x3; x5= +=x4;";
       }
    } 
  }