
Санкт-Петербургский государственный политехнический университет
Факультет технической кибернетики
Кафедра компьютерных систем и программных технологий
Отчёт по лабораторной работе №2
Дисциплина: "Системное программное обеспечение"
Тема: Обфускация кода
Выполнил студент гр. 5081/13 Залеский А. А.
Преподаватель __________ Душутина Е. В.
\
-
Выполнение работы
Написать программу обычным образом и с вставками непрозрачных булевых предикатов.
Обфуска́ция (от лат. obfuscare — затенять, затемнять; и англ. obfuscate — делать неочевидным, запутанным, сбивать с толку) или запутывание кода — приведение исходного текста или исполняемого кода программы к виду, сохраняющему ее функциональность, но затрудняющему анализ, понимание алгоритмов работы и модификацию при декомпиляции.
«Запутывание» кода может осуществляться на уровне алгоритма, исходного текста и/или ассемблерного текста. Для создания запутанного ассемблерного текста могут использоваться специализированные компиляторы, использующие неочевидные или недокументированные возможности среды исполнения программы.
В качестве примера возьмём программу сортировки массива.
Программа без обфускации:
int main () //Программа сортировки массива методом «пузырек»
{
const int size= 10;
int a[size];
srand(time(NULL));
for (int i = 0; i < size; i++) //Генерация массива случайными числами
a[i] = rand() % 11 - 5;
for (int i = 0; i < size; i++) // вывод на экран
cout << a[i] << " ";
for (int i = 0; i < size; i++) // сама сортировка
for (int j = i+1; j < size; j++)
if (a[i] < a[j])
{
int buf = a[i];
a[i] = a[j];
a[j] = buf;
}
system("PAUSE > NULL");
return 0;
}
Отчёт о профилировании необфусцированной программы.
Программа с обфусцированием:
int main ()
{
const int size= 10;
int a[size];
srand(time(NULL));
for (int i = 0; i < size; i++)
if( (i+1)*i*(i-1) % 3 == 0 ) // Предикат всегда верен
if(int((i*i/2)) % 2 == 0) // Предикат всегда верен
a[i] = rand() % 11 - 5;
for (int i = 0; i < size; i++)
cout << a[i] << " ";
for (int i = 0; i < size; i++)
for (int j = i+1; j < size; j++)
if (a[i] < a[j] && 7*a[i]*a[i] - 1 != a[j]) // Предикат всегда верен
{
int buf = a[i];
if(a[i]*a[i] -1 % 7 != 0 ) // Предикат всегда верен
a[i] = a[j];
switch(i*i*(i+1)*(i+1) % 4){ //Предикат всегда принимает значение 0, поэтому все case никогда не выполнятся
case 1:
for (int j=i+1; j<size; j++)
if (a[j] < a[i])
a[i]=j;
break;
case 2:
a[i] = 2*a[j];
a[j] = 0;
break;
case 3:
a[i] = rand() % 11 - 5;
i++;
a[i] = a[j--];
break;
default:
if(a[j]*(a[j] + 1) +7 %81 != 0 && 4*i*i + 4 % 19 != 0 ) // Предикаты всегда верны
a[j] = buf;
}
}
cout << endl << endl;
for (int i = 0; i < size; i++)
cout << a[i] << " ";
system("PAUSE > NULL");
return 0;
}
Отчёт о профилировании обфусцированной программы.
Таким образом, видно, что производительность программы при добавлении в неё непрозрачных предикатов практически не изменилась.
Непрозрачные предикаты.
Основной проблемой при проектировании запутывающих преобразований графа потока управления является то, как сделать их не только дешёвыми, но и устойчивыми. Для обеспечения устойчивости многие преобразования основываются на введении непрозрачных переменных и предикатов. Сила таких преобразований зависит от сложности анализа непрозрачных предикатов и переменных.
Переменная
v
является
непрозрачной,
если существует свойство
относительно этой переменной, которое
априори известно в момент запутывания
программы, но трудноустанавливаемо
после того, как запутывание завершено.
Аналогично, предикат P
называется непрозрачным,
если его значение известно в момент
запутывания программы, но трудноустанавливаемо
после того, как запутывание завершено.
В моей программе использованы некоторые
булевые утверждения, которые принимают
всегда значение True
или False
вне зависимости от значения используемых
в них переменных. Это позволяет сильно
запутать понимание работы программы.