Содержание статьи
warning
Все продемонстрированные методы приведены в ознакомительных и учебных целях. Ни редакция, ни автор статьи не несут ответственности за практическое использование этих техник.
ARM (Advanced RISC Machine) — это 32-битная архитектура, разработанная компанией Acorn Computers Ltd в 1983 году. Процессоры ARM имеют 32-разрядные регистры, но из них только 16 доступны программисту. Этими 16 так называемыми видимыми регистрами можно манипулировать с помощью набора инструкций.
Они делятся на регистры общего назначения и специализированные. К первым относятся регистры от R0
до R10
, к специализированным — следующие:
-
R11
— frame pointer (FP); -
R12
— intra-procedure (IP); -
R13
— указатель на стек (stack pointer, SP); -
R14
— связующий регистр (link register, LR); -
R15
— счетчик (program counter, PC). Грубо говоря, EIP/RIP.
Об ARM можно почитать на сайте, посвященном этим процессорам, или в Википедии. Также имеются публикации об AArch64. Про ассемблер под ARM советую следующие книги:
- Ассемблер для Raspberry Pi. Практическое руководство;
- Эксплуатация систем ARM Linux;
- Blue Fox: Arm Assembly Internals and Reverse Engineering.
Создание виртуальной машины в QEMU
QEMU — это эмулятор различных процессорных архитектур. Как правило, он используется для эмуляции целого компьютера (то есть для запуска виртуальной машины), однако для отладки одной программы это необязательно. В Linux можно использовать эмуляцию QEMU User-Mode, именно этот способ и будет рассмотрен первым.
Наша конечная цель — запускать скомпилированные программы для ARM 64-bit. Для начала необходимо установить сам пакет эмулятора:
sudo apt-get update
sudo apt-get install qemu qemu-user qemu-user-static
Для AArch64 устанавливаем следующие компоненты:
sudo apt install gcc-arm-linux-gnueabihf binutils-arm-linux-gnueabihf binutils-arm-linux-gnueabihf-dbg
sudo apt install gcc-aarch64-linux-gnu binutils-aarch64-linux-gnu binutils-aarch64-linux-gnu-dbg
Чтобы эмулировать процессор типа ARM64, необходимо создать две директории — deb11_inst
и deb11_start
: mkdir
. Затем перейти в deb11_inst
и скачать два файла — installer-linux
и installer-initrd.
.
Для этого используем следующие команды:
wget -O installer-linux http://http.us.debian.org/debian/dists/bullseye/main/installer-arm64/current/images/netboot/debian-installer/arm64/linux
wget -O installer-initrd.gz http://http.us.debian.org/debian/dists/bullseye/main/installer-arm64/current/images/netboot/debian-installer/arm64/initrd.gz
Дальше создаем диск для установки на него системы:
qemu-img create -f raw hda.img 20G
Создаем файл instDebARM64.
, в него необходимо записать следующий скрипт:
#!/bin/bashqemu-system-aarch64 -M virt -m 2G -cpu cortex-a53 -smp 2 \-kernel installer-linux \-initrd installer-initrd.gz \-drive if=none,file=hda.img,format=raw,id=hd \-device virtio-blk-pci,drive=hd \-netdev user,id=mynet \-device virtio-net-pci,netdev=mynet \-display gtk,gl=on \-device virtio-gpu-pci \-no-reboot \-device qemu-xhci -device usb-kbd -device usb-tablet
Здесь
-
qemu-system-aarch64
— эмуляция полной системы для архитектуры; -
-M
— выбор эмулируемой машины; -
-m
— объем ОЗУ; -
-cpu
— тип эмулируемого процессора; -
-smp
— количество виртуальных ядер ЦП и их распределение по сокетам; -
-kernel
— для использования указанного образа ядра Linux; -
-initrd
— для загрузки Linux; -
netdev/
иdevice drive
— описание сетевой карты и виртуальных дисков; -
if
— опция указывает, через интерфейс какого типа подключен диск; -
file
— определяет, какой образ использовать для какого диска; -
format
— указывает явным образ формат дисков, не использовать автоопределение; -
-display
— выбор типа отображения, доступноsdl
,curses
,gtk
,none
,vga
; -
-no-reboot
— отмена перезагрузки.
Сохраняем и запускаем скрипт. Начнется классическая установка Debian 11. На первом экране надо выбрать английский язык в качестве основного.
Далее необходимо выбрать UNITED STATES. Ближе к завершению установки появится ошибка.
Выбираем Continue и дожидаемся окончания установки Debian. Переносим образ hda.
с установленной системой в директорию deb11_start
. Затем создаем файл debARM64.
, в который поместим следующий скрипт:
#!/bin/bashqemu-system-aarch64 -M virt -m 3G -cpu cortex-a53 -smp 2 \-kernel vmlinuz-5.10.0-8-arm64 \-initrd initrd.img-5.10.0-8-arm64 \-append 'root=/dev/vda2' \-drive if=none,file=hda.img,format=raw,id=hd \-device virtio-blk-pci,drive=hd \-netdev user,id=mynet \-device virtio-net-pci,netdev=mynet \-display gtk,gl=on \-device virtio-gpu \-no-reboot \-device qemu-xhci -device usb-kbd -device usb-tablet\
Щелкаем на созданном диске hda.
правой клавишей мыши и монтируем его: «Открыть с помощью → Монтирование дискового образа». На смонтированном диске нас интересуют два файла: initrd.
и vmlinuz-5.
(ну а в общем случае initrd.
и vmlinuz-xxxxxxx-arm64
). Версии системы должны быть одинаковыми! Запускаем файл debARM64.
:
./debARM64
Для настройки сети в скрипт debARM64.
нужно добавить строчку -net
. Эта строчка создаст еще и SSH-подключение.
Docker
Есть и альтернативный вариант: эмуляция Raspberry Pi с использованием Docker. Для этого нужно установить Docker:
sudo apt-get install docker.io
Затем скачать соответствующий образ:
docker pull lukechilds/dockerpi
Как только все прогрузится, вводим команду
docker run -it --name имя_для_контейнера -v $HOME/.dockerpi:/sdcard lukechilds/dockerpi
В качестве имя_для_контейнера
можно выбрать любое, я назову свой ap_security
. После этого начнется распаковка и запуск Raspberry Pi.
Итогом успешного запуска будет такое окно.
Учетные данные для входа в систему стандартны: pi:
. Собственно, всё. Теперь в нашей виртуальной лаборатории есть Raspberry Pi. Чтобы выключить устройство, используй команду sudo
, а чтобы запустить — docker
, где имя_для_контейнера
— выбранное тобой имя контейнера.
Установка GDB и PEDA/GEF
Установка GDB и плагина PEDA довольно проста. Для GDB используем команду
sudo apt install gdb
Для установки PEDA команды такие:
git clone https://github.com/longld/peda.git ~/peda
echo "source ~/peda/peda.py" >> ~/.gdbinit
GEF установим командой
bash -c "$(curl -fsSL https://gef.blah.cat/sh)"
Отладку я буду выполнять в основном в GEF.
Пробуем GDB и пишем первую программу
Чтобы написать программу на ассемблере, потребуются три инструмента:
- текстовый редактор — nano;
- программа для создания объектного файла — as;
- программа для динамической привязки — ld.
Текстовый редактор — это вкусовщина. Многие пишут в Vim, мне удобнее в nano, поэтому код я буду писать там. Программа as создает объектный файл, а ld выполняет динамическую привязку. Работать с этими программами нужно следующим образом:
- Пишем команду
as
, которая создает объектный файл с названиемsource. asm -o source. o source.
.o - Связываем объектный файл и преобразуем его в исполняемый с помощью команды
ld
.source. o -o source. bin
У любого файла с кодом на языке ассемблера должна быть точка, с которой начинается программа. Она выглядит так:
_start:
Эта точка определяется как глобальное имя для всей программы. Каждый оператор имеет следующий синтаксис:
<обозначение:> <инструкция> @ комментарий
Первая программа
В первой программе, по классике, реализован вывод приветственной строчки — H3ll0, ][
:
.global _start
_start:
mov r7, #4 @ номер системного вызова mov r0, #1 @ вывод - stdout mov r2, #13 @ длина строки ldr r1, =string @ строка находится на метке string
swi 0 @ системный вызов
mov r7, #1 @ выход swi 0
.data
string:
.ascii "H3ll0, ][akep!\n"
Здесь
-
r7
— номер процедуры; -
r0
определяет поток (stdin/
);stdout/ stderr -
r2
— количество выводимых символов; -
r1
хранит адрес строки.
Все это схоже с ассемблером для i386. В ARM регистры для взаимодействия такие:
-
r7
— номер системного вызова; -
r0
— аргумент 1; -
r1
— аргумент 2; -
r2
— аргумент 3; -
r3
— аргумент 4; -
r4
— аргумент 5; -
r5
— аргумент 6; -
r0
— возвращаемое значение или код ошибки.
Информация обо всех системных вызовах есть в справке программы J0llyTr0LLz. Там же описано, что именно должно лежать в регистрах. Скачать программу и узнать, как с ней работать, можно на GitHub.
Компилируем приложение и запускаем.
as -g proga1.asm -o proga1.o
ld proga1.o -o proga1.bin
Здесь -g
— ключ для включения отладочной информации. После запуска увидим следующее:
$ file proga1.bin
proga1.bin: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), statically linked, not stripped
./proga1.bin
H3ll0, ][akep!
Попробуем продебажить это приложение.
Первый опыт в отладке
Запускаем GDB и загружаем в него бинарный файл, после чего переходим к разделу start
:
$ gdb
gef➤ file proga1.bin
Reading symbols from proga1.bin...done.
gef➤ disassemble _start
Dump of assembler code for function _start:
0x00010074 <+0>: mov r7, #4
0x00010078 <+4>: mov r0, #1
0x0001007c <+8>: mov r2, #19
0x00010080 <+12>: ldr r1, [pc, #8] ; 0x10090 <_start+28>
0x00010084 <+16>: svc 0x00000000
0x00010088 <+20>: mov r7, #1
0x0001008c <+24>: svc 0x00000000
0x00010090 <+28>: muleq r2, r4, r0
End of assembler dump.
Поставим точку останова на первой инструкции и запустим программу:
b *_start
r
Окно отладки GDB-GEF выглядит так.
Шпаргалку по GDB-командам можно найти в документации, опубликованной на сайте Darkdust.
Продолжение доступно только участникам
Вариант 1. Присоединись к сообществу «Xakep.ru», чтобы читать все материалы на сайте
Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», позволит скачивать выпуски в PDF, отключит рекламу на сайте и увеличит личную накопительную скидку! Подробнее
Вариант 2. Открой один материал
Заинтересовала статья, но нет возможности стать членом клуба «Xakep.ru»? Тогда этот вариант для тебя! Обрати внимание: этот способ подходит только для статей, опубликованных более двух месяцев назад.
Я уже участник «Xakep.ru»