Nasm shell kali linux

Прогрессивные методы разработки шеллкодов в ОС класса UNIX.

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

Автор: hr0nix (hr0nix 0 front.ru)

Содержание:

  1. Введение.
  2. Шеллкоды “As Is”.
    1. Основные принципы разработки.
    2. Избавляемся от нуль-байтов.
    3. Адресация.
    4. «Боевой» пример.
  3. Advanced in shellcode development.
    1. Шифрованное тело.
    2. Замена NOP-ам. Полиморфный код.
    3. Прямой поиск кода в памяти.

. ПРЕДУПРЕЖДЕНИЕ . Для того, чтобы понять все, что последует далее, от Вас потребуется знание ассемблера (под архитектуру x86), Си и ОС Linux. Впрочем, даже если чего-то из этого вы не знаете, читать дальше вам все равно никто не запретит. =)

1. Введение.

Причиной создания этой статьи послужило отсутствие достаточного количества информации по этой теме на русском языке, а так же собственные наработки автора в этой области. Первая часть статьи посвящена рассмотрению методов разработки шеллкодов в целом и решения проблем, возникающих при этом (к примеру, как эффективнее избавиться от нуль-байтов в коде). Во второй части статьи мы коснемся аспектов профессиональной разработки байт-кодов. Сюда входит решение таких проблем, как укрывание байт-кода от IDS, разделение шеллкода в памяти на несколько частей и прочие трюки, необходимые для более эффективной работы. Пока я писал эту статью, я создал небольшой программный пакет со скромным названием SH311G0d =), весьма полезный (по крайней мере, мне он часто был нужен) при разработке байт-кодов. По ходу статьи я буду рассказывать о его функциях и о том, как они были реализованы. Скачать сам пакет вы сможете с . ТУТ ССЫЛКА. Все примеры в статье реализованы для ОС Linux. Почему? Просто эта система – одна из самых распространенных и удобных на сегодняшний день. А, освоив технику написания байт-кода в ней, можно без труда писать шеллкоды для любой UNIX-системы.

2. Шеллкоды “As Is”.

Итак, понеслась. Для начала, что же такое шеллкод? В настоящее время шеллкодом принято называть последовательность опкодов процессора, представимую в виде строки символов и удовлетворяющую ряду свойств:

  • Отсутствие в коде нуль-байтов (символов с кодом 0x00) – это свойство следует из требования того факта, что код должен быть представим в виде строки. Напоминаю, что в Си (и еще ряде языков) нуль-байт служит признаком конца строки, следовательно, если в шеллкоде будет нуль-байт, то при его копировании (или еще какой-нибудь строковой операции) обработается только часть строки до первого нуль-байта, что может вызвать некоторые неприятности.
  • Шеллкод должен выполниться корректно в любой момент, когда на него будет передано управление (свойство вызвано спектром использования шеллкодов – в различных атаках, в ходе которых повреждается структура памяти), и его работа не должна зависеть от того, в какой области памяти находится байт-код.
  • Шеллкод по возможности должен быть универсальным, то есть иметь работать корректно на всех системах одного типа (данное свойство наиболее критично в win32-системах, но речь у нас не о них).
  • Чем меньше размер байт-кода, тем лучше (больше вероятность того, что мы успешно поместим его в какой-нибудь буфер). В принципе, это свойство можно отнести к категории универсальности.

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

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

Другая проблема – признаки конца строки. Если использовать в коде выражения вида msg db ‘hello world’,0x0 – ни до чего хорошего это не доведет. Самый простой способ решения этой проблемы – получить 0x0 в одном из регистров, а дальше сделать mov byte [ + ], . Пример использования (только с символом конца строки) – строка номер 4 вышеприведенного листинга. Так же рекомендуется всегда использовать минимально возможные регистры для хранения данных и явно указывать размеры операндов (push byte, jmp short и т.д.). Вот пример кода, содержащего большинство причин появления нуль-байтов: Вывод ndisasm –b 32 : Как мы видим, в шестнадцатеричном представлении каждой из команд присутствует нуль-байт. А теперь исправленный вариант: В придачу ко всему, исправленный код еще и оказался намного короче певоначального. Теперь мы готовы исправить hello.asm, превратив его в полноценный шелл-код: Ну что же, 0x0-байты нам теперь не помеха. Тут надо заметить, что существует еще один весьма эффективный способ избавиться от нуль-байтов – шифрование кода в памяти. Подробно об этом будет рассказано в соответствующем разделе.

2.3 Адресация.

Каждый раз, когда мы помещаем какие-либо данные в стек при помощи push, регистр ESP меняет свое значение на адрес этих данных в памяти. Мы уже использовали ESP для определения адреса строки в памяти (shello.asm): EIP

Когда мы используем инструкцию вида call _label, она заменяется парой При использовании ret EIP вынимается из стека, после чего на него происходит jmp. Этих двух фактов вполне достаточно для определения адреса любых наших данных в памяти. Вот многострадальный shello.asm, который теперь достает адрес строки, используя EIP:

2.4 «Боевой» пример.

3. Advanced in shellcode development.

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

В 95% случаев IDS использует для обнаружения вредоносных пакетов сигнатурный анализ. Вот список ключевых сигнатур, на основании наличия которых в данных пакета IDS делает вывод о степени его вредоносности по отношению к системе:

  • “\xcd\x80” – классика. Считается, что шеллкод обязательно должен вызывать прерывание номер 0x80 (иначе как же он навредит?). Однако, как мы увидим в дальнейшем, можно написать любой код, не используя эту сигнатуру.
  • Строки вида “/bin/sh”, “cmd.exe” и т.п. Очевидно, избавиться от подобных сигнатур очень просто вручную. Достаточно сперва положить в память несколько измененную строку, а потом придать ей вид нужной так же, как мы делали в shello.asm, добавляя в конец символ перевода строки.
  • Наличие цепочек из байтов 0x90 – команд NOP. Обычно добавляют длинную цепочку NOP-ов в буфер перед шеллкодом, чтобы проще было попасть на него адресом возврата. О замене NOP-ам мы поговорим в соответствующем разделе.

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

3.1 Шифрованное тело.

Одно из наиболее красивых решений для скрытия шеллкодов – шифрование рабочего тела. Это происходит следующим образом:

  1. Само тело шеллкода шифруется при помощи какого-нибудь алгоритма шифрования, позволяющего осуществить однозначную расшифровку криптованного текста (далее, не заостряясь на деталях шифрования, будем использовать банальный XOR).
  2. Перед зашифрованным телом помещается компактный (по возможности) расшифровщик, который придает байт-коду в памяти его первоначальный вид.
  3. После расшифровки управление передается на расшифрованный байт-код.

Чаще всего для шифрования кода используется XOR (по одно-, двух- или четырехбайтовому ключу), как наиболее быстрый и просто реализуемый из алгоритмов шифрования. Вот пример реализации однобайтового XOR-расшифровщика: Поддержка шифрования предусмотрена и в моей программе. При указания ключа –c SH311G0d сделает из данного ему на входе байт-кода зашифрованную программную систему с расшифровщиком, которая сама по себе является полноценным шеллкодом.

Итак, какие плюсы мы получаем при шифровании?

  1. В зашифрованном коде почти наверняка будут отсутствовать различные сигнатуры наподобие “\xcd\x80”, а значит, обнаружить его при помощи IDS будет практически невозможно.
  2. Если в написанном вами коде очень сложно обойтись без нуль-байтов (или их использование дает серьезный выигрыш по размерам кода), то, зашифровав код так, чтобы в зашифрованном виде их не встречалось, мы навеки забудем про эту проблему (тут надо сказать, что SH311G0d так выбирает XOR-ключ, чтобы 0x0-байтов в зашифрованном байт-коде не возникло). В случае XOR-шифрования, для выполнения этого условия достаточно, чтобы значение ключа не совпадало ни с одним из байтов кода (т.к. a xor a = 0).

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

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 и посмотреть, как вживую сканируется память.

Ну вот и все, что я хотел рассказать о современных методах разработки шеллкодов. Искренне надеюсь, моя статья поможет кому-нибудь из вас перестать использовать готовые решения, уподобляясь скрипт-кидди, и начать Творить, как и подобает человеку, что хочет называться Хакером.

Great tnx всем, кто помогал вылавливать баги в статье: ov3r, Nagatoky, virusman, [HEX] — ваша помощь была неоценима. Спасибо всем, кто не давал мне спать, пока я работал. Респект #m00 и #darkwired (просто потому, что там хорошие люди). И привет моему преподу по матану: еще раз завалите на экзамене, Дмитрий Валерьевич, будете страдать (да, это угроза)…

Источник

Статья Hello world в виде шелл-кода: Особенности написания шелл-кодов

Приветствую всех читателей этой статьи и посетителей Codeby.net >

Хочу рассказать о шелл-кодах и особенностях их написания вручную. Вам понадобятся знания ассемблера на базовом уровне. Рассмотрим как пишут шелл-коды без инструментов, которые могут их автоматически создать. Вредоносные шелл-коды писать не будем! Будем писать псевдо шелл-коды для простоты и понимания. Если эта статья и её формат вам понравиться, тогда расскажу о вредоносных шелл-кодах
Написание шелл-кода будет показано для архитектуры x86 . Алгоритм не сильно отличается для архитектуры x64. Для практики я рекомендую вам установить Linux в VirtualBox или VMware. Так же можно экспортировать готовый образ виртуальной машины.

План:
Теория: Что такое шелл-код и системные вызовы
Практика: Сравниваем программу на ассемблере и языке Си . Делаем hello world в виде шелл-кода

Что такое шелл-код и системные вызовы

Шелл-код — это двоичный исполняемый код, который выполняет определенную задачу. Например: Передать управление командной оболочке ( /bin/sh ) или даже выключить компьютер. Шелл-код пишут на языке ассемблер с помощью опкодов (Например: \x90 означает команду: nop ).

Программы взаимодействуют с операционной системой через функции. Функции расположены в библиотеках . Функция printf() , exit() в библиотеке libc . Помимо функций существуют системные вызовы. Системные вызовы находятся в ядре операционной системы . Взаимодействие с операционной системой происходит через системные вызовы . Функции используют системные вызовы.
Системные вызовы не зависят от версии какой-либо из библиотеки . Из-за универсальности системные вызовы используют в шелл-кодах.

У системных вызовов есть кода. Например, функция printf() использует системный вызов write() с кодом 4.
Машины с архитектурой x86: Системные вызовы определены в файле /usr/include/i386-linux-gnu/asm/unistd_32.h
Машины с архитектурой x64: Системные вызовы определены в файле /usr/include/x86_64-linux-gnu/asm/unistd_64.h
Справочник системных вызовов с объяснениями.

Проверим существование системных вызовов на практике

Напишем программу на языке Си, печатающую строку BUG .

Компиляция: gcc printf_prog.c -o printf_prog

Проверим наличие системных вызовов с помощью команды: strace ./printf_prog

В конце strace мы можем видеть системный вызов write(1, «BUG», 3BUG) . Количество кода для шелл-кода слишком много, если использовать функции . Старайтесь писать небольшие шелл-коды . Так они будут меньше обнаруживаться и вероятность их срабатывания будет больше.

Сравниваем программу на ассемблере и языке Си

Шелл-код можно написать, как программу на языке Си, скомпилировать, при необходимости отредактировать и перевести в байтовое представление. Такой способ подходит, если мы пишем сложный шелл-код.
Шелл-код можно написать на языке ассемблер. Этот способ я хочу рассмотреть более подробно. Для сравнения мы напишем 2 программы, печатающие сроку Hello world! . Первая будет написана на языке Си, а вторая на ассемблере.

Код на языке Си:

Компиляция: gcc hello_world_c.c -o hello_world_c

Код на ассемблере:

Получаем объектный файл с помощью nasm: nasm -f elf32 hello_world.asm -o hello_world.o
Объединяем объектный файл в один исполняемый: ld -m elf_i386 hello_world.o -o hello_world

Посмотрим на ассемблерный код получившихся файлов с помощью objdump .

Функция main в программе на языке Си:

Кажется, что больше кода в ассемблерном листинге, но это не так. В листинге языка Си я показал только функцию main , а она там не одна! В листинге ассемблера я показал программу целиком!

Делаем hello world в виде шелл-кода

Взгляните на листинг программы, написанной на ассемблере. Сначала идут адреса, затем байты, а далее инструкции ( 8049000: b8 04 00 00 00 mov eax, 0x4 ). Запишем опкоды инструкций в виде шелл-кода.
Вручную всё делать очень не удобно. Bash нам в помощь: objdump -d ./hello_world|grep ‘[0-9a-f]:’|grep -v ‘file’|cut -f2 -d:|cut -f1-6 -d’ ‘|tr -s ‘ ‘|tr ‘\t’ ‘ ‘|sed ‘s/ $//g’|sed ‘s/ /\\x/g’|paste -d » -s |sed ‘s/^/»/’|sed ‘s/$/»/g’ (вместо ./hello_world можно подставить любую другую программу ).

Опкоды ( представлены в читаемом виде )

Но работать этот шелл-код не будет , так как в нём присутствуют байты \x00 и строка hello_world указана по адресу ( «\xb9\x1f»\x90\x04\x08» — это инструкция mov ecx, 0x8040901f ), а в программе адрес может быть разный из-за механизма защиты ASLR. В шелл-коде точных адресов быть не должно . Решим проблему постепенно, начав заменять данные, расположенные по точному адресу, а затем уберём байты \x00 .

Убираем точные адреса

Строка, которую нам нужно напечатать — Hello, world! Представим её в виде байтов. Утилита xxd нам поможет: echo «Hello, World!» | xxd -pu

Байтовое представление строки Hello, world! : 48656c6c6f2c20576f726c64210a . Для удобства разделим по 4 всю последовательность байтов: 48656c6c 6f2c2057 6f726c64 210a . Байтов в конце недостаточно. Во всех отделённых нами наборов байтов, их по 4, а в последнем всего лишь 2. Добавим любые байты кроме \x00 , так как потом добавленные нами байты обрежутся программой. Я выберу байты \x90 . Нам нужно расположить байты в порядке: little-enidan ( в обратном порядке ). Получится такая последовательность байт: 90900a21 646c726f 57202c6f 6c6c6548 . Это просто байты строки.

Теперь превратим их в инструкции на ассемблере. Тут нам поможет фреймворк radare2 с утилитой rasm2.

Получаем опкоды инструкций

Флаг -a x86 -b 32 обозначают вывод для архитектуры x86.

Чтобы передать байты в стек нужна инструкция push . Регистр [/COLOR]esp[COLOR=rgb(97, 189, 109)] указывает на вершину стека . Переместим на значение вершине стека в регистр ecx .

В итоге получаем: 68210a9090 686f726c64 686f2c2057 6848656c6c 89e1 . Заменим точный адрес в нашем шелл-коде на новые инструкции.

Замена нулевых байтов

Для удобства мы представим эти инструкции в виде ассемблерных команд. Нам поможет утилита ndisasm . Первым делом запишем наши байты в файл, а затем применим утилиту ndisasm .

Нам нужно заменить инструкции с нулевыми байтами на другие. Нулевые байты образуются из-за того, что инструкция mov — двухбайтовая , а оставшиеся 2 байта из 4 компилятору нужно заменить нулями. Предлагаю заменить эти инструкции mov на сочетание двухбайтовых инструкций xor и mov .

Ассемблерные инструкции и их опкоды

Оформим весь этот набор байтов в виде программы на языке Си.

Компилируем: gcc hello_world_test.c -o hello_world_test -z execstack
Проверяем работоспособность: ./hello_world_test

Довольно долго это всё делать, если вы не хотите делать шелл-код для атаки на определённую компанию.
Существует замечательный инструменты Msfvenom и подобные ему. Msfvenom позволяет делать шелл-код по шаблону и даже закодировать его. Про этот инструмент и про сам metasploit на Codeby.net написано много информации. Про энкодеры информации в интернете тоже достаточно. Например: эта статья.
Хочу порекомендовать сайты: exploit-db и shell-storm. На этих сайтах вы сможете найти множество шелл-кодов.

Желаю вам удачи и здоровья. Не болейте и прокачивайте мозги.

Источник

Читайте также:  Создайте оффлайн профиль windows live
Оцените статью