Intereting Posts
Чтение и запись двоичных данных через serial port Beginner C: ошибка: управление достигает конца не-void функции? Вызов командного файла из службы Windows Почему может быть разница между union * и struct *? Как сохранить и вызвать скомпилированную функцию в C / C ++? Проблемы с именем windows XLib Как объявить указатель функции в заголовке и c-файле? Печать последовательности в ширину первого порядка с использованием fork () C: #define use Поиск N смежных нулевых битов в целое число слева от позиции MSB другого целого числа ISO C эквивалент скобок-групп в выражениях Получение диаграммы streamа управления из кода ANSI C Как я могу получить доступ к определенной группе бит из переменной? Эффективный способ проверки, если побитовая сетевая маска IPv6 смежна В C, можно ли инициализировать строку в объявлении указателя так же, как я могу инициализировать строку в объявлении массива?

Поиск отображаемой памяти изнутри процесса

Настроить:

  • Ubuntu 18×64
  • приложение x86_64
  • Произвольное выполнение кода внутри приложения

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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