

addr:要修改的内存页起始地址,必须按页大小对齐。
len:要修改的区域长度(会被自动向上对齐到页边界)。
prot:新的保护标志,可取 PROT_NONE、PROT_READ、PROT_WRITE、PROT_EXEC 的组合。
只要 mprotect 调用成功,所设置的内存区域 [addr, addr+len-1] 的访问权限就会立即生效,这就为我们修改字符串常量提供了理论可能。下面我们来做个实验看看 mprotect 是否能够帮我们成功的修改字符串常量。
既然做实验我们就需要有对比,首先来看看直接修改字符串常量的后果是啥,代码如下,先打印一次字符串,然后用一个 getchar 函数把程序停住,这样我们可以根据字符串的地址去该进程的 /proc/<pid>/maps 中查看对应的内存段的属性。然后我们尝试使用 strcpy 修改该字符串,最后打印修改后的字符串。

下面我们编译运行该程序,并且查看该进程的 /proc/<pid>/maps 文件,可以发现该字符串常量所属的内存段的属性为 “r--p”,说明该内存段确实只有只读权限。

下面我们按回车让程序继续运行,不出意料的程序 Segmentation fault 了,因为我们尝试去访问只读内存。

下面我们在这个程序的基础上加上对 mprotect 的调用,我们根据字符串常量的地址,找到它所属的页面,使用 mprotect 把该页面的 PROT_WRITE 权限给设置上,然后再使用 strcpy 函数对该字符串常量进行改写。

编译运行该程序,并且查看该进程的 /proc/<pid>/maps 文件,可以发现该字符串常量所属的内存段的属性变成了 “rw-p”,这次就是可读可写的权限了。

我们按回车让程序继续运行,可以看到这次程序成功的把字符串常量给修改了,而且也没出 Segmentation fault,程序正常结束。这说明 mprotect 确实能够帮我们修改字符串常量的内容。

系统调用 mprotect 实际上是在修改进程页表中对应虚拟地址的权限位。当 CPU 访问内存时,MMU 会检查页表项中的权限标志,若写入操作与权限不符(只读),则产生缺页异常,这就是为啥我们第一个例子会出 Segmentation fault。在我们的第二个例子中,我们通过 mprotect 赋予了该字符串常量所在页面的写权限,使得 MMU 允许后续的写入操作,所以我们修改字符串常量可以成功。