Нахождение отображенной памяти изнутри процесса

Настроить:

  • Ubuntu 18x64
  • приложение x86_64
  • Выполнение произвольного кода внутри приложения

Я пытаюсь написать код, который должен быть в состоянии найти структуры в памяти, даже с включенным ASLR. К сожалению, я не смог найти каких-либо статических ссылок на эти регионы, поэтому я полагаю, что мне нужно использовать метод грубой силы и сканировать память процесса. Я попытался отсканировать все адресное пространство приложения, но это не сработало, поскольку некоторые области памяти не выделены и, следовательно, дают SIGSEGV при доступе. Теперь я думаю, что было бы неплохо getpid()затем используйте pid для доступа /proc/$PID/maps и попробуйте разобрать данные оттуда.

Но мне интересно, есть ли лучший способ определить выделенные регионы? Может быть, даже способ, который не требует от меня доступа к libc (=getpid, open, close) или возиться со строками?

1 ответ

Решение

Я не думаю, что для этого есть какой-либо стандартный POSIX API.

анализ /proc/self/maps это ваша лучшая ставка. (Там может быть библиотека, чтобы помочь с этим, но IDK).

Вы пометили этот ASLR, хотя. Если вы просто хотите узнать, где находятся сегменты text / data / bss, вы можете поместить метки в их начало / конец, чтобы эти адреса были доступны в C. Например, extern const char bss_end[]; было бы хорошим способом сослаться на метку, которую вы поместили в конце BSS, используя скрипт компоновщика и, возможно, некоторый рукописный ассм. Генерируемый компилятором asm будет использовать относящуюся к RIP инструкцию LEA, чтобы получить адрес в регистре относительно текущего адреса инструкции (который CPU знает, потому что он выполняет код, отображенный там).

Или, может быть, просто скрипт компоновщика и объявление фиктивных переменных C в пользовательских разделах.

Я не уверен, что вы можете сделать это для отображения стека. При большой среде и / или argv начальный стек при входе в main() или даже _start может быть не на той же странице, что и самый высокий адрес в отображении стека.


Для сканирования нужно либо поймать SIGSEGV или сканировать с помощью системных вызовов вместо загрузки или сохранения в пространстве пользователя.

mmap а также mprotect не может запрашивать старые настройки, поэтому они не очень полезны для неразрушающих вещей. mmap с подсказкой, но без MAP_FIXED может отобразить страницу, а затем вы могли бы munmap Это. Если фактический выбранный адрес!= Подсказка, то вы можете предположить, что адрес использовался.

Возможно, лучшим вариантом будет сканирование с madvise(MADV_NORMAL) и проверить на EFAULT, но только одна страница за раз.

Вы могли бы даже сделать это с errno=0; posix_madvise(page, 4096, POSIX_MADV_NORMAL), Тогда проверь errno: ENOMEM: Адреса в указанном диапазоне частично или полностью находятся вне адресного пространства вызывающего.

В Linux с madvise(2) вы могли бы использовать MADV_DOFORK или что-то, что даже менее вероятно, будет не по умолчанию для каждой страницы.

Но в Linux еще лучший выбор для запросов к отображению памяти процесса только для чтения mincore(2): Также используется код ошибки ENOMEM для недопустимых адресов в запрашиваемом диапазоне. " addr в addr + length содержит не отображенную память ". (EFAULT для вектора результата, указывающего на не отображенную память, а не на адрес).

Только errno результат полезен; vec Результат показывает, горячие ли страницы в оперативной памяти или нет. (Я не уверен, показывает ли он, какие страницы подключены к таблицам страниц HW, или он будет считать страницу, которая находится в памяти в кэше страниц для файла с отображением в памяти, но не является проводной, поэтому при доступе будет запускаться программный ошибка страницы).

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

Но, к сожалению, я не вижу эквивалента для нахождения следующего сопоставления после неотображенной страницы, что было бы гораздо полезнее, потому что большая часть адресного пространства будет не отображена. Особенно в x86-64 с 64-битными адресами!

Для разреженных файлов есть lseek(SEEK_DATA), Интересно, работает ли это на Linux? /proc/self/mem? возможно нет.

Так что, возможно, большой (например, 256 МБ) (tmp=mmap(page, blah blah)) == page вызовы были бы хорошим способом для сканирования через не отображенные регионы в поисках отображенных страниц. В любом случае вы просто munmap(tmp), будь то mmap использовал ваш адрес подсказки или нет.

анализ /proc/self/maps почти наверняка более эффективно.

Но наиболее эффективным было бы разместить метки там, где вы хотите их для статических адресов, и отслеживать динамическое распределение, чтобы вы уже знали, где находится ваша память. Это работает, если у вас нет утечек памяти. (glibc malloc может иметь API для обхода сопоставлений, но я не уверен.)


Обратите внимание, что любой системный вызов произведет errno=EFAULT если вы передадите ему не назначенный адрес для параметра, который должен указывать на что-то.

Один из возможных кандидатов access(2), который принимает имя файла и возвращает целое число. Он никак не влияет на состояние чего-либо еще, успех или неудача, но недостатком является доступ к файловой системе, если указанная память является допустимой строкой пути. И он ищет строку C неявной длины, поэтому может быть медленным, если передать указатель на память без 0 байт в любом месте в ближайшее время. Похоже ENAMETOOLONG включится, но он все равно определенно прочитает каждую доступную страницу, на которой вы его используете, с ошибкой, даже если она была выгружена.

Если вы откроете дескриптор файла на /dev/null Вы могли бы сделать write() системные вызовы с этим. Или даже с writev(2) : writev(devnull_fd, io_vec, count) передать ядру вектор указателей в одном системном вызове и получить EFAULT, если любой из них плохой. (С длиной 1 байт каждый). Но (если только /dev/null драйвер пропускает чтение достаточно рано) это действительно читает со страниц, которые являются действительными, приводя их к ошибкам в отличие mincore(), В зависимости от того, как это реализовано внутри, /dev/null Драйвер может увидеть запрос достаточно рано для его реализации "возврат истины"-без-делания-чего-либо, чтобы избежать фактического касания страниц после проверки на EFAULT. Было бы интересно проверить.

Другие вопросы по тегам