如何隐藏二进制代码中的string?
有时,从二进制文件(可执行文件)隐藏一个string是有用的。 例如,从二进制文件隐藏encryption密钥是有意义的。
当我说“隐藏”时,我的意思是让string在编译后的二进制文件中难以find。
例如,这个代码:
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 |
你可以看到,我们的秘密string可以很容易地find和/或修改。
我可以隐藏string…
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:我知道,仅仅隐藏秘密对于一个坚定的攻击者是行不通的,但是总比没有好。
另外,我知道不对称encryption,但在这种情况下是不能接受的。 我正在重构使用Blowfishencryption并将encryption数据传递给服务器的现有应用程序(服务器使用相同的密钥对数据进行解密)。
我无法更改encryptionalgorithm,因为我需要提供向后兼容性。 我什至不能改变encryption密钥。
正如在对公会答复的评论中指出的那样,你有两个select:
- 确保密钥
- 保护解密algorithm
不幸的是,如果你必须在代码中embedded密钥和algorithm,那么这两者都不是真正的秘密,所以你只能通过默默无闻的方式将安全性 (远远弱于安全性 )替代。 换句话说,正如你所提到的,你需要一个聪明的方法来隐藏你的可执行文件中的一个或两个。
以下是一些选项,但是您需要记住,根据任何encryption最佳实践, 这些选项都不是真正安全的 ,并且每个都有其缺点:
- 将密钥伪装成通常会出现在代码中的string。 一个例子是
printf()
语句的格式string,它往往有数字,字母和标点符号。 - 在启动时散列部分或全部代码或数据段 ,并将其用作关键字。 (为了确保密钥不会意外改变,你需要有点聪明!)每次运行时,validation代码的散列部分都会带来潜在的副作用。
- 在运行时生成密钥,例如,通过对networking适配器的MAC地址进行散列处理,从系统中唯一(并且在系统内)不变的地方生成密钥 。
- 通过从其他数据中select字节来创build密钥。 如果你有静态或全局数据,无论types(
int
,char
等等 )如何,在每个variables被初始化之后(当然是一个非零值),在它变化之前,从每个variables的某个地方取一个字节。
请让我们知道你是如何解决问题的!
编辑:你评论说,你正在重构现有的代码,所以我会假设你不一定自己select密钥。 在这种情况下,请按照以下两个步骤进行操作:使用上述方法之一对密钥本身进行encryption,然后使用该密钥来解密用户的数据。
我很抱歉回答很长。
你的答案是绝对正确的,但问题是如何隐藏string,并很好地做到这一点。
我是这样做的:
#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; }
“我的强encryption密钥”的数据如下所示:
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.........
非常感谢您的回答!
在密码中隐藏密码是安全的。 这是有害的,因为让你觉得你有一定程度的保护,而事实上你有很less的保护。 如果有什么值得保证的话,值得妥善保护。
PS:我知道这对真正的黑客并不起作用,但总比没有好。
实际上,在很多情况下,没有什么比弱安全性更好的了。 至less你知道你的立场。 你不需要成为一个“真正的黑客”,以规避一个embedded式密码…
编辑:回应这个评论:
我知道一对钥匙,但在这种情况下是不能接受的。 我重构使用Blowfishencryption的现有应用程序。 传递给服务器和服务器的encryption数据解密数据。 我不能改变ecryptionalgorithm,因为我应该提供向后兼容性。
如果你关心安全性,保持向下兼容性是一个很不好的理由,让你自己的embedded式密码易受攻击。 打破向后兼容不安全的安全scheme是一件好事。
就好像街上的孩子们发现你把前门钥匙放在垫子下面,但是你继续这样做,是因为爷爷希望在那里find钥匙。
- 发布它作为一个代码高尔夫问题
- 等待用J写成的解决scheme
- 在你的应用中embedded一个J解释器
你的例子根本不隐藏string; 该string仍然呈现为输出中的一系列字符。
有多种方法可以混淆string。 有简单的replace密码 ,或者你可能对每个字符(比如XOR)进行math运算,结果input到下一个字符的操作等等。
我们的目标是最终得到的数据看起来不像一个string,例如,如果你在大多数西方语言中工作,你的大部分angular色值将在32-127范围内 – 所以你的目标是因为手术主要是把它们放在这个范围之外,所以不会引起注意。
这就像在荷兰阿姆斯特丹附近的中央车站解锁你的自行车一样安全。 (眨眼,它消失了!)
如果您正在尝试将安全性添加到您的应用程序中,那么注定会从一开始就失败,因为任何保护scheme都将失败。 你所能做的就是让黑客更加复杂,find他需要的信息。 还有一些技巧:
*)确保string在您的二进制文件中存储为UTF-16。
*)将数字和特殊字符添加到string。
*)使用一个32位整数而不是一个string的数组! 将每个转换为一个string并将它们连接在一起。
*)使用GUID,将其存储为二进制文件,并将其转换为string以供使用。
如果你真的需要一些预定义的文本,encryption它并将encryption的值存储在你的二进制文件中。 在解密密钥是我之前提到的选项之一的运行时解密它。
意识到黑客会倾向于用其他方式破解你的应用程序。 即使是密码学方面的专家也无法保证安全。 一般来说,保护你的唯一的东西就是黑客从破解你的代码中获得的利润,而不是黑客的代价。 (这些成本通常只是很多时间,但是如果需要一个星期的时间来破解你的应用程序,并且只需要两天的时间来破解别的东西,那么其他东西更可能被攻击。
回复评论:UTF-16每个字符两个字节,因此对于查看二进制转储的用户来说很难识别,因为每个字母之间都有一个额外的字节。 不过,你仍然可以看到这些单词。 UTF-32甚至会更好,因为它增加了字母之间的空间。 然后再一次,你也可以通过改变为每字符6位scheme来压缩文本。 每4个字符会紧凑到三个数字。 但是,这会限制你2×26个字母,10个数字,也许空间和点在64个字符。
如果您将GUID存储为二进制格式,而不是文本格式,则使用GUID是可行的。 GUID长度为16个字节,可以随机生成。 因此很难猜测用作密码的GUID。 但是,如果您仍然需要发送纯文本,可以将GUID转换为string表示forms,如“3F2504E0-4F89-11D3-9A0C-0305E82C3301”。 (或Base64编码为“7QDBkvCA1 + B9K / U0vrQx1A ==”)。但是用户在代码中看不到任何纯文本,只是一些明显的随机数据。 不过,并不是所有的GUID字节都是随机的。 GUID中隐藏了一个版本号。 不过,使用GUID并不是encryption用途的最佳select。 它可以根据您的MAC地址或伪随机数进行计算,使其具有合理的可预测性。 不过,创build和存储,转换和使用都很容易。 创build更长的东西不会增加更多的价值,因为黑客只是试图find其他技巧来破解安全。 这只是一个问题,他们是多么愿意花更多的时间来分析二进制文件。
一般来说,保持应用程序安全的最重要的事情是对其感兴趣的人数。 如果没有人关心你的应用程序,那么没有人会打扰它。 当您拥有5亿用户的顶级产品时,您的应用程序在一个小时内就被破解了。
我曾经处于同样尴尬的境地。 我有数据需要在二进制文件,而不是纯文本。 我的解决scheme是使用非常简单的scheme来encryption数据,使其看起来像程序的其余部分。 我通过编写一个带string的程序对它进行了encryption,将所有字符转换为ASCII码(必要时用零填充以获得三位数字),然后将随机数字添加到3位数字的开始和结尾。 因此,string的每个字符都由encryptionstring中的5个字符(所有数字)表示。 我把这个string作为常量粘贴到应用程序中,然后当我需要使用这个string时,我解密并将结果存储在一个variables中,只需要足够长的时间来完成我所需要的操作。
因此,使用您的示例,“我的强encryption密钥”变为“207719121310329211541116181145111157110071030703283101101109309926114151216611289116161056811109110470321510787101511213”。 然后,当你需要你的encryption密钥时,解码它,但取消这个过程。
这当然不是防弹的,但我不是瞄准的。
encryption技术足够强大,可以保护重要数据, 而不会将其隐藏在二进制文件中。
或者,您的想法是使用二进制文件来掩饰事实隐藏的事实?
这将被称为隐写术 。
这是一个客户端 – 服务器应用程序! 不要把它存储在客户端本身,这是黑客显然看起来的地方。 相反,添加(仅适用于您的新客户端)额外的服务器function(通过HTTPS)来检索此密码。 因此,这个密码不应该击中客户端磁盘。
作为奖励,稍后修复服务器变得更容易。 每次只发送一个不同的,每个客户端有时间限制的密码。 不要忘记在新的客户端允许更长的密码。
您可以使用一些简单的编码来编码string,例如用二进制01010101进行xor编码。当然没有真正的保护,但是阻止使用string
之类的工具。
如果您将encryption密钥反向存储(“yek noitpyrcne gnorts yM”),然后在您的代码中反转(String.Reverse),则会阻止您简单searchencryption密钥文本的二进制文件。
然而,要重申所有其他海报在这里所提出的观点,这将在安全方面为您完成任何事情。
下面是他们解释的一个例子,但是请注意,任何一个“黑客”,但是会用hex编辑器阻止kiddy的人会相当简单地破解这个例子。 我提供的例子只是简单的添加值80,并从中减去索引,然后重新生成一个string。 如果您计划将其存储在二进制文件中,则有很多方法可以将string转换为byte []数组。
当你在你的应用程序中有这个工作,我会使我使用一些更复杂的“math”
要说清楚,对于那些不了解的人来说……在你保存之前,你需要对string进行encryption,这样它就不会以明文forms保存。 如果encryption的文本永远不会改变你甚至不包括你的版本中的encryptionfunction,你只需要解密一个。 所以当你想解密string的时候,你读取文件,然后解密内容。 意味着你的string永远不会以纯文本格式存储在文件中。
你也可以将encryption的string作为常量string存储在应用程序中,并在需要时解密,根据string的大小和改变的频率select适合你的问题。
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; }
你可以使用我为此开发的c ++库 。 另一篇实现起来更简单的文章赢得了2017年9月份最好的c ++文章。
我想你想让它看起来像指示,你的例子
X [Y ++] = 'M'; X [Y ++] = 'Y'; …
这样做,具有一点变化的重复指令的长序列可能是突出的,这将是坏的,有问题的字节可能被编码在指令中,这将是不好的,所以也许是异或方法,也许一些其他的技巧,使长段的代码不突出,也许一些虚拟的函数调用。 ARM也取决于你的处理器,例如,看看二进制数据并从数据中挑出指令并从那里(如果你正在寻找一个默认的关键字)来select可能是关键的因素,它是数据而不是ascii和攻击。 同样,即使你有一个常量的编译器异或数据,即时字段变化类似的指令块。
我想知道是否像其他人提到的那样先将其隐藏起来后,就可以将你的stringembedded到一个程序集块中,以使其看起来像指令一样。 然后,你可以有一个“if 0”或“goto just_past_string_assembly”来跳过真正隐藏你的string的“代码”。 这可能需要更多的工作来检索代码中的string(一次性编码成本),但它可能被certificate是有点模糊。
用另一个代码encryptionencryption密钥。 向用户显示其他代码的图像。 现在,用户必须input他看到的密钥(如validation码,但始终是相同的代码)。 这使得其他程序也不可能预测代码。 您也可以select保存代码的散列(哈希)以validation用户的input。
我build议m4 。
-
存储你的string像macros一样的
const string sPassword = _ENCRYPT("real password");
-
在构build之前,用m4将macros展开成encryption的string,所以你的代码看起来像是
const string sPassword = "encrypted string";
-
在运行时环境中解密。
创build一个函数,将密码分配给一个静态字符数组并返回一个指向这个函数的指针。 然后通过一个混淆程序运行这个函数。
如果该scheme做得很好。 应该不可能使用hex编辑器来读取纯文本密码来检查程序二进制文件。 (至less,不是没有对汇编语言进行逆向工程,那应该阻止所有那些用“string”或者hex编辑器编写的脚本小子,除了那些没有任何好处的疯狂黑客)。
这里有一个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;"; } } }
对于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”,string在二进制string表中是隐藏的。