字符串常量区读写属性强制修改为可写
常见的0xC0000005错误,往往是程序尝试对不可写内存区域写入数据导致的,这是使用指针的时候的常见错误。不过字符串常量区(也叫数据区,确切的说,字符串常量区是数据区的一部分)、代码区、堆、栈都是系统临时分配的一段内存而已,他们本质上没有区别。通过修改PE文件,我们可以修改对应内存的读写属性,从而规避0xC0000005错误(程序成功地把数据写入字符串常量区)。示例程序:
#include "stdafx.h"
#include <stdlib.h>
int _tmain(int argc, _TCHAR* argv[])
{
char *p = "hello world";
system("pause");
*(p + 2) = 'm'; //此处触发C05错误
printf("%s\r\n", p);
system("pause");
return 0;
}
如图所示,这个程序是可以编译通过并运行的:
但是当我们把程序运行到*(p + 2) = 'm'
这一行,程序会因为我们尝试对字符串常量区进行写入而触发C05错误,如图所示:
现在我们对字符串常量区的读写属性进行修改。停止VS中的调试,用文本编辑器打开刚才编译链接生成的可执行文件,如图所示:
可以看到里面有各种区域,我们关心的是rdata区,也就是只读数据区。这个区域的读写属性被编码成了一个字节,位于段落开始后的两行半处(习惯上每行看16字节,两行半就是40字节,十六进制偏移为0x28)。把它从原来的40
修改成和data区一样的C0
,如图所示:
保存文件,再次运行(注意不要用VS重新编译源代码。重新编译的话,链接器会把rdata区域的读写属性重新置为默认的只读)。可以发现,我们成功地执行了程序的意图,把“hello”里的字母l改成了m,如图所示:
PE文件中各个区域的读写属性是通过位来编码的,前四位二进制数(0000)分别代表写、读、执行、共享(英文缩写为WRES)。所以原来的40(4的二进制0100)代表的只读,被我们修改成了C0(C的二进制1100),也就是可写可读。