Поиск отображенной памяти внутри процесса - PullRequest
0 голосов
/ 27 октября 2018

Настройка:

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

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

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

1 Ответ

0 голосов
/ 27 октября 2018

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

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

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

Или, может быть, простоскрипт компоновщика и объявление фиктивных переменных 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 для недопустимых адресов в запрашиваемом диапазоне.Msgstr "addr to 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.Было бы интересно проверить.

...