Шелл-код для ARM. Пишем на ассемблере свой реверс-шелл для устройств на ARM
Многие знают, как создать шелл‑код для обычных машин с архитектурой x86. Про это написана куча мануалов, статей и книг, но многие современные девайсы, включая смартфоны и роутеры, используют процессоры ARM. Я собрал всю необходимую информацию для подготовки стенда, написания и инъекции шелл‑кода для устройств на ARM.
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 deb11_start. Затем перейти в deb11_inst и скачать два файла — installer-linux и installer-initrd.gz.
Для этого используем следующие команды:
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.sh, в него необходимо записать следующий скрипт:
#!/bin/bash
qemu-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.img с установленной системой в директорию deb11_start. Затем создаем файл debARM64.sh, в который поместим следующий скрипт:
#!/bin/bash
qemu-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.img правой клавишей мыши и монтируем его: «Открыть с помощью –> Монтирование дискового образа». На смонтированном диске нас интересуют два файла: initrd.img-5.10.0-20-arm64 и vmlinuz-5.10.0-20-arm64 (ну а в общем случае initrd.img-xxxxxxx-arm64 и vmlinuz-xxxxxxx-arm64). Версии системы должны быть одинаковыми! Запускаем файл debARM64.sh:
./debARM64
Для настройки сети в скрипт debARM64.sh нужно добавить строчку -net user,hostfwd=tcp::10022-:22 . Эта строчка создаст еще и 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. Собственно, всё. Теперь в нашей виртуальной лаборатории есть Raspberry Pi. Чтобы выключить устройство, используй команду sudo poweroff, а чтобы запустить — docker start -ai имя_для_контейнера, где имя_для_контейнера — выбранное тобой имя контейнера.
Установка 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, ][akep!:
.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.
