问题描述
假设我们有一些 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 这样的通用编译器),还是只有有人在幕后修改汇编代码时才会发生这种情况?
问题限制:
附加小问题:如果故意修改函数指针以从函数序言中跳过一些字节会发生什么?这被认为是未定义的行为吗?
示例:
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。)
并非所有函数都需要序言,它们只需要能够接受它们的参数(做某事)并返回给调用者——这不一定需要序言或结尾,但它们仍然是函数(具有单个入口点) ).