反编译汇编代码(真的)有多难? [关闭]
Posted
技术标签:
【中文标题】反编译汇编代码(真的)有多难? [关闭]【英文标题】:How hard is it (really) to decompile assembly code? [closed] 【发布时间】:2012-12-27 19:58:39 【问题描述】:我正在努力寻找确凿的事实,以帮助我的管理层了解对已编译的 C 代码进行逆向工程的难易程度。
以前曾在本网站上提出过类似的问题(例如,参见 Is it possible to “decompile” a Windows .exe? Or at least view the Assembly? 或 Possible to decompile DLL written in C?),但这些问题的要点是反编译已编译的 C 代码“很难,但并非完全不可能”。
为了便于提供基于事实的答案,我将包含一个神秘函数的编译代码,并且我建议对这个问题的答案通过它们是否可以确定该函数的作用来衡量所提出技术的成功或失败.这对于 SO 来说可能是不寻常的,但我认为这是对这个工程问题获得“良好的主观”或事实答案的最佳方式。因此,您对这个函数在做什么以及如何做的最佳猜测是什么?
这是编译后的代码,在 Mac OSX 上使用 gcc 编译:
_mystery:
Leh_func_begin1:
pushq %rbp
Ltmp0:
movq %rsp, %rbp
Ltmp1:
movsd LCPI1_0(%rip), %xmm1
subsd %xmm0, %xmm1
pxor %xmm2, %xmm2
ucomisd %xmm1, %xmm2
jbe LBB1_2
xorpd LCPI1_1(%rip), %xmm1
LBB1_2:
ucomisd LCPI1_2(%rip), %xmm1
jb LBB1_8
movsd LCPI1_0(%rip), %xmm1
movsd LCPI1_3(%rip), %xmm2
pxor %xmm3, %xmm3
movsd LCPI1_1(%rip), %xmm4
jmp LBB1_4
.align 4, 0x90
LBB1_5:
ucomisd LCPI1_2(%rip), %xmm1
jb LBB1_9
movapd %xmm5, %xmm1
LBB1_4:
movapd %xmm0, %xmm5
divsd %xmm1, %xmm5
addsd %xmm1, %xmm5
mulsd %xmm2, %xmm5
movapd %xmm5, %xmm1
mulsd %xmm1, %xmm1
subsd %xmm0, %xmm1
ucomisd %xmm1, %xmm3
jbe LBB1_5
xorpd %xmm4, %xmm1
jmp LBB1_5
LBB1_8:
movsd LCPI1_0(%rip), %xmm5
LBB1_9:
movapd %xmm5, %xmm0
popq %rbp
ret
Leh_func_end1:
更新
@Igor Skochinsky 是第一个找到正确答案的人:它确实是 Heron 计算平方根算法的幼稚实现。原始源代码在这里:
#include <stdio.h>
#define EPS 1e-7
double mystery(double x)
double y=1.;
double diff;
diff=y*y-x;
diff=diff<0?-diff:diff;
while(diff>=EPS)
y=(y+x/y)/2.;
diff=y*y-x;
diff=diff<0?-diff:diff;
return y;
int main()
printf("The square root of 2 is %g\n", mystery(2.));
【问题讨论】:
您拥有 7k+ 的声誉并称呼“网站版主”??你没有弄清楚这个网站是如何运作的吗? @djechlin:“猜猜我的汇编器做了什么?”曾经是一个有效的问题吗? (或者那是讽刺?) @lindelof - 我会给你另一个例子here,其中 10 行内联函数和 C++ 模板被编译成 4-5 条机器指令。任何人都可以复制原始源代码的几率是多少? 一般情况下是不可能的,原始源是绝对不可能的,在极少数情况下没有使用优化器并且代码是如此琐碎以至于您无需费心回到C,然后您可以重构功能相同的东西。 将此视为将 wav 文件转换为 mp3,(将图像转换为 jpg,将电影转换为 mpeg 等)一种有损压缩。您无法取回原始信号。同样的事情发生在编译器中,正在编译的源代码中的信息丢失,在输出中不可见,您无法返回原始代码。功能相似的 C 代码在可能的情况下并不比汇编语言更具可读性或可维护性,如果您必须在 asm 中进行修改或通过对 asm 的分析手动编写 C 代码,则最好。 【参考方案1】:这是我将代码转换为x86(目前不支持x64)后用Hex-Rays Decompiler反编译的结果,添加了原帖中缺少的一些数据定义,并组装了它:
//-------------------------------------------------------------------------
// Data declarations
double LCPI1_0 = 1.0; // weak
double LCPI1_1[2] = 0.0, 0.0 ; // weak
double LCPI1_2 = 1.2; // weak
double LCPI1_3 = 1.3; // weak
//----- (00000000) --------------------------------------------------------
void __usercall mystery(__m128d a1<xmm0>)
__m128d v1; // xmm1@1
__m128d v2; // xmm1@4
__int128 v3; // xmm2@4
__m128d v4; // xmm5@7
__m128d v5; // xmm1@7
v1 = (__m128d)*(unsigned __int64 *)&LCPI1_0;
v1.m128d_f64[0] = LCPI1_0 - a1.m128d_f64[0];
if ( LCPI1_0 - a1.m128d_f64[0] < 0.0 )
v1 = _mm_xor_pd(v1, *(__m128d *)LCPI1_1);
if ( v1.m128d_f64[0] >= LCPI1_2 )
v2 = (__m128d)*(unsigned __int64 *)&LCPI1_0;
v3 = *(unsigned __int64 *)&LCPI1_3;
while ( 1 )
v4 = a1;
v4.m128d_f64[0] = (v4.m128d_f64[0] / v2.m128d_f64[0] + v2.m128d_f64[0]) * *(double *)&v3;
v5 = v4;
v5.m128d_f64[0] = v5.m128d_f64[0] * v5.m128d_f64[0] - a1.m128d_f64[0];
if ( v5.m128d_f64[0] < 0.0 )
v5 = _mm_xor_pd(a1, (__m128d)*(unsigned __int64 *)LCPI1_1);
if ( v5.m128d_f64[0] < LCPI1_2 )
break;
v2 = a1;
// 90: using guessed type double LCPI1_0;
// 98: using guessed type double LCPI1_1[2];
// A8: using guessed type double LCPI1_2;
// B0: using guessed type double LCPI1_3;
// ALL OK, 1 function(s) have been successfully decompiled
显然,它可以进行一些改进(XMM 支持现在有点基本),但我认为基本算法已经可以理解了。
编辑:由于显然只使用了所有 XMM 寄存器的低位双精度,因此该函数似乎实际上适用于标量双精度而不是向量。至于 _mm_xor_pd (xorpd) 内在函数,我认为这只是编译器实现符号反转的方式——通过与一个预定义的常量进行异或运算,该常量在符号位位置为 1,在其他所有位置为 0。考虑到上述情况,经过一些清理,我得到以下代码:
double mystery(double a1)
double v1; // xmm1@1
double v2; // xmm1@4
double v3; // xmm2@4
double v4; // xmm5@7
double v5; // xmm1@7
v1 = LCPI1_0 - a1;
if ( v1 < 0.0 )
v1 = -v1;
if ( v1 < LCPI1_2 )
v4 = LCPI1_0;
else
v2 = LCPI1_0;
v3 = LCPI1_3;
while ( 1 )
v4 = a1;
v4 = (v4 / v2 + v2) * v3;
v5 = v4;
v5 = v5 * v5 - a1;
if ( v5 < 0.0 )
v5 = -v5;
if ( v5 < LCPI1_2 )
break;
v2 = a1;
return v4;
它生成的程序集与原始帖子非常相似。
【讨论】:
那么,您对这段代码在做什么的最佳猜测是什么?我认为您需要在低级代码恢复之上进行算法识别。 PS:很好的逆向工程到你得到的地方,+1 尽管被关闭了:) 看起来像巴比伦的平方根计算方法。 LCPI1_0 是初始近似值,LCPI1_2 是 epsilon,LCPI1_3 是常数 0.5。 @IgorSkochinsky 恭喜,你成功了!【参考方案2】:逆向工程/反编译任何代码都是时间与收益的问题;不是做起来有多难。
如果您有一些绝对不能泄露的秘方,那么您唯一能做的就是将秘方作为一种网络服务,在必要时调用它。这样,二进制文件就永远不会离开您的公司。
即使是混淆也只能达到一旦黑客在他们控制的系统上拥有已编译的二进制文件就可以追踪任何东西的程度。哎呀,最初的 PC 克隆是通过对 IBM Bios 进行逆向工程创建的。
所以,回到正题:同样,这不是一个事情有多难的问题,而是一个是否有人愿意尝试的问题......这是基于他们将从中获得的感知价值。无论是直接美元(接收或储蓄)、竞争优势还是只是吹嘘自己的权利。与此相关的还有应用程序的可用性:更广泛的分布等于更有可能找到它进入黑客的工作桶。
如果存在这些价值观,那么您可以放心,有人会尝试并且他们会成功。这应该会引导您进入下一个问题:如果他们这样做了怎么办?最坏的结果是什么?
在某些情况下,这只是一次失败的销售,您可能无论如何都没有得到。在其他情况下,这可能是业务的损失。
【讨论】:
【参考方案3】:从根本上说,执行单独的机器指令“逆向工程”非常容易,因为机器指令具有非常明确的语义。这会给你糟糕的 C 代码,但肯定不是目标。 (知道文件中的某些二进制模式是机器指令在技术上是图灵难的,例如,在某些情况下是不可能的;在编译器生成的代码的情况下不太可能)。 p>
除此之外,您正在尝试推断算法和意图。这非常困难;包含这一切的知识从何而来?
您可能会觉得我的 paper on reverse engineering 很有趣。它提出了一种编码必要知识的方法。
在某种程度上也有commercial tools to do this。据我了解,这并没有达到我的论文概述的方案,但仍然产生了相当合理的 C 代码。 (我对这个工具没有具体的经验,但非常尊重作者和他的工具)。
【讨论】:
以上是关于反编译汇编代码(真的)有多难? [关闭]的主要内容,如果未能解决你的问题,请参考以下文章