【问题标题】:Encrypting / obfuscating a string literal at compile-time在编译时加密/混淆字符串文字
【发布时间】:2011-10-19 12:55:15
【问题描述】:

我想在编译时加密/编码一个字符串,这样原始字符串就不会出现在编译的可执行文件中。

我见过几个例子,但它们不能将字符串文字作为参数。请参阅以下示例:

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");

但它限制了字符串的大小。

有什么方法可以实现我想要的吗?

【问题讨论】:

  • 解决此类问题的常用方法是编写一个脚本,将源文件作为输入并创建一个修改后的文件作为输出,然后由构建过程使用。在这种情况下,脚本将查找 EncryptString&lt;"String to encrypt"&gt; 并将字符串替换为加密/编码版本。
  • 重复:How to hide a string in binary code?(虽然没有关闭,经过 10 年的积极工作)。
  • 我有用于 XOR 混淆的 C++ 代码,不需要预处理,只需用户定义的文字,如 an answer to How to encrypt strings at compile time?。它可能无需太多工作就可以适应其他简单的密码,例如 Vigenère。正如评论所说,我们可能需要降低编译器的优化级别。

标签: c++ string templates metaprogramming compile-time


【解决方案1】:

我认为这个问题值得更新。

几年前我问这个问题时,我没有考虑the difference between obfuscation and encryption。如果我当时知道这种差异,我之前会在标题中包含术语混淆

C++11 和 C++14 具有能够以有效且有效的方式实现 compile-time 字符串混淆(可能还有加密,虽然我还没有尝试过)的特性。相当简单的方法,并且已经完成了。

ADVobfuscator 是由 Sebastien Andrivet 创建的混淆库,它使用 C++11/14 生成编译时混淆代码,无需使用任何外部工具,只需 C++ 代码。无需创建额外的构建步骤,只需包含它并使用它。我不知道不使用外部工具或构建步骤的更好的编译时字符串加密/混淆实现。如果有,请分享。

它不仅混淆字符串,而且还有其他有用的东西,比如可以随机混淆函数调用的编译时 FSM (Finite State Machine) 和编译时伪随机数生成器,但这些不在这个答案的范围。

这是一个使用 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's 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's still deobfuscation
    cout << narrator.decrypt() << endl;
}

您可以用自己的宏替换宏 DEF_OBFUSCATEDOBFUSCATED。例如:

#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),以及由所选算法用于xor 字符串字符的随机char

“嘿,但如果我们算一下,3 个算法 * 255 个可能的字符键(0 未使用)= 765 个混淆字符串的变体”

你是对的。相同的字符串只能以 765 种不同的方式进行混淆。如果您有理由需要更安全的东西(您是偏执狂/您的应用程序需要更高的安全性),您可以扩展库并实现自己的算法,使用更强的混淆甚至加密(White-Box cryptography 在库的路线图中)。


它在哪里/如何存储混淆字符串?

我觉得这个实现有趣的一点是它不会将混淆后的字符串存储在可执行文件的数据部分。 相反,它被静态存储到MetaString 对象本身(在堆栈上),并且算法在运行时将其解码到位。这种方法使得在静态或运行时查找混淆字符串变得更加困难。

您可以自己深入了解实施。这是一个非常好的基本混淆解决方案,可以作为更复杂解决方案的起点。

【讨论】:

  • 虽然您的回答确实详细说明了如何使用您已链接到的工具,但如果您也可以在此处添加(至少一些)实施细节,那就太好了.否则,如果您的链接失效,未来的读者将只能看到如何使用他们无权访问的混淆器的实现,这会有点令人生气。
  • 感谢您的建议。我将发布实施的详细信息。
  • github.com/adamyaxley/Obfuscate 是一个在 C++14 中构建的优秀字符串文字混淆库,它不需要任何额外的步骤来使用,因为它只是标题并且适用于字符串文字可以是的每一种情况使用过(免责声明:这是我写的一个库)。
  • @AdamYaxley 太棒了,特别是对于默认情况下没有 boost 的 ndk 开发人员,完全完成了这项工作。非常感谢
  • @AdamYaxley 你能看看这里的评论吗stackoverflow.com/questions/1648618/…“通过简单地修补到 MOV R0, 1 + BX LR 可以在 ARM 上轻松击败混淆”
【解决方案2】:

使用模板元编程为自己省去一大堆麻烦,只需编写一个独立的程序来加密字符串并生成一个 cpp 源文件,然后编译进去。这个程序将在你编译之前运行并生成一个 cpp和/或包含加密字符串供您使用的头文件。

所以这是你的开始:

  1. encrypted_string.cpp 和 encrypted_string.h(空白)
  2. 将文本文件作为输入并重写 encrypted_string.cpp 和 encrypted_string.h 的脚本或独立应用程序

如果脚本失败,您的编译将失败,因为您的代码中将引用一个不存在的变量。你可以变得更聪明,但这足以让你开始。

【讨论】:

    【解决方案3】:

    您找到的示例不能将字符串文字作为模板参数的原因是因为 ISO C++ 标准不允许这样做。这是因为,即使 c++ 有一个字符串类,字符串文字仍然是一个 const char *。因此,即使您可以访问此类编译时字符串文字的字符,您也不能或不应该更改它(导致未定义的行为)。

    我看到的唯一方法是使用定义,因为它们在编译器之前由预处理器处理。在这种情况下,也许 boost 会为您提供帮助。

    【讨论】:

      【解决方案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;
      }
      

      【讨论】:

        猜你喜欢
        • 2011-11-08
        • 1970-01-01
        • 2011-05-05
        • 1970-01-01
        • 2010-11-02
        • 2017-05-16
        • 1970-01-01
        • 2011-03-14
        • 2013-11-28
        相关资源
        最近更新 更多