在Linux上用C和x64程序集编写自定义加载程序



我想在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处开始。我使用hexeditor验证了这一点。

下面是我的加载器。目前还没有错误处理,而且代码非常难看。然而,它应该证明,我想要实现的是:

#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 + 
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对此进行了验证。

操作码存在于堆中是个问题吗?事实上,我决定让我想调用的函数尽可能简单(副作用、函数参数所需的堆栈或寄存器等(。但是,还是有一些东西,我真的不明白。

有人能给我指一下这里的正确方向吗?任何关于这方面有用文献的提示也将不胜感激!

为了使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会做各种很酷的技巧,比如只加载你最终访问的文件的一部分(当文件很大时,延迟加载也会使程序非常流畅(,如果多个程序都在访问同一个文件,那么它们都将共享相同的物理存储器位置。

这是我的"加载器"的最终版本,它基于Nicholas Pipiton的答案。再次:没有错误处理,简化了,没有考虑到现实世界的场景要困难得多,等等:

#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;
}

最新更新