考虑以下代码,它将函数f()的整个代码复制到一个缓冲区中,修改其代码并运行更改后的函数。实际上,返回数字22的原始函数被克隆并修改为返回数字42。
请注意:在某些系统上,从可写内存中执行代码是不可能的,这段代码会崩溃。它已经在运行在 x86_64 架构的 Linux 上使用 gcc 4.4.4 进行了测试。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define ENOUGH 1000
#define MAGICNUMBER 22
#define OTHERMAGICNUMBER 42
int f(void)
{
return MAGICNUMBER;
}
int main(void)
{
int i,k;
char buffer[ENOUGH];
/* Pointer to original function f */
int (*srcfptr)(void) = f;
/* Pointer to hold the manipulated function */
int (*dstfptr)(void) = (void*)buffer;
char* byte;
memcpy(dstfptr, srcfptr, ENOUGH);
/* Replace magic number inside the function with another */
for (i=0; i < ENOUGH; i++) {
byte = ((char*)dstfptr)+i;
if (*byte == MAGICNUMBER) {
*byte = OTHERMAGICNUMBER;
}
}
k = dstfptr();
/* Prints the other magic number */
printf("Hello %d!\n", k);
return 0;
}
现在的代码只是猜测函数能够适应1000个字节的缓存。它也违反了规则,因为复制到缓冲区的内容太多,因为函数f()很可能比1000个字节短得多。
这就引出了一个问题:有没有一种方法来确定C语言中任何给定函数的大小?一些方法包括查看中间链接器输出,并根据函数中的指令进行猜测,但这还不够。有没有办法确保呢?
请注意:它可以在我的系统上编译和运行,但不完全符合标准,因为函数指针和void*之间的转换并不被允许:
$ gcc -Wall -ansi -pedantic fptr.c -o fptr
fptr.c: In function 'main':
fptr.c:21: warning: ISO C forbids initialization between function pointer and 'void *'
fptr.c:23: warning: ISO C forbids passing argument 1 of 'memcpy' between function pointer and 'void *'
/usr/include/string.h:44: note: expected 'void * __restrict__' but argument is of type 'int (*)(void)'
fptr.c:23: warning: ISO C forbids passing argument 2 of 'memcpy' between function pointer and 'void *'
/usr/include/string.h:44: note: expected 'const void * __restrict__' but argument is of type 'int (*)(void)'
fptr.c:26: warning: ISO C forbids conversion of function pointer to object pointer type
$ ./fptr
Hello 42!
$
请注意:在某些系统上,从可写内存中执行代码是不可能的,这段代码会崩溃。它已经在运行在 x86_64 架构的 Linux 上使用 gcc 4.4.4 进行了测试。
MAGICNUMBER
不会出现在函数的代码中,而不是表示返回值,而只是因为它恰好是某个操作码的一部分。 - Steve Jessop