我一直在思考如何保护我的C/ c++代码不被反汇编和逆向工程破坏。通常情况下,在我的代码中,我绝不会宽恕这种行为;然而,为了各种人的安全,我目前正在研究的协议决不能被检查或理解。

现在这对我来说是一个新的主题,互联网并没有真正的资源来防止逆向工程,而是描述了大量关于如何逆向工程的信息

到目前为止我想到的一些事情是:

Code injection (calling dummy functions before and after actual function calls) Code obfustication (mangles the disassembly of the binary) Write my own startup routines (harder for debuggers to bind to) void startup(); int _start() { startup( ); exit (0) } void startup() { /* code here */ } Runtime check for debuggers (and force exit if detected) Function trampolines void trampoline(void (*fnptr)(), bool ping = false) { if(ping) fnptr(); else trampoline(fnptr, true); } Pointless allocations and deallocations (stack changes a lot) Pointless dummy calls and trampolines (tons of jumping in disassembly output) Tons of casting (for obfuscated disassembly)

我的意思是,这些是我想过的一些事情,但它们都可以在适当的时间框架内由代码分析师解决。我还有别的选择吗?


当前回答

为了避免逆向工程,您不能将代码提供给用户。也就是说,我建议使用在线应用程序……然而(因为你没有给出背景),这对你来说可能是毫无意义的。

其他回答

使代码难以进行逆向工程称为代码混淆。

你提到的大多数技术都很容易解决。他们专注于添加一些无用的代码。但是无用的代码很容易被发现和删除,留下一个干净的程序。

为了有效地混淆,您需要使程序的行为依赖于正在执行的无用部分。例如,与其这样做:

a = useless_computation();
a = 42;

这样做:

a = complicated_computation_that_uses_many_inputs_but_always_returns_42();

或者不这样做:

if (running_under_a_debugger()) abort();
a = 42;

这样做(其中running_under_a_debugger不应该很容易被识别为测试代码是否在调试器下运行的函数-它应该将有用的计算与调试器检测混合在一起):

a = 42 - running_under_a_debugger();

有效的混淆并不是仅仅在编译阶段就能做到的。编译器能做的,反编译器也能做。当然,您可以增加反编译器的负担,但这不会有太大的帮助。有效的混淆技术,就其存在而言,包括从第一天开始编写混淆的源代码。让你的代码自修改。你的代码中充斥着从大量输入中得到的计算跳跃。例如,而不是简单的调用

some_function();

这样做,你碰巧知道some_data_structure中精确的位的预期布局:

goto (md5sum(&some_data_structure, 42) & 0xffffffff) + MAGIC_CONSTANT;

如果你认真对待混淆,那就在你的计划中增加几个月的时间;混淆视听代价不菲。请务必考虑到,到目前为止,避免人们对您的代码进行逆向工程的最好方法是使其无用,这样他们就不会费心了。这是一个简单的经济考虑:如果对他们来说价值大于成本,他们就会逆向工程;但提高他们的成本也会大大提高你的成本,所以尽量降低他们的价值。

既然我已经告诉过你,混淆是困难和昂贵的,我要告诉你,无论如何,它不适合你。你写

目前我正在研究的协议绝不能被检查或理解,为了各种人的安全

这是一个危险的信号。它是通过默默无闻来保证安全的,而默默无闻的记录非常糟糕。如果协议的安全性依赖于人们不知道协议,那么你已经输了。

推荐阅读:

安全圣经:Ross Anderson的《安全工程》 混淆的圣经:由Christian Collberg和Jasvir Nagra开发的Surreptitious软件

很多时候,担心你的产品被逆向工程是多余的。是的,它可以被逆向工程;但它是否会在短时间内变得如此出名,以至于黑客们会发现它值得逆转。它吗?(对于大量的代码行来说,这项工作不是一个小时间活动)。

如果它真的能赚钱,那么你就应该筹集足够的资金,通过专利或版权等合法途径来保护它。

恕我直言,采取你将要采取的基本预防措施,然后释放它。如果它成为逆向工程的一个点,这意味着你已经做得非常好,你自己会找到更好的方法来克服它。祝你好运。

Take, for example, the AES algorithm. It's a very, very public algorithm, and it is VERY secure. Why? Two reasons: It's been reviewed by lots of smart people, and the "secret" part is not the algorithm itself - the secret part is the key which is one of the inputs to the algorithm. It's a much better approach to design your protocol with a generated "secret" that is outside your code, rather than to make the code itself secret. The code can always be interpreted no matter what you do, and (ideally) the generated secret can only be jeopardized by a massive brute force approach or through theft.

我认为一个有趣的问题是“为什么你想让你的代码变得模糊?”你想让攻击者难以破解你的算法?让他们更难在你的代码中发现可利用的漏洞?如果代码一开始就不可破解,那么您就不需要混淆代码。问题的根源在于易破解的软件。解决问题的根源,不要只是混淆它。

而且,你的代码越混乱,你就越难找到安全漏洞。是的,这对黑客来说很难,但你也需要找到漏洞。从现在开始,代码应该很容易维护,即使是编写良好的清晰代码也很难维护。不要让事情变得更糟。

与大多数人所说的相反,基于他们的直觉和个人经验,我不认为密码安全的程序混淆通常被证明是不可能的。

这是一个完美混淆的程序语句的例子,以证明我的观点:

printf("1677741794\n");

人们永远猜不到它真正的作用是什么

printf("%d\n", 0xBAADF00D ^ 0xDEADBEEF);

关于这个问题有一篇有趣的论文,它证明了一些不可能的结果。它叫做“关于混淆程序的(Im)可能性”。

尽管这篇论文确实证明了使程序与它实现的函数不可区分的混淆是不可能的,但以某种较弱的方式定义的混淆仍然是可能的!

最好的反反汇编技巧,特别是在可变字长指令集上,是在汇编程序/机器代码中,而不是在c中

CLC
BCC over
.byte 0x09
over:

The disassembler has to resolve the problem that a branch destination is the second byte in a multi byte instruction. An instruction set simulator will have no problem though. Branching to computed addresses, which you can cause from C, also make the disassembly difficult to impossible. Instruction set simulator will have no problem with it. Using a simulator to sort out branch destinations for you can aid the disassembly process. Compiled code is relatively clean and easy for a disassembler. So I think some assembly is required.

I think it was near the beginning of Michael Abrash's Zen of Assembly Language where he showed a simple anti disassembler and anti-debugger trick. The 8088/6 had a prefetch queue what you did was have an instruction that modified the next instruction or a couple ahead. If single stepping then you executed the modified instruction, if your instruction set simulator did not simulate the hardware completely, you executed the modified instruction. On real hardware running normally the real instruction would already be in the queue and the modified memory location wouldnt cause any damage so long as you didnt execute that string of instructions again. You could probably still use a trick like this today as pipelined processors fetch the next instruction. Or if you know that the hardware has a separate instruction and data cache you can modify a number of bytes ahead if you align this code in the cache line properly, the modified byte will not be written through the instruction cache but the data cache, and an instruction set simulator that did not have proper cache simulators would fail to execute properly. I think software only solutions are not going to get you very far.

上面这些都是老的和众所周知的,我对当前的工具了解不够,不知道它们是否已经围绕这些事情工作了。自修改代码可能/将使调试器出错,但是人类可以/将缩小问题范围,然后看到自修改代码并解决它。

It used to be that the hackers would take about 18 months to work something out, dvds for example. Now they are averaging around 2 days to 2 weeks (if motivated) (blue ray, iphones, etc). That means to me if I spend more than a few days on security, I am likely wasting my time. The only real security you will get is through hardware (for example your instructions are encrypted and only the processor core well inside the chip decrypts just before execution, in a way that it cannot expose the decrypted instructions). That might buy you months instead of days.

另外,读读凯文·米特尼克的《欺骗的艺术》。这样的人可以拿起电话,让你或同事把秘密交给系统,以为那是公司其他部门的经理、其他同事或硬件工程师。你的安全系统也被破坏了。安全不仅仅是管理技术,还要管理人。