Доброго времени суток.
Сегодня мы будем смотреть дизассемблированный код инструкций
if, for, while, swich
, которые написаны на языке
Си.
Инcтрукция if
Данную инструкцию довольно просто отличить в дизассемблированном виде от других инструкций. Её отличительное свойство - одиночные инструкции условного перехода je, jne и другие команды jump.
1605790962062.png1605790989407.pngНапишем небольшую программу на языке Си и дизассемблируем её с помощью radare2. Разницы между IDA PRO и radare2 при дизассемблировании этих программ не было обнаружено, поэтому я воспользуюсь radare2. Вы можете использовать IDA PRO.
IDA PRO
2020-11-17_08-22.png2020-11-17_08-23.pngradare2
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Первым делом в программе происходит объявление переменных ( 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Немного усложним задачу. Добавим вложенные инструкции. Попробуйте проанализировать этот код.
Код на Си
#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Режим графов
1605792950680.pngВ режиме графов это воспринимать намного проще.
Инструкция for
Циклы for всегда состоят из четырех этапов: инициализации, сравнения, выполнения инструкций и инкремента/декремента. По этим четырём этапом мы будем распозновать for в ассемблерном коде.
Код на Си
#include <stdio.h>void main() { int x; for(x = 0; x < 100; x++) { printf("x = %d", x); }}
Дизассемблированный вариант в radare2
1605792973718.png1 - инициализации переменной var_ch (x = 0)
2 - сравнение, а затем jle. ( пока x не будет меньше или равен 2,
выполнять цикл.)
3 - выполнения инструкций (printf)
4 - инкрмент переменной var_ch (++x)
Режим графов
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.png1 - инициализации переменной var_4h (status = 0)
2 - сравнение, а затем je. ( пока x равен 0, выполнять цикл.)
3 - выполнения инструкций (func1, printf, change_status)
Режим графов
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.png1 - инициализации переменной var_4h (i = 3)
2 - выполнения инструкций (add, printf)
Чтобы понять какой "case" выбран, происходит сравниение (cmp, а затем je, jne) переменной i с значением case.
Режим графов
1605793347813.pngscreen13.pngscreen_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.png1605793656018.png1 - инициализации переменной var_4h (i = 3)
2 - выполнения инструкций (add, printf)
Вот этот дизасcемблированный код довольно сложно быстро отличить
от if и вообще понять что и как тут. В режиме графов всё будет
более понятно.
Режим графов
1605793750977.png1605793673414.png1605793684884.png1605793691266.pngРежим графов - ваш друг в дизасcемблировании :)
На этом всё. Рекомендую попробовать самому написать программы на Си, скомпилировать и изучить дизасcемблированный код. Практика и ещё раз практика!
Спасибо за внимание. Не болейте.