С ростом популярности дистрибутивов Linux, ориентированных на безопасность, таких как Parrot OS и Kali Linux, в комплекте с их наборами наступательных инструментов безопасности и отсутствием недостатка руководств на YouTube и HackForums по их использованию, кажется, что каждый может быть «хакером». " Настоящее время. Не требуется никаких навыков или даже знаний, чтобы запустить такой инструмент, как Wifite или Fern, чтобы попытаться взломать плохо защищенную сеть Wi-Fi, но если вы зависите от нескольких инструментов, написанных людьми более осведомленными, чем вы, вы не добьетесь успеха в реальных проектах красной команды с реальной IDS вместо маршрутизатора, оставленного с настройками по умолчанию невежественным низкооплачиваемым сотрудником.

Взлом — это искусство, практика копать глубже, стремиться понять вещи до тех пор, пока вы не узнаете их лучше, чем все остальные. Лучшие не зависят от учебных пособий и готовых инструментов; лучший инструмент тот, который вы сделали сами. Эти два принципа привели меня к созданию Surgeon, Binary Pwning Tool (ранее CaveMan; ссылка для скачивания в конце). Это заставило меня понимать двоичные исполняемые файлы до такой степени, что я мог их декодировать и выполнять вычисления в регистрах вручную, если это необходимо, и в то же время позволял мне иметь полный контроль над тем, как редактировать исполняемый файл. Если вы когда-нибудь задавались вопросом, как заблокировать исполняемый файл, либо теоретически, либо готовы замарать руки и потратить бог знает сколько времени на отладку и обеспечение того, чтобы вас невозможно было обнаружить, то это для вас.

Эта статья не является материалом для начинающих и требует понимания ассемблера и машинного кода.

Формат ELF (исполняемый и связываемый файл)

ELF — это стандарт для исполняемых двоичных файлов, библиотек и дампов ядра, а также один из наиболее распространенных форматов файлов, используемых в системах Linux. В этом руководстве основное внимание уделяется файлам ELF, хотя Surgeon также может работать с файлами PE (Portable Executable). Каждый файл ELF начинается с так называемого заголовка ELF, первые четыре байта которого всегда равны 0x7f 0x45 0x4c 0x46. Это Magic Number, 0x7f, за которым следуют значения ASCII для букв ELF. Это магическое число сообщает машинам, которые могут использовать файлы ELF, что этот файл является файлом ELF. Заголовок ELF имеет размер 50 или 62 байта, в зависимости от того, является ли программа 32-битной или 64-битной; первые 24 байта всегда указывают одну и ту же информацию, включая архитектуру. Байт, следующий за магическим числом, обозначает архитектуру, байт, следующий за порядком байтов, и т. д.

Заголовок ELF содержит всю необходимую информацию, необходимую для чтения и выполнения файла, включая макет исполняемого файла. Здесь вы можете найти фантастическое подробное руководство от автора Medium Джеймса Фишера. Заголовок ELF указывает на смещения таблицы заголовков программы и таблицы заголовков разделов, сообщает нам размер заголовков, количество записей в них и, самое главное, точку входа. Поскольку все, и данные, и инструкции, состоят из байтов, мы не можем просто начать выполнение байтов в начале файла; точка входа указывает на часть программы, где хранятся фактические исполняемые инструкции, и должно начаться выполнение инструкции.

Помимо заголовка ELF, другой частью этого руководства, представляющей интерес для нас, является таблица заголовков разделов. Эта таблица в основном представляет собой оглавление файла ELF, указывающее на различные разделы, их длины, их флаги и т. д. Это говорит нам, где найти разделы .data, .bss, .comment и .text, а также как PLT (таблица связи процедур) и GOT (таблица глобальных смещений) и другие.

Поведение Surgeon по умолчанию при запуске без аргументов или только с аргументом -f ‹file› состоит в том, чтобы анализировать для нас заголовок ELF и таблицу заголовков разделов и возвращать соответствующую информацию. (Он также сканирует пещеры кода предопределенным образом, что будет обсуждаться ниже).

Если мы посмотрим выше, мы увидим проанализированный заголовок и часть таблицы заголовков разделов. Исполняемыми являются только разделы, отмеченные знаком «X» под флажками; попытка выполнить код в разделе, помеченном как неисполняемый, приведет к тому, что программа выдаст SIGSEGV и может вызвать IDS. Мы также можем видеть, что точка входа находится по адресу 0x00000440, что соответствует смещению начала раздела .text, как и следовало ожидать, поскольку он содержит скомпилированные инструкции.

Кодовые пещеры и черные ходы

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

  1. Найдите место для хранения кода, который мы хотим выполнить.
  2. Перезапишите точку входа, чтобы она указывала на наши дополнения.

Распространенным и простым, но неаккуратным методом было бы добавить наш собственный раздел, пометить его как исполняемый, добавить запись в таблицу заголовков разделов и указать запись туда. Это, однако, создает проблему: мы увеличиваем размер исполняемого файла. (Конечно, если кто-то хочет оставить точку входа нетронутой, указывая на нужное место, вы можете перезаписать первые инструкции с помощью JMP в свою полезную нагрузку, выполнить ее, а затем запустить перезаписанные инструкции, прежде чем вернуться к исходному потоку выполнения. )

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

Скрытность — это название игры здесь, есть способ получше, и он называется пещерой кода. Пещеры кода — это, по сути, длинная непрерывная строка нулевых байтов. Иногда компиляторы добавляют пещеры кода для целей выравнивания между функциями или их можно использовать для выделения памяти для сгенерированного во время выполнения или самомодифицирующегося кода. Какой бы ни была причина, мы можем использовать их для хранения нашего кода, не перезаписывая существующий код и не увеличивая размер файла.

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

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

  1. Вы можете позволить программе продолжить работу, и, скорее всего, произойдет сбой или segfault. Это может привести к срабатыванию антивирусного программного обеспечения и предупреждению пользователя и, возможно, администратора о ваших действиях.
  2. Сделайте чистый выход и сделайте системный вызов exit(). Хотя это и немного лучше, это лишь незначительно, потому что тогда наша pwned-программа все равно не будет вести себя так, как должна, и запускать свой собственный код. Это также подскажет пользователю, что что-то не так.
  3. Сделайте прыжок в потоке выполнения назад к исходной точке входа и запустите как обычно. Это идеальное поведение, которое может оставить наше присутствие незамеченным, если все сделано правильно.

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

Редактировать что-либо в скомпилированной программе опасно, и малейшие изменения могут иметь каскадный эффект, радикально меняющий поведение программы. Здесь легко выстрелить себе в ногу.

Готовый? Давайте попробуем.

Простой пример

В следующем примере я решил использовать простую программу, которую я написал, которая может действовать как CrackMe и как простое доказательство концепции, чтобы продемонстрировать, как вызвать переполнение буфера и перехватить поток выполнения. Поскольку это скомпилированный исполняемый файл ELF, он также отлично подойдет для демонстрации концепции, описанной выше. Наша миссия проста: найти подходящее место для вставки кода, изменить точку входа программы на это место и вставить какой-нибудь машинный код, который мы сможем выполнить. Машинный код, который мы вставим, будет делать только одно: переходить к инструкциям в исходной точке входа, чтобы программа могла продолжить работу в обычном режиме. Код ниже, если вы хотите следовать:

#include<stdio.h>
#include<string.h>
void benderdontcare(char* input){
 char name[181];
 strcpy(name, input);
 printf(“%s? Yeah, I don’t care, meatbag.\n”, name);
}
int main(int argc, char** argv){
 if(argc != 2){
 printf(“That’s now how you interact with me, Bender. You need to say something! And if you put spaces in it, put the whole thing in quotes! What am I, an AI language parser? (I’m only 40 percent language parser!)\n”);}
 else{
 int bender = 1;
 printf(“I’m Bender, the Magnificent body-stealing robot!\n”);
 if(bender==2){
 printf(“But Inspector 7 said I was perfect!\n”);
 }
 benderdontcare(argv[1]); 
 printf(“Anything less than immortality is a complete waste of time.\n”);
 return 0;}
}

Действия Surgeon по умолчанию ищут любые пещеры кода во всех секциях (аргумент -A) нулевых байтов (0x00) длиной не менее 64 байт, во всех секциях, а не только в исполняемых. Фрагмент вывода по умолчанию был показан выше, но Surgeon не смог найти подходящие кодовые пещеры. Поскольку нам понадобится всего несколько байтов, мы можем указать меньшее количество последовательных нулей, чтобы найти хорошую пещеру.

Чтобы сузить рабочие результаты, мы будем искать только исполняемые разделы, и нам потребуется всего 8 байт, чтобы найти пещеру. И вот, один из них раскрывается в Таблице процедурных связей:

Хорошо, у нас есть место, чтобы поместить наши инструкции! Поскольку мы собираемся выполнять только JMP, мы можем просто посмотреть синтаксис здесь. Нам повезло, потому что смещение пещеры кода находится на 0x3e9, а исходная точка входа — на 0x440, что означает, что они находятся всего в 0x57 (десятичное число 87) байтов от нашей цели! Это позволяет нам выполнить короткий переход, требующий наименьшего количества байтов для наших кодов операций, 2 байта.

Согласно приведенному выше руководству по инструкциям x86, код операции для короткого перехода — 0xEB, за которым следует значение байта со знаком (-128/+127) для позиции перехода. Как правило, всякий раз, когда я вставляю свой собственный код в пещеру кода, я обязательно оставляю первый байт нулевым. Нулевые байты часто используются в качестве разделителей, особенно когда вы должны использовать раздел, который ранее был данными, а не инструкциями и не исполняемым. Хотя это и не всегда необходимо, следует последовательно применять передовой опыт.

В результате мы начнем вставлять наш код с адреса 0x3ea вместо 0x3e9. Первый байт 0xEB указывает процессору выполнить короткий переход. Байт после находится по смещению 0x3eb. Следующий байт обычно располагался бы по смещению 0x3ec, что составляет 0x54 (десятичные 84) байта от нашей цели, исходной точки входа, 0x440. Итак, нам нужно перепрыгнуть на 84 байта вперед. Затем нам нужно вставить коды операций 0xeb 0x54 со смещением 0x3ea, например:

Это сработало? Мы все еще можем запустить исполняемый файл без заметной разницы в производительности с того места, где мы его видим, поэтому давайте запустим GDB и пройдемся по инструкциям! Используя команду «info file», мы видим, что точка входа находится по адресу 0x3ea. Однако, когда файл действительно запускается, он будет загружен в другом месте. Указание GDB поместить точку останова в 0x3ea приведет к тому, что он не установит точку останова в начале выполнения. Чтобы определить, где нам нужно установить точку останова, нам нужно использовать команду «starti». У GDB есть полезная функция starti, позволяющая нам перейти к первой инструкции, даже до _start или main, и остановиться в точке останова. Мой останавливается на «0xf7fd6c70 в ?? () из /lib/ld-linux.so.2'. Использование здесь команды «info file» показывает нам нашу истинную точку входа, на которой может быть установлена ​​точка останова: «Точка входа: 0x565553ea».

Итак, установим точку останова по адресу 0x565553ea и запустим программу. Это остановит выполнение прямо на введенном нами шелл-коде, что позволит нам отслеживать коды операций по мере его прохождения. Команда «disas/r 0x565553ea, 0x565553ec» заставит GDB вернуть нам коды операций и ассемблерный код, которые мы ищем, вместо того, чтобы ворчать о том, что по указанному адресу нет никакой функции. GDB отражает именно те изменения, которые мы ищем: инструкция JMP для 0x56555440, ‹_start›, наша исходная точка входа.

Здесь мы видим, что мы успешно добавили наш собственный шелл-код в завершенный, скомпилированный двоичный файл внутри доступной пещеры кода. Этот шелл-код выполняется точно так, как ожидалось, прежде чем вернуть управление исходной точке входа, что позволяет нашей программе работать так, как было задумано изначально, не предупреждая конечного пользователя о наличии нашего кода. Нам не нужно было изменять двоичный файл, добавляя наш код таким образом, чтобы изменить размер файла, и нам не нужно было изменять существующие разделы, которые не были помечены как исполняемые. Это идеальное доказательство концепции для нашего файла pwned.

Surgeon находится в стадии разработки с добавлением поддержки файлов PE Windows. Вы можете скачать его бесплатно на github здесь. Вы и только вы несете ответственность за то, что вы с ним делаете. Ни в коем случае эта статья не должна толковаться как одобрение какой-либо незаконной деятельности; это предназначено для расширения возможностей членов Красной команды, которые, возможно, не знали, как это сделать, и членов Синей команды, которым необходимо понять, как это работает, чтобы защититься от него. С учетом сказанного, счастливого взлома!