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


73

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

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

例如,此代码:

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

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


13
有些公共密钥加密系统不需要隐藏密钥。
AProgrammer

5
我知道密钥对,但是在这种情况下不可接受。我重构了使用Blowfish加密的现有应用程序。传递给服务器的加密数据和服务器解密数据。我不能更改加密算法,因为我应该提供向后兼容性。
德米特里(Dmitriy)'2009年

7
在可执行文件中隐藏密钥几乎总是一个好主意。
埃文·特兰

Answers:


53

对不起,我回答很久。

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

我这样做的方式是:

#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
感谢您分享您的解决方案!我需要对十六进制编辑器和基本的反编译器隐藏字符串。
Nikolay Spassov 2014年

@Dmitriy您可以为C lang共享吗?也许在github链接上。
佛罗里达州

1
@佛罗里达-抱歉,但是该解决方案使用Boost库,该库通常不支持C
Dmitriy

@Dmitriy没问题,我已经找到了另一个解决方案,我现在不记得了,似乎是字节字节字符串代码。
佛罗里达州,

@NikolaySpassov-了解机器代码的人呢?还是认识朋友/可以雇用知道机器代码的人?
斯蒂芬C,

47

如对pavium答案的评论中所述,您有两种选择:

  • 固定钥匙
  • 保护解密算法

不幸的是,如果您必须将密钥和算法都嵌入到代码中,那么两者都不是真正的秘密,因此您会因为晦涩难懂而留下了(远远不够)安全性的选择。换句话说,正如您提到的那样,您需要一种巧妙的方法将其中一个或两个都隐藏在可执行文件中。

这里有一些选择,尽管您需要记住,根据任何加密最佳实践,这些选择都不是真正安全的,并且每个都有其缺点:

  1. 将您的密钥伪装成通常会出现在代码中的字符串。 一个示例是printf()语句的格式字符串,该字符串通常具有数字,字母和标点符号。
  2. 在启动时散列一些或所有代码或数据段,并将其用作键。(您需要对此有所了解,以确保密钥不会意外更改!)这具有潜在的希望的副作用,即每次运行时验证代码的哈希部分。
  3. 例如,通过对网络适配器的MAC地址进行哈希处理,在运行时从系统独有的(并在系统内保持不变)生成密钥
  4. 通过从其他数据中选择字节来创建密钥。 如果你有静态或全局数据,而不管类型(intchar),它的初始化(为非零值,当然)后,采取从某处一个字节每个变量中,并在改变之前。

请让我们知道您如何解决问题!

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


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

10

隐藏密码在代码中是安全性,一无所知。这是有害的,因为使您认为您具有某种程度的保护,而实际上却很少。如果有值得保护的东西,则值得适当保护。

PS:我知道这对真正的黑客是行不通的,但是总比没有好……

实际上,在许多情况下,没有什么比弱安全性更好。至少您确切地知道自己的立场。您无需成为“真正的黑客”即可规避嵌入式密码...

编辑:对此评论作出响应:

我知道密钥对,但是在这种情况下不可接受。我重构了使用Blowfish加密的现有应用程序。传递给服务器的加密数据和服务器解密数据。我不能更改加密算法,因为我应该提供向后兼容性。

如果您根本不关心安全性,那么保持向后兼容性是让自己容易受到嵌入式密码攻击的真正坏理由。用不安全的安全方案破坏向后兼容性是一件好事。

就像流浪儿童发现您将前门钥匙留在垫子下一样,但您还是继续这样做,因为爷爷希望在那里找到它。


32
几乎所有的软件许可证密钥和序列号都是模糊不清的安全示例,并且完全是合法的用例。根据您和其他人的推理,您永远不要将自行车锁定在自行车架上,因为使用正确的工具可以轻松地将所有自行车锁打碎。至少当您的自行车解锁时,“您才知道确切的位置”。
哈维2012年

1
@StephenC-关于不给用户一种错误的安全感的公平点,但是(对我来说)对于当今确实有许多软件许可方案起作用的人,永远不要使用模糊的嵌入式密钥。
哈维2012年

1
@Harvey-这正是人们能够找到密钥并盗版软件的原因。许可证密钥只能保护您免受诚实或不想冒被抓到的风险的人的侵害。
史蒂芬C

8
大多数门锁只能保护您免受诚实或不想冒被抓到的人的伤害。大多数人认为门锁确实是安全的,但我不会在门未锁的情况下离开家。
dureuill 2015年

2
“如果值得保护,就值得正确保护。”我对您投了反对票,因为像其他所有人一样,您说出了这种笼统的垃圾,却没有提供应对方法。你没有帮助 你在伤害
Krythic '17

8

您的示例根本不隐藏字符串;该字符串在输出中仍显示为一系列字符。

您可以通过多种方式混淆字符串。有一个简单的替换cypher,或者您可以对每个字符(例如XOR)执行数学运算,结果将馈送到下一个字符的运算中,依此类推,等等。

目标是最终得到看起来不像字符串的数据,因此例如,如果您使用的是大多数西方语言,则大多数字符值都将在32-127范围内,因此您的目标是为手术主要是把他们大多该范围的,这样他们就不会引起注意。


我可以使用诸如加密密钥[n ++] ='M'^ 0x79;之类的东西。但它仍然是“不好的方法”。
德米特里(Dmitriy)'2009年

1
您对“好”的定义是什么?
TJ Crowder

3
@Dmitry我认为这种“隐藏”是您可以做的最好的事情。出于您想要的相同原因,我之前做过类似的事情(除了我在字符串的长度上旋转了4个不同的xor常量)。如果黑客能够找到并解码我的字符串,那么他们也可以直接编辑例程的返回值,等等。我希望我知道更好的答案。
彼得M,2009年

4
“旋转4个字符”将破解时间从大约30秒增加到大约120秒-这就是笔和纸的问题。
MSalters

8

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

如果您想为应用程序增加安全性,那么注定要从一开始就失败,因为任何保护方案都会失败。您所能做的就是让黑客更加复杂地找到所需的信息。仍然有一些技巧:

*)确保字符串在您的二进制文件中存储为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亿用户的顶级产品时,您的应用程序将在一小时内被破解。


2
您的担忧都是正确的,但不幸的是,商业环境并不理想,必须在可接受的参数范围内解决问题。也就是说,我很好奇您的一些建议。UTF-16:为什么?位就是位。:-)允许所有值,包括不可打印的值,会更好吗?GUID:为什么这比其他任何字节序列(可能更长)更好?
亚当·利斯

我读过的其他技巧:在应用程序中与授权无关的多个位置找到秘密字符串。同样,如果您可以延迟可能由定时事件触发的使用机密的代码的运行,那么破解程序将更难以调试。
哈维2012年

除非您对某些许可服务器有加密请求,否则虚拟化很容易破坏基于时间的安全性。因此,他们必须先破解数据(网络数据),然后才能有效地使用虚拟化代码进行测试。
m3nda

8

对于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”,并且该字符串从二进制表的字符串表中隐藏。


4

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

因此,以您的示例为例,“我的强加密密钥”变为“ 207719121310329211541116181145111157110071030703283101101109309926114151216611289116161056811109110470321510787101511213”。然后,当您需要加密密钥时,对其进行解码,但可以撤消该过程。

这当然不是防弹的,但我的目的不是。


9
我刚刚破解了您的申请。感谢您提供所需的信息。
Thomas Eding

10
您刚刚破解了什么应用程序?
科林


3

这是一个客户端服务器应用程序!不要将其存储在客户端本身中,这显然是黑客可以看到的地方。而是(仅针对您的新客户端)添加额外的服务器功能(通过HTTPS)以检索此密码。因此,该密码永远不要打到客户端磁盘上。

另外,以后修复服务器变得容易得多。只需每次发送一个不同的,每个客户端有时间限制的密码。不要忘记在新客户端中允许使用更长的密码。


1
密码(AKA client_secret)用于向服务器验证客户端。那么,如何阻止恶意客户端从服务器请求密码?
ma11hew28 2014年

@MattDiPasquale:考虑到恶意客户端本质上可以是带有注入的DLL的常规客户端,什么也没有-但这就是这里的任何答案的情况。
MSalters 2014年

2

您可以使用一些琐碎的编码对字符串进行编码,例如,使用二进制01010101的xor。当然并没有真正的保护,但是可以避免使用这类工具string


3
这没有说明如何自动进行转换。
EFraim

2

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

还是您想使用二进制文件来掩盖某些东西被隐藏的事实?

那将被称为隐写术


1
@pavium:无论加密强度如何,都必须有一个密钥和一种算法来解密数据。尽管该算法可以是公开的,但密钥不能是公开的。我认为问题是关于嵌入密钥的,因此它不会(轻松)显示。
亚当·利斯

@亚当·利斯(Adam Liss):请参阅我对这个问题的评论。
德米特里(Dmitriy)'2009年

@亚当·利斯(Adam Liss):我认为问题是关于通过将消息隐藏在二进制文件中来伪装消息,我不认为在问题中使用“密钥”而不是“纯文本”是一个简单的误解。也许我跳了一个错误的结论。希望我们能找到答案。
pavium

答案链接已死。

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;
    }

1
如果我能让我所有的开发人员都编写这样清晰的代码!谢谢你的例子。
亚当·利斯

5
这不会改变“ AAbbBb”在二进制文件中仍以纯文本形式存在的事实。就二进制文件而言,这仅能保护原始情况。
ezpz

2
不,不是,您对字符串进行加密,然后将其保存在二进制文件中。然后读取二进制文件并解密。如果您不需要,请在发行的软件中完成加密功能(除非您更改该文件)
EKS 2009年


1

如果将加密密钥反向存储(“ yek noitpyrcne gnorts yM”),然后在代码中将其反向存储(String.Reverse),则这将阻止在二进制文件中简单搜索加密密钥的文本。

但是,在此重申所有其他张贴者的观点,就安全性而言,这实际上对您没有任何帮助。


1

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

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


0

我想您想使它看起来像说明,您的示例

x [y ++] ='M'; x [y ++] ='y'; ...

这样做,可能会引起重复的,稍有变化的长指令序列,这将是不好的,所讨论的字节可能会按原样编码在指令中,那将是不好的,所以可能是xor方法,也许其他一些使长段代码不突出的技巧,也许是一些伪函数调用。同样取决于您的处理器,例如ARM,查看二进制数据并从数据中挑选指令,然后从那里选择指令(如果您正在寻找默认密钥),很可能会选择可能的密钥,这确实很容易,因为它是数据,而不是ascii并对其进行攻击。同样,即使您使编译器将数据与常量进行异或运算,一组类似指令的立即字段也会有所不同。


0

我想知道是否像其他人所说的那样先将其遮盖之后,才可以将字符串嵌入到汇编块中以尝试使其看起来像指令。然后,您可以使用“ if 0”或“ goto just_past_string_assembly”跳过实际上隐藏字符串的“代码”。检索代码中的字符串可能需要做更多的工作(一次性编码成本),但事实可能会更加晦涩。


0

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


0

我建议m4

  1. 用像这样的宏存储字符串 const string sPassword = _ENCRYPT("real password");

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

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


0

这是一个Perl脚本,用于生成模糊的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;";
       }
    } 
  }
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.