复制一个函数到内存并执行它

10

我想知道如何在C语言中将一个函数的内容复制到内存中并执行它?

我尝试做这样的事情:

typedef void(*FUN)(int *);
char * myNewFunc;

char *allocExecutablePages (int pages)
{
    template = (char *) valloc (getpagesize () * pages);
    if (mprotect (template, getpagesize (), 
          PROT_READ|PROT_EXEC|PROT_WRITE) == -1) {
        perror ("mprotect");
    } 
}

void f1 (int *v) {
    *v = 10;
}

// allocate enough spcae but how much ??
myNewFunc = allocExecutablePages(...)

/* Copy f1 somewere else
 * (how? assume that i know the size of f1 having done a (nm -S foo.o))
 */

((FUN)template)(&val);
printf("%i",val);

感谢您的回答。

2
你已经在这里问过这个问题了,http://stackoverflow.com/questions/4541419/dynamic-obfuscation-by-self-modifying-code/4541911#4541911 - 为什么还要再问一遍呢? - David Heffernan
搜索“嵌入式复制C函数RAM”以获取更多信息。 - Thomas Matthews
5个回答

7

看起来您已经了解了保护标志的部分。如果您知道函数的大小,现在您只需使用memcpy(),并将f1的地址作为源地址传递即可。

一个重要的注意点是,在许多平台上,您将无法从正在复制的函数(f1)调用任何其他函数,因为相对地址已经硬编码到函数的二进制代码中,并将其移动到内存中的不同位置可能会使这些相对地址失效。


2
你总是可以执行来自.o文件的重定位。尽管如此,写自己的链接器也是件有趣的事情 :-) - R.. GitHub STOP HELPING ICE
2
函数的起始地址可以通过使用函数名称轻松获取。但是函数的长度则是另一回事。 - Thomas Matthews

4
这个技巧的实现原因是function1和function2在内存中大小完全相同。 我们需要function2的长度来进行memcopy,所以应该执行以下操作: int diff = (&main - &function2);
你会发现可以随意编辑function 2而不影响程序的正常运行!
顺便说一句,这个技巧很不错。不幸的是,g++编译器会报告invalid conversion from void* to int的错误...但确实使用gcc编译可以完美通过 ;)
源码已修改:
//Hacky solution and simple proof of concept that works for me (and compiles without warning on Mac OS X/GCC 4.2.1):
//fixed the diff address to also work when function2 is variable size    
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
#include <sys/mman.h>

int function1(int x){ 
   return x-5;
}   

int function2(int x){ 
  //printf("hello world");
  int k=32;
  int l=40;
  return x+5+k+l;
}   


int main(){
  int diff = (&main - &function2);
  printf("pagesize: %d, diff: %d\n",getpagesize(),diff);

  int (*fptr)(int);

  void *memfun = malloc(4096);

  if (mprotect(memfun, 4096, PROT_READ|PROT_EXEC|PROT_WRITE) == -1) {
      perror ("mprotect");
  }   

  memcpy(memfun, (const void*)&function2, diff);

  fptr = &function1;
  printf("native: %d\n",(*fptr)(6));
  fptr = memfun;
  printf("memory: %d\n",(*fptr)(6) );
  fptr = &function1;
  printf("native: %d\n",(*fptr)(6));

  free(memfun);
  return 0;
}   

输出:

Walter-Schrepperss-MacBook-Pro:cppWork wschrep$ gcc memoryFun.c 
Walter-Schrepperss-MacBook-Pro:cppWork wschrep$ ./a.out 
pagesize: 4096, diff: 35
native: 1
memory: 83
native: 1

另外需要注意的是,调用printf函数将导致segfault(段错误),因为printf函数很可能找不到,这是由于其相对地址错误所致...

不错的回答!小问题:malloc(4096)不能保证返回对齐于4096字节页面的内存(尽管我认为大多数分配器通常会这样做),但是mprotect()通常要求其第一个参数对齐到页面。为确保安全,您可能需要使用aligned_alloc()(自C11以来可用)并使用getpagesize()代替常量4096。https://en.cppreference.com/w/c/memory/aligned_alloc - jcsahnwaldt Reinstate Monica

1

一个我自己想出来的折衷解决方案和简单的概念验证,对我来说很有效(而且在Mac OS X/GCC 4.2.1上编译没有警告):

#include "stdio.h"
#include "stdlib.h"
#include "string.h"
#include <sys/mman.h>

int function1(int x){
   return x-5;
}

int function2(int x){
  return x+5;
}


int main(){
  int diff = (&function2 - &function1);
  printf("pagesize: %d, diff: %d\n",getpagesize(),diff);

  int (*fptr)(int);

  void *memfun = malloc(4096);

  if (mprotect(memfun, 4096, PROT_READ|PROT_EXEC|PROT_WRITE) == -1) {
      perror ("mprotect");
  }

  memcpy(memfun, (const void*)&function2, diff);

  fptr = &function1;
  printf("native: %d\n",(*fptr)(6));
  fptr = memfun;
  printf("memory: %d\n",(*fptr)(6) );
  fptr = &function1;
  printf("native: %d\n",(*fptr)(6));

  free(memfun);
  return 0;
}

不错的回答!小问题:malloc(4096)不能保证返回对齐于4096字节页面的内存(尽管我认为大多数分配器通常会这样做),但是mprotect()通常要求其第一个参数对齐到页面。为确保安全,您可能需要使用aligned_alloc()(自C11以来可用)并使用getpagesize()代替常量4096。https://en.cppreference.com/w/c/memory/aligned_alloc - jcsahnwaldt Reinstate Monica

0

我在C语言中尝试了很多次这个问题,得出结论仅使用C语言无法完成。我主要的困难是找到要复制的函数的长度。

标准的C语言没有提供任何方法来获得函数的长度。然而,可以使用汇编语言和“段”来查找长度。一旦找到长度,复制和执行就很容易了。

最简单的解决方案是创建或定义一个包含函数的链接器段。编写一个汇编语言模块来计算并公开声明该段的长度。使用此常量作为函数大小。

还有其他方法涉及设置链接器,例如预定义区域或固定位置并复制这些位置。

在嵌入式系统领域,大多数将可执行内容复制到RAM中的代码都是用汇编语言编写的。


你不需要使用汇编语言来计算函数的长度 - 你可以通过链接脚本来完成这一部分。 - Ponkadoodle

0

这可能是一个hack解决方案。您能否在要复制的函数之后直接创建一个虚拟变量或函数,获取该虚拟变量/函数的地址,然后使用地址执行某些算术运算以获取函数大小的地址?由于内存是按顺序分配的(而不是随机的),因此这可能是可能的。这也将使函数复制保持在ANSI C可移植性范围内,而不需要涉及系统特定的汇编代码。我发现C语言非常灵活,只需要认真思考。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接