mprotect всю программу, чтобы запустить опасный код - PullRequest
4 голосов
/ 13 ноября 2011

У меня есть небольшая программа, которая отображает потенциально опасный исполняемый код (с PROT_EXEC), вызывает prctl(PR_SET_SECCOMP, 1) и затем выполняет этот код mmap'd.Это все хорошо, и позволяет мне «сохранить» состояние оценки путем синхронизации области mmap с диском и перезагрузить ее позже (скорее всего, на другой машине для балансировки нагрузки).Однако этот метод не всегда работает - потому что этот код мог внести изменения в программу, которые не находятся в области mmap, и эта информация будет потеряна.

Так что я бы хотелdo, сделать абсолютно все (кроме этого региона mmap) доступным только для чтения перед вызовом кода.Таким образом, у меня есть гарантия, что исполняемый код не может изменить состояние чего-либо, кроме области mmap'd, которую я могу сериализовать / десериализовать по желанию.

Кстати, это Linux на x86_64

Спасибо

1 Ответ

4 голосов
/ 13 ноября 2011

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