Мно­гие зна­ют, как соз­дать шелл‑код для обыч­ных машин с архи­тек­турой 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 советую сле­дующие кни­ги:

 

Создание виртуальной машины в 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 выпол­няет динами­чес­кую при­вяз­ку. Работать с эти­ми прог­рамма­ми нуж­но сле­дующим обра­зом:

  1. Пи­шем коман­ду as source.asm -o source.o, которая соз­дает объ­ектный файл с наз­вани­ем source.o.
  2. Свя­зыва­ем объ­ектный файл и пре­обра­зуем его в исполня­емый с помощью коман­ды 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.

Продолжение доступно только участникам

Вариант 1. Присоединись к сообществу «Xakep.ru», чтобы читать все материалы на сайте

Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», позволит скачивать выпуски в PDF, отключит рекламу на сайте и увеличит личную накопительную скидку! Подробнее

Вариант 2. Открой один материал

Заинтересовала статья, но нет возможности стать членом клуба «Xakep.ru»? Тогда этот вариант для тебя! Обрати внимание: этот способ подходит только для статей, опубликованных более двух месяцев назад.


  • Подпишись на наc в Telegram!

    Только важные новости и лучшие статьи

    Подписаться

  • Подписаться
    Уведомить о
    0 комментариев
    Межтекстовые Отзывы
    Посмотреть все комментарии