Во-первых, наблюдение: ничто не говорит о том, что вам нужно mmap()
, чтобы получить машинные инструкции в память или сохранить их обратно в файл.read()
и write()
тоже могут это сделать, просто отметьте, что для этой цели вы должны сделать доступное для записи и исполняемое частное отображение.
Очевидно, что вы не можете надежно отключить запись в область стека, которая будетвызывать исполняемый код, который вы загрузите, если он будет выполнен в том же процессе, так как это сделает стек непригодным для использования.Вы можете обойти это путем аннотирования ваших переменных или использования ассемблера.
Ваш следующий вариант - fork()
.Вы можете exec
в дочернем файле создать специальный исполняемый файл-обертку, который обеспечивает минимальный ущерб и самоанализ с помощью вредоносного исполняемого кода (обеспечивает просто загрузку / выгрузку), или вы можете сделать то же самое, если дочерний процесс модифицирует себя с тем же эффектом.Это все еще не на 100% безопасно.
Proposal0
- Создание отдельного двоичного файла, который связан с минимальными библиотеками (
-nodefaultlibs
). - После
fork
, ptrace(PTRACE_TRACEME)
в дочернем (так что вы можете надежно читать содержимое памяти и выполнять другие действия) и закрыть все дескрипторы, кроме дескриптора канала (просто в stdin
для простоты).exec()
в вышеупомянутый двоичный файл оболочки.
В двоичном файле оболочки:
mmap
частный регион в известном месте с разрешениями на запись и выполнение.В качестве альтернативы вы можете статически выделить эту область, если размер фиксирован. - Считать содержимое канала в область.
- Закрыть канал.Теперь у процесса нет открытых ручек.
prctl(PR_SET_SECCOMP, 1)
.Теперь единственными действительными системными вызовами являются _exit
и sigreturn
.Поскольку процесс не может raise
, sigreturn
не должен иметь никакого полезного эффекта. - Удаление разрешений на запись из основного стека (должен быть единственным стеком).Так как вы не собираетесь возвращаться и будете сразу же прыгать, вам не нужно снова прикасаться к стеку.
- Перейти в начальную позицию внутри региона.Сделайте это, используя сборку, или создайте указатель на функцию и вызовите его (если вы можете заставить его работать, не помещая его в стек).Теперь вы должны выполнить область памяти, которая является единственной доступной для записи областью.Основной стек был защищен, и куча не должна использоваться из-за отсутствия поддержки библиотеки.
В родительском:
- Использование
ptrace
или wait
, поймайте ошибочное или успешное завершение. - Считайте отображенную область в известном месте через
/proc/<pid>/mem
или эквивалентный файл.