如何用二进制代码隐藏字符串?

时间:2009-08-31 10:53:55

标签: c++ obfuscation

有时,隐藏二进制(可执行)文件中的字符串很有用。 例如,隐藏二进制文件中的加密密钥是有意义的。

当我说“隐藏”时,我的意思是在编译的二进制文件中更难找到字符串。

例如,此代码:

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加密的现有应用程序,并将加密数据传递给服务器(服务器使用相同的密钥解密数据)。

无法更改加密算法,因为我需要提供向后兼容性。我无法甚至更改加密密钥。

22 个答案:

答案 0 :(得分:50)

对不起,我很抱歉。

你的答案绝对正确,但问题是如何隐藏字符串并做得很好。

我是这样做的:

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

非常感谢您的回答!

答案 1 :(得分:46)

正如对pavium answer的评论所述,您有两种选择:

  • 保护密钥
  • 保护解密算法

不幸的是,如果你必须求助于在代码中嵌入密钥和算法,那么这两者都不是真正的秘密,所以你只剩下({far})替代security through obscurity。换句话说,正如您所提到的,您需要一种聪明的方法来隐藏可执行文件中的一个或两个。

以下是一些选项,但您需要记住,根据任何加密最佳做法,这些都不是真正的安全,并且每个选项都有其缺点:

  1. 将您的密钥伪装成通常出现在代码中的字符串。一个示例是printf()语句的格式字符串,它往往包含数字,字母和标点符号
  2. 启动时
  3. Hash部分或全部代码或数据段,并将其用作密钥。 (你需要对此有点聪明,以确保密钥不会意外地改变!)这有可能产生的副作用,即每次运行时验证代码的散列部分。
  4. 在运行时生成密钥,从系统中唯一的(并且在系统内),通过散列网络适配器的MAC地址。
  5. 通过从其他数据中选择字节来创建密钥。如果您有静态或全局数据,则无论类型如何(intchar等< / em>),在每个变量初始化之后从某个地方取一个字节(当然是非零值)并在变化之前。
  6. 请告诉我们您是如何解决问题的!

    编辑:您评论说您正在重构现有代码,因此我假设您不一定要自己选择密钥。在这种情况下,请遵循两个步骤:使用上述方法之一加密密钥本身,然后使用 密钥解密用户的数据。

答案 2 :(得分:19)

  1. 将其作为代码高尔夫问题发布
  2. 等待用J
  3. 编写的解决方案
  4. 在您的应用中嵌入J解释器

答案 3 :(得分:11)

隐藏代码中的密码是默默无闻的。这是有害的,因为你认为你有一定程度的保护,而实际上你很少。如果有什么东西值得保障,那就值得保证。

  PS:我知道它不起作用   反对真正的黑客,但它很多   总比没有好......

实际上,在很多情况下,没有什么比弱安全更好。至少你知道你的确切位置。你不需要成为一个“真正的黑客”来规避嵌入式密码...

编辑:回应此评论:

  

我知道关键字对,但事实并非如此   在这种情况下可以接受我重构   使用的现有应用程序   Blowfish加密。加密数据   传递给服务器和服务器解密   数据。我无法改变生气   因为我应该提供算法   向后兼容。

如果您完全关心安全性,那么保持向后兼容性是一个让您自己容易受到嵌入密码攻击的绝对不利原因。打破向后兼容不安全的安全方案是一件好事。

就像街头小孩发现你把前门钥匙放在垫子下面一样,但你继续这样做是因为爷爷希望在那里找到它。

答案 4 :(得分:8)

你的例子根本不隐藏字符串;字符串仍然显示为输出中的一系列字符。

有多种方法可以混淆字符串。有简单的substitution cypher,或者您可以对每个字符(例如XOR)执行数学运算,其中结果将输入到下一个字符的操作等等。

目标是最终得到看起来不像字符串的数据,例如,如果您使用大多数西方语言,大多数字符值将在32-127范围内 - 所以目标是操作主要是将它们大部分放在 out 的范围内,所以它们不会引起注意。

答案 5 :(得分:7)

这就像在荷兰阿姆斯特丹中央车站附近解锁自行车一样安全。 (眨眼,它消失了!)

如果您正在尝试为应用程序添加安全性,那么您注定要从一开始就失败,因为任何保护方案都将失败。您所能做的就是让黑客找到所需信息变得更加复杂。还是,一些技巧:

*)确保字符串在二进制文件中存储为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地址或伪随机数计算,使其合理可预测。不过,它易于创建,易于存储,转换和使用。创建更长的东西不会增加更多的价值,因为黑客会试图找到其他技巧来破解安全性。这只是一个问题,他们愿意花更多的时间来分析二进制文件。

一般而言,保证应用程序安全的最重要因素是对其感兴趣的人数。如果没有人关心您的申请,那么没有人会费心去破解它。如果您是拥有5亿用户的顶级产品,那么您的应用程序将在一小时内破解。

答案 6 :(得分:4)

我曾经处于同样尴尬的境地。我的数据需要在二进制文件中,而不是纯文本。我的解决方案是使用一个非常简单的方案加密数据,使其看起来像程序的其余部分。我通过编写一个带有字符串的程序对其进行加密,将所有字符转换为ASCII代码(根据需要用零填充以获得三位数字),然后在3位数代码的开头和结尾添加一个随机数字。因此,字符串的每个字符由加密字符串中的5个字符(所有数字)表示。我将该字符串粘贴到应用程序中作为常量,然后当我需要使用该字符串时,我解密并将结果存储在变量中足够长的时间来完成我需要的操作。

因此,使用你的榜样,“我有很强的加密密钥”变为“207719121310329211541116181145111157110071030703283101101109309926114151216611289116161056811109110470321510787101511213”。然后,当您需要加密密钥时,对其进行解码,但撤消该过程。

这当然不是防弹的,但我的目的并非如此。

答案 7 :(得分:3)

加密技术足以保护重要数据,而不会将其隐藏在二进制文件中。

或者您的想法是使用二进制文件伪装隐藏某些内容的事实?

这将被称为steganography

答案 8 :(得分:3)

对于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是&#34;测试&#34;现在,字符串在二进制文件中的字符串表中隐藏。

答案 9 :(得分:3)

这是一个客户端 - 服务器应用程序!不要将它存储在客户端本身,这是黑客显然会看到的地方。相反,添加(仅适用于您的新客户端)额外的服务器功能(通过HTTPS)以检索此密码。因此,此密码永远不会命中客户端磁盘。

作为奖励,稍后修复服务器变得容易多了。只需每次都发送一个不同的每个客户端限时密码。不要忘记在新客户端中允许更长的密码。

答案 10 :(得分:2)

如果你反向存储加密密钥(“yek noitpyrcne gnorts yM”)然后在你的代码中反转它(String.Reverse),这将阻止在二进制文件中搜索加密密钥的文本。< / p>

重申这里所有其他海报所提出的观点,在安全方面,这几乎不会对你造成任何影响。

答案 11 :(得分:2)

您可以使用一些简单的编码对字符串进行编码,例如: xor with binary 01010101.当然没有真正的保护,但却使用了像string这样的工具。

答案 12 :(得分:2)

以下是他们解释的一个例子,但请注意,任何有“黑客”但会用十六进制编辑器阻止小子的人都会相当简单地打破这个问题。我提供的示例只是添加值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 :(得分:2)

您可以看看antispy C/C++ Obfuscation Library for all platforms,它们提供了多种混淆技术。

他们的字符串加密将解决您的问题。

答案 14 :(得分:1)

您可以使用我为此目的开发的c++ libraryAnother article更容易实现,赢得了2017年9月最好的c ++文章。

答案 15 :(得分:0)

可以使用 llvm-obfuscator(例如 this fork)来进行透明的字符串加密。设置可能有点痛苦,特别是如果您想将其集成到 XCode 中(可在线获得说明12,但需要针对llvm 和 XCode 的每个新版本)。

答案 16 :(得分:0)

这是一个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;";
       }
    } 
  }

答案 17 :(得分:0)

创建一个函数,将密码分配给静态char数组并返回指向此函数的指针。然后通过混淆程序运行此函数。

如果该计划做得很好。使用十六进制编辑器检查程序二进制文件是不可能读取明文密码的。 (至少,并非没有逆向工程汇编语言。这应该会阻止所有带有“字符串”或十六进制编辑器的脚本小子,除了那些没有什么好浪费时间的犯罪疯狂的黑客。)

答案 18 :(得分:0)

我建议 m4

  1. 使用const string sPassword = _ENCRYPT("real password");

  2. 等宏存储字符串
  3. 在构建之前,使用 m4 将宏展开为加密字符串,因此您的代码看起来像const string sPassword = "encrypted string";

  4. 在运行时环境中解密。

答案 19 :(得分:0)

使用其他代码加密加密密钥。向用户显示其他代码的图像。现在用户必须输入他看到的密钥(如验证码,但始终是相同的代码)。这使得其他程序也无法预测代码。 (可选)您可以保存代码的(盐渍)哈希,以验证用户的输入。

答案 20 :(得分:0)

我想知道在第一次像其他人提到的那样模糊它之后,你可以将你的字符串嵌入到一个程序块中,试着让它看起来像指令。然后你可以有一个“if 0”或“goto just_past_string_assembly”来跳过真正隐藏你的字符串的“代码”。这可能需要更多的工作来检索代码中的字符串(一次性编码成本),但它可能会更加模糊。

答案 21 :(得分:0)

我认为你想让它看起来像说明,

的例子

X [Y ++] = 'M'; X [Y ++] = 'Y'; ...

就是这样,具有一点变化的重复指令的长序列可能会突出并且那将是坏的,所讨论的字节可能在指令中被编码并且那将是坏的,所以也许是xor方法,也许还有其他一些技巧可以让那长段代码不突出,也许是一些虚函数调用。取决于您的处理器,例如ARM,它很容易查看二进制数据并从数据中选择指令(如果您正在寻找默认密钥)可能选择可能是关键的因素,因为它是数据,但不是ascii和攻击。同样,即使你有编译器xor数据带有常量,一个类似指令的块也会立即变化。