用C语言解码函数指针
是否可以将函数指针内容存储在C中。我知道可以将各种指针存储在变量中。但如果我能“展开”一个整数指针(指向整数)或字符串指针(指向无符号字符),我就不能对函数指针进行解码了用C语言解码函数指针,c,function-pointers,C,Function Pointers,是否可以将函数指针内容存储在C中。我知道可以将各种指针存储在变量中。但如果我能“展开”一个整数指针(指向整数)或字符串指针(指向无符号字符),我就不能对函数指针进行解码了 更清楚地说,我的意思是将机器代码指令存储在一个变量中。您缺少一个重要的事实:函数不是C中的(一级)对象 C中有两种基本类型的指针:数据指针和函数指针。可以使用*取消对这两个变量的引用 相似之处到此为止。数据对象具有存储的值,因此取消引用数据指针可访问此值: int a = 5; int *b = &a; int c =
更清楚地说,我的意思是将机器代码指令存储在一个变量中。您缺少一个重要的事实:函数不是C中的(一级)对象 C中有两种基本类型的指针:数据指针和函数指针。可以使用
*
取消对这两个变量的引用
相似之处到此为止。数据对象具有存储的值,因此取消引用数据指针可访问此值:
int a = 5;
int *b = &a;
int c = *b; // 5
函数就是这样,一个函数。您可以调用函数,因此可以调用取消引用函数指针的结果。它没有存储值:
为了便于处理,C允许在将函数分配给函数指针时省略&
运算符,并且在通过函数指针调用函数时也可以省略显式解引用
简而言之:使用指针不会改变数据对象和函数的语义
在此上下文中还要注意,函数和数据指针不兼容。无法将函数指针指定给
void*
。甚至可以有一个平台,其中函数指针的大小与数据指针的大小不同
实际上,在函数指针与数据指针具有相同格式的平台上,您可以通过将指针强制转换为
const char*
“说服”编译器访问位于那里的实际二进制代码。但请注意,这是未定义的行为。C中的指针是内存中某个对象的地址。int*
是int
的地址,指向函数的指针是函数代码存储在内存中的地址
虽然可以从内存中函数的地址读取一些字节,但它们只是字节,而不是其他任何字节。您需要知道如何解释这些字节,以便“将机器代码指令存储在变量中”。这里真正的问题是知道在哪里停止,一个函数的代码在哪里结束,另一个函数的代码在哪里开始
这些东西不是由语言定义的,它们取决于许多因素:处理器体系结构、操作系统、编译器、用于编译代码的编译器标志(用于优化f.e.)
这里真正的问题是:假设您可以“将机器代码指令存储在变量中”,您希望如何使用它?它只是一个字节序列,对大多数人来说毫无意义,不能用于执行函数。如果您不是在编写编译器、链接器、仿真器、操作系统或类似的东西,那么对于函数的机器码指令,您就没有什么有用的方法。(如果您正在编写上述内容之一,那么您知道答案,并且您不会就此或其他地方提出此类问题。)此处的代码应该是一个框架,用于将代码注入程序。但是,如果您在诸如Linux或Windows之类的SO中执行它,则在执行第一条指令之前会出现异常
fn_ptr
点
#include <stdio.h>
#include <malloc.h>
typedef int FN(void);
int main(void)
{
FN * fn_ptr;
char * x;
fn_ptr = malloc(10240);
x = (char *)fn_ptr;
// ... Insert code into x that points the same memory of fn_ptr;
x[0]='\xeb'; x[1]='\xfe'; // jmp $ that is like while(1)
fn_ptr();
return 0;
}
#包括
#包括
类型定义内部FN(无效);
内部主(空)
{
FN*FN_ptr;
char*x;
fn_ptr=malloc(10240);
x=(char*)fn_ptr;
//…将指向fn_ptr相同内存的代码插入x;
x[0]='\xeb';x[1]='\xfe';//jmp$类似于while(1)
fn_ptr();
返回0;
}
如果使用gdb执行此代码,将获得以下结果:
(gdb)l
2#包括
3.
4类型定义内部FN(无效);
5.
6内部干管(空)
7 {
8 FN*FN_ptr;
9个字符*x;
10
11 fn_ptr=malloc(10240);
12 x=(字符*)fn_ptr;
13
14/…在x中插入代码,该代码指向fn_ptr的相同内存;
15 x[0]='\xeb';x[1]='\xfe';//jmp$类似于while(1)
16 fn_ptr();
17
18返回0;
19 }
(gdb)b 11
断点1位于0x400535:文件p.c,第11行。
(gdb)r
启动程序:/home/sergio/a.out
断点1,主()位于p.c:11
11 fn_ptr=malloc(10240);
(gdb)p fn_ptr
$1=(FN*)0x7fffffffde30
(gdb)n
12 x=(字符*)fn_ptr;
(gdb)n
15 x[0]='\xeb';x[1]='\xfe';//jmp$类似于while(1)
(gdb)Px[0]
$3 = 0 '\000'
(gdb)n
16 fn_ptr();
(gdb)Px[0]
$5 = -21 '\353'
(gdb)Px[1]
$6 = -2 '\376'
(gdb)s
程序接收信号SIGSEGV,分段故障。
0x00000000000602010英寸??()
(gdb)在哪里
#0 0x00000000000602010英寸??()
#1 0x0000000000400563位于p.c:16的主管道()中
(gdb)
您如何看到GDB信号aSIGSEGV,分段故障
,位于fn_ptr指向的地址,尽管内存中的指令是有效指令
请注意,LM代码:EB FE仅对英特尔(或兼容)处理器有效。此LM代码对应于汇编代码:jmp$。假设我们讨论的是冯·诺依曼体系结构 基本上,我们只有一个包含指令和数据的内存。然而,现代操作系统能够控制内存访问权限(读/写/执行) 按标准,将函数指针强制转换为数据指针是未定义的行为。虽然我们说的是Linux、gcc和现代x86-64 CPU,但您可以进行这样的转换,您将得到一个指向内存只读可执行段的指针 例如,看看这个简单的程序:
#包括
int func(){
返回1;
}
int main(){
无符号字符*代码=(void*)func;
printf(“%02x\n%02x%02x%02x\n%02x%02x%02x%02x%02x\n%02x\n%02x\n”,
*代码,
*(代码+1),*(代码+2),*(代码+3),
*(代码+4),*(代码+5),*(代码+6),*(代码+7),*(代码+8),
*(代码+9),
*(代码+10));
}
汇编时使用:
gcc -O0 -o tst tst.c
我的机器上的输出是:
gcc -O0 -o tst tst.c
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <malloc.h>
#include <sys/mman.h>
#include <stdint.h>
#include <inttypes.h>
typedef int FNi(int,int);
typedef double FNd(double,double);
const char prg[][250] = {
// int multiply(int x,int y)
{
0x55, // push %rbp
0x48,0x89,0xe5, // mov %rsp,%rbp
0x89,0x7d,0xfc, // mov %edi,-0x4(%rbp)
0x89,0x75,0xf8, // mov %esi,-0x8(%rbp)
0x8B,0x45,0xfc, // mov -0x4(%rbp),%eax
0x0f,0xaf,0x45,0xf8, // imul -0x8(%rbp),%eax
0x5d, // pop %rbp
0xc3 // retq
},
// double multiply(double x,double y)
{
0x55, // push %rbp
0x48,0x89,0xe5, // mov %rsp,%rbp
0xf2,0x0f,0x11,0x45,0xf8, // movsd %xmm0,-0x8(%rbp)
0xf2,0x0f,0x11,0x4d,0xf0, // movsd %xmm1,-0x10(%rbp)
0xf2,0x0f,0x10,0x45,0xf8, // movsd -0x8(%rbp),%xmm0
0xf2,0x0f,0x59,0x45,0xf0, // mulsd -0x10(%rbp),%xmm0
0xf2,0x0f,0x11,0x45,0xe8, // movsd %xmm0,-0x18(%rbp)
0x48,0x8b,0x45,0xe8, // mov -0x18(%rbp),%rax
0x48,0x89,0x45,0xe8, // mov %rax,-0x18(%rbp)
0xf2,0x0f,0x10,0x45,0xe8, // movsd -0x18(%rbp),%xmm0
0x5d, // pop %rbp
0xc3 // retq
}
};
int main(void)
{
#define FMT "0x%016"PRIX64
int ret=0;
FNi * fnI_ptr=NULL;
FNd * fnD_ptr=NULL;
void * x=NULL;
//uint64_t p = PAGE(K), l = p*4; //Max memory to use!
uint64_t p = 0, l = 0, line=0; //Max memory to use!
do {
p = getpagesize();line = __LINE__;
if (!p) {
ret=line;
break;
}
l=p*2;
printf("Mem page size = "FMT"\n",p);
printf("Mem alloc size = "FMT"\n\n",l);
x = mmap(NULL, l, PROT_EXEC | PROT_READ | PROT_WRITE, MAP_PRIVATE|MAP_ANON, -1, 0);line = __LINE__;
if (x==MAP_FAILED) {
x=NULL;
ret=line;
break;
}
//Prepares function-pointers. They point the same memory! :)
fnI_ptr=(FNi *)x;
fnD_ptr=(FNd *)x;
printf("from x="FMT" to "FMT"\n\n",(int64_t)x,(int64_t)x + l);
// Calling the functions coded into the array prg
puts("Copying prg[0]");
// It injects the function prg[0]
memcpy(x,prg[0],sizeof(prg[0]));
// It executes the injected code
printf("executing int-mul = %d\n",fnI_ptr(10,20));
puts("--------------------------");
puts("Copying prg[1]");
// It injects the function prg[1]
memcpy(x,prg[1],sizeof(prg[1]));
//Prepares function pointers.
// It executes the injected code
printf("executing dbl-mul = %f\n\n",fnD_ptr(12.3,3.21));
} while(0); // Fake loop to be breaked when an error occurs!
if (x!=NULL)
munmap(x,l);
if (ret) {
printf("[line"
"=%d] Error %d - %s\n",ret,errno,strerror(errno));
}
return errno;
}
int multiply(int a, int b)
{
return a*b;
}
double dMultiply(double a, double b)
{
return a*b;
}