是否有修改函数的代码在运行时一个Linux C程序的方法吗?(Is there a way to m

2019-09-24 06:24发布

简单地说,我们有两个类似的功能:

void f1()
{
    printf("%d", 123);
}
void f2()
{
    printf("%d", 124);
}

现在我们所说的F1在主,它打印123当编译的拆卸f1可能是这样:

08048424 <f1>:
 8048424:       55                      push   %ebp
 8048425:       89 e5                   mov    %esp,%ebp
 8048427:       83 ec 18                sub    $0x18,%esp
 804842a:       b8 40 86 04 08          mov    $0x8048640,%eax
 804842f:       c7 44 24 04 7b 00 00    movl   $0x7b,0x4(%esp)
 8048436:       00
 8048437:       89 04 24                mov    %eax,(%esp)
 804843a:       e8 05 ff ff ff          call   8048344 <printf@plt>
 804843f:       c9                      leave
 8048440:       c3                      ret

F2的机器代码类似于F1的。

现在,我想在运行时f2的机器代码替换F1。 我使用memcpy(F1,F2,SIZE_OF_F2_MACHINE_CODE)。 当然谈到这个问题 - 段故障。

现在,我想知道,如果存在一个解决方案来解决这个问题。 这是一种常见的C程序。 据我所知,我们可以用下面这样的代码来设置页写在Linux内核:

int set_page_rw(long unsigned int addr)
{
    unsigned int level;
    pte_t *pte = lookup_address(addr, &level);

    if(pte->pte & ~_PAGE_RW)
        pte->pte |= _PAGE_RW
}

但它并没有在正常的Linux C程序的工作。 那么是什么在起作用?

Answer 1:

请勿覆盖过程中,覆盖在符号表中的符号引用,而不是。 这确实需要动态链接。 另外,您可以覆盖调用(S)与其他函数的调用功能,但事情好像NX位可能会在用自己的方式站立。 自修改代码时一般不赞成。



Answer 2:

你为什么要问? 如果你的愿望就是最终能够调用它的代码是由同一过程中产生的一些功能,你可以进行不同的:

  1. 始终使用函数指针来调用这种动态生成功能; 我的建议是,对于可读性原因, typedef声明指针之前,他们的签名,看到这个答案 。
  2. 生成的功能,并得到一个指向它。

    • 你可以例如生成C源文件generated.c ,创建一个进程,或许与system("gcc -fPIC -O -shared generated.c -o generated.so"); 编译它,然后dlopen("./generated.so", RTLD_GLOBAL)并获得与所生成的函数的指针dlsym 。 见的dlopen(3)了解详细信息手册页。 仅供参考, MELT是这样做。

    • 你也可以在内存中生成(可能与所获得的功能的机器代码的mmap(2)使用PROT_EXEC标志)。 一些JIT(刚刚在时间翻译)库可用: GNU闪电 (快速生成缓慢运行的机器代码), myjit , libjit , LLVM (慢一代的优化,机器代码), LuaJIT ...

如果你真的要覆盖现有的一些功能的代码,你可以做到这一点,但它需要一个大的很多关心的,是痛苦的(例如,因为新的函数代码需要比旧的,也因为更多的空间搬迁问题)。 使用MMAP(2)和/或则mprotect(2)系统调用来获得许可,这样的招数。 但在调试噩梦准备。 您可能希望你的脚本gdb与调试您的Python脚本 。

对于内核模块的故事是不同的。 我听说一些网络相关的内核代码( iptables吧?)可以使用JIT技术来生成机器代码并运行它。



Answer 3:

我试图找到一个答案给你,但失败了。 我居然成功了的事 - 不仅是为了简化可疑的代码:

void f1( )
{
}
int main( )
{
  *(char*) f1 = *(char*) f1;
  return( 0 );
}

是的,它失败在段故障(在GCC)或存储访问冲突(在MS VC)。

编辑:

其实我成功地做你想做的

(立足于巴西莱Starynkevitch的答案)。 但是,只有86只在海湾合作委员会,并只针对您的具体例子。 下面是几个代码示例。

第一 - 的简化示例。

#include <stdio.h>
#include <sys/mman.h>
#include <unistd.h>

void f1( )
{
}

int main( )
{
  int rc;
  int pagesize;
  char *p;

  f1( );

  pagesize = sysconf( _SC_PAGE_SIZE );
  printf( "pagesize=%d (0x%08X).\n", pagesize, pagesize );
  if( pagesize == -1 )
    return( 2 );

  p = (char*) f1;
  printf( "p=0x%08X.\n", p );
  p = (char*) ((size_t) p & ~(pagesize - 1));
  printf( "p=0x%08X.\n", p );

  rc = mprotect( p, pagesize, PROT_READ | PROT_WRITE | PROT_EXEC );
  printf( "rc=%d.\n", rc );
  if( rc != 0 )
    return( 2 );

  printf( "'mprotect()' succeeded.\n" );

  *(char*) f1 = *(char*) f1;

  printf( "Write succeeded.\n" );

  f1( );

  printf( "Call succeeded.\n" );

  return( 0 );
}

您编译这个并启动一次。 它会失败,但你会知道页面大小。 比方说,它是4096。 然后你编译这个例子是这样的:

gcc a1.c -falign-functions=4096

它应该工作。

输出:

pagesize=4096 (0x00001000).
p=0x00402000.
p=0x00402000.
rc=0.
'mprotect()' succeeded.
Write succeeded.
Call succeeded.

现在高级的例子:

#include <stdio.h>
#include <sys/mman.h>
#include <unistd.h>
#include <string.h>

//extern void f1( void ) __attribute__(( aligned( 4096 ) ));
__asm__( ".text" );
__asm__( ".align 4096" );
void f1( void )
{
  printf( "%d\n", 123 );
}

void f2( void )
{
  printf( "%d\n", 124 );
}

int main( void )
{
  int rc;
  int pagesize;
  char *p;
  int i;

  printf( "f1=0x%08X.\n", f1 );
  printf( "f2=0x%08X.\n", f2 );

  f1( );
  f2( );

  pagesize = sysconf( _SC_PAGE_SIZE );
  printf( "pagesize=%d (0x%08X).\n", pagesize, pagesize );
  if( pagesize == -1 )
    return( 2 );

  p = (char*) f1;
  printf( "p=0x%08X.\n", p );
  p = (char*) ((size_t) p & ~(pagesize - 1));
  printf( "p=0x%08X.\n", p );

  rc = mprotect( p, pagesize, PROT_READ | PROT_WRITE | PROT_EXEC );
  printf( "rc=%d.\n", rc );
  if( rc != 0 )
    return( 2 );

  printf( "'mprotect()' succeeded.\n" );

  for( i = 0; i < (size_t) f2 - (size_t) f1; i++ ) {
    if( ((char*) f2)[ i ] == 124 ) {
      printf( "i=%d.\n", i );
      ((char*) f1)[ i ] = ((char*) f2)[ i ];
    }
  }

  //memcpy( f1, f2, (size_t) f2 - (size_t) f1 );

  printf( "Write succeeded.\n" );

  f1( );
  f2( );

  printf( "Call succeeded.\n" );

  return( 0 );
}

不能使用“ 的memcpy()”在这里(这是注释),因为调用的“printf()”内的“F1()”“F2()”是相对的,不是绝对的。 我怎么也找不到让他们绝对(“既不-fPIC”,也不是“-fno-PIC”在我的情况下工作)。 如果你没有在相对函数调用“F1()”“F2()”,我相信你可以使用“ 的memcpy()”(但我没有尝试)。

您还应该使用“F1()”到页面大小的排列(除非你确信你有“F1()”开始之前足够的代码)。 如果你有GCC 4.3及更高版本,可以使用属性(这是评论,因为我有GCC V4.1.2)。 如果没有,你可以使用丑恶和不可靠“_ _ 汇编 ”。

输出:

f1=0x00402000.
f2=0x0040201E.
123
124
pagesize=4096 (0x00001000).
p=0x00402000.
p=0x00402000.
rc=0.
'mprotect()' succeeded.
i=12.
Write succeeded.
124
124
Call succeeded.

而且,当然,前提是可怕“ 如果(((字符*)F2)[I] == 124)”。 它服务之间什么应更换(印刷号码)和什么来区分不应该(相对引用)。 显然,这是非常简单的算法。 你将不得不实现自己的,适合自己的任务。



文章来源: Is there a way to modify the code of a function in a Linux C program at runtime?