Шифрование/обфускание строкового литерала во время компиляции

Я хочу зашифровать/закодировать строку во время компиляции, чтобы исходная строка не отображалась в скомпилированном исполняемом файле.

Я видел несколько примеров, но они не могут принимать строковый литерал в качестве аргумента. См. Следующий пример:

template<char c> struct add_three {
    enum { value = c+3 };
};

template <char... Chars> struct EncryptCharsA {
    static const char value[sizeof...(Chars) + 1];
};

template<char... Chars>
char const EncryptCharsA<Chars...>::value[sizeof...(Chars) + 1] = {
    add_three<Chars>::value...
};

int main() {   
    std::cout << EncryptCharsA<'A','B','C'>::value << std::endl;
    // prints "DEF"
}

Я не хочу предоставлять каждый символ отдельно, как он. Моя цель - передать строковый литерал следующим образом:

EncryptString<"String to encrypt">::value

Также есть несколько примеров, таких как:

#define CRYPT8(str) { CRYPT8_(str "\0\0\0\0\0\0\0\0") }
#define CRYPT8_(str) (str)[0] + 1, (str)[1] + 2, (str)[2] + 3, (str)[3] + 4, (str)[4] + 5, (str)[5] + 6, (str)[6] + 7, (str)[7] + 8, '\0'

// calling it
const char str[] = CRYPT8("ntdll");

Но он ограничивает размер строки.

Есть ли способ достичь того, чего я хочу?

Ответ 1

Я думаю, что этот вопрос заслуживает обновленного ответа.

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

С++ 11 и С++ 14 имеют функции, которые позволяют реализовать компиляцию string obfuscation (и, возможно, шифрование, хотя я еще не пробовал это) в эффективном и достаточно простой способ, и это уже сделано.

ADVobfuscator - это библиотека обфускации, созданная Sebastien Andrivet, которая использует С++ 11/14 для генерации сфабрифицированного кода времени компиляции без используя любой внешний инструмент, только код на С++. Нет необходимости создавать дополнительные шаги сборки, просто включите его и используйте. Я не знаю лучшей реализации шифрования/обфускации строкой времени компиляции, которая не использует внешние инструменты или шаги сборки. Если да, разделите.

Он не только обдумывает строки, но и имеет другие полезные вещи, такие как FSM компиляции (Конечный автомат), который может случайным образом запутывать вызовы функций и генератор псевдослучайных чисел времени компиляции, но они не входят в объем ответа.

Вот пример простой обфускации с использованием ADVobfuscator:

#include "MetaString.h"

using namespace std;
using namespace andrivet::ADVobfuscator;

void Example()
{
    /* Example 1 */

    // here, the string is compiled in an obfuscated form, and
    // it only deobfuscated at runtime, at the very moment of its use
    cout << OBFUSCATED("Now you see me") << endl;

    /* Example 2 */

    // here, we store the obfuscated string into an object to
    // deobfuscate whenever we need to
    auto narrator = DEF_OBFUSCATED("Tyler Durden");

    // note: although the function is named `decrypt()`, it still deobfuscation
    cout << narrator.decrypt() << endl;
}

Вы можете заменить макросы DEF_OBFUSCATED и OBFUSCATED на свои собственные макросы. Например:.

#define _OBF(s) OBFUSCATED(s)

...

cout << _OBF("klapaucius");

Как это работает?

Если вы посмотрите на определение этих двух макросов в MetaString.h, вы увидите:

#define DEF_OBFUSCATED(str) MetaString<andrivet::ADVobfuscator::MetaRandom<__COUNTER__, 3>::value, andrivet::ADVobfuscator::MetaRandomChar<__COUNTER__>::value, Make_Indexes<sizeof(str) - 1>::type>(str)

#define OBFUSCATED(str) (DEF_OBFUSCATED(str).decrypt())

В принципе, существует три разных варианта класса MetaString (ядро обфускации строки). Каждый из них имеет свой собственный алгоритм обфускации. Один из этих трех вариантов выбирается случайным образом во время компиляции, используя генератор псевдослучайных чисел библиотеки (MetaRandom), а также случайный char, который используется выбранным алгоритмом для xor символов строки.

"Эй, но если мы сделаем математику, 3 алгоритма * 255 возможных char ключей (0 не используется) = 765 вариантов обфускации строки

Ты прав. Та же строка может быть запущена только 765 различными способами. Если у вас есть причина нуждаться в чем-то более безопасном (вы параноидны/ваше приложение требует повышенной безопасности), вы можете расширить библиотеку и реализовать свои собственные алгоритмы, используя более сильное обфускацию или даже шифрование (White -Box-криптография находится в дорожной карте lib).


Где/как он хранит запутанные строки?

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

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

Ответ 2

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

Итак, вот что вы начинаете с:

  • encrypted_string.cpp и encrypted_string.h(которые пусты)
  • A script или автономное приложение, которое принимает текстовый файл как ввод и запись write encrypted_string.cpp и encrypted_string.h

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

Ответ 3

Причина, по которой найденные вами примеры не могут принимать строковые литералы в качестве аргумента шаблона, потому что это запрещено стандартом ISO С++. Это потому, что, хотя С++ имеет строковый класс, строковый литерал по-прежнему является const char *. Таким образом, вы не можете или не должны его изменять (приводит к поведению undefined), даже если вы можете получить доступ к символам такого литерала строки времени компиляции.

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

Ответ 4

Решение на основе макросов должно было бы принимать вариационный аргумент и передавать в каждой части строки как один токен. Затем строчите токен и зашифруйте его и соедините все токены. Конечный результат будет выглядеть примерно так.

CRYPT(m y _ s t r i n g)

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

Что-то вроде этого может это сделать, хотя последовательность Boost.PP не делает ее более красивой.

#include <iostream>
#include <boost/preprocessor/stringize.hpp>
#include <boost/preprocessor/seq/for_each.hpp>

#define GARBLE(x) GARBLE_ ## x
#define GARBLE_a x
#define GARBLE_b y
#define GARBLE_c z

#define SEQ (a)(b)(c)
#define MACRO(r, data, elem) BOOST_PP_STRINGIZE(GARBLE(elem))

int main() {
  const char* foo = BOOST_PP_SEQ_FOR_EACH(MACRO, _, SEQ);
  std::cout << foo << std::endl;
}