14

コンパイルされたCコードをリバースエンジニアリングすることがどれほど難しいか/簡単かを経営陣が理解するのに役立つ難しい事実を見つけようとしています。

このサイトでは以前にも同様の質問がありました(たとえば、Windows .exeを「逆コンパイル」することは可能ですか?または少なくともアセンブリを表示することはできますか?またはCで記述されたDLLを逆コンパイルすることは可能ですか?を参照)が、これらの質問の要点は次のとおりです。コンパイルされたCコードの逆コンパイルは「難しいですが、完全に不可能というわけではありません」。

実際に基づいた回答を容易にするために、ミステリー関数のコンパイル済みコードを含めます。この質問への回答は、この関数が何をするかを判断できるかどうかによって、提案された手法の成功または失敗を測定することを提案します。これはSOにとっては珍しいことかもしれませんが、このエンジニアリングの質問に対する「良い主観的」または事実に基づく回答を得るのに最適な方法だと思います。したがって、この関数が何をしているのか、そしてどのように行うのかについてのあなたの最良の推測は何ですか?

これは、MacOSXで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.));
}
4

3 に答える 3

16

以下は、コードを 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 レジスタの下位 double のみが使用されていることが明らかであるため、この関数は実際にはベクターではなくスカラー double で機能するようです。_mm_xor_pd (xorpd) 組み込み関数については、コンパイラが符号反転を実装する方法だと思います。符号ビット位置に 1 があり、それ以外は 0 である定義済みの定数を使用して xor 処理することです。上記を念頭に置いて、いくつかのクリーンアップの後、次のコードを取得します。

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

元の投稿とかなり似たアセンブリが生成されます。

于 2013-01-14T17:16:44.273 に答える
6

リバースエンジニアリング/コードの逆コンパイルは、時間とそうするメリットの問題です。するのがどれほど難しいかではありません。

絶対に外に出してはいけない秘密のソースがある場合、できることは、必要に応じて呼び出される Web サービスとしてその秘密のソースを保持することだけです。このようにして、バイナリが企業の壁から離れることはありません。

難読化でさえ、ハッカーが制御するシステムでコンパイル済みのバイナリを取得すると、追跡できる範囲でのみ実行されます。なんと、元の PC クローンは、IBM BIOS のリバース エンジニアリングによって作成されたものです。

要点に戻りますが、何かがどれほど難しいかという問題ではなく、誰かが試してみたいかどうかという問題です... それは、彼らがそれからどのような価値を得ることができるかに基づいています. 直接的なドルの受け取り(受け取りまたは貯蓄)、競争上の優位性、または単に自慢する権利など。これをさらに複雑にしているのは、アプリケーションの可用性です。配布範囲が広がれば、ハッカーのバケツに取り込まれる可能性が高くなります。

これらの値が存在する場合、誰かが試して成功することが保証されます。次の質問につながるのはどれですか。最悪の結果は?

場合によっては、単に販売の損失であり、とにかく手に入れることができなかった可能性があります. 他の場合には、ビジネスの損失になる可能性があります。

于 2013-01-14T17:59:47.947 に答える
1

基本的に、マシン命令には非常に明確に定義されたセマンティクスがあるため、個々のマシン命令の「リバースエンジニアリング」を行うのは非常に簡単です。これはあなたに悪いCコードを与えるでしょう、しかし確かにそれは目標ではありません。(ファイル内のいくつかのバイナリパターンが機械命令であることを知ることは、技術的には難しいです。たとえば、場合によっては不可能です。コンパイラで生成されたコードの場合はそうなる可能性は低くなります)。

それを超えて、あなたは推論アルゴリズムと意図を試みています。それは非常に難しいです。これらすべてを含む知識はどこから来るのでしょうか?

リバースエンジニアリングに関する私の論文がおもしろいと思うかもしれません。必要な知識をエンコードする方法を提案します。

これをある程度行うための商用ツールもあります。これは私の論文で概説されているスキームには及ばないが、私が理解しているように、それでもかなり合理的なCコードを生成する。(私はこのツールについて特別な経験はありませんが、作者と彼のツールに大きな敬意を払っています)。

于 2013-01-14T18:44:05.697 に答える