间接`call`指令是否总是指向函数序言?

问题描述

假设我们有一些 C 代码通过函数指针调用函数,无论是通过函数指针表还是作为参数或其他传递的函数指针,如下所示:

/* ... some other code .. */
void (*f)(void) = something; // f function pointer to some function
(*f)();

这应该被编译为(或等效的)

mov  %rcx,[something] ; here f=ecx
callq *%rcx

问题:%ecx 总是指向函数序言还是可以指向函数末尾的一小段代码

示例:

void big_func(){
    /* lots of code here */
    printf("bar");
    printf("foo");
}
void small_func(){
    printf("foo");

big_func C 编译为

; some more code up here
1: 48 8d 3d c4 0e 00 00    lea    %rdi,[0xec4+%rip]  ;ptr to "bar"
2: b8 00 00 00 00          mov    %eax,$0x0
3: e8 e6 fe ff ff          callq  1030 <printf@plt>
4: 48 8d 3d b7 0e 00 00    lea    %rdi,[0xeb7+%rip]  ;ptr to "foo"
5: b8 00 00 00 00          mov    %eax,$0x0
6: e8 d5 fe ff ff          callq  1030 <printf@plt>
7: b8 00 00 00 00          mov    %eax,$0x0
8: 5d                      pop    %rbp
9: c3                      ret

small_func调用是否可以指向 4: 作为入口点?这种情况会发生吗(使用像 gcc 这样的通用编译器),还是只有有人在幕后修改汇编代码时才会发生这种情况?

问题限制:

  • 考虑由非专业编译器(如 gcc、clang 等)编译的所有汇编代码代码,没有花哨的 asm 在幕后表现的怪癖)
  • 仅考虑正常行为。让我们假设开发人员是一个人并且没有实施任何讨厌的未定义行为

附加小问题:如果故意修改函数指针以从函数序言中跳过一些字节会发生什么?这被认为是未定义的行为吗?
示例:

void (*f)(void) = something; // f function pointer to some function
f=(void (*func_ptr)(void)) ((*char)f+2)
(*f)(); //skips `push ebp`

编辑:我应该更清楚为什么问这个问题。它是在研究大师的背景下,寻求一种在非常低的软件或硬件级别上减轻基于 ROP 的攻击的新方法。如果间接调用可能指向函数序言以外的其他地方,它可能会破坏我们基于标签的实现之一(丢失标签并在错误检测到攻击后终止程序)

解决方法

函数指针总是指向函数的开头。 C 标准不允许从其他指针类型进行转换。它调用了一个未定义的行为。

但是特定的实现可能会生成正确的代码,尤其是在函数没有设置堆栈帧的情况下。但大多数情况下会失败

但这根本没有任何意义 - 您应该将@big@ 函数拆分为两个较小的函数,并在需要时调用它们而无需使用伪技巧。

,

在 C 中,与大多数语言一样,函数具有非常具体的含义、目的和实现。在调用中,函数指针必须像函数一样工作。我们不能调用不是函数的代码,无论是直接调用还是函数指针。函数通常不知道它是直接调用还是通过指针调用——它必须接受传递的参数,执行其功能并返回调用者(通常),无论它是如何调用的(直接调用或通过函数指针调用)。在 C 中,函数只有一个入口点。

在 C 中没有不是函数的代码片段的概念,可以通过函数指针传输(或调用)。 (例如,C 有标签和 goto,但没有标签指针、标签变量或标签变量 goto。)

并非所有函数都需要序言,它们只需要能够接受它们的参数(做某事)并返回给调用者——这不一定需要序言或结尾,但它们仍然是函数(具有单个入口点) ).