Что такое шелл код
Кот или шеллКод?
Может ли обычная картинка нести угрозу и стоит ли обращать внимание на факт загрузки изображений при разборе инцидентов информационной безопасности? На этот и другие вопросы мы ответим в данном тексте на примере работы инструмента DKMC (Don’t Kill My Cat).
В чем суть?
Посмотрите на изображение ниже
Видите ли вы в нем что-то странное?
Я не вижу ничего необычного. Данное изображение загружено на сайт в формате jpeg, но его оригинал хранится в формате bmp. Если посмотреть на исходный bmp-файл в HEX-редакторе, то никаких бросающихся в глаза странностей мы тоже не увидим.
Однако это изображение содержит обфусцированный шеллкод по адресу 0x00200A04. В то же время мы не видим никаких странных пикселей на изображении. Дело в том, что в BMP заголовке высота изображения была искусственно уменьшена. В полном размере изображение выглядело бы так. Обратите внимание на правый верхний угол.
Высота оригинального изображения на 5 пикселей больше, чем вредоносного, но для человека это обычно не заметно.
Инъекция возможна из-за того, что байты, указывающие на тип файла, с которых и начинается файл, BM в ASCII, в шестнадцатеричном виде — 42 4D, при конвертации в инструкции ассемблера не приводят к ошибке выполнения, а дальнейшие 8 байт заголовка никак не влияют на интерпретацию изображения. Так что, можно заполнить эти 8 байт любыми инструкциями ассемблера, например записать в них jmp-инструкцию, которая укажет на шелл-код, хранимый в изображении, т.е. на 0x00200A04.
Далее нужно лишь как-то выполнить код, хранимый в изображении, вместо его просмотра в графическом виде.
Для этого может использоваться, например, набор PowerShell команд.
Все описанные мной действия уже автоматизированы и собраны воедино в инструменте DKMC, который мы рассмотрим ниже.
Это правда работает?
Инструмент доступен на GitHub и не требует установки. Там же, в репозитории, есть презентация, подробно описывающая принцип работы DKMC.
Нам доступно несколько действий
Для начала нужно создать шеллкод. Для этого можно воспользоваться msfvenom
Будет сгенерирован бек-коннект шелл на хост 192.168.1.3, порт 4444, в бинарном виде.
Далее используется опция меню sc, чтобы преобразовать код в HEX формат для его использования вдальнейшем
Далее выбирается изображение в формате BMP для инъекции шеллкода при помощи команды gen
Здесь сказано, что шелл-код был обфусцирован, указаны его итоговый размер, видоизмененный BMP заголовок с jump инструкцией и сказано, что высота сократилась с 700 пикселей до 695.
Далее при помощи команды ps можно сгенерировать powershell команду для загрузки данного изображения с веб-сервера и последующего его выполнения
и при помощи команды web можно тут же запустить веб-сервер для предоставления этого изображения
Я запущу сниффер Wireshark и посмотрю, что происходит в сети при запуске Powershell скрипта на стороне жертвы
Жертва инициирует обыкновенный HTTP запрос и получает картинку, а мы сессию метерпретера
Так как изображение нельзя запустить «нормальными» способами, то и средства защиты и технические специалисты могут «легкомысленно» относиться к его содержимому. Данный пример призывает внимательно относиться к настройке систем предотвращения вторжений, следить за новостями в мире информационной безопасности и быть на чеку.
Прогрессивные методы разработки шеллкодов в ОС класса UNIX.
Причиной создания этой статьи послужило отсутствие достаточного количества информации по этой теме на русском языке, а так же собственные наработки автора в этой области. Первая часть статьи посвящена рассмотрению методов разработки шеллкодов в целом и решения проблем, возникающих при этом (к примеру, как эффективнее избавиться от нуль-байтов в коде). Во второй части статьи мы коснемся аспектов профессиональной разработки байт-кодов. Сюда входит решение таких проблем, как укрывание байт-кода от IDS, разделение шеллкода в памяти на несколько частей и прочие трюки, необходимые для более эффективной работы.
Автор: hr0nix (hr0nix 0 front.ru)
1. Введение.
2. Шеллкоды “As Is”.
2.1 Основные принципы разработки.
В некоторых системах метод вызова syscalls несколько отличается – это лучше уточнить в каком-нибудь специальном справочнике вашей системы или у разработчиков. Пример вызова sys_exit() – программа, выходящая из программы =) : strace – чрезвычайно полезная для отладки шеллкодов программа. Ее работа заключается в том, что она запускает программу, переданную вторым аргументом, и составляет подробный отчет обо всех системных вызовах, сделанных этой программой (а так же предоставляет список параметров этих вызовов). Я потерял очень много времени на отладке, не используя трэйсер системных вызовов. Итак, теперь мы знаем достаточно, чтобы приступать к самому главному.
2.2 Избавляемся от нуль-байтов.
Рассмотрим следующий ассемблерный код: Скомпилируем и выполним: На первый взгляд, все просто прекрасно. Однако вот досадный момент: Здесь надо заметить, что SG сперва компилирует исходник в объект-код, а потом уже работает с ним. Посмотрим, откуда взялся нуль-байт: Как можно заметить, первый нуль-байт встречается в инструкции mov edx,0xc. Откуда он берется? Регистр edx имеет размер 4 байта (подразумевается, что у вас 32-битный процессор архитектуры x86 =) ), однако мы в него пытаемся поместить константу, имеющую всего один значимый байт – младший. Остальные байты – нули. В конечном виде эта инструкция будет выглядеть как mov edx,0x0000000c. Отсюда и 3 нуль-байта дизассемблерного листинга. В принципе, нуль-байты в коде в 90% случаев берутся именно в результате компилирования подобных команд. Бороться с этим очень просто – необходимо предварительно обнулить весь регистр (при помощи xor), а потом записать в младший байт нашу константу. То есть наше mov edx,0x0000000c будет выглядеть так: В откомпилированном варианте, кстати, второй вариант даже будет короче.
2.3 Адресация.
Каждый раз, когда мы помещаем какие-либо данные в стек при помощи push, регистр ESP меняет свое значение на адрес этих данных в памяти. Мы уже использовали ESP для определения адреса строки в памяти (shello.asm): EIP
Когда мы используем инструкцию вида call _label, она заменяется парой При использовании ret EIP вынимается из стека, после чего на него происходит jmp. Этих двух фактов вполне достаточно для определения адреса любых наших данных в памяти. Вот многострадальный shello.asm, который теперь достает адрес строки, используя EIP:
2.4 «Боевой» пример.
3. Advanced in shellcode development.
Последнее время появляется все больше и больше систем обнаружения вторжения, способных обнаружить в анализируемых пакетах данных шеллкоды. Давайте разберемся, как это происходит и как от этого защититься.
3.1 Шифрованное тело.
3.2 Замена NOP-ам. Полиморфный код.
Но, как говорится, не NOP-ом единым жив человек. Вот пример инструкций, которые не имеют абсолютно никакого воздействия на процесс исполнения и, следовательно, могут послужить заменой NOP-у: Очевидно, в нашей псевдо-цепочке мы можем как угодно модифицировать содержимое пользовательских регистров (все равно в шеллкоде мы их обнулим), а так же переносить данные из любого регистра в самого себя. Однако здесь есть одна небольшая проблема – почти у всех этих команд опкоды занимают в памяти минимум два байта. Таким образом, нет никакой гарантии, что попадем мы именно на начало необходимой нам команды, а не на середину, получив в результате лаконичное Illegal Instruction. В принципе, это лечится довольно просто – каждый адрес возврата нужно пробовать использовать тремя способами: без сдвига, со сдвигом в один и в два байта.
Теперь что касательно полиморфизма кода. Избавляться от сигнатур можно по-разному, тут нужно не забывать, что запрограммировать один и тот же алгоритм можно довольно большим количеством способов. В придачу к этому, чтобы избавиться от постоянного вида нашего кода, достаточно добавить в случайные места нашего кода некоторое количество случайно (да, повсюду царит random) выбранных псевдо-NOP-ов. Здесь необходимо помнить, что мы уже не можем модифицировать содержимое пользовательских регистров, однако можем заменять команды вида “mov al, 5” на что-нибудь типа “mov al, 4; inc al” Применяя подобные преобразования к вашему шеллкоду (особенно вкупе с шифрованием), вы почти 100-процентно защищаете его от обнаружения IDS. SH311G0d также умеет случайным образом модифицировать данный на входе шеллкод, уменьшая вероятность его обнаружения. Для этого используется ключ –m. В случае использования ключа –m совместно с –c, программа сперва зашифрует байт-код, а потом модифицирует расшифровщик. Результат – каждый раз – новый код, что приводит к практически полному отсутствию сигнатур. В дальнейшем я буду работать над усовершенствованием эвристического модификатора исходного кода, т.к. эта идея меня весьма заинтересовала.
3.3 Прямой поиск кода в памяти.
Помещаем свой шеллкод в любое место адресного пространства программы (другой буфер, heap), а в уязвимый буфер кладем небольшой байт-код, который найдет по какой-либо сигнатуре «главный» код в памяти. Услышав слова «поиск в памяти», хочется сразу спросить: а как же SIGSEGV? Ведь всегда существует возможность наткнуться на область памяти, не принадлежащую программе и умереть с лаконичным и до боли знакомым сообщением.
Но и тут нас не оставят в беде. Оказывается, существует системный вызов chdir(), принимающий в качестве единственного аргумента указатель на строку – имя каталога. Если этот указатель указывает (и снова тавтология =( ) за пределы доступного адресного пространства, то вызов возвращает значение 0xfffffff2, иначе 0xfffffffe, чем мы и воспользуемся. Итак, общая структура данных в этом случае такова: Сам шеллкод для прямого поиска выглядит следующим образом: Вот пример программы на Си, использующей эту технологию на практике: При желании, можете запустить все это через strace и посмотреть, как вживую сканируется память.
Ну вот и все, что я хотел рассказать о современных методах разработки шеллкодов. Искренне надеюсь, моя статья поможет кому-нибудь из вас перестать использовать готовые решения, уподобляясь скрипт-кидди, и начать Творить, как и подобает человеку, что хочет называться Хакером.
Пишем шеллкод под Windows на ассемблере
В этой статье я хочу показать и подробно объяснить пример создания шеллкода на ассемблере в ОС Windows 7 x86. Не смотря на солидный возраст данной темы, она остаётся актуальной и по сей день: это стартовая точка в написании своих шеллкодов, эксплуатации переполнений буферов, обфускации шеллкодов для сокрытия их от антивирусов, внедрения кода в исполняемый файл (PE Backdooring). В качестве примера я выбрал TCP bind shellcode, т.к. на мой взгляд — это лучший пример, потому что все остальные базовые шеллкоды имеют много общего с ним. Статья будет полезна для специалистов по информационной безопасности, пентестеров, начинающих реверс-инженеров и всем, кто желает разобраться в базовых принципах работы ОС Windows. Плюсом — улучшаются навыки программирования. Начнём, как и всегда, с подготовки.
Подготовка
Для хорошего понимания статьи вам понадобятся:
Инструменты
В процессе написания шеллкода я буду кратко описывать аргументы используемых системных функций. Для более подробного изучения возможных значений аргументов можете воспользоваться ссылками на официальную документацию, которые будут даны к каждой функции.
Общий алгоритм шеллкода
По сравнению с созданием шеллкодов в Linux, где нужные нам системные вызовы имеют свои уникальные номера, в Windows дела обстоят несколько сложнее. Во-первых, чтобы вызвать необходимую системную функцию нам необходимо знать её точный адрес в памяти, а поскольку все современные ОС имеют Address Space Layout Randomization (ASLR), то необходимо реализовать алгоритм, который будет находить адреса нужных нам функций без привязки к конкретным адресам. Во-вторых, аргументы функции помещаются не в регистры процессора, а в стэк. Учитывая вышесказанное, общий алгоритм шеллкода будет таким:
— Инициация использования нашим процессом библиотеки Winsock DLL: WSAStartup;
— Создание сокета: WSASocket;
— Привязка его к интерфейсу: bind;
— Перевод созданного сокета в состояние listening;
— Приём входящего сетевого соединения: accept;
— Создание процесса командной оболочки cmd.exe для выполнения команд в ОС по сети;
— Завершение родительского процесса: ExitProcess.
Написание шеллкода
Определение адреса библиотеки kernel32.dll
Для каждого потока (выполнение нашего кода происходит в потоке) в Windows есть структура, в которой хранится информация о процессе, в котором живёт наш поток. Мы можем обратиться за этой информацией. Хорошее объяснение что это за структура можно найти здесь. Адрес kernel32.dll мы можем получить из следующей цепочки:
Подобные участки кода удобно анализировать в winrepl’е.
Алгоритм поиска функций
После того как нашли адрес kernel32.dll мы сможем находить адреса функций внутри этой библиотеки. Чтобы лучше понять алгоритм поиска функций в библиотеке нам понадобится PEview. В этой программе открываем C:\Windows\System32\kernel32.dll. Тем, кто хочет более детально разобрать структуру PE (.exe) файлов, рекомендую почитать здесь. Затем в искомой библиотеке (в нашем случае kernel32.dll) находим Offset to New EXE Header:
PEView kernel32.dll — offset to new EXE header
После того как нашли нужное смещение, находим адрес EXPORT Table:
PEView kernel32.dll — Export Tables
В этой таблице нас будут интересовать следующие значения:
В случае с общим количеством функций есть 2 нюанса. 1-ый нюанс заключается в том, что иногда количество функций, полученное из файла может не совпадать с количеством имён функций самой библиотеки, тогда в нашем алгоритме поиска возникнет исключение и его необходимо обработать. С другой стороны, зная количество функций, мы можем остановить поиск, когда дойдём до 0, таким образом, корректно завершая цикл. 2-ой нюанс в том, что если вы точно уверены в том, что вы найдёте нужную функцию в библиотеке, то вам не нужно знать и использовать общее количество функций. В таком случае необходимо переделать цикл поиска имени функций: не уменьшать счётчик от общего числа функций к 0, а увеличивать на 1 начиная с 0.
При поиске от общего количества функций к 0 в ws2_32.dll у меня возникало исключение, поэтому, можно или использовать системную функцию GetProcAddress, которая позволяет средствами системы получать адрес искомой функции, или переделать алгоритм поиска функций, как описано выше: от 0 и выше.
С учетом вышесказанного, напишем алгоритм, который может искать функции в любой библиотеке. Это полезно в случае, когда нам нужно искать не только в kernel32.dll, но и, например, в ws2_32.dll. Как аргументы мы передаём хэш имени функции (алгоритм хэширования будет рассмотрен чуть позже), и базовый адрес самой библиотеки, в которой будем производить поиск.
Алгоритм поиска адреса функции в библиотеке
В начале алгоритма (метка find_function_name) поиска функции очищаем регистр ESI, подготавливаем стековый фрейм, находим все необходимые для нас таблицы и сохраняем их в локальные переменные (код до комментария «Find function loop»).
После чего, начинается цикл поиска функций. Сперва проверяется значение регистра ECX. Если мы прошли всё количество функций, указанных в библиотеке, то поиск завершен (инструкция jecxz find_function_finished). Если нет, то находим имя функции из таблицы Name Pointer Table в соответствии с значением нашего счётчика. Затем, высчитываем хэш для полученного имени. Хэш высчитывается от имени функции с использованием побитового сдвига (метка compute_hash_again).Полученное значение сохранятся в EDI. Когда нашли конец строки, то считаем, что хэш подсчитан и затем сравниваем его с нашим искомым хэшем. Конечно, нам необходимо просчитать хэши нужных нам функций заранее, можно при помощи скрипта, который указан в разделе «Подготовка». Если хэш не совпадает, то мы берём следующую функцию и продолжаем поиск (инструкция jnz find_function_loop). Если же хэш совпал, то по Ordinal Table находим адрес смещения нашей функции в таблице Address Table и затем высчитываем адрес нашей искомой функции (комментарий Get address of Function). Поначалу, шаг с получением смещения из ordinal table мне казался избыточным и я просто находил смещение умножая значение счётчика (в ECX, порядковый номер функции) на 4 (каждые 4 байта для новой функции по таблице). Однако это работало далеко не всегда, переделав алгоритм с использованием ordinal table, мой код стал находить нужные функции всегда. Сохраняем результат в EAX и переходим к написанию основного тела шеллкода.
Тело шеллкода
Когда мы реализовали все необходимые для нас алгоритмы поиска, мы можем приступать к написанию основной части нашего шеллкода.
Общее описание кода шеллкода
Перед работой нашего шеллкода желательно сохранить данные регистров и флагов, которые были до начала работы, эта практика поможет нам при внедрении нашего кода в тело другой программы. Локальные переменные для шеллкода выглядят следующим образом:
Описание локальных переменных
После чего находим адреса функций:
Поиск адресов функций
После чего вызываем LoadLibraryA со строкой ws2_32.dll в качестве аргумента. Данная функция принимает 1 аргумент — указатель на строку с именем библиотеки. В нашем случае указатель на ws2_32.dll.
Загрузка модуля ws2_32.dll
Затем находим адреса функций:
Поиск функций осуществляется при помощи GetProcAddress.
Аргументы GetProcAddress:
HMODULE hModule, — адрес библиотеки в которой ищем функцию, т.е. адрес ws2_32.dll
LPCSTR lpProcName — указатель на имя функции. При каждом вызове создаём новый указатель на строку с именем искомой функции.
Поиск функций библиотеки ws2_32.dll
Теперь необходимо инициализировать Winsock DLL нашим процессом, для использования функций библиотеки ws2_32.dll, таких как: WSASocket, bind, listen, accept. Помним, что аргументы для функций передаются в обратном порядке, поскольку помещаются в стэк. Вызываем WSAStartup.
WORD wVersionRequired — версия спецификации Windows Sockets. Устанавливаем в 0x00000202;
LPWSADATA lpWSAData — указатель на область памяти, в которую будут записаны детали имплементации Windows Socket. Необходимо создать такую область памяти размером 400 байт (именно такой размер у этой структуры) и отдать указатель на неё.
Затем создаём сокет WSASocketA.
int af — спецификация семейства адресов. Будем использовать IPv4, потому указываем 2;
int type — тип сокета. Нас интересует SOCK_STREAM, поскольку хотим использовать TCP — 1;
int protocol — оставляем в 0;
LPWSAPROTOCOL_INFOA lpProtocolInfo — оставляем в 0;
GROUP g — группа сокетов, к которой будет относится созданный сокет. Здесь также оставляем 0;
DWORD dwFlags — нам дополнительные параметры для сокета не нужны, поэтому он равен 0.
После этого вызываем функцию WSASocketA, созданный дескриптор сокета система оставит в EAX, его необходимо сохранить.
Следом идёт вызов функции bind.
Её необходимо вызвать для связи созданного сокета с локальным адресом.
Аргументы bind:
SOCKET s, — дескриптор сокета, который мы привязываем к интерфейсу. Мы его положили в ESI;
const sockaddr *addr, — указатель на область памяти, где хранится структура sockaddr. Структуру сначала необходимо заполнить и затем отдать указатель на неё;
int namelen — размер структуры sockaddr, 16 байт.
Затем идёт вызов функции listen. Данная функция переводит сокет в состояние ожидания входящего соединения.
SOCKET s, — дескриптор сокета, который будем переводить в listening. По-прежнему в ESI;
int backlog — максимальная длина очереди ожидающих соединений. В нашем случае не меньше 1.
После того как перевели созданный сокет в состояние listening можем принимать входящие соединения: accept.
Аргументы accept:
SOCKET s, — дескриптор сокета, который ожидает соединение. Снова в ESI;
sockaddr *addr, — указатель на буфер, который принимает информацию о входящем соединении. Структуру заполнять не надо;
int *addrlen — указатель на целочисленное значение длины структуры на которую указывает addr параметр: 16 байт;
Когда к нашему сокету подключится клиент, данная функция вернет целочисленный дескриптор вновь созданного сокета. После того, как приняли входящее соединение, нам необходимо вызвать командную оболочку, чтобы появилась возможность удаленного выполнения команд. Функция, у которой больше всего аргументов — CreateProcessA.
LPCSTR lpApplicationName — Имя приложения. Для нас не обязателен, 0;
LPSTR lpCommandLine — Имя команда. cmd.exe;
LPSECURITY_ATTRIBUTES lpProcessAttributes — указатель на атрибуты процесса, 0;
LPSECURITY_ATTRIBUTES lpThreadAttributes — указатель на атрибуты потока, 0;
BOOL bInheritHandles — если в TRUE, то создаваемый процесс унаследует дескрипторы от процесса-создателя, 1.
DWORD dwCreationFlags — флаги контроля класса приоритета и создания процесса. Для нас 0.
LPVOID lpEnvironment — указатель на блок окружения для нового процесса. Для нас 0
LPCSTR lpCurrentDirectory — полный путь к текущей директории процесса. Для нас 0
LPSTARTUPINFOA lpStartupInfo — указатель на STARTUPINFO или STARTUPINFOEX структуру.
LPPROCESS_INFORMATION lpProcessInformation — указатель на PROCESS_INFORMATION структуру.
И завершаем наш шеллкод завершением родительского процесса.
Теперь сложим это всё вместе.
Для компиляции шеллкода выполним:
Запустим его и установим соединение.
Компиляция, запуск и установление соединения с шеллкодом
Заключение
Таким образом, мы рассмотрели один из вариантов создания Windows TCP Bind шеллкода. Другие типовые шеллкоды типа Reverse TCP или Exec cmd могут быть легко написаны после разбора этого примера.
Направления дальнейшей работы могут быть такими:
Самый маленький шелл-код. Создаем 44-байтовый Linux x86 bind shellcode
Содержание статьи
Shell-код представляет собой набор машинных команд, позволяющий получить доступ к командному интерпретатору (cmd.exe в Windows и shell в Linux, от чего, собственно, и происходит его название). В более широком смысле shell-код — это любой код, который используется как payload (полезная нагрузка для эксплоита) и представляет собой последовательность машинных команд, которую выполняет уязвимое приложение (этим кодом может быть также простая системная команда, вроде chmod 777 /etc/shadow) :
Немного теории
Уверен, что многие наши читатели и так знают те истины, которые я хочу описать в теоретическом разделе, но не будем забывать про недавно присоединившихся к нам хакеров и постараемся облегчить их вхождение в наше непростое дело.
Системные вызовы
Системные вызовы обеспечивают связь между пространством пользователя (user mode) и пространством ядра (kernel mode) и используются для множества задач, таких, например, как запуск файлов, операции ввода-вывода, чтения и записи файлов.
Для описания системного вызова через ассемблер используется соответствующий номер, который вместе с аргументами необходимо вносить в соответствующие регистры.
Регистры
Регистры — специальные ячейки памяти в процессоре, доступ к которым осуществляется по именам (в отличие от основной памяти). Используются для хранения данных и адресов. Нас будут интересовать регистры общего назначения: EAX, EBX, ECX, EDX, ESI, EDI, EBP и ESP.
Стеком называется область памяти программы для временного хранения произвольных данных. Важно помнить, что данные из стека извлекаются в обратном порядке (что сохранено последним — извлекается первым). Переполнение стека — достаточно распространенная уязвимость, при которой у атакующего появляется возможность перезаписать адрес возврата функции на адрес, содержащий shell-код.
Проблема нулевого байта
Многие функции для работы со строками используют нулевой байт для завершения строки.
Таким образом, если нулевой байт встретится в shell-коде, то все последующие за ним байты проигнорируются и код не сработает, что нужно учитывать.
Необходимые нам инструменты
Если бы мы создавали bind shell классическим способом, то для этого нам пришлось бы несколько раз дергать сетевой системный вызов socketcall() :
И в конечном итоге наш shell-код получился бы достаточно большим. В зависимости от реализации в среднем выходит 70 байт, что относительно немного. Но не будем забывать нашу цель — написать максимально компактный shell-код, что мы и сделаем, прибегнув к помощи netcat!
Почему размер так важен для shell-кода?
Ты, наверное, слышал, что при эксплуатации уязвимостей на переполнение буфера используется принцип перехвата управления, когда атакующий перезаписывает адрес возврата функции на адрес, где лежит shell-код. Размер shell-кода при этом ограничен и не может превышать определенного значения.
Сохраним ее как super_small_bind_shell_1.nasm и далее скомпилируем:
а затем слинкуем наш код:
и запустим получившуюся программу через трассировщик (strace), чтобы посмотреть, что она делает:
Запуск bind shell через трассировщик
Введение в Assembler
execve() имеет следующий прототип:
Синтаксис нашего системного вызова (функции) выглядит следующим образом:
Описываем системные вызовы через ассемблер
Как было сказано в начале статьи, для указания системного вызова используется соответствующий номер (номера системных вызовов для x86 можно посмотреть здесь: /usr/include/x86_64-linux-gnu/asm/unistd_32.h ), который необходимо поместить в регистр EAX (в нашем случае в регистр EAX, а точнее в его младшую часть AL было занесено значение 11, что соответствует системному вызову execve() ).
Аргументы функции должны быть помещены в регистры EBX, ECX, EDX:
Ныряем в код
Разберем код по блокам.
Блок 1 говорит сам за себя и предназначен для определения секции, содержащей исполняемый код и указание линкеру точки входа в программу.
Блок 2
Важно!
Аргументы для execve() мы отправляем в стек, предварительно перевернув их справа налево, так как стек растет от старших адресов к младшим, а данные из него извлекаются наоборот — от младших адресов к старшим.
Для того чтобы перевернуть строку и перевести ее в hex, можно воспользоваться следующей Linux-командой:
Блок 3
Ты, наверное, заметил странноватый путь к бинарнику с двойными слешами. Это делается специально, чтобы число вносимых байтов было кратным четырем, что позволит не использовать нулевой байт (Linux игнорирует слеши, так что /bin/nc и /bin//nc — это одно и то же).
Блок 4
Блок 5
Почему в AL, а не в EAX? Регистр EAX имеет разрядность 32 бита. К его младшим 16 битам можно обратиться через регистр AX. AX, в свою очередь, можно разделить на две части: младший байт (AL) и старший байт (AH). Отправляя значение в AL, мы избегаем появления нулевых байтов, которые бы автоматически появились при добавлении 11 в EAX.
Извлекаем shell-код
Чтобы наконец получить заветный shell-код из файла, воспользуемся следующей командой Linux:
и получаем на выходе вот такой вот симпатичный shell-код:
Тестируем
Для теста будем использовать следующую программу на С:
Компилируем. NB! Если у тебя x86_64 система, то может понадобиться установка g++-multilib :
Проверяем bind shell
Хех, видим, что наш shell-код работает: его размер — 58 байт, netcat открывает шелл на порте 12345.
Оптимизируем размер
58 байт — это довольно неплохо, но если посмотреть в shellcode-раздел exploit-db.com, то можно найти и поменьше, например вот этот размером в 56 байт.
Можно ли сделать наш код существенно компактнее?
А теперь попробуем подключиться и получить удаленный шелл-доступ. С помощью Nmap узнаем, на каком порте висит наш шелл, после чего успешно подключаемся к нему все тем же netcat :
И снова проверяем bind shell
Bingo! Цель достигнута: мы написали один из самых компактных Linux x86 bind shellcode. Как видишь, ничего сложного ;).