Дизассемблируем код на Си — switch() { case assembler: disass(); }

от автора

Доброго времени суток.

Сегодня мы будем смотреть дизассемблированный код инструкций if, for, while, swich, которые написаны на языке Си.

1605795529033.png
1605795529033.png

Инcтрукция if

Данную инструкцию довольно просто отличить в дизассемблированном виде от других инструкций. Её отличительное свойство — одиночные инструкции условного перехода je, jne и другие команды jump.

1605790962062.png
1605790962062.png
1605790989407.png
1605790989407.png

Напишем небольшую программу на языке Си и дизассемблируем её с помощью radare2. Разницы между IDA PRO и radare2 при дизассемблировании этих программ не было обнаружено, поэтому я воспользуюсь radare2. Вы можете использовать IDA PRO.

IDA PRO

2020-11-17_08-22.png
2020-11-17_08-22.png
2020-11-17_08-23.png
2020-11-17_08-23.png

radare2

1605792900362.png
1605792900362.png

Код на Си

#include <stdio.h>  void main() {     int x = 1;     int y = 2;      if(x == y) {         printf("x = y\n");     }     else{         printf("x != y\n");     } }

Компилируем при помощи gcc. Команда gcc -m32 prog_if.c -o prog_if. -m32 озночает, что компилироваться код будет под архитектуру x86.

Чтобы посмотреть на код в radare2, напишем команду r2 prog_if. Далее прописываем aaa для анализа кода и переходим к функции main s main. Посмотрим на код с помощью команды pdf.

Дизассемблированный вариант в radare2

1605792900362.png
1605792900362.png

Первым делом в программе происходит объявление переменных ( int x; int y ), а затем значение 1 перемещается в varch (это переменная x) и значение 2 в var10h (это переменная y). Далее идёт сравнение (cmp) 1 и 2 (cmp edx, dword [var_10h]). Эти значения не равны. Значит jne ( jump if noe equal) перейдёт по адресу 0x000011e1. Проще всего инструкцию if запомнить и опрелелить в режиме графов (команда VV для для radare2 или клавиша пробел для IDA).

Режим графов

1605792914861.png
1605792914861.png

Немного усложним задачу. Добавим вложенные инструкции. Попробуйте проанализировать этот код.

Код на Си

#include <stdio.h>  void main() {     int x = 0;     int y = 1;     int z = 2;      if(x == y) {         if(z == 0) {         printf("z = 0; x = y\n");         }         else{             printf("z = 0; x != y\n");         }     }     else {         if(z == 0) {             printf("z = zero and x != y.\n");         } else {             printf("z non-zero and x != y.\n");         }     } }

Дизассемблированный вариант в radare2

1605792935104.png
1605792935104.png

Режим графов

1605792950680.png
1605792950680.png

В режиме графов это воспринимать намного проще.

Инструкция for

Циклы for всегда состоят из четырех этапов: инициализации, сравнения, выполнения инструкций и инкремента/декремента. По этим четырём этапом мы будем распозновать for в ассемблерном коде.

Код на Си

#include <stdio.h>  void main() {     int x;      for(x = 0; x < 100; x++) {         printf("x = %d", x);     } }

Дизассемблированный вариант в radare2

1605792973718.png
1605792973718.png

1 — инициализации переменной var_ch (x = 0)
2 — сравнение, а затем jle. ( пока x не будет меньше или равен 2, выполнять цикл.)
3 — выполнения инструкций (printf)
4 — инкрмент переменной var_
ch (++x)

Режим графов

1605792987678.png
1605792987678.png

Инструкция while

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

Код на Си

#include <stdio.h>  int func_1(int x); int change_status();  int main() {     int status = 0;      while(status == 0) {         printf("int e = %d", func_1(5) );         status = change_status();     }      return 0; }  int change_status() {     return 1; }  int func_1(int x) {     int c;     int e;     int l;      c = 1 + 2;     e = x / 5;     l = 4 - 2;      return e; }

Дизассемблированный вариант в radare2

1605793002542.png
1605793002542.png

1 — инициализации переменной var_4h (status = 0)
2 — сравнение, а затем je. ( пока x равен 0, выполнять цикл.)
3 — выполнения инструкций (func
1, printf, change_status)

Режим графов

1605793019409.png
1605793019409.png

Инструкция switch

Конструкция switch обычно компилируется двумя способами: по примеру условного выражения или как таблица переходов.

Компиляция по примеру условного выражения

Код на Си

#include <stdio.h>  int main() {     int i = 3;      switch(i) {         case 1:             printf("CASE_1 i = %d", i+4);             break;         case 2:             printf("CASE_2 i = %d", i+9);             break;         case 3:             printf("CASE_3 i = %d", i+14);             break;     }     return 0; }

Дизассемблированный вариант в radare2

1605793045836.png
1605793045836.png

1 — инициализации переменной var_4h (i = 3)
2 — выполнения инструкций (add, printf)

Чтобы понять какой «case» выбран, происходит сравниение (cmp, а затем je, jne) переменной i с значением case.

Режим графов

1605793347813.png
1605793347813.png
screen13.png
screen13.png
screen_13_2.png
screen_13_2.png

Глядя на этот код, сложно (если вообще возможно) сказать, что представлял собой оригинальный исходный текст — конструкцию switch или последовательность выражений if . В обоих случаях код выглядит одинаково, поскольку оба выражения используют множество инструкций cmp и je или jne.

Таблица переходов

Следующий пример ассемблерного кода часто можно встретить в больших смежных выражениях switch. Мы добавим case 4 и инструкцию по умолчанию.

Код на Си

#include <stdio.h>  int main() {     int i = 3;      switch(i) {         case 1:             printf("CASE_1 i = %d", i+4);             break;         case 2:             printf("CASE_2 i = %d", i+9);             break;         case 3:             printf("CASE_3 i = %d", i+14);             break;         case 4:             printf("CASE_3 i = %d", i+19);             break;         default:             break;     }     return 0; }

Дизассемблированный вариант в radare2

1605793648917.png
1605793648917.png
1605793656018.png
1605793656018.png

1 — инициализации переменной var_4h (i = 3)
2 — выполнения инструкций (add, printf)

Вот этот дизасcемблированный код довольно сложно быстро отличить от if и вообще понять что и как тут. В режиме графов всё будет более понятно.

Режим графов

1605793750977.png
1605793750977.png
1605793673414.png
1605793673414.png
1605793684884.png
1605793684884.png
1605793691266.png
1605793691266.png

Режим графов — ваш друг в дизасcемблировании 🙂

На этом всё. Рекомендую попробовать самому написать программы на Си, скомпилировать и изучить дизасcемблированный код. Практика и ещё раз практика!

Спасибо за внимание. Не болейте.

ссылка на оригинал статьи https://habr.com/ru/post/528978/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *