考虑以下带有函数f()的代码,将函数本身完整地复制到缓冲区,修改其代码并运行更改的函数。在实践中,返回编号22的原始函数被克隆并修改为返回编号42.
#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进行了测试。
答案 0 :(得分:2)
你不能在C中做到这一点。即使你知道长度,函数的地址也很重要,因为函数调用和对某些类型数据的访问将使用程序计数器相对寻址。因此,位于不同地址的函数的副本将不会与原始函数做同样的事情。当然还有很多其他问题。
答案 1 :(得分:1)
在C标准中,没有内省或反思的概念,因此你需要自己设计一个方法,如你所做的那样,然而存在一些其他更安全的方法。
有两种方法:
RETN
/ JMP
/ etc,同时考虑切换/跳转表。这当然需要对你拆卸的功能进行一些繁重的分析(使用类似beaEngine的引擎),这当然是最可靠的,但它的速度很慢而且很重。滥用编译单元,这是非常危险的,而不是万无一失的,但如果你知道编译器在编译单元中按顺序生成函数,你可以按照这些方式做一些事情:
void MyFunc()
{
//...
}
void MyFuncSentinel()
{
}
//somewhere in code
size_t z = (uintptr_t)MyFuncSentinel - (uintptr_t)MyFunc;
uint8_t* buf = (uint8_t*)malloc(z);
memcpy(buf,(char*)MyFunc,z);
这将有一些额外的填充,但它将是最小的(和无法访问)。虽然风险很高,但它的反汇编方法要快得多。
注意:这两种方法都要求目标代码具有读取权限。
@R ..提出了一个非常好的观点,你的代码将无法重新定位,除非它的PIC或你就地重新调整它以调整地址等。
答案 2 :(得分:0)
这是符合标准的方法,可以实现您想要的结果:
int f(int magicNumber)
{
return magicNumber;
}
int main(void)
{
k = f(OTHERMAGICNUMBER);
/* Prints the other magic number */
printf("Hello %d!\n", k);
return 0;
}
现在,你可能会在没有任何参数的地方使用f()
,并且不想通过你的代码更改每一个,所以你可以这样做
int f()
{
return newf(MAGICNUMBER);
}
int newf(int magicNumber)
{
return magicNumber;
}
int main(void)
{
k = newf(OTHERMAGICNUMBER);
/* Prints the other magic number */
printf("Hello %d!\n", k);
return 0;
}
我并不是说这是对你的问题的直接回答,但是你所做的事情太可怕了,你需要重新考虑你的设计。
答案 3 :(得分:0)
好吧,您可以使用标签在运行时获取函数的长度:
int f()
{
int length;
start:
length = &&end - &&start + 11; // 11 is the length of function prologue
// and epilogue, got with gdb
printf("Magic number: %d\n", MagicNumber);
end:
return length;
}
执行此函数后,我们知道它的长度,因此我们可以malloc
获得正确的长度,复制和编辑代码,然后执行它。
int main()
{
int (*pointerToF)(), (*newFunc)(), length, i;
char *buffer, *byte;
length = f();
buffer = malloc(length);
if(!buffer) {
printf("can't malloc\n");
return 0;
}
pointerToF = f;
newFunc = (void*)buffer;
memcpy(newFunc, pointerToF, length);
for (i=0; i < length; i++) {
byte = ((char*)newFunc)+i;
if (*byte == MagicNumber) {
*byte = CrackedNumber;
}
}
newFunc();
}
现在还有一个更大的问题,就是@R。提及。修改(正确)后使用此函数会在调用printf
时导致分段错误,因为call
指令必须指定偏移,这将是错误的。您可以使用gdb
查看此内容,使用disassemble f
查看原始代码,x/15i buffer
查看已编辑的代码。
顺便说一句,我的代码和你的代码都在没有警告的情况下编译,但在调用编辑过的函数时在我的机器(gcc 4.4.3
)上崩溃。