Подтвердить что ты не робот

Насколько сложно (действительно) декомпилировать код сборки?

Я пытаюсь найти твердые факты, которые помогут моему руководству понять, насколько сложно/легко перепрограммировать скомпилированный код C.

Аналогичные вопросы задавались на этом сайте (см., например, Возможно ли "декомпилировать" Windows.exe? Или, по крайней мере, просмотреть сборку? или Возможно декомпилировать DLL, написанную на 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

@Егор Скочинский первым найдет правильный ответ: это действительно наивная реализация алгоритма Херона для вычисления квадратных корней. Исходный код находится здесь:

#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.));
}
4b9b3361

Ответ 1

Вот результаты декомпиляции с декомпилятором Hex-Rays после того, как я преобразовал код в 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; // [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 с предопределенной константой, которая имеет 1s в позициях битовых знаков и 0s всюду. Учитывая это, и после некоторой очистки, я получаю следующий код:

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

Он создает сборку, очень похожую на исходную запись.

Ответ 2

Обратное проектирование/декомпиляция любого кода - это вопрос времени, которое требуется для этого; а не как трудно это сделать.

Если у вас есть какой-то секретный соус, который вы абсолютно не можете позволить себе выбраться, то единственное, что вы можете сделать, - это секретный соус в качестве веб-сервиса, который получает по необходимости. Таким образом, двоичные файлы никогда не покидают ваши корпоративные стены.

Даже обфускация только доходит до того, что все можно проследить, как только хакер имеет скомпилированные двоичные файлы в системе, которую они контролируют. Хек, оригинальные клоны ПК были созданы путем обратного проектирования IBM BIOS.

Итак, вернемся к сути: опять же, это не вопрос о том, как сложно что-то, это вопрос о том, захочет ли кто-нибудь попробовать... который основан на том, какую воспринимаемую ценность они выберут из него. Прямые доллары (получение или сбережение), конкурентные преимущества или просто бахвальство. Усугубляет это доступность приложения: более широкое распространение равно более высокому потенциалу для поиска пути к хакерскому ведру вещей для работы.

Если эти значения существуют, вы можете быть уверены, что кто-то попытается, и они добьются успеха. Что должно привести вас к следующему вопросу: что делать? Какой худший результат?

В некоторых случаях это просто потерянная продажа, которую вы, возможно, не получили. В других это может быть потеря бизнеса.

Ответ 3

В принципе, индивидуальная машинная инструкция "обратная инженерия" довольно проста, потому что машинные инструкции имеют очень четко определенную семантику. Это даст вам плохой код C, но, конечно, это не цель. (Знание того, что какой-то двоичный шаблон в файле является машинной инструкцией, технически Тьюрингом является жестким, например, невозможным в некоторых случаях, менее вероятно, что в случае кода, сгенерированного компилятором).

Кроме того, вы пытаетесь вывести алгоритмы и намерения. Это чрезвычайно сложно; где знание, содержащее все это, происходит?

Вы можете найти мою статью об обратной инженерии. Он предлагает способ кодирования необходимых знаний.

Есть также коммерческие инструменты, чтобы сделать это в некоторой степени. Это не касается той схемы, о которой я рассказываю в статье, но все же производит довольно разумный код С, как я понимаю. (У меня нет конкретного опыта работы с этим инструментом, но я очень уважаю автора и его инструменты).