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 комментария

Подпишитесь на ][, чтобы участвовать в обсуждении

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

Check Also

Кеш-атаки по сторонним каналам. Что произошло в области утечек на аппаратном уровне за последние два года

Несмотря на то что до 2016 года существовало лишь несколько публикаций о кеш-атаках на сма…