Санкт-Петербургский государственный политехнический университет
Факультет технической кибернетики
Кафедра компьютерных систем и программных технологий
Отчёт по лабораторной работе №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 вне зависимости от значения используемых в них переменных. Это позволяет сильно запутать понимание работы программы.