[英]How to determine the length of a function?
考慮以下帶有函數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進行了測試。
你不能在C中做到這一點。即使你知道長度,函數的地址也很重要,因為函數調用和對某些類型數據的訪問將使用程序計數器相對尋址。 因此,位於不同地址的函數的副本將不會與原始函數做同樣的事情。 當然還有很多其他問題。
在C標准中,沒有內省或反思的概念,因此你需要自己設計一個方法,如你所做的那樣,然而存在一些其他更安全的方法。
有兩種方法:
RETN
/ JMP
/等,同時考慮開關/跳轉表。 這當然需要對你拆卸的功能進行一些繁重的分析(使用像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或你就地重新調整它以調整地址等。
以下是符合標准的實現所需結果的方法:
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;
}
我並不是說這是對你的問題的直接回答,但你所做的是如此可怕,你需要重新考慮你的設計。
那么,您可以使用標簽在運行時獲取函數的長度:
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
)。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.