IoT — самый настоящий тренд последнего времени. Почти везде в нем используется ядро Linux. Однако статей по вирусописательству и шелл-кодингу под эту платформу сравнительно мало. Думаешь, писать шелл-код под Linux — только для избранных? Давай выясним, так ли это!

 

Что нужно для работы?

Для компиляции шелл-кода нам понадобится компилятор и линковщик. Мы будем использовать nasm и ld. Для проверки работы шелл-кода мы напишем небольшую программку на С. Для ее компиляции нам понадобится gcc. Для некоторых проверок будет нужен rasm2 (часть фреймворка radare2). Для написания вспомогательных функций мы будем использовать Python.

 

Что нового в x64?

x64 является расширением архитектуры IA-32. Основная отличительная ее особенность — поддержка 64-битных регистров общего назначения, 64-битных арифметических и логических операций над целыми числами и 64-битных виртуальных адресов.

Если говорить более конкретно, то все 32-битные регистры общего назначения сохраняются, добавляются их расширенные версии (rax, rbx, rcx, rdx, rsi, rdi, rbp, rsp) и несколько новых регистров общего назначения (r8, r9, r10, r11, r12, r13, r14, r15).

Появляется новое соглашение о вызовах (в отличие от архитектуры x86, оно только одно). Согласно ему, при вызове функции каждый регистр используется для определенных целей, а именно:

  • первые четыре целочисленных аргумента функции передаются через регистры rcx, rdx, r8 и r9 и через регистры xmm0xmm3 для типов с плавающей точкой;
  • остальные параметры передаются через стек;
  • для параметров, передаваемых через регистры, все равно резервируется место в стеке;
  • результат работы функции возвращается через регистр rax для целочисленных типов или через регистр xmm0 для типов с плавающей точкой;
  • rbp содержит указатель на базу стека, то есть место (адрес), где начинается стек;
  • rsp содержит указатель на вершину стека, то есть на место (адрес), куда будет помещено новое значение;
  • rsi, rdi используются в syscall.

Немного о стеке: так как адреса теперь 64-битные, значения в стеке могут иметь размер 8 байт.

 

Syscall. Что? Как? Зачем?

Syscall — это способ, посредством которого user-mode взаимодействует с ядром в Linux. Он используется для различных задач: операции ввода-вывода, запись и чтение файлов, открытие и закрытие программ, работа с памятью и сетью и так далее. Для того чтобы выполнить syscall, необходимо:

  • загрузить соответствующий номер функции в регистр rax;
  • загрузить входные параметры в остальные регистры;
  • вызвать прерывание под номером 0x80 (начиная с версии ядра 2.6 это делается через вызов syscall).

В отличие от Windows, где нужно еще найти адрес необходимой функции, здесь все довольно просто и лаконично.

Номера нужных syscall-функций можно найти, например, здесь.

 

execve()

Если мы посмотрим на готовые шелл-коды, то многие из них используют функцию execve().

execve() имеет следующий прототип:

int execve(const char *filename, char *const argv[], char *const envp[]);

Она вызывает программу filename. Программа filename может быть либо исполняемым бинарником, либо скриптом, который начинается со строки #! interpreter [optional-arg].

argv[] является указателем на массив, по сути, это тот самый argv[], который мы видим, например, в C или Python.

envp[] — указатель на массив, описывающий окружение. В нашем случае не используется, будет иметь значение null.

 

Основные требования к шелл-коду

Существует такое понятие, как position-independent code. Это код, который будет выполняться независимо от того, по какому адресу он загружен. Чтобы наш шелл-код мог выполняться в любом месте программы, он должен быть позиционно-независимым.

Чаще всего шелл-код загружается функциями вроде strcpy(). Подобные функции используют байты 0x00, 0x0A, 0x0D как разделители (зависит от платформы и функции). Поэтому лучше такие значения не использовать. В противном случае функция может скопировать шелл-код не полностью. Рассмотрим следующий пример:

$ rasm2 -a x86 -b 64 'push  0x00'
6a00

Как видно, код push 0x00 скомпилируется в следующие байты 6a 00. Если бы мы использовали такой код, наш шелл-код бы не сработал. Функция скопировала бы все, что находится до байта со значением 0x00.

В шелл-коде нельзя использовать «захардкоженные» адреса, потому что мы заранее эти самые адреса не знаем. По этой причине все строки в шелл-коде получаются динамически и хранятся в стеке.

Вот вроде бы и все.

 

Just do it!

Если ты дочитал до этого места, то уже должна сложиться картина, как будет работать наш шелл-код.

Первым делом необходимо подготовить параметры для функции execve() и затем правильно расположить их на стеке. Функция будет выглядеть следующим образом:

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

Вариант 1. Оформи подписку на «Хакер», чтобы читать все материалы на сайте

Подписка позволит тебе в течение указанного срока читать ВСЕ платные материалы сайта. Мы принимаем оплату банковскими картами, электронными деньгами и переводами со счетов мобильных операторов. Подробнее о подписке

Вариант 2. Купи один материал

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


2 комментария

  1. Riketta

    18.08.2016 at 13:33

    Еще можно эффективно собирать бинарники с шеллами через ragg2 🙂
    Это компонент radare2, не зря же его предлагается ставить в статье.
    На примере шелла из статьи:
    ragg2 -a x86 -b 64 -k linux -f elf -o shell -B 4831d25248b82f62696e2f2f7368504889e752574889e64831c0b03b0f05
    После этой команды вводим ./shell, и если вы увидели заветный $ в начале новой строки (а может и # О_о), то все гуд.

Оставить мнение

Check Also

Беспощадный буст. Как ускорить многопоточный код на C++

Уже много лет твой компьютер умеет выполнять код любимого ПО параллельно на всех своих ядр…