<分区>
我正在努力寻找确凿的事实,以帮助我的管理层了解对编译后的 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.));
}
这是使用 Hex-Rays Decompiler 进行反编译的结果在我将代码转换为 x86(目前不支持 x64)之后,添加了一些原始帖子中缺少的数据定义,并组装了它:
//-------------------------------------------------------------------------
// 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 寄存器的低 double ,因此该函数似乎实际上适用于标量 double 而不是 vector 。至于 _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;
}
它生成的程序集与原始帖子非常相似。