在 Linux 上用 C 和汇编语言为 x64 编写自定义加载程序

dub*_*uga 3 c assembly linker compilation

我想在 x64 Linux 上编写自己的二进制代码加载器。将来我希望能够自己执行链接步骤,从而能够从.o对象文件调用代码。但现在,我想从已链接的可执行二进制文件中调用函数。

为了创建一些可以从“外部”调用的函数,我从以下源代码开始:

void foo(void)
{
  int a = 2;
  int b = 3;
  a + b;
}

int main(void)
{
  foo();
  return 0;
}
Run Code Online (Sandbox Code Playgroud)

这是foo()我想使用加载程序调用的函数。使用以下命令链

gcc -o /tmp/main main.c
strip -s /tmp/main
objdump -D /tmp/main
Run Code Online (Sandbox Code Playgroud)

我获得了该函数的汇编代码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
...
Run Code Online (Sandbox Code Playgroud)

这意味着该foo()函数从 中的偏移量 0x1125 开始main。我使用十六进制编辑器验证了这一点。

以下是我的装载机。目前还没有错误处理,而且代码非常难看。但是,它应该展示我想要实现的目标:

#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;
}
Run Code Online (Sandbox Code Playgroud)

执行该程序时,objloader /tmp/main会产生 SEGFAULT。

变量mainFunc指向正确的位置。我使用 验证了这一点gdb

操作码存在于堆上是不是有问题?实际上,我决定让我想要调用的函数尽可能简单(副作用、函数参数所需的堆栈或寄存器,...)。但仍然有一些事情我不太明白。

谁能在这里指出正确的方向吗?任何有关这方面有用文献的提示也受到高度赞赏!

Nic*_*one 5

为了使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);
Run Code Online (Sandbox Code Playgroud)

这应该为内存区域提供您想要的权限,并使其与周围的代码一起工作。事实上,如果您想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);
Run Code Online (Sandbox Code Playgroud)

使用MAP_ANONYMOUS将使内存区域与文件描述符无关,但其想法是,如果它代表一个文件,则文件描述符应该与它关联。当你这样做时,Linux 会做各种很酷的技巧,比如只加载你实际最终访问的文件的部分(当文件很大时,延迟加载也会使程序非常流畅),如果多个程序都访问同一个文件,那么它们将共享相同的物理内存位置。