如何修改一个字符串常量的值?

关于程序内存布局的问题在面试中经常会被问到,其中尤其常见的是问字符串常量到底存在哪个地方,是堆区,栈区,还是全局变量区?一般答案都会说是全局变量区,但是在我尝试更改字符串常量的值的时候却引发了一些问题。首先我尝试了使用VirtualProtect更改Windows下的字符串常量值的读写权限,很成功的更改了其值,但是在对Linux进行处理时则不然,使用先是mprotect失败,原因为EINVAL,查询手册得到说明为,addr和len必须为内存页的整倍,修改了程序为操作整页内存后,直接报出了SIGSEGV,经过观察发现,字符串常量在linux的虚拟内存中(起码是Ubuntu64位中)并没有存在只读数据rodata段,而是存在代码text段,因此使用mprotect将这一页的权限更改为READ|WRITE后,会将原有的EXEC属性去掉,因此程序无法继续执行,立刻报出了SIGSEGV。

首先一点小细节的知识,C和C++对类型的限制不同,C++对类型有较强的限制如代码

char* a = "abcdefg";

在VS环境下C++编译是不过的,g++编译会提示ISO C++禁止将const char*赋值给char*,

而将文件名重命名为.c后或是手动设置按照C编译,则二者都无任何提示编译通过。

那么字符串常量到底存在哪儿呢?

对于linux中并不是存在全局变量区,而是存在代码段中(64位linux,32位未验证),考虑如下代码

#include <stdio.h>
#include <errno.h>
#include <sys/mman.h>
#include <unistd.h>
int d = 1000;
int main()
{
    char* h = "hello world.\n";
    char a[] = "hello.\n";
    int b = 10;
    static int c = 100;

    printf("%llx\n%llx\n%llx\n%llx\n%llx\n",a,&b,&c,&d,h);
    return 0;
}

在Ubuntu上运行后得到结果

7ffd6da62cf0
7ffd6da62cdc
5578d572e014
5578d572e010
5578d552d834

一般来说a、b存在栈区,c、d存在静态变量区

对照/proc/[pid]/maps下的进程内存信息可发现,字符串常量h的地址位于第一个段中,而该段就是代码段,其权限为r-x。

(但是使用objdump查看a.out的信息却会看到这段字符串常量在elf文件中位于rodata段)

5578d552d000-5578d552e000 r-xp 00000000 08:10 14                         /home/asd/1/a.out
5578d572d000-5578d572e000 r--p 00000000 08:10 14                         /home/asd/1/a.out
5578d572e000-5578d572f000 rw-p 00001000 08:10 14                         /home/asd/1/a.out
5578d75e5000-5578d7606000 rw-p 00000000 00:00 0                          [heap]
7f822b9db000-7f822bbc2000 r-xp 00000000 08:02 3282198                    /lib/x86_64-linux-gnu/libc-2.27.so
7f822bbc2000-7f822bdc2000 ---p 001e7000 08:02 3282198                    /lib/x86_64-linux-gnu/libc-2.27.so
7f822bdc2000-7f822bdc6000 r--p 001e7000 08:02 3282198                    /lib/x86_64-linux-gnu/libc-2.27.so
7f822bdc6000-7f822bdc8000 rw-p 001eb000 08:02 3282198                    /lib/x86_64-linux-gnu/libc-2.27.so
7f822bdc8000-7f822bdcc000 rw-p 00000000 00:00 0
7f822bdcc000-7f822bdf3000 r-xp 00000000 08:02 3282170                    /lib/x86_64-linux-gnu/ld-2.27.so
7f822bfd8000-7f822bfda000 rw-p 00000000 00:00 0
7f822bff3000-7f822bff4000 r--p 00027000 08:02 3282170                    /lib/x86_64-linux-gnu/ld-2.27.so
7f822bff4000-7f822bff5000 rw-p 00028000 08:02 3282170                    /lib/x86_64-linux-gnu/ld-2.27.so
7f822bff5000-7f822bff6000 rw-p 00000000 00:00 0
7ffd6da43000-7ffd6da64000 rw-p 00000000 00:00 0                          [stack]
7ffd6db24000-7ffd6db27000 r--p 00000000 00:00 0                          [vvar]
7ffd6db27000-7ffd6db29000 r-xp 00000000 00:00 0                          [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]

因此如果需要修改字符串常量的内容,就需要一些处理,使用mprotect更改内存页的权限,该系统调用要求其操作必须为整页,即addr必须是系统页的整数倍,len也要是系统页的整数倍才行,该值可使用getpagesize()获取,具体修改字符串常量的代码则如下

#include <stdio.h>
#include <errno.h>
#include <sys/mman.h>
#include <unistd.h>

int main()
{
    char* h = "hello world.\n";
    unsigned long long address = (unsigned long long)(h);
    address = address/4096*4096;
    int result = mprotect((void*)(address),4096,PROT_EXEC|PROT_READ|PROT_WRITE);
    printf ("%d\n",result);
    h[5] = 'A';
    printf(h);

    return 0;
}

值得一提的是,对于Windows来说就没有那么麻烦,直接使用VirtualProtect修改字符串常量那段地址的权限即可,代码如下

#include <stdio.h>
#include <Windows.h>
int main()
{
	char* a = const_cast<char*>("hello world.\n");
	printf(a);
	DWORD MemoryOldProtect = 0;
	VirtualProtect(a, 13, PAGE_READWRITE, &MemoryOldProtect);
	a[5] = 'A';
	printf(a);
	return 0;
}

查看MemoryOldProtect的值可知,原内存状态为0x02只读,可以推测Windows将字符串常量存到了rdata段,即只读变量区,使用x64dbg等工具可印证这点。

  • 4
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值