在不重新编译源代码的情况下更改C++(C)程序中定义的数字

Changing a number defined in a C++(C) program without compiling the source again

本文关键字:C++ 程序 数字 定义 情况下 编译 源代码 新编译      更新时间:2023-10-16

假设我有一个打印数字的简单程序:

#include <iostream>
int unique_id = 112233;
int main()
{
std::cout << unique_id;
return 0;
}

然后我把它编译成类似a.exe的东西。现在我想创建另一个应用程序,打开a.exe并将unique_id更改为其他内容。有可能吗
由于某些限制,我不会将参数传递给程序。

顾名思义,我想使用unique_id来唯一标识程序运行的位置。但我不想为1000个客户编译我的程序1000次。我知道我可以使用硬盘序列号,但在虚拟机中,这个序列号可能会被省略。我知道我可以使用CPU序列号,但我在S.O的帖子中读到这个序列号被弃用了。我知道我也可以使用MAC地址:),但这个地址可以很容易地更改。所以我决定把唯一的ID放在exe文件本身。

通常,如果不重新编译,就无法更改任何内容。

在实践中,在非常有限的情况下,您可能会修补二进制文件。这主要是特定于处理器的(以及特定于可执行格式和ABI的),对您的特定操作系统版本的依赖性较小(例如,如果它适用于Windows 9,则它可以适用于Windows 10)。

(然而,我不知道也从未使用过Windows;我只使用Linux;你应该根据你的操作系统调整我的答案)

因此,在的某些情况下,您可能会对二进制可执行文件进行逆向工程。如果你有C源代码,你可以要求你的编译器发出汇编代码(例如,用gcc -O -fverbose-asm -S和GCC编译)。然后,您可以反汇编可执行文件,并使用二进制或十六进制编辑器更改包含该常量的机器代码。

这并不总是有效的,因为机器指令(及其大小)可能取决于常数的大小。

举一个简单的例子,在C中,对于Linux/x86-64上的GCC 7,考虑以下C文件:

/// A, B, C are preprocessor symbols defined as integers
int f(int x) { 
if (x > 0)
return A*x + B;
return C;
}

如果我用gcc -fverbose-asm -S -O -DA=12751 -DB=32 -DC=11 e.c编译它,我会得到:

.type   f, @function
f:
.LFB0:
.cfi_startproc
# e.c:3:   if (x > 0)
testl   %edi, %edi  # x
jle .L3 #,
# e.c:4:     return A * x + B;
imull   $12751, %edi, %edi  #, x, tmp90
leal    32(%rdi), %eax  #, <retval>
ret
.L3:
# e.c:5:   return C;
movl    $11, %eax   #, <retval>
# e.c:6: }
ret
.cfi_endproc
.LFE0:
.size   f, .-f

但如果我做gcc -S -O -fverbose-asm -DA=12753 -DB=32 -DC=10 e.c,我会得到

.type   f, @function
f:
.LFB0:
.cfi_startproc
# e.c:3:   if (x > 0)
testl   %edi, %edi  # x
jle .L3 #,
# e.c:4:     return A * x + B;
imull   $12753, %edi, %edi  #, x, tmp90
leal    32(%rdi), %eax  #, <retval>
ret
.L3:
# e.c:5:   return C;
movl    $10, %eax   #, <retval>
# e.c:6: }
ret

因此,实际上,在上述情况下,我可以修补二进制(我需要在机器代码中找到1275111常量;在这种情况下,这是可行的,但很乏味)。


现在,让我们尝试A是2的小幂,比如16,C是0,所以gcc -S -O -fverbose-asm -DA=16 -DB=32 -DC=0 e.c:

f:
.LFB0:
.cfi_startproc
# e.c:4:     return A * x + B;
leal    2(%rdi), %eax   #, tmp90
sall    $4, %eax    #, tmp93
testl   %edi, %edi  # x
movl    $0, %edx    #, tmp92
cmovle  %edx, %eax  # tmp93,, tmp92, <retval>
# e.c:6: }
ret

由于编译器优化,代码发生了显著变化。修补起来并不容易。

重要通知

只要付出足够的努力、金钱和时间(想想美国国家安全局的能力),很多事情都是可能的。

如果你的目标是混淆二进制文件中的一些数据(例如,一些密码),你可能会对其进行加密,使黑客的生活更加艰难(但不要天真,美国国家安全局会得到它的)。记住座右铭:没有银弹;看起来这是你的目标,但不要太天真(顺便说一句,围绕你的软件的法律保护,例如许可证,更重要;所以你需要一位律师来写一份好的EULA)。

如果您的目标恰恰相反,是调整一些性能关键的代码,那么您可以使用元编程和部分评估技术。我喜欢做的一种做法是在运行时生成一些临时C(或C++)代码(更适合您的特定情况和数据),将该临时C或C++代码编译为某个插件,然后动态加载该临时插件(在Linux上使用dlopendlsym;在Windows上,您需要LoadLibrary,但我让您了解细节和后果)。您可以使用一些JIT编译库,如libgccjit,而不是在运行时生成C或C++代码。如果你喜欢这样的技术,如果你的管理允许,可以考虑使用更好的编程语言(比如带有SBCL的Common Lisp)。

但我不想为1000名客户编译我的程序1000次

这让我大吃一惊。编译一个只包含常量的简单(短)C文件很快,链接时间也很快。相反,我会考虑为每个客户重新编译。

顺便说一句,我觉得你太天真了。最重要的保护不是二进制文件中的技术性保护,而是法律保护(你需要一份好的合同,所以找一个好的律师并付钱)。

你是否考虑过让你的产品成为免费软件?许多公司都在这样做(并在其他许可证上赚钱,例如支持)。


NB。现有的许可证管理器很多。你有没有考虑过购买和使用一个?还要注意,公司有很大的动机来避免作弊,而那些愿意窃取你的软件的人无论如何都可以这样做。你将通过提高软件质量来销售更多的产品,而不是花精力在徒劳的"保护"措施上,因为这些措施会惹恼你的客户,增加你的物流、分销和维护成本,并加强对客户发现的错误的调试。

考虑到您添加到问题中的动机,您可以简单地让exe从.txt文件中读取id,并为每个客户提供不同的.txt文件和exe。

或者,等效地,您可以创建一个DLL(或平台的等效DLL),该DLL具有返回id的函数,并且只为每个客户重新编译DLL。

否,更改const变量的行为是未定义的。所以你不能用标准的C或C++来做到这一点。

您最好的选择是采用内联组装解决方案;但请注意,CCD_ 15可能被完全编译出来(C和C++都不是反射语言)。为了增加UNIQUE_ID被保留的概率,删除const限定符并可能引入volatile

就我个人而言,我会在命令行中将UNIQUE_ID传递给您的程序。

起点:https://msdn.microsoft.com/en-us/library/fabdxz08.aspx