Использование mprotect для установки main()
как доступного для записи правильно работает с использованием этого кода.
https://godbolt.org/z/68vfrTq8z
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <sys/mman.h>
int main()
{
long page_size = sysconf(_SC_PAGE_SIZE);
printf("page_size = %li\n", page_size);
void* main_address = (void*)&main;
printf("main_address = %p\n",main_address);
void* main_start = (void*)(((uintptr_t)&main) & ~(sysconf(_SC_PAGE_SIZE) - 1));
printf("main_start = %p\n", main_start);
size_t main_size = (void*)&&main_end - (void*)&main;
printf("main_size = %d\n", main_size);
mprotect(main_start, main_size, PROT_READ | PROT_WRITE | PROT_EXEC);
// nop the breakpoint to allow printf() to run!
*((char*)&&breakpoint) = 0x90;
breakpoint:
__asm("int $3");
printf("\nHello, World!\n");
return 0;
main_end:
}
Программа выводит
page_size = 4096
main_address = 0x401156
main_start = 0x401000
main_size = 193
Hello, World!
Вопрос
Сначала я закодировал его, передавая main_address
в mprotect, но это не удалось с ошибкой «Недопустимый аргумент» (22), потому что он не был выровнен по границе страницы! Передача main_start
работает, потому что это выровненный адрес точки входа в программу, но что такое main_address
и
почему он изначально тоже не выровнен?
Что меня смущает, так это то, что если mprotect(0x401000, 193, ...)
включена запись, но main_address
находится по адресу 0x401156, что на 342
байт дальше в памяти, как main()
все еще доступен для записи? Изменяет ли mprotect()
страницы, а не определенный диапазон байтов?
Если страницы разрешены для записи, означает ли это, что участки памяти после конца main()
также доступны для записи?
Обратите внимание, что сделать страницу, содержащую main
, доступной для записи, не всегда достаточно, чтобы изменение кода работало. Инструкции могут кэшироваться отдельно от данных, поэтому изменение, сделанное с помощью инструкции сохранения, может не повлиять на выполнение программы, пока не будут предприняты другие шаги для работы с кэшем.
Защита памяти работает в единицах страниц. Аргумент len
используется только для того, чтобы узнать, сколько страниц нужно изменить.
Согласно документации POSIX:
Функция mprotect() должна изменить защиту доступа на ту, которая указана в prot. для тех целых страниц, содержащих любую часть адресного пространства процесса, начиная с address addr и продолжается для len байтов.
Таким образом, он выяснит, какие страницы содержат указанные байты (по сути, округление addr до границы страницы и addr+len до границы страницы) и изменит разрешения на этих страницах. Таким образом, это может повлиять на разрешения на байты до и после запрошенного диапазона.
Однако в ошибках написано:
Функция mprotect() может завершиться ошибкой, если:
EINVAL Аргумент addr не кратен размеру страницы, возвращаемому функцией sysconf().
Таким образом, адрес, не выровненный по странице, может дать сбой вместо округления в меньшую сторону, но len, не выровненный по странице, будет работать. На практике это зависит от фактической ОС — некоторые системы POSIX поддерживают здесь невыровненные адреса, а другие — нет.
Я предполагаю, что 342 байта до main()
, возможно, являются инициализацией среды выполнения C, которая вызывает main()
, поэтому mprotect
делает этот диапазон страниц 0x401000 - 0x402000
доступным для записи, таким образом, на самом деле он разрешает запись на 3561 (4096-342-193) байт после main()
, верно? Вероятно, следует загрузить gdb для подтверждения. Спасибо!
В пространстве перед main будет все, что вы связали перед main. Обычно это включает в себя любые функции, определенные в вашем файле .c перед main, а также код запуска среды выполнения C. Если main начинается ближе к концу страницы и переходит на следующую страницу, простое округление &main
не даст результата — вам также нужно округлить len в большую сторону.
Основываясь на вашем ответе, я могу изменить пример, чтобы передать main_size
= 1 (вместо вычисления размера main), что запишет enable 4096 байт, и пока main()
полностью содержится на этой странице, все хорошо. :)
Что вы имеете в виду под «что такое
main_address
»? Это ваша переменная в вашем коде, так что это то, что вы установили и что вы имели в виду. Вы ожидали, что это будет согласовано? Почему? Кто вам сказал, чтоmain
будет выровнен по границе страницы? С чего бы это?