2013-01-13 194 views
12

我正在努力寻找能够帮助我的管理层理解对已编译的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: 

UPDATE

@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.)); 
} 
+15

你有7k +的声望和地址“网站版主”?你没有研究过这个网站的工作原理吗? –

+0

我想知道如果我现在应该启动meta.so线程来处理此问题的合法性... – djechlin

+0

可惜我没有hexrays反编译器许可证。我怀疑这代码很简单。 – CodesInChaos

回答

15

下面是反编译的结果与Hex-Rays Decompiler后,我转换代码,以86(它不支持在目前64位),增加了一些数据定义在原岗位缺失,和组装它:

//------------------------------------------------------------------------- 
// 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; // [email protected] 
    __m128d v2; // [email protected] 
    __int128 v3; // [email protected] 
    __m128d v4; // [email protected] 
    __m128d v5; // [email protected] 

    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)内部函数,我认为这只是编译器实现符号反转的方式 - 通过用一个预定义的常量进行xoring,其中符号位位置为1,其他位置为0。考虑到上述情况,并在进行一些清理之后,我得到以下代码:

double mystery(double a1) 
{ 
    double v1; // [email protected] 
    double v2; // [email protected] 
    double v3; // [email protected] 
    double v4; // [email protected] 
    double v5; // [email protected] 

    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; 
} 

它生成的组件与原始帖子非常相似。

+0

因此,对于此代码是做什么*的最佳猜测是什么?我认为你需要在低级代码恢复的基础上进行算法识别。 PS:不错的工作逆向工程到你得到的地方,+1尽管被关闭:) –

+0

看起来像平方根计算的巴比伦方法。 LCPI1_0是初始近似值,LCPI1_2是ε,LCPI1_3是常数0.5。 –

+0

@IgorSkochinsky恭喜你,你该死! – lindelof

6

对任何代码进行反向工程/反编译都是需要花费的时间和这样做的好处的问题;没有多难。

如果你有一些绝对不能允许的秘密酱汁,那么你唯一可以做的就是将这个秘密酱汁作为一种网络服务,必要时会被调用。这样的二进制文件永远不会离开你的企业墙。

甚至一旦黑客在他们控制的系统上编译了二进制文件,任何事情都可以被追踪。哎呀,原始的PC克隆是通过对IBM BIOS进行逆向工程创建的。

所以,回到关键点:再说一遍,这不是一个问题:问题是否有人想尝试......这是基于他们从中获得的感知价值的问题。无论是直接美元(接收还是储蓄),竞争优势还是干脆吹嘘权利。更复杂的是应用程序的可用性:更广泛的分布意味着更高的潜力,可以发现它正在成为黑客工作的一部分。

如果这些值存在,那么你可以放心,有人会尝试,他们会成功。哪个应该引导你到下一个问题:如果他们这样做会怎样?最糟糕的结果是什么?

在某些情况下,它只是一个丢失销售,你可能还没有得到。在其他情况下,这可能是业务的损失。

1

从根本上讲,做单独的机器指令“逆向工程”非常容易,因为机器指令具有非常明确的语义。这会给你错误的C代码,但肯定不是目标。 (知道文件中的某些二进制模式是,机器指令在技术上是图灵硬的,例如在某些情况下是不可能的;在编译器生成的代码的情况下不太可能)。

除此之外,您正在尝试推断算法和意图。这非常困难;包含所有这些知识从哪里来?

您可能会发现我的 paper on reverse engineering有趣。它提出了一种编码必要知识的方法。在一定程度上也有commercial tools to do this。据我的理解,这并不像我的论文所概述的方案那样远,但仍然产生非常合理的C代码。 (我没有使用这个工具的具体经验,但对作者和他的工具非常尊重)。