Нахождение отображенной памяти изнутри процесса
Настроить:
- 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. Было бы интересно проверить.