c-带有函数指针的Memcpy会导致segfault



我知道我可以通过引用复制函数,但我想了解以下代码中产生segfault的情况。

#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: %xn", r0c);
    printf("copied is: %dn", (*r0c)());
    return 0;
}

这是我的心理模型,我认为什么应该起作用。

进程拥有分配给r0c的内存。我们正在复制return0对应的数据段中的数据,复制成功。

我认为取消引用函数指针与调用函数指针指向的数据段是一样的。如果是这样的话,那么指令指针应该移动到r0c对应的数据段,它将包含函数return0的指令。return0对应的二进制代码不包含任何依赖于return0地址的跳转或函数调用,因此它应该只返回0并恢复ip。。。对于函数指针来说,100个字节肯定足够了,0xc3完全在r0c的范围内(位于字节11)。

那么,为什么分割会出错呢?这是对C函数指针语义的误解,还是有一些安全功能可以防止我不知道的自我修改代码?

malloc用于分配内存的内存页未标记为可执行。您不能将代码复制到堆中并期望它运行。

如果你想做这样的事情,你必须深入操作系统,自己分配页面。然后您需要将这些标记为可执行文件。您很可能需要管理员权限才能在内存页上设置可执行标志。

这真的很危险。如果您在分发的程序中执行此操作,并且存在某种错误,使攻击者可以使用我们的程序写入分配的内存页,则攻击者可以获得管理员权限并控制计算机。


代码还有其他问题,比如指向函数的指针可能无法很好地转换为所有平台上的通用指针。很难(更不用说非标准的)预测或以其他方式获得函数的大小。您还可以在代码示例中打印出错误的指针。(使用"%p"格式打印void *,需要将指针投射到void *)。

此外,当您声明像int fun()这样的函数时,这与声明不带参数的函数不同。如果要声明一个不带参数的函数,则应像在int fun(void)中那样显式使用void

标准规定:

memcpy函数将n字符从s2指向的对象复制到s1指向的对象中。

[C011,7.24.2.1/2;增加了重点]

在标准的术语中,函数不是"对象"。标准没有定义源指针指向函数的情况下的行为,因此这样的memcpy()调用会产生未定义的行为。

另外,malloc()返回的指针是一个对象指针。C不提供对象指针到函数指针的直接转换,也不提供将对象作为函数调用。可以通过中间整数值在对象指针和函数指针之间进行转换,但这样做的效果至少是双重定义的。在某些情况下,它是未定义的。

在其他情况下,UB可能正是你所希望的行为,但依赖它是不安全的。在这种特殊的情况下,其他答案为提供了充分的理由,而不是期望得到你所希望的行为。

正如一些评论中所说,您需要使数据可执行。这需要与操作系统通信以更改对数据的保护。在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: %pn", (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: %dn", (*r0c)());
    return 0;
}

它是有效的。

Malloc返回一个指向已分配内存(在您的情况下为100字节)的指针。此内存区域未初始化;假设内存可以由CPU执行,为了让代码工作,您必须用函数实现的可执行指令填充这100个字节(如果它确实可以保存在100个字节中)。但正如已经指出的,您的分配是在堆上,而不是在文本(程序)段中,我认为它不能作为指令执行。也许这将实现你想要的:

int return0()
{
    return 0;
}
typedef int (*r0c)(void);
int main(void)
{
    r0c pf = return0;
    printf("Address of r0c is: %xn", pf);
    printf("copied is: %dn", pf());
    return 0;
}

最新更新