我知道我可以通过引用复制该函数,但我想了解以下产生段错误的代码中发生了什么。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int return0()
{
return 0;
}
int main()
{
int (*r0c)(void) = malloc(100);
memcpy(r0c, return0, 100);
printf("Address of r0c is: %x\n", r0c);
printf("copied is: %d\n", (*r0c)());
return 0;
}
这是我认为应该起作用的心理模型。
该进程拥有分配给r0c的内存。我们正在复制与return0对应的数据段中的数据,并且复制成功。
我认为取消引用函数指针与调用函数指针指向的数据段相同。如果是这种情况,那么指令指针应该移动到对应于r0c的数据段,其中包含函数return0的指令。对应于return0的二进制代码不包含任何依赖于return0的地址的跳转或函数调用,所以它应该只返回0并且恢复ip ... 100个字节对于函数指针肯定已经足够了,并且0xc3很好在r0c的范围内(它在字节11处)。
为什么分段错误?这是对C函数指针的语义的误解,还是有一些安全功能会阻止我不知道的自修改代码?
答案 0 :(得分:6)
malloc
用于分配内存的内存页面未标记为可执行文件。您无法将代码复制到堆中并期望它运行。
如果你想做类似的事情,你必须深入了解操作系统,并自己分配页面。然后你需要将它们标记为可执行文件。您很可能需要管理员权限才能在内存页面上设置可执行标志。
这真的很危险。如果您在分发的程序中执行此操作并且遇到某种错误,攻击者可以使用我们的程序写入分配的内存页面,则攻击者可以获得管理员权限并控制计算机。
您的代码还存在其他问题,例如指向函数的指针可能无法很好地转换为所有平台上的常规指针。预测或以其他方式获得函数的大小非常困难(更不用说非标准)。您还在代码示例中打印出错误的指针。 (使用"%p"
格式打印void *
,需要将指针转换为void *
。
同样,当您声明类似int fun()
的函数与声明不带参数的函数不同时。如果要声明不带参数的函数,则应明确使用void
中的int fun(void)
。
答案 1 :(得分:2)
标准说:
memcpy
函数将n
指向的 对象 中的s2
个字符复制到{{1}指向的对象中1}}。
[C2011,7.24.2.1/2;重点补充]
在标准的术语中,函数不是“对象”。标准没有定义源指针指向函数的情况的行为,因此这样的s1
调用会产生未定义的行为。
此外,memcpy()
返回的指针是一个对象指针。 C不提供将对象指针直接转换为函数指针,并且它不提供要作为函数调用的对象。可以通过中间整数值在对象指针和函数指针之间进行转换,但这样做的效果至少是双重实现定义的。在某些情况下,它是未定义的。
与其他情况一样,UB可能正是您所希望的行为,但依靠它是不安全的。在这种特殊情况下,其他答案提供了不期望获得您希望的行为的充分理由。
答案 2 :(得分:0)
正如在一些评论中所说,您需要使数据可执行。这需要与操作系统通信以更改对数据的保护。在Linux上,这是系统调用int mprotect(void* addr, size_t len, int prot)
(请参阅http://man7.org/linux/man-pages/man2/mprotect.2.html)。
这是使用VirtualProtect的Windows解决方案。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#ifdef _WIN32
#include <Windows.h>
#endif
int return0()
{
return 0;
}
int main()
{
int (*r0c)(void) = malloc(100);
memcpy((void*) r0c, (void*) return0, 100);
printf("Address of r0c is: %p\n", (void*) r0c);
#ifdef _WIN32
long unsigned int out_protect;
if(!VirtualProtect((void*) r0c, 100, PAGE_EXECUTE_READWRITE, &out_protect)){
puts("Failed to mark r0c as executable");
exit(1);
}
#endif
printf("copied is: %d\n", (*r0c)());
return 0;
}
它有效。
答案 3 :(得分:-1)
Malloc返回指向已分配内存的指针(在您的情况下为100字节)。此内存区域未初始化;假设CPU可以执行内存,为了使代码正常工作,您必须使用函数实现的可执行指令填充这100个字节(如果它确实可以保存在100个字节中)。但正如已经指出的那样,你的分配是在堆上,而不是在文本(程序)段中,我不认为它可以作为指令执行。也许这会实现你想要的东西:
int return0()
{
return 0;
}
typedef int (*r0c)(void);
int main(void)
{
r0c pf = return0;
printf("Address of r0c is: %x\n", pf);
printf("copied is: %d\n", pf());
return 0;
}