Насколько сложно (действительно) декомпилировать код сборки?
Я пытаюсь найти твердые факты, которые помогут моему руководству понять, насколько сложно/легко перепрограммировать скомпилированный код 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.));
}
Ответы
Ответ 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, но, конечно, это не цель. (Знание того, что какой-то двоичный шаблон в файле является машинной инструкцией, технически Тьюрингом является жестким, например, невозможным в некоторых случаях, менее вероятно, что в случае кода, сгенерированного компилятором).
Кроме того, вы пытаетесь вывести алгоритмы и намерения. Это чрезвычайно сложно; где знание, содержащее все это, происходит?
Вы можете найти мою статью об обратной инженерии. Он предлагает способ кодирования необходимых знаний.
Есть также коммерческие инструменты, чтобы сделать это в некоторой степени. Это не касается той схемы, о которой я рассказываю в статье, но все же производит довольно разумный код С, как я понимаю. (У меня нет конкретного опыта работы с этим инструментом, но я очень уважаю автора и его инструменты).