如果我要构建C ++应用程序并且想使反向工程更加困难,我可以采取哪些步骤来做到这一点?


编译器的选择会对此产生影响吗?
关于编译器标志,大概是较高的优化水平会有所帮助,其他标志又如何呢?
剥离符号是否有帮助,而不是使用调试符号进行构建?
我应该加密任何内部数据(例如静态字符串)吗?
我还可以采取什么其他步骤?


#1 楼

编译器

选择编译器对反向工程代码的难度影响最小。最小化的重要事项都与代码中的信息泄漏有关。您至少要禁用任何运行时类型信息(RTTI)。类型信息的泄漏和虚拟机指令集的简单性是CLR和JVM代码更易于逆向工程的原因之一。它们还具有JIT,该JIT将优化应用于代码,这可能会降低混淆的强度。混淆基本上是与优化相反的,许多混淆都可以通过首先应用优化遍历来解决。 ,即使今天没有泄漏任何重要的信息,也可能明天泄漏。从调试信息泄漏的信息量随编译器的不同以及二进制格式与二进制格式的不同而不同。例如,Microsoft Visual C ++通常将所有重要的调试信息以PDB的形式保存在外部数据库中。您可能泄漏的最多的东西是构建软件时使用的路径。我的目标是将所有用于错误跟踪和错误日志记录的内容替换为数字枚举。字符串无法显示有关二进制文件中当前正在发生的事情的任何信息。如果您对字符串进行加密,它们将被解密。尝试尽可能避免它们。

系统API

信息泄漏的另一个重要来源是系统API的导入。您想确保任何导入的具有已知签名的功能都是隐藏的,并且无法使用自动分析找到。因此,来自诸如LoadLibrary / GetProcAddress之类的函数指针数组是不可能的。所有对导入函数的调用都需要通过一种函数,并且需要嵌入在混淆的块中。

标准运行时库

很多人忘记了标准库泄漏的信息(例如C ++编译器的运行时)也要考虑在内。我会完全避免使用它。这是因为大多数经验丰富的逆向工程师都将为许多标准库准备好签名。

混淆

您还应该对某些重要的代码进行复杂的混淆。现在,一些较重和较便宜的混淆是CodeVirtualizer / Themida和VMProtect。请注意,尽管这些程序包有很多缺陷。他们有时会将您的代码转换为与原始代码不等的代码,从而导致不稳定。它们还会大大降低混淆代码的速度。慢10000倍的情况并不罕见。还存在使用防病毒软件触发更多误报的问题。我建议您使用信誉良好的证书颁发机构对软件进行签名。

功能块的分离

将代码分为功能是另一回事,这使得对程序进行反向工程变得更加容易。这尤其适用于混淆功能的情况,因为它会创建边界,反向工程师可以围绕边界来推理您的软件。这样,逆向工程师可以分而治之地解决您的程序。理想情况下,您希望将软件放在一个有效的块中,并将混淆作为一个整体均匀地应用于整个块。因此,减少了块的数量,非常慷慨地使用了内联,并将它们包装在良好的混淆算法中。编译器可以轻松地进行一些繁重的优化和堆栈排序,这将使该块更难以逆向工程。

运行时

隐藏信息时,重要的是信息要好在运行时也隐藏。称职的逆向工程师将在程序运行时检查其状态。因此,使用在加载时解密的静态变量或使用在加载时完全解压缩的打包将导致快速查找。请注意您在堆上分配的内容。所有堆操作都通过API调用进行,并且可以轻松地记录到文件中并进行推理。通常,仅由于堆栈操作的频繁程度,就很难跟踪它们。动态分析与静态分析同样重要。您需要时刻了解程序的状态以及信息的位置。

反调试

反调试毫无价值。不要花时间在上面。花时间来确保您的秘密完全隐藏起来,而与软件是否处于静止状态无关。

打包和加密代码段

我将加密和打包归为同一类。它们既具有相同的目的,又具有相同的问题。为了执行代码,CPU需要查看纯文本。因此,您必须在二进制文件中提供密钥。加密和打包代码段的唯一远程有效方法是:在功能边界处对其进行加密和解密,并且仅在函数输入时进行解密,然后在离开函数时进行重新加密。这将为转储运行时的二进制文件提供一个小障碍,但必须结合强大的混淆功能。 IDA。您的目标是确保反向工程师几乎不可能找到稳定的思维基础。泄漏的信息越少,环境变化就越大,研究起来就越困难。如果您不是经验丰富的逆向工程师,那么几乎不可能设计出难以逆向工程师的东西。

如果您正在设计一个版权保护系统,请做好使其在精神上受到破坏的准备。确保您有一个计划,该计划将如何处理中断以及如何确保软件的下一版本增加了足够的价值来推动升级。在不会被破坏的坚实基础上构建您的系统,不要诉诸使用我上述方式隐藏的自定义算法来生成自己的许可证密钥。该系统需要建立在可靠的密码基础上,以确保消息不可伪造。

评论


“非常慷慨地使用内联,并将它们包装在良好的混淆算法中” <-只需添加Boost。 Boost +内联+ LTCG =车轮上的地狱。具有不同寄存器的相同函数的十个副本用于传递参数和不同的内联子函数,二十种智能指针,哎呀!

–伊戈尔·斯科钦斯基♦
13年3月21日在14:24

哈哈,是的,您绝对可以选择复杂的,高度模板化的代码,以获得一些免费的混淆信息。 / r / re上是否没有文章显示仅使用模板来创建具有不透明谓词的纯C ++模糊处理框架,并使用模板元编程使用线性同余生成器生成随机数?我似乎至少记得一个。

–彼得·安德森(Peter Andersson)
13年3月21日在14:46

“因此,使用在加载[..]时解密的静态变量将导致快速查找”。有什么选择?

–user2005
14年6月20日在21:41

@Sosukodo我会将变量放入本地缓存中,可能在堆栈上,在那里对其进行解密,使用它们,然后将内存清零。我还要确保解密算法,用法和密钥完全嵌入在基于VM的混淆中。重要的是,没有一种简单的方法可以通过某种自动化方式来查找和解密所有变量。将密钥嵌入到虚拟化代码中而不是数据中,则变得更加困难。当然,要使其成为不可能是非常困难的,但是我们正在努力使逆向工程师的工作变得尽可能乏味和乏味。

–彼得·安德森(Peter Andersson)
14年6月21日在10:18

我同意-我们只是试图建立更好的锁定。您能否提供指向讨论您提到的这些内容的文章的链接?

–user2005
2014年6月21日15:32