- Новое
- Добавить закладку
- #1
Пожалуйста, обратите внимание, что пользователь заблокирован
# Вступление
Уже в который наверное раз для читателей погрузимся в такую интересную тему как шеллкоды и сделаем это через реализацию простого примера программы которая удаляет файл по указанному пути при помощи системного вызова unlink. Но как знать, возможно именно этот раз кому-то из читателей, особенно новичков, данная тематика покажется чуть более понятным. Для начала достаточно запомнить, без излишней пока детализации, что шеллкод, как одно из возможных представлений программного кода - должен быть максимально оптимальным для использования, а так же знать о том, что он тесно и даже неразрывно связан с языком ассемблера. А там где ассемблер, там самое непосредственное взаиможействие со всевозможными видами данных почти напрямую и без высокоуровневых конструкций представленных в виде битов, байтов, их последовательностей и объединений которые на более высоком программном уровне уже обобщены в различные типы и союзы в виде всевозможных структур объединенных в контексты программных процессов и являющихся их же частями, то и дело снующих по аппаратным транспортным магистралям - шинам между процессорами и пространствами витруальной памяти. И при этом делающих свою, в основном полезную, хотя и не всегда, работу по выполнению компьютерных программ в рамках работающей операционной системы. Ну и раз уж мы с вами пришли к выводу что в деле шеллкодинга плотно замешан ассемблер, а вместе с ним и данные и определенные правила по которым они живут в компьютерных системах, то посмотрим на эти вопросы чуть более подробно. Прежде чем мы перейдем непосредственно к самим шеллкодам. Поехали с самого начала.
## Введение в основные понятия
На минуту отвлечемся и перечислим то, что для более полного понимания нам пригодится и о чем обязательно знать или хотя-бы иметь представление. Хотя я и уверен, что большинство читателей разбираются или по крайне мере видели и в целом понимают те вещи которые будут кратко описаны здесь, но пройтись по основным понятиям для того чтобы освежить память будет в любом случае не лишним.
Стек OSI - логически структурированная сетевая иерархия в виде ступенек от прикладного уровня до аппаратного. Наверху иерархии расположена прикладная программа или если проще-бизнес логика, внизу-те правила согласно которым эта программа последовательно (почти всегда) выполняется аппаратным конвеером процессора. Вот и все. Если еще проще для нашего случая, то сверху Си, снизу ассемблер.
Системный вызов(syscall) - это системная функция при помощи которой пользователь запрашивает у ядра операционной системы специальное действие для выполнения этого действия аппаратным обеспечением, например запись либо чтение данных с устройства и куда в целях безопасности пользователю доступ закрыт. Правила запросов к системным вызовам и порядок их выполнения имеют строго определенные производителями процессоров и операционной системы формат. В нашем случае мы быдем использовать системный вызов unlink()
Стек(стек программы) - часть памяти выполняющейся программы где располагаются локальные переменные функций. Туда же мы в качестве аргумента системного вызова unlink() поместим строку, указывающую на удаляемый файл, причем не указатель на строку, а непосредственно ее саму. Стек в памяти процесса всегда растет от старшего адреса к младшему, а уменьшается наоборот - от младшего к старшему по принципу LIFO очереди(первым вошел-последним вышел).
Регистр процессора - это внутренняя память процессора разбитая на отдельные и независимые ячейки, используемые процессором для последовательного выполнения инструкций при работе программы и оперированием данных которые необходимы этим инструкциям. В 64 - битном исполнении это такие регистры как: RSP, RBP, RAX, RCX, RBX, RDX, RSI, RDI, R8-R15.
Конструкции высокоуровневого языка - в данной статье это операторы, операции и прочие конструкции языка Си, такие как if-then-else, for, объявления переменных, функций, структур, итд.
Инструкция ассемблера - инструкция(или команда) ассемблера выраженная в понятных человеку мнемонических обозначениях(аббревиатурах), например: MOV, XOR, PUSH, POP, ADD, LEA итд.
Инструкция процессора - то же самое что инструкция ассемблера, только понятная непосредственно микропрограмме процессора которая в конечном счете и отправляет их на исполнение на процессорный конвеер.
Нулевой байт или NULL-байт или '\0' - большое число функций(в основном работающих со строками) Си используют нулевой байт для завершения строки. В hex представлении 00 или '\x00', выражаться или отображаться визуально может по-разному.
Соответственно, в том случае если NULL-байт встретится в шеллкоде, все последующие за ним байты просто проигнорируются и она может завершиться совсем не так как ожидалось.
# Высокий уровень. Создаем целевую программу на языке Си.
Всем известно, что программы пишутся на разных языках программирования в соответствии с той логикой, которая, в свою очередь, реализует цель программы - задачу которая выполняется в более масштабной программной среде - операционной системе. Итак, что же в конечном счете происходит в компьютере в процессе работы программы? А происходит последовательное выполнение кода из которого она состоит. В зависимости от выбранного языка программирования и его парадигмы, код программы в представлении пользователя который ее составляет разбит на отдельные операторы, функции и классы, и другие объекты которые согласно установленному алгоритму и вместе с ним и определяют задачу и ход ее выполнения. И чем выше в иерархии известного стека OSI реализован код, тем сложнее и нагроможденнее его логическая структура, а чем глубже мы спускаемся вниз, ближе к аппаратному обеспечению, тем соответственно структура кода выглядит проще, так как мере продвижения вниз состоит из все более примитивных конструкций, понятных процессору. Давайте начнем с самого верха имерархии и доберемся до интересующего нас ассемблера.
Итак, код нашей программы на Си.
Уже в который наверное раз для читателей погрузимся в такую интересную тему как шеллкоды и сделаем это через реализацию простого примера программы которая удаляет файл по указанному пути при помощи системного вызова unlink. Но как знать, возможно именно этот раз кому-то из читателей, особенно новичков, данная тематика покажется чуть более понятным. Для начала достаточно запомнить, без излишней пока детализации, что шеллкод, как одно из возможных представлений программного кода - должен быть максимально оптимальным для использования, а так же знать о том, что он тесно и даже неразрывно связан с языком ассемблера. А там где ассемблер, там самое непосредственное взаиможействие со всевозможными видами данных почти напрямую и без высокоуровневых конструкций представленных в виде битов, байтов, их последовательностей и объединений которые на более высоком программном уровне уже обобщены в различные типы и союзы в виде всевозможных структур объединенных в контексты программных процессов и являющихся их же частями, то и дело снующих по аппаратным транспортным магистралям - шинам между процессорами и пространствами витруальной памяти. И при этом делающих свою, в основном полезную, хотя и не всегда, работу по выполнению компьютерных программ в рамках работающей операционной системы. Ну и раз уж мы с вами пришли к выводу что в деле шеллкодинга плотно замешан ассемблер, а вместе с ним и данные и определенные правила по которым они живут в компьютерных системах, то посмотрим на эти вопросы чуть более подробно. Прежде чем мы перейдем непосредственно к самим шеллкодам. Поехали с самого начала.
## Введение в основные понятия
На минуту отвлечемся и перечислим то, что для более полного понимания нам пригодится и о чем обязательно знать или хотя-бы иметь представление. Хотя я и уверен, что большинство читателей разбираются или по крайне мере видели и в целом понимают те вещи которые будут кратко описаны здесь, но пройтись по основным понятиям для того чтобы освежить память будет в любом случае не лишним.
Стек OSI - логически структурированная сетевая иерархия в виде ступенек от прикладного уровня до аппаратного. Наверху иерархии расположена прикладная программа или если проще-бизнес логика, внизу-те правила согласно которым эта программа последовательно (почти всегда) выполняется аппаратным конвеером процессора. Вот и все. Если еще проще для нашего случая, то сверху Си, снизу ассемблер.
Системный вызов(syscall) - это системная функция при помощи которой пользователь запрашивает у ядра операционной системы специальное действие для выполнения этого действия аппаратным обеспечением, например запись либо чтение данных с устройства и куда в целях безопасности пользователю доступ закрыт. Правила запросов к системным вызовам и порядок их выполнения имеют строго определенные производителями процессоров и операционной системы формат. В нашем случае мы быдем использовать системный вызов unlink()
Стек(стек программы) - часть памяти выполняющейся программы где располагаются локальные переменные функций. Туда же мы в качестве аргумента системного вызова unlink() поместим строку, указывающую на удаляемый файл, причем не указатель на строку, а непосредственно ее саму. Стек в памяти процесса всегда растет от старшего адреса к младшему, а уменьшается наоборот - от младшего к старшему по принципу LIFO очереди(первым вошел-последним вышел).
Регистр процессора - это внутренняя память процессора разбитая на отдельные и независимые ячейки, используемые процессором для последовательного выполнения инструкций при работе программы и оперированием данных которые необходимы этим инструкциям. В 64 - битном исполнении это такие регистры как: RSP, RBP, RAX, RCX, RBX, RDX, RSI, RDI, R8-R15.
Конструкции высокоуровневого языка - в данной статье это операторы, операции и прочие конструкции языка Си, такие как if-then-else, for, объявления переменных, функций, структур, итд.
Инструкция ассемблера - инструкция(или команда) ассемблера выраженная в понятных человеку мнемонических обозначениях(аббревиатурах), например: MOV, XOR, PUSH, POP, ADD, LEA итд.
Инструкция процессора - то же самое что инструкция ассемблера, только понятная непосредственно микропрограмме процессора которая в конечном счете и отправляет их на исполнение на процессорный конвеер.
Нулевой байт или NULL-байт или '\0' - большое число функций(в основном работающих со строками) Си используют нулевой байт для завершения строки. В hex представлении 00 или '\x00', выражаться или отображаться визуально может по-разному.
Соответственно, в том случае если NULL-байт встретится в шеллкоде, все последующие за ним байты просто проигнорируются и она может завершиться совсем не так как ожидалось.
# Высокий уровень. Создаем целевую программу на языке Си.
Всем известно, что программы пишутся на разных языках программирования в соответствии с той логикой, которая, в свою очередь, реализует цель программы - задачу которая выполняется в более масштабной программной среде - операционной системе. Итак, что же в конечном счете происходит в компьютере в процессе работы программы? А происходит последовательное выполнение кода из которого она состоит. В зависимости от выбранного языка программирования и его парадигмы, код программы в представлении пользователя который ее составляет разбит на отдельные операторы, функции и классы, и другие объекты которые согласно установленному алгоритму и вместе с ним и определяют задачу и ход ее выполнения. И чем выше в иерархии известного стека OSI реализован код, тем сложнее и нагроможденнее его логическая структура, а чем глубже мы спускаемся вниз, ближе к аппаратному обеспечению, тем соответственно структура кода выглядит проще, так как мере продвижения вниз состоит из все более примитивных конструкций, понятных процессору. Давайте начнем с самого верха имерархии и доберемся до интересующего нас ассемблера.
Итак, код нашей программы на Си.
programm.c
C:
#include <unistd.h>
int main() {
//системный вызов unlink,
//удалающий файл по указанному пути
unlink("/tmp/test.txt");
}
Программа имеет всего две рабочие функции: основную - main которая выполняет одну простую операцию-удаляет существующий в файловой системе файл по пути: /tmp/test.txt с использованием системной функции (или системного вызова) unlink. Тут все понятно.
Откомпилируем вышеуказанную программу
gcc -o program_c program.cСоздадим файл для удаления
touch /tmp/test.txtПроверяем выполнение программы.
strace ./programm_cprogramm_c.strace
Как видим, все прошло как надо. unlink удаляет нужный нам файл, а системный вызов и сама программа возвращают значение 0, что означает успех выполнения задачи.Но все это что мы видим в вышеприведенном коде - это представление программы на высоком уровне. При этом известно, что все программы, каким-то образом выполняются на железе, что реализует более низкий уровень представления. Так давайте попробуем заглянуть под капот, ближе к железу.
# Низкий уровень. Вникаем в основы ассемблера. Переписываем программу на языке ассемблера.
## Дизассемблируем нашу программу на Си
И что же представляют из себя все эти операторы, функции и классы высокоуровневого языка на более низком уровне? Каждый из таких объектов высокоуровневого языка является набором(или объединением) низкоуровневых инструкций понятных процессору(или опкоды) и необходимых ему для выполнения этого оператора целиком. Как мы уже отмечали выше, ассемблерная программа состоит из процессорных инструкций, но выраженных более понятным человеку языком мнемоник. Некоторые обераторы высокоуровневых языков состоят из одной-двух ассемблерных инструкций, а какие-то могут выражаться десятками и даже сотнями инструкций. Теперь же мы готовы вернуться к нашей программе и посмотрим что представляет из себя бинарник нашей программы в дизассемблированном виде, в ассемблерном коде. Для наглядности необходимой для анализа нашего вопроса мы не будем приводить весь листинг выдаемый дизассемблером, а покажем только интересующие нас секции.
Дизассемблируем бинарный код скомпилированной программы Си:
# Низкий уровень. Вникаем в основы ассемблера. Переписываем программу на языке ассемблера.
## Дизассемблируем нашу программу на Си
И что же представляют из себя все эти операторы, функции и классы высокоуровневого языка на более низком уровне? Каждый из таких объектов высокоуровневого языка является набором(или объединением) низкоуровневых инструкций понятных процессору(или опкоды) и необходимых ему для выполнения этого оператора целиком. Как мы уже отмечали выше, ассемблерная программа состоит из процессорных инструкций, но выраженных более понятным человеку языком мнемоник. Некоторые обераторы высокоуровневых языков состоят из одной-двух ассемблерных инструкций, а какие-то могут выражаться десятками и даже сотнями инструкций. Теперь же мы готовы вернуться к нашей программе и посмотрим что представляет из себя бинарник нашей программы в дизассемблированном виде, в ассемблерном коде. Для наглядности необходимой для анализа нашего вопроса мы не будем приводить весь листинг выдаемый дизассемблером, а покажем только интересующие нас секции.
Дизассемблируем бинарный код скомпилированной программы Си:
objdump -d programm_cПолучаем приблизительно такой вывод.
programm_c.disasm
Как мы можем видеть в самой правой части листинга, в ассемблере такие инструкции представлены в мнемонической форме, а соответствующие каждой ассемблероной команде инструкции процессора, или опкоды - в средней части, чуть левее. Так же из листинга видно, что обращение идет не через системный вызов unlink, а вызывается библиотечная функция unlink из главной функции main инициированием инструкцией call для исполнения по адресу 1050 кода расположенного, в свою очередь, в секции plt.sec и безусловно ссылающегося на библиотеку glibc. Это достаточно громоздко и сложно и не является оптимальным для нашей с вами задачи. Кроме того мы увидели лишь малую часть генерируемого компилятором кода, а потому очевидно, что создать из такой громадины адекватный шеллкод просто не получится. Так же этот код содержит NULL байты в конце инструкций. А для создания шеллкода это крайне нежелательно. Почему-мы проясним дальше.
## Переходим непосредственно к переводу нашей целевой программы на язык ассемблера.
Давайте попробуем еще больше упростить внутреннюю логику нашей программы, переписав ее таким образом, чтобы она была проще в исполнении в части автономности от других частей операционной системы, в частности системных библиотек Си, но без потери основной функциональной составляющей и своего главного назначения - удаления целевого файла. Для лучшей оптимальности напишем ассемблерный код вручную, без использования в компиляции системных библиотек Си.
Программа на ассемблере:
## Переходим непосредственно к переводу нашей целевой программы на язык ассемблера.
Давайте попробуем еще больше упростить внутреннюю логику нашей программы, переписав ее таким образом, чтобы она была проще в исполнении в части автономности от других частей операционной системы, в частности системных библиотек Си, но без потери основной функциональной составляющей и своего главного назначения - удаления целевого файла. Для лучшей оптимальности напишем ассемблерный код вручную, без использования в компиляции системных библиотек Си.
Программа на ассемблере:
programm.asm
Код:
section .data
msg db "/tmp/test.txt",0
section .text
global main
main:
;номер системного вызова unlink записываем так же в стек
mov rdi, msg
mov rax, 87
syscall
;здесь не должно быть вопросов, правильно завершаем программу
;с помощью системного вызова exit - номер 60
mov rax, 60
xor rdi, rdi
syscall
Компилируем в исполняемый формат двухэтапно:
Ассемблируем, создавая объектный файл.
Ассемблируем, создавая объектный файл.
nasm -f elf64 programm.asm -o asm_programm.oЛинкуем, и тем самым создаем исполняемый ELF файл.
ld asm_programm.o -o asm_programmВыполняем программу.
./asm_programmВсе должно работать. Теперь смотрим что получилось при компиляции в дизассемблированном виде.
objdump -f -s -d asm_programmprogramm_asm.disasm
Такой код хоть уже гораздо более компактный и понятный чем тот что получился при компиляции из Си-программы, но для нашей основной задачи - создания рабочего шеллкода он все равно мало подходит, поскольку он опять генерирует NULL-байты как и в случае с дизассемблированным листингом из Си программы, что соответственно неприемлемо так как предназначенный для выполнения шеллкод, не должен содержать NULL последовательностей, иначе он просто прервется в процессе исполнения, так как некоторые функции, в том числе системные вызовы работающие со строками могут посчитать NULL-байт концом строки - завершающим символом \0, и тогда весь поток выполнения шеллкода прервется.
## Пишем версию программы для перевода в шеллкод.
Давайте все-таки еще раз задумаемся, а что же такое шеллкод в прямом смысле? Очень просто - а вот это и есть машинный код понятный процессору и предназначенный для исполнения им, только в отличие от опкода в чистом виде - сильно оптимизированный и обладающий следующими основными свойствами: независимость от позиции (position-independent), отсутствия нулевых байтов и выполнением системных вызовов напрямую.Собственного говоря и вот и все!
Теперь, когда у нас есть базовое понимание того как должен выглядеть шеллкод и некоторые рабочие наработки для этого, оптимизируем ассемблерный код таким образом, чтобы его можно было выразить в готовый шеллкод. Как уже говорилось ранее, ассемблерный код который мы написали ранее нам не подходит как минимум по той причине, что содержит недопустимые NULL-байты, а так же является полноценной исполняемой программой с дополнительными артефактами в виде подключаемых стандартных библиотек, других подготовительных процедур, в том числе определения точки входа и та далее. Шеллкоду всего этого не нужно, а фактически нас интересует только основная функция main без заголовков исполняемой программы формата elf. Кроме того, в предыдущем примере мы ссылались на строку msg, расположенной где-то в области виртуальной памяти, а если быть точнее, в области кучи в памяти процесса. Для нас это непримемлемо поскольку налагает большое число дополнительных расходов в контексте сборки программы. А потому немного упростим задачу и приблизим ее нашей основной цели-шеллкоду. А потому для того чтобы программа стала пригодной для преобразования в рабочий шеллкод, нам необходимо полностью избавиться от все нулевых байт. Представим функцию в таком виде.
## Пишем версию программы для перевода в шеллкод.
Давайте все-таки еще раз задумаемся, а что же такое шеллкод в прямом смысле? Очень просто - а вот это и есть машинный код понятный процессору и предназначенный для исполнения им, только в отличие от опкода в чистом виде - сильно оптимизированный и обладающий следующими основными свойствами: независимость от позиции (position-independent), отсутствия нулевых байтов и выполнением системных вызовов напрямую.Собственного говоря и вот и все!
Теперь, когда у нас есть базовое понимание того как должен выглядеть шеллкод и некоторые рабочие наработки для этого, оптимизируем ассемблерный код таким образом, чтобы его можно было выразить в готовый шеллкод. Как уже говорилось ранее, ассемблерный код который мы написали ранее нам не подходит как минимум по той причине, что содержит недопустимые NULL-байты, а так же является полноценной исполняемой программой с дополнительными артефактами в виде подключаемых стандартных библиотек, других подготовительных процедур, в том числе определения точки входа и та далее. Шеллкоду всего этого не нужно, а фактически нас интересует только основная функция main без заголовков исполняемой программы формата elf. Кроме того, в предыдущем примере мы ссылались на строку msg, расположенной где-то в области виртуальной памяти, а если быть точнее, в области кучи в памяти процесса. Для нас это непримемлемо поскольку налагает большое число дополнительных расходов в контексте сборки программы. А потому немного упростим задачу и приблизим ее нашей основной цели-шеллкоду. А потому для того чтобы программа стала пригодной для преобразования в рабочий шеллкод, нам необходимо полностью избавиться от все нулевых байт. Представим функцию в таком виде.
programm_shellcode.asm
Код:
section .text
global main
main:
;кладем в стек нуль-терминатор в конце строки "\0"
xor rax, rax
push rax
;запсываем первые 8 байт строки "/tmp/test.txt" в стек в обратном порядке
;в стеке окажется вот такая последовательность: "txt.tset"
mov rbx, 0x7478742e74736574
push rbx
;записываем вторую часть в обратном порядке.
;помним, что стек растет от старшего к младшему адресу
;в стеке получим "/pmt///". дополняем двумя символами "/"
;для полного заполнения 8 байт во избежинии нулей в стеке
mov rbx, 0x2f706d742f2f2f2f
push rbx
;закидываем в rdi указатель строки расположенной в стеке
mov rdi, rsp
;смещаем позицию указателя на строку на 3, так как добавляли
;дополнительные символы "/", хотя можно этого и не делать
add rdi, 3
;номер системного вызова unlink записываем так же в стек
push 87
pop rax
syscall
;здесь не должно быть вопросов, правильно завершаем прогамму
;с помощью системного вызова exit - номер 60
xor rdi, rdi
push 60
pop rax
syscall
Передаем все аргументы системного вызова полностью через стек, а его номер сначала записываем в стке, а затем получаем в регистр RAX. Все согласно установленным конвенциями правилам. Ассемблируем, линкуем, проверяем. Все должно выполняться без проблем. С этим разобрались. Ассемблерный листинг компилируется в исполняемый формат файла, и даже запускается и делает свою работу. А самое главное это то, что мы и получили рабочий вариант вполне готовой программы годный для ее преобразования. И даже успели его преобразовать в инструкции шеллкода, что и показано выше.
После дизассемблирование получаем вот такой вывод. Все то же самое, что и в написанной програме.
После дизассемблирование получаем вот такой вывод. Все то же самое, что и в написанной програме.
programm_shellcode.disasm
Как видим, ни одного нулевого байта в коде нет, как и нет обращений к памяти вне стека программы. Теперь мы подошли к тому моменту, когда нам нужно преобразовать ассемблерную программу полностью готовую в строку рабочего шеллкода. Делаем это.
В итоге у нас получилась вот такая последовательность.
for i in $(objdump -d asm_programm.o | grep "^ " | cut -f2); do echo -n "\\x$i"; done; echoВ итоге у нас получилась вот такая последовательность.
shellcode
Сделаем ее более наглядной и сопоставим с последней версией рукописной программы на ассемблере(не той версии что выдал дизассемблер) для того, чтобы можно было увидеть соответствия между каждой инструкцией ассемблера и инструкцией процессора в шеллкоде.
И проверяем правильность написания шеллкода, выполняя обратное преобразование.
Bash:
echo -ne "\x48\x31\xc0\x50\x48\xbb\x74\x65\x73\x74\x2e\x74\x78\x74\x53\x48\xbb\x2f\x2f\x2f\x2f\x74\x6d\x70\x2f\ / x53\x48\x89\xe7\x48\x83\xc7\x03\x6a\x57\x58\x0f\x05\x48\x31\xff\x6a\x3c\x58\x0f\x05" > asm_programm.bin
Затем
objdump -D -b binary -m i386:x86-64 asm_programm.binили можно так
ndisasm -b 64 asm_programm.binНа выходе мы получим тот самый ассемблерный код который нам нужен. Но следует понимать, что шеллкод записанный в виде массива инструкций сам по себе что делать не в состоянии в виду отсутствия в его составе необходимой служебной информации соответствующей формату исполняемого файла. И пока его принудительно не отправить на выполнение специальным образом, например из кода другой исполняемой программы или обертки в специальный формат, например elf, ничего не выйдет. Давайте выполним запуск шеллкода из другой программы, тоже написанной на Си.
Программа на Си:
Программа на Си:
exploit.c
C:
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
unsigned char shellcode[] =
"\x48\x31\xc0"
"\x50"
"\x48\xbb\x74\x65\x73\x74\x2e\x74\x78\x74"
"\x53"
"\x48\xbb\x2f\x2f\x2f\x2f\x74\x6d\x70\x2f"
"\x53"
"\x48\x89\xe7"
"\x48\x83\xc7\x03"
"\x6a\x57"
"\x58"
"\x0f\x05"
"\x48\x31\xff"
"\x6a\x3c"
"\x58"
"\x0f\x05";
int main() {
//здесь мы запрашиваем у ядра область памяти в куче процесса и задаем необходимые разрешения для выполнения кода
//в выделяемой области памяти напрямую, что позволяет обходить простую защиту от выполнения типа NX/DEP
void *exec_mem = mmap(0, sizeof(shellcode), PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
memcpy(exec_mem, shellcode, sizeof(shellcode));
int (*ret)() = (int(*)())exec_mem;
ret();
return 0;
}
Если читатель уже обратил свое внимание, что то та самая обертка которую мы представили выше - это некий прообраз эксплоита, доставляющего наш шеллкод в среду выполнения. В данном случае, - в собственный процесс программы-обертки, и который при этом, внутри своей логики уже содержит простой механизм обхода NX/DEP защиты которая блокирует исполнение кода напрямую из динамически выделенной области памяти процесса, в которую наша Си-программа и доставляет этот испоняемый шеллкод.
Компилируем программу, отключив все необходимы защиты, впрочем, не всегда это обязательно.
Компилируем программу, отключив все необходимы защиты, впрочем, не всегда это обязательно.
gcc -fno-stack-protector -no-pie -fcf-protection=none -z execstack exploit.c -o exploitЗапускаем, предварительно не забывая создать наш /tmp/test.txt.
Bash:
touch /tmp/test.txt
./exploit
И если все верно сделано, то должно отлично сработать. Файл успешно удален, и программа даже не свалится в segment fault!
Надеюсь, что данный пример для кого-то станет напоминанием о классических методиках реализации и использования шеллкодов для решения самых разных задач, и совсем необязательно нелегальных. И конечно же, используйте это только в законных и полезных целях, но естественно на свой страх и риск.
Спасибо и надеюсь до встреч.Надеюсь, что данный пример для кого-то станет напоминанием о классических методиках реализации и использования шеллкодов для решения самых разных задач, и совсем необязательно нелегальных. И конечно же, используйте это только в законных и полезных целях, но естественно на свой страх и риск.
Автор vtun: /members/441307
Специально для xss
Последнее редактирование: