Как скрыть строку в двоичном коде?
Иногда полезно скрыть строку из двоичного (исполняемого) файла. Например, имеет смысл скрывать ключи шифрования от двоичных файлов.
Когда я говорю "скрыть", я имею в виду усложнение поиска строк в скомпилированном двоичном файле.
Например, этот код:
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 и передает зашифрованные данные на сервер (сервер расшифровывает данные с помощью того же ключа).
Я не могу изменить алгоритм шифрования, потому что мне нужно обеспечить обратную совместимость. Я даже не могу изменить ключ шифрования.
23 ответа
Как отмечено в комментарии к ответу павиума, у вас есть два варианта:
- Зафиксируйте ключ
- Безопасный алгоритм дешифрования
К сожалению, если вам нужно прибегнуть к встраиванию ключа и алгоритма в код, то ни один из них не является по-настоящему секретным, поэтому у вас остается (гораздо более слабая) альтернатива безопасности через неизвестность. Другими словами, как вы упомянули, вам нужен умный способ скрыть один или оба из них внутри вашего исполняемого файла.
Вот несколько вариантов, хотя вы должны помнить, что ни один из них не является действительно безопасным в соответствии с какими-либо криптографическими рекомендациями, и у каждого есть свои недостатки:
- Замаскируйте ваш ключ как строку, которая обычно появляется в коде. Одним из примеров будет строка формата
printf()
утверждение, которое имеет тенденцию иметь цифры, буквы и знаки препинания. - Хэшируйте часть или весь код или сегменты данных при запуске и используйте это как ключ. (Вы должны быть немного сообразительны в этом вопросе, чтобы гарантировать, что ключ не изменится неожиданно!) Это имеет потенциально желательный побочный эффект проверки хешированной части вашего кода каждый раз, когда он выполняется.
- Генерируйте ключ во время выполнения из чего-то, что является уникальным (и постоянным внутри) системы, например, путем хеширования MAC-адреса сетевого адаптера.
- Создайте ключ, выбрав байты из других данных. Если у вас есть статические или глобальные данные, независимо от типа (
int
,char
и т. д.), возьмите байт где-то внутри каждой переменной после ее инициализации (конечно, с ненулевым значением) и до ее изменения.
Пожалуйста, дайте нам знать, как вы решаете проблему!
Изменить: Вы прокомментировали, что вы рефакторинг существующего кода, поэтому я предполагаю, что вы не можете выбрать ключ самостоятельно. В этом случае выполните двухэтапный процесс: используйте один из приведенных выше методов для шифрования самого ключа, а затем используйте этот ключ для расшифровки данных пользователей.
Прошу прощения за длинный ответ.
Ваши ответы абсолютно верны, но вопрос заключался в том, как скрыть строку и сделать это красиво.
Я сделал это таким образом:
#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.........
Большое спасибо за ваши ответы!
- Опубликуйте это как проблему с кодом гольфа
- Ждите решения, написанного на J
- Встроить переводчик J в ваше приложение
Скрытие паролей в вашем коде - это безопасность от неясности. Это вредно, потому что заставляет вас думать, что у вас есть какой-то уровень защиты, а на самом деле у вас очень мало. Если что-то стоит обезопасить, оно стоит правильно
PS: я знаю, что это не работает против настоящего хакера, но это намного лучше, чем ничего...
На самом деле, во многих ситуациях нет ничего лучше слабой безопасности. По крайней мере, вы точно знаете, где вы стоите. Вам не нужно быть "настоящим хакером", чтобы обойти встроенный пароль...
РЕДАКТИРОВАТЬ: Отвечая на этот комментарий:
Я знаю о парах ключей, но это не приемлемо в этом случае. Я рефакторинг существующего приложения, которое использует шифрование Blowfish. Зашифрованные данные передаются на сервер, а сервер расшифровывает данные. Я не могу изменить алгоритм шифрования, потому что я должен обеспечить обратную совместимость.
Если вы заботитесь о безопасности вообще, поддержание обратной совместимости является ДЕЙСТВИТЕЛЬНО ПЛОХОЙ причиной, чтобы оставить себя уязвимым с помощью встроенных паролей. ХОРОШО сломать обратную совместимость с небезопасной схемой безопасности.
Это похоже на случай, когда уличные дети обнаруживают, что вы оставляете ключ от входной двери под ковриком, но вы продолжаете делать это, потому что дедушка ожидает найти его там.
Для 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" и строка скрыта из таблицы строк в двоичном виде.
Ваш пример не скрывает строку вообще; строка по-прежнему представлена в виде последовательности символов в выводе.
Существует множество способов запутать строки. Есть простой шифр замещения, или вы можете выполнить математическую операцию над каждым символом (например, XOR), где результат передается в операцию следующего символа и т. Д., И т. Д.
Цель состоит в том, чтобы в итоге получить данные, которые не похожи на строку, поэтому, например, если вы работаете на большинстве западных языков, большинство значений ваших символов будет находиться в диапазоне 32-127 - поэтому ваша цель будет для операции, чтобы вывести их в основном из этого диапазона, чтобы они не привлекли внимание.
Это так же безопасно, как оставить свой мотоцикл разблокированным в Амстердаме, Нидерланды, недалеко от Центрального вокзала. (Моргни, и это ушло!)
Если вы пытаетесь повысить безопасность своего приложения, вы обречены на провал с самого начала, так как любая схема защиты потерпит неудачу. Все, что вы можете сделать, это усложнить хакеру поиск нужной ему информации. Еще несколько хитростей:
*) Убедитесь, что строка хранится в вашем бинарном файле как 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 миллионами пользователей, ваше приложение будет взломано в течение часа.
Вы можете использовать библиотеку C++, которую я разработал для этой цели. Еще одна статья, которую гораздо проще реализовать, стала лучшей статьей на C++ за сентябрь 2017 года.
Я был когда-то в столь же неловком положении. У меня были данные, которые должны были быть в двоичном, но не в текстовом формате. Мое решение состояло в том, чтобы зашифровать данные, используя очень простую схему, которая делала их похожими на остальную часть программы. Я зашифровал его, написав программу, которая взяла строку, преобразовал все символы в код ASCII (дополненный нулями, чтобы получить трехзначное число), а затем добавил случайную цифру в начало и конец трехзначного кода., Таким образом, каждый символ строки был представлен 5 символами (все числа) в зашифрованной строке. Я вставил эту строку в приложение как константу, а затем, когда мне понадобилось использовать строку, я расшифровал и сохранил результат в переменной достаточно долго, чтобы сделать то, что мне нужно.
Итак, чтобы использовать ваш пример, "Мой надежный ключ шифрования" становится "20771912131032921154111618114511115711007103070328310110110930992611415121661121111151610568116161056811109110470321510787101511213". Затем, когда вам нужен ключ шифрования, расшифруйте его, но отмените процесс.
Это, конечно, не пуленепробиваемый, но я не стремился к этому.
Технология шифрования достаточно сильна, чтобы защитить важные данные, не пряча их в двоичном файле.
Или ваша идея использовать двоичный файл, чтобы скрыть тот факт, что что-то скрыто?
Это будет называться стеганографией.
Это клиент-серверное приложение! Не храните это в самом клиенте, это место, где хакеры, очевидно, будут искать. Вместо этого добавьте (только для вашего нового клиента) дополнительную серверную функцию (через HTTPS) для восстановления этого пароля. Таким образом, этот пароль никогда не должен попадать на клиентский диск.
В качестве бонуса становится намного проще исправить сервер позже. Просто каждый раз отправляйте другой, ограниченный по времени пароль для каждого клиента. Не забудьте учесть более длинные пароли в вашем новом клиенте.
Если вы храните ключ шифрования в обратном порядке ("yek noitpyrcne gnorts yM"), а затем переворачиваете его в своем коде (String.Reverse), это помешает простому поиску в двоичном коде текста вашего ключа шифрования.
Однако, чтобы повторить точку зрения каждого другого автора, это практически ничего не даст вам в плане безопасности.
Вы можете закодировать строку с помощью некоторой тривиальной кодировки, например, xor с двоичным кодом 01010101. Конечно, никакой реальной защиты нет, но мешает использование таких инструментов, как string
,
Вот пример того, что они объяснили, но имейте в виду, что это будет просто сломано любым, кто является "хакером", но остановит детишек с помощью шестнадцатеричного редактора. Приведенный мною пример просто добавляет значение 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;
}
Вы можете взглянуть на антишпионскую библиотеку обфускации C/C++ для всех платформ, которые предлагают ряд техник обфускации.
Их строковое шифрование решит вашу проблему.
Создайте функцию, которая присваивает ваш пароль статическому массиву символов и возвращает указатель на эту функцию. Затем запустите эту функцию через программу запутывания.
Если программа делает хорошую работу. должно быть невозможно прочитать ваш простой текстовый пароль с помощью шестнадцатеричного редактора для проверки двоичного кода программы. (по крайней мере, не без обратного инжиниринга языка ассемблера. Это должно остановить всех детишек сценария, вооруженных "строками" или шестнадцатеричными редакторами, за исключением преступно-безумного хакера, которому нечем потратить свое время.)
Я думаю, вы хотите, чтобы это выглядело как инструкции, ваш пример
х [у ++]='М'; х [у ++]='у'; ...
Если бы это было сделано, длинная последовательность повторяющихся инструкций с небольшим изменением может выделиться, и это было бы плохо, рассматриваемый байт может быть закодирован в инструкции как есть, и это было бы плохо, так что, возможно, метод xor и, возможно, некоторые другие приемы, чтобы сделать этот длинный участок кода не выделяющимся, возможно, некоторые фиктивные вызовы функций. Зависит также от вашего процессора, например, ARM очень легко смотреть на двоичные данные и выбирать инструкции из данных и оттуда (если вы ищете ключ по умолчанию), чтобы, возможно, выбрать то, что может быть ключом, потому что это данные, но не ASCII и атаковать это. Аналогично, блок похожих инструкций с изменяющимся непосредственным полем, даже если у вас есть компилятор или данные с константой.
Я предлагаю M4.
Храните строку с макросами, такими как
const string sPassword = _ENCRYPT("real password");
Перед сборкой разверните макросы до зашифрованной строки с помощью m4, чтобы ваш код выглядел следующим образом
const string sPassword = "encrypted string";
Расшифровывать в среде выполнения.
Как уже упоминалось, запутывание строк - не лучший способ обеспечить защиту. Если вы не хотите случайного изучения строки в двоичном исполняемом файле; тогда есть пара вещей, которые я бы сделал.
Я бы создал большую главную строку (Ms) и вставил в нее отдельные части защищаемой строки (S1) в разных местах строки (p1,p2,p3... и т. д.). Сделайте эти места довольно случайными. В каждом месте я помещу небольшую подстроку S1. Каждая подстрока может иметь разную длину (l1, l2 и т. д.). Затем в коде я буду динамически извлекать каждую часть и объединять все части вместе, чтобы реконструировать S1 в коде. Таким образом, код хранит два массива целых чисел: один для получения каждой позиции подстроки [p1,p2,p3...], а второй массив объясняет, сколько символов нужно прочитать из каждой позиции [l1,l2,l3...].
В таком случае мастер-строка (Ms) будет видна, но любознательный человек не сможет узнать, как вернуть S1, если код не будет реконструирован, два массива извлечены и тот же процесс выполнен. Конечно, основная строка (Ms) должна быть достаточно большой и случайной.
В некоторых случаях такие струны можно сделать сильнее. В случае, если у вас есть способ использовать разные строки для одной и той же внутренней задачи, которую помогала выполнить строка. Например, я могу захотеть динамически построить перец для хеширования... Я бы объединил текущую временную метку unix с указанной выше (s1) и использовал временную метку s1+ в качестве строки.
Конечно, если защищаемая строка является константой, то этот дополнительный подход, основанный на метках времени, невозможен.
Это было навеяно случаем в истории Алибабы: один из 40 воров обнаружил дом Алибабы в деревне. Он использовал мел, чтобы написать «х» на двери этого дома. К счастью, девушка Алибабы увидела это и поняла, что вор вернется с остальными 39 и нападет на дом. Поэтому она поставила крестик во всех домах деревни. Вот что заставило меня подумать, что можно сделать то же самое — просто обрезать строку, чтобы ее запутать, и похоронить каждую часть во всей мастер-строке «деревни».
Надеюсь, это соответствует тому, что было задано, и это помогает. Обратная связь приветствуется.
Зашифруйте ключ шифрования другим кодом. Показать изображение другого кода пользователю. Теперь пользователь должен ввести ключ, который он видит (как капчу, но всегда один и тот же код). Это также делает невозможным предсказание кода другими программами. При желании вы можете сохранить (соленый) хеш-код для проверки ввода пользователя.
Интересно, можно ли после первого затенения, как уже упоминали другие, вставить вашу строку в блок сборки, чтобы она выглядела как инструкция. Тогда вы могли бы иметь "if 0" или "goto just_past_string_assembly", чтобы перепрыгнуть через "код", который действительно скрывает вашу строку. Это, вероятно, потребовало бы немного больше работы для извлечения строки в коде (единовременная стоимость кодирования), но это может оказаться немного более неясным.
Вот скрипт perl для генерации запутанного c-кода, чтобы скрыть незашифрованный пароль от программы "strings".
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;";
}
}
}