我想在x64 Linux上编写自己的二进制代码加载器。将来,我希望能够自己执行链接步骤,从而能够从.o
对象文件中调用代码。但是现在,我想从已经链接的可执行二进制文件中调用函数。
要创建一些应从“外部”调用的功能,我从以下源代码开始:
void foo(void)
{
int a = 2;
int b = 3;
a + b;
}
int main(void)
{
foo();
return 0;
}
这是我想使用加载程序调用的foo()
函数。使用以下命令链
gcc -o /tmp/main main.c
strip -s /tmp/main
objdump -D /tmp/main
我获得了foo()
函数的汇编代码,如下所示:
...
0000000000001125 <foo>:
1125: 55 push %rbp
1126: 48 89 e5 mov %rsp,%rbp
1129: c7 45 fc 02 00 00 00 movl $0x2,-0x4(%rbp)
1130: c7 45 f8 03 00 00 00 movl $0x3,-0x8(%rbp)
1137: 90 nop
1138: 5d pop %rbp
1139: c3 retq
...
这意味着foo()
函数从main
中的偏移量0x1125开始。我使用六角编辑器对此进行了验证。
以下是我的装载机。目前还没有错误处理,并且代码非常丑陋。但是,它应该演示我想要实现的目标:
#include <stdio.h>
#include <stdlib.h>
typedef void(*voidFunc)(void);
int main(int argc, char* argv[])
{
FILE *fileptr;
char *buffer;
long filelen;
voidFunc mainFunc;
fileptr = fopen(argv[1], "rb"); // Open the file in binary mode
fseek(fileptr, 0, SEEK_END); // Jump to the end of the file
filelen = ftell(fileptr); // Get the current byte offset in the file
rewind(fileptr); // Jump back to the beginning of the file
buffer = (char *)malloc((filelen+1)*sizeof(char)); // Enough memory for file + \0
fread(buffer, filelen, 1, fileptr); // Read in the entire file
fclose(fileptr); // Close the file
mainFunc = ((voidFunc)(buffer + 0x1125));
mainFunc();
free(buffer);
return 0;
}
执行此程序objloader /tmp/main
时会产生SEGFAULT。
mainFunc
变量指向正确的位置。我使用gdb
进行了验证。
操作码存在于堆中是否有问题?实际上,我决定使要调用的函数尽可能简单(副作用,函数参数所需的堆栈或寄存器,...)。但是,仍然有些东西,我真的不明白。
有人可以在这里指出正确的方向吗?在这方面对有用的文献的任何暗示也将受到高度赞赏!
答案 0 :(得分:3)
为了使buffer
存储器区域可执行,您将必须使用mmap
。试试
#include <sys/mman.h>
...
buffer = (char *)mmap(NULL, filelen /* + 1? Not sure why. */, PROT_EXEC | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
这应该为内存区域提供所需的权限,并使其与周围的代码一起使用。实际上,如果您想按原意使用mmap
,请
int fd = open(argv[1], O_RDONLY);
struct stat myfilestats;
fstat(fd, &myfilestats);
buffer = (char*)mmap(NULL, myfilestats.st_size, PROT_EXEC, MAP_PRIVATE, fd, 0);
fclose(fd);
...
munmap(buffer, myfilestats.st_size);
使用MAP_ANONYMOUS
将使内存区域与文件描述符不关联,但是想法是,如果它表示文件,则文件描述符应与其关联。当您执行此操作时,Linux会执行各种很酷的技巧,例如仅加载您最终最终访问的文件部分(如果文件很大,则延迟加载也将使程序非常流畅),并且如果全部包含多个程序访问相同的文件,它们将共享相同的物理内存位置。
答案 1 :(得分:0)
这是基于Nicholas Pipiton's answer的“加载程序”的最终版本。再说一次:没有错误处理,没有简化,没有考虑到现实世界中的场景要困难得多等等。
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>
typedef void(*voidFunc)(void);
int main(int argc, char* argv[])
{
char* buffer;
voidFunc mainFunc;
struct stat myfilestats;
int fd;
fd = open(argv[1], O_RDONLY);
fstat(fd, &myfilestats);
buffer = mmap(NULL, myfilestats.st_size, PROT_EXEC, MAP_PRIVATE, fd, 0);
close(fd);
mainFunc = ((voidFunc)(buffer + 0x1125));
mainFunc();
munmap(buffer, myfilestats.st_size);
return EXIT_SUCCESS;
}