简单来说,我们有两个类似的功能:
void f1()
{
printf("%d", 123);
}
void f2()
{
printf("%d", 124);
}
现在我们在main中调用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程序中不起作用。什么有用?
答案 0 :(得分:3)
请勿覆盖该过程,而是覆盖符号表中的符号引用。这确实需要动态联系。或者,您可以通过调用其他函数来覆盖函数的调用,但NX
位之类的东西可能会阻挡您。自修改代码通常不受欢迎。
答案 1 :(得分:3)
你为什么这么问?如果您希望最终能够调用某些函数,这些函数的代码是由同一个进程生成的,那么您可以采用不同的方式:
typedef
签名,请参阅this answer。生成函数并获取指向它的指针。
您可以生成一个C源文件generated.c
,分叉一个进程,可能用system("gcc -fPIC -O -shared generated.c -o generated.so");
编译它,然后dlopen("./generated.so", RTLD_GLOBAL)
并获取生成函数的指针与dlsym
。有关详细信息,请参见dlopen(3)手册页。仅供参考,MELT正在这样做。
您还可以在内存中生成函数的机器代码(可能使用PROT_EXEC
使用gdb
标志获得。有几个JIT(即时翻译)库可供使用:mmap(2)(快速生成慢速运行的机器代码),GNU lightning,myjit,libjit(生成缓慢)优化的机器代码),LLVM ...
如果你真的希望覆盖一些现有的功能代码,你可能会这样做,但它需要非常小心并且很痛苦(例如因为新功能代码需要比旧功能代码更多的空间,并且因为{ {3}}问题)。使用LuaJIT和/或relocation系统调用获取此类技巧的权限。但要做好调试噩梦的准备。您可能希望使用mmap(2)为iptables
调试器添加脚本。
对于内核模块,故事情况有所不同。我听说一些与网络相关的内核代码(也许是{{1}}?)可能会使用JIT技术生成机器代码并运行它。
答案 2 :(得分:1)
我试图找到你的答案,但失败了。 我真正成功的做法 - 只是为了简化可疑的代码:
void f1( )
{
}
int main( )
{
*(char*) f1 = *(char*) f1;
return( 0 );
}
是的,它在分段错误(在gcc中)或内存访问冲突(在MS VC中)失败。
编辑:
(基于Basile Starynkevitch的答案)。但仅适用于x86,仅适用于gcc,仅适用于您的具体示例。以下是几个代码示例。
首先 - 简化的例子。
#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()”(已注释),因为在“ f1()<”内部调用“ printf()” / strong>“和” f2()“是相对的,而不是绝对的。我无法找到如何使它们绝对(“ -fPIC ”,也不是“ -fno-PIC ”在我的情况下工作)。如果您在“ f1()”和“ f2()”中没有相对函数调用,我相信您可以使用“ memcpy()“(但我没试过)。
您还应该使用“ f1()”对齐页面大小(除非您确定在“ f1()”开始之前有足够的代码)。如果你有gcc 4.3及更高版本,你可以使用属性(它被评论,因为我有gcc v4.1.2)。如果没有,你可以使用那个丑陋且不可靠的“ _ asm _ ”。
输出:
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.
当然,那可怕的“ if(((char *)f2)[i] == 124)”。它用于区分应该替换的内容(打印的数字)和不应该替换的内容(相对引用)。显然,这是非常简化的算法。您必须实施自己的,适合您的任务。