Linux下mprotect设置main()可写的技术疑问
关于mprotect()修改main()内存权限的问题与解答
示例代码
#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!
技术问答
问题1:最初将main_address传入mprotect()时,因地址未对齐页边界触发「无效参数(22)」错误,传入对齐的程序入口地址main_start则可正常运行,请问main_address是什么,为何它初始未对齐?
main_address是main()函数在内存中的起始地址,也就是函数第一条指令的内存位置。- 程序内存是按页划分的(示例中为4096字节一页),但编译器链接程序时,会把代码段(包含
main())放到内存页中,main()不一定刚好从页的起始位置开始——代码段通常还包含其他初始化代码、其他函数的代码,所以main()的起始地址会相对于页起始有偏移,自然不对齐页边界。而mprotect()要求第一个参数必须是页对齐地址,因为它按页管理内存权限,因此传入未对齐的main_address会触发错误。
问题2:执行mprotect(0x401000, 193, ...)启用写权限,但main_address位于0x401156(偏移342字节),main()仍可写,是否mprotect()修改的是内存页而非特定字节范围?
是的,mprotect()的作用对象是整个内存页,而非你指定的字节范围。哪怕传入的长度只覆盖页的一部分,它也会修改对应整页的权限。示例中0x401000是页起始地址,不管长度填193还是4096,整个0x401000到0x401fff的内存页都会被设置为PROT_READ | PROT_WRITE | PROT_EXEC权限,所以main()所在页内的所有区域(包括main_address位置)都具备写权限。
问题3:若mprotect()针对内存页启用写权限,是否意味着main()末尾之外的内存区域也会被设为可写?
是的。因为mprotect()修改的是整个页的权限,只要是在main_start(0x401000)所在内存页里的内容,不管是main()函数内部的代码,还是页内main()之前、之后的其他代码(比如全局初始化代码、其他小函数的代码),都会被设置为可写。示例中main_size是193,但整个页有4096字节,所以页内超出main()范围的其他区域权限也会被修改。
内容的提问来源于stack exchange,提问作者vengy
相关产品推荐
相关产品推荐

