Системне програмування на асемблері для кібербезпеки
Вступ до асемблера
Що таке мова асемблера?
Мова асемблера — це мова програмування низького рівня, яка дуже близька до інструкцій, що їх розуміє процесор комп'ютера. На відміну від мов високого рівня, як-от Python чи Java, які використовують складні команди, асемблер працює з базовими операціями, які процесор виконує безпосередньо.
Уявіть, що ви віддаєте накази безпосередньо солдату, а не генералу, який потім передає їх далі. Асемблер — це розмова із солдатом.
Цей посібник зосереджений на мові асемблера, мові програмування низького рівня, що забезпечує прямий зв'язок з апаратним забезпеченням комп'ютера.
Вивчення асемблера дає змогу зрозуміти, як насправді працює комп'ютер. Це корисно для оптимізації критичних ділянок коду, розробки драйверів або просто для того, щоб стати кращим програмістом.
Структура програми
Програма на асемблері зазвичай складається з кількох секцій. Найважливіші з них — .text, .data та .bss.
.text: Тут міститься ваш код — інструкції, які виконуватиме процесор..data: У цій секції зберігаються ініціалізовані дані, тобто змінні, які мають початкове значення..bss: Тут оголошуються неініціалізовані дані. Для них просто резервується місце в пам'яті, без початкового значення.
; Програма Hello, world! для Linux x86_64 (синтаксис NASM)
section .data
msg db 'Hello, world!', 0x0a ; Наш рядок, 0x0a - символ нового рядка
len equ $ - msg ; Довжина рядка
section .text
global _start
_start:
; Системний виклик для виведення на екран (write)
mov rax, 1 ; Номер системного виклику (sys_write)
mov rdi, 1 ; Файловий дескриптор (1 - stdout)
mov rsi, msg ; Вказівник на наше повідомлення
mov rdx, len ; Довжина повідомлення
syscall ; Виклик ядра операційної системи
; Системний виклик для завершення програми (exit)
mov rax, 60 ; Номер системного виклику (sys_exit)
xor rdi, rdi ; Код повернення 0 (успішне завершення)
syscall ; Виклик ядра
Цей приклад демонструє базову структуру: ми оголошуємо дані в секції .data та пишемо виконуваний код у секції .text. Точка входу в програму позначена міткою _start.
Регістри та пам'ять
Процесор не працює з даними безпосередньо з оперативної пам'яті (RAM). Спочатку він завантажує їх у спеціальні комірки всередині себе, які називаються регістрами. Регістри — це надзвичайно швидка пам'ять, такий собі робочий стіл процесора.
В архітектурі x86_64 є кілька регістрів загального призначення. Ось основні з них:
| Регістр | Призначення |
|---|---|
RAX | Акумулятор; використовується для повернення значень |
RBX | Базовий регістр; для зберігання даних |
RCX | Лічильник; часто використовується в циклах |
RDX | Регістр даних; розширення для RAX |
RSI | Вказівник на джерело (Source Index) |
RDI | Вказівник на приймач (Destination Index) |
RSP | Вказівник стека (Stack Pointer) |
RBP | Вказівник бази стека (Base Pointer) |
R8-R15 | Додаткові регістри загального призначення |
Для переміщення даних між регістрами та пам'яттю використовується інструкція mov (від англ. move). Це одна з найпоширеніших команд в асемблері.
mov destination, source
Ця команда копіює значення з source (джерела) в destination (приймач). Наприклад, mov rax, 10 запише число 10 у регістр RAX. Щоб завантажити значення зі змінної в пам'яті, ми використовуємо її ім'я: mov rcx, [my_variable].
Звернення до операційної системи
Програми на асемблері не можуть самостійно виконувати складні дії, як-от виведення тексту на екран чи читання файлу. Для цього вони просять допомоги в операційної системи (ОС). Цей процес називається системним викликом.
Щоб зробити системний виклик, потрібно:
- Помістити номер потрібного виклику в регістр
RAX. - Помістити аргументи для цього виклику в регістри
RDI,RSI,RDXтощо. - Виконати інструкцію
syscall.
Інструкція
syscallпередає керування ядру ОС. Ядро виконує необхідну роботу, а потім повертає керування назад вашій програмі.
У нашому прикладі "Hello, world!" ми робили два системні виклики:
sys_write(номер 1 вRAX) для виведення тексту.sys_exit(номер 60 вRAX) для коректного завершення програми.
Тепер, коли ви знаєте основи, час перевірити свої знання.
Що таке мова асемблера?
У якій секції програми на асемблері слід оголошувати змінні, які мають початкове значення (ініціалізовані дані)?
Це лише верхівка айсберга. Асемблер відкриває двері до глибокого розуміння роботи комп'ютера, від оптимізації продуктивності до аналізу зловмисного програмного забезпечення.
