Архитектура приложений и основы низкоуровневого программирования
Архитектура приложений и основы низкоуровневого программирования
Добро пожаловать в курс «Основы этичного хакинга и реверс-инжиниринга». Вы здесь, потому что хотите не просто нажимать на кнопки, а понимать, как эти кнопки работают изнутри, как их можно переделать под себя и где в них скрыты уязвимости.
Чтобы стать хакером или специалистом по реверс-инжинирингу (обратной разработке), нельзя оставаться на поверхности. Нужно уметь «нырять» глубоко в недра компьютера. В этой первой статье мы разберем фундамент: как устроены программы, как процессор понимает команды и как выглядит память компьютера изнутри.
Уровни абстракции: от электричества до Python
Современные компьютеры построены по принципу слоеного пирога. Обычный пользователь видит только верхний слой — красивый интерфейс. Программист видит код. Хакер должен видеть всё, вплоть до того, как данные перемещаются по проводам.
В информатике это называется уровнями абстракции. Чем ниже уровень, тем больше контроля вы имеете над системой, но тем сложнее писать код.
- Высокий уровень (High-Level): Языки вроде Python или JavaScript. Они делают много работы за вас (управляют памятью, типами данных). Это удобно, но скрывает детали.
- Низкий уровень (Low-Level): Языки C и C++. Здесь вы обязаны сами следить за памятью. Именно на этих языках написаны операционные системы (Windows, Linux) и драйверы.
- Язык Ассемблера (Assembly): Это текстовое представление машинного кода. Каждая команда здесь соответствует одному действию процессора.
- Машинный код: Те самые нули и единицы, которые реально исполняет процессор.
«Чтобы взломать систему, нужно думать как система, а не как пользователь.»
Как компьютер хранит данные: Биты, Байты и Шестнадцатеричная система
Для компьютера всё есть число. Картинка, текст, звук, программа — это просто набор чисел.
Двоичная система (Binary)
В основе всего лежит бит (0 или 1). Это как выключатель: тока нет (0) или ток есть (1). Группа из 8 битов называется байтом.
Математически любое число можно представить в двоичном виде. Рассмотрим формулу перевода двоичного числа в привычное нам десятичное:
Где:
- — итоговое десятичное число.
- — номер старшего разряда (позиция самой левой цифры минус 1).
- — цифра в -й позиции (0 или 1).
- — основание системы (2) в степени позиции.
Рассмотрим пример для числа (в двоичной системе):
Где — это цифры числа, а — веса разрядов.
Шестнадцатеричная система (Hex)
Читать нули и единицы человеку невозможно. Поэтому в реверс-инжиниринге используют шестнадцатеричную систему (Hex). Она компактнее.
В ней используются цифры от 0 до 9 и буквы от A до F, где A=10, B=11 ... F=15. Один байт (8 бит) всегда записывается двумя символами Hex. Например, байт 11111111 в двоичной системе — это FF в Hex и 255 в десятичной.
Архитектура фон Неймана и работа процессора
Почти все современные компьютеры построены по архитектуре фон Неймана. Её суть проста: программа и данные хранятся в одной и той же памяти.
Это критически важно для хакера. Если программа и данные лежат в одном месте, значит, мы можем обмануть компьютер и заставить его прочитать наши данные (например, вредоносный код) как программу и исполнить их. Это основа многих эксплойтов.
Основные компоненты:
- CPU (Центральный процессор): Мозг. Исполняет инструкции.
- RAM (Оперативная память): Временное хранилище для запущенных программ и данных.
- Регистры: Сверхбыстрая память прямо внутри процессора.
Память процесса: Стек и Куча
Когда вы запускаете программу, операционная система выделяет ей кусок памяти. Этот кусок делится на важные зоны. Понимание этих зон — ключ к поиску уязвимостей (например, переполнения буфера).
1. Стек (Stack)
Это область памяти для временных данных функций. Стек работает по принципу LIFO (Last In, First Out — «последним пришел, первым ушел»). Представьте стопку тарелок: вы можете взять только верхнюю тарелку.
В стеке хранятся:
- Локальные переменные функций.
- Адреса возврата (куда программе вернуться после выполнения функции).
Именно здесь происходит классическая атака Stack Buffer Overflow. Если записать в переменную больше данных, чем она вмещает, можно «перелить» данные и перезаписать адрес возврата, заставив программу прыгнуть туда, куда нужно хакеру.
2. Куча (Heap)
Это память для динамических данных, размер которых заранее неизвестен (например, загружаемые пользователем файлы). Кучей управлять сложнее, и ошибки в работе с ней (Use-After-Free, Heap Overflow) тоже ведут к взлому.
Основы Ассемблера и Регистры
Когда мы занимаемся реверс-инжинирингом, у нас часто нет исходного кода. У нас есть только готовый файл .exe. Чтобы понять, что он делает, мы используем дизассемблер, который превращает машинный код в Ассемблер.
Регистры процессора
Процессор не умеет работать с данными в оперативной памяти напрямую. Сначала он должен загрузить их в регистры — свои «карманы».
Основные регистры архитектуры x86 (32-бит) и x64 (64-бит):
- EAX / RAX: Аккумулятор. Используется для арифметики и возврата значений функций.
- EBX / RBX: Базовый регистр.
- ECX / RCX: Счетчик (для циклов).
- ESP / RSP: Указатель на вершину стека (Stack Pointer). Самый важный регистр для отслеживания стека.
- EIP / RIP: Указатель инструкций (Instruction Pointer). Хранит адрес следующей команды, которую выполнит процессор.
Важно: Если вы контролируете регистр EIP/RIP, вы контролируете весь компьютер. Вы решаете, какой код выполнится следующим.
Примеры команд Ассемблера
Ассемблер выглядит страшно только на первый взгляд. Вот базовые команды:
MOV EAX, 5 ; Положить число 5 в регистр EAX
ADD EAX, 3 ; Добавить 3 к тому, что лежит в EAX (теперь там 8)
SUB EAX, 1 ; Вычесть 1 из EAX (теперь там 7)
PUSH EAX ; Положить значение EAX на вершину Стека
POP EBX ; Забрать значение с вершины Стека и положить в EBX
JMP 0x00401000 ; Безусловный прыжок (Jump) по адресу памяти
Жизненный цикл программы: Компиляция
Чтобы понять, как «разломать» программу обратно (декомпиляция), нужно знать, как она собиралась.
- Препроцессинг: Обработка текста кода (подключение библиотек).
- Компиляция: Перевод кода C++ в Ассемблер.
- Ассемблирование: Перевод Ассемблера в машинный код (объектный файл).
- Линковка (Компоновка): Сборка всех кусков кода и библиотек в один исполняемый файл (.exe или ELF).
Реверс-инжиниринг — это попытка пройти этот путь в обратном направлении: от машинного кода к пониманию логики.
Заключение
Сегодня мы заложили фундамент. Мы узнали, что программы — это наборы инструкций в памяти, что память делится на Стек и Кучу, и что процессор слепо выполняет то, на что указывает регистр EIP/RIP.
В следующих статьях мы начнем применять эти знания: настроим лабораторию для хакинга и попробуем проанализировать нашу первую программу.
Помните: любой софт написан людьми, а людям свойственно ошибаться. Ваша задача — найти эти ошибки.