Дизассемблируем код на Си — 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/


Интересные статьи

Интересные статьи

На работе я занимаюсь поддержкой пользователей и обслуживанием коробочной версии CRM Битрикс24, в том числе и написанием бизнес-процессов. Нужно отметить, что на самом деле я не «чист...
Предыстория Когда-то у меня возникла необходимость проверять наличие неотправленных сообщений в «1С-Битрикс: Управление сайтом» (далее Битрикс) и получать уведомления об этом. Пробле...
Как-то у нас исторически сложилось, что Менеджеры сидят в Битрикс КП, а Разработчики в Jira. Менеджеры привыкли ставить и решать задачи через КП, Разработчики — через Джиру.
Приветствую вас (лично вас, а не всех кто это читает)! Сегодня мы: Создадим приложение (навык) Алисы с использованием нового (октябрь 2019) сервиса Yandex Cloud Functions. Настроим н...
Периодически мне в разных вариантах задают вопрос, который «в среднем» звучит так: «что лучше: заказать интернет-магазин на бесплатной CMS или купить готовое решение на 1С-Битрикс и сделать магазин на...