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

Dmi*_*riy 64 c++ obfuscation

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

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

例如,这段代码:

const char* encryptionKey = "My strong encryption key";
// Using the key
Run Code Online (Sandbox Code Playgroud)

编译后生成一个可执行文件,其数据部分中包含以下内容:

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        |
Run Code Online (Sandbox Code Playgroud)

您可以看到我们的秘密字符串可以轻松找到和/或修改.

我可以隐藏字符串......

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';
Run Code Online (Sandbox Code Playgroud)

......但这不是一个好方法.有更好的想法吗?

PS:我知道仅仅隐藏秘密并不能对付坚定的攻击者,但它总比没有好......

另外,我知道非对称加密,但在这种情况下它是不可接受的.我正在重构现有的应用程序,它使用Blowfish加密并将加密数据传递给服务器(服务器使用相同的密钥解密数据).

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

Dmi*_*riy 51

对不起,我很抱歉.

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

我是这样做的:

#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;
}
Run Code Online (Sandbox Code Playgroud)

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;\
}
Run Code Online (Sandbox Code Playgroud)

HideString.h中最棘手的一行是:

BOOST_PP_SEQ_ENUM(BOOST_PP_SEQ_FOR_EACH_I(CRYPT_MACRO, SEED, SEQ))
Run Code Online (Sandbox Code Playgroud)

让我解释一下这条线.代码:

DEFINE_HIDDEN_STRING(EncryptionKey2, 0x27, ('T')('e')('s')('t'))
Run Code Online (Sandbox Code Playgroud)

BOOST_PP_SEQ_FOR_EACH_I(CRYPT_MACRO, SEED, SEQ)
生成序列:

( 'T'  ^ ( 0x27 - 0 ) ) ( 'e'  ^ ( 0x27 - 1 ) ) ( 's'  ^ ( 0x27 - 2 ) ) ( 't'  ^ ( 0x27 - 3 ) )
Run Code Online (Sandbox Code Playgroud)

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 )
Run Code Online (Sandbox Code Playgroud)

最后,

DEFINE_HIDDEN_STRING(EncryptionKey2, 0x27, ('T')('e')('s')('t'))
Run Code Online (Sandbox Code Playgroud) 生成:

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;
}
Run Code Online (Sandbox Code Playgroud)

"我的强加密密钥"的数据如下所示:

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.........
Run Code Online (Sandbox Code Playgroud)

非常感谢您的回答!

  • 感谢您分享您的解决方案!我需要对十六进制编辑器和基本反编译器隐藏字符串。 (2认同)

Ada*_*iss 47

正如对pavium 答案的评论所指出的,你有两个选择:

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

不幸的是,如果你必须求助于在代码中嵌入密钥和算法,那么这两者都不是真正的秘密,所以你通过默默无闻的方式留下了(远远弱于)安全性的替代方案.换句话说,正如您所提到的,您需要一种聪明的方法来隐藏可执行文件中的一个或两个.

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

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

请告诉我们您是如何解决问题的!

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


Ken*_*Ken 20

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


Ste*_*n C 11

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

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

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

编辑:回应此评论:

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

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

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

  • 几乎所有软件许可证密钥和序列号都是通过默默无闻的安全性示例,并且是完全合法的用例.由您和其他人推理,您不应该将自行车锁在自行车架上,因为所有自行车锁都可以使用合适的工具轻松敲打.至少当你的自行车解锁时"你确切知道你的位置." (27认同)
  • 大多数门锁只能保护您免受诚实或不想冒被抓的风险.大多数人认为门锁是真的不安全,但我不会离开我的家门解锁. (8认同)
  • “如果值得保护,就值得正确保护。”我对您投反对票,是因为像其他所有人一样,您也指出了这种笼统的垃圾,但并未提供应对方法。你没有帮助 你在伤害 (2认同)
  • 我认为这里的批评是非常公正的。你有一些优点,但你完全错过了另一面。重复一下自行车的比喻,即使是最蹩脚的自行车锁也能阻止那些只想骑自行车回家的典型醉汉偷走你的自行车。我还记得我 14 岁左右的时候做过一些黑客工作。由于我的知识不是很高,我所能做的就是编辑二进制文件中的字符串。我做到了。例如,我替换了旧 dos 程序“format”中的警告文本,以说出您肯定想用“是”回答的内容。 (2认同)

maf*_*nya 9

对于 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;
    }
};
Run Code Online (Sandbox Code Playgroud)

为了使用它,只需包含 Alpha 和:

Alpha str("");
string myStr = str.c('T').c('e').c('s').c('t');
Run Code Online (Sandbox Code Playgroud)

所以 mystr 现在是“测试”,并且该字符串以二进制形式从字符串表中隐藏。


T.J*_*der 8

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

您可以通过多种方式混淆字符串.有简单的替换密码,或者您可以对每个字符(例如,XOR)执行数学运算,其中结果将输入到下一个字符的操作中,等等.

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

  • "旋转4个字符"将开裂时间从大约30秒增加到大约120秒 - 这就是笔和纸. (4认同)
  • @Dmitry我认为这种"隐藏"是你能做的最好的.我之前做过类似的事情,原因与你想要的相同(除了我在字符串的长度上旋转了4个不同的xor常量).如果黑客能够查找和解码我的字符串,那么他们也可以直接编辑例程的返回值等我希望我知道更好的答案. (2认同)

Wim*_*ink 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亿用户的顶级产品,那么您的应用程序将在一小时内被破解.

  • 您的担忧都是有效的,但不幸的是,商业世界并不理想,问题必须在可接受的参数范围内解决.那就是说,我对你的一些建议感到好奇.UTF-16:为什么?比特是比特.:-)允许_all_值包括不可打印的值不是更好吗?GUID:为什么这比任何其他(可能更长)的字节序列更好? (2认同)

Mic*_*ati 5

您可以使用我为此目的开发的C++ 库另一篇实现起来更简单的文章,获得了 2017 年 9 月最佳 c++ 文章。 更简单的隐藏字符串的方法,请参阅TinyObfuscate