C语言 有没有可能调用一个指向字符串的函数指针?

发布于 2022-09-11 23:45:08 字数 2066 浏览 9 评论 0

我是个在学C的萌新,一天突发奇想,指令和数据只是对人来说才有意义,
一段二进制串对CPU来说既可是数据,也可是指令,IP指向哪里就当作指令执行。那这样的话是不是意味着可以在C中执行字符串呢?

可,在探索中我发现没那么简单。

Program received signal SIGSEGV, Segmentation fault

我不确定这背后具体原因,我想可能是系统的保护机制。想到内存可执行可写等属性会不会有影响,于是加上mprotect。但,还是不行。或许除代码段外的其它段都是不可执行的?
所以调用一个字符串可行吗?
如果不可行有具体的原因吗?

如果您抽空回答,我十分感谢!
Ps:如果觉得我上面说的和下面的代码很幼稚,那还望多多指教。谢谢 >_<

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

#define  NOP 0x90 
#define  M_SIZE 128

/*
    在Ubuntu19.04 gcc version 8.3.0 (Ubuntu 8.3.0-6ubuntu1) 中编译通过
*/

static char justAJust[]    = "Hello world!";

//这是个我自己构造的字符串,其功能是同justAFun一样的
static char justAString[M_SIZE] = {0x55,0x48,0x89,0xe5,
                                   0x48,0x83,0xec,0x10,
                                   0x48,0x89,0x7d,0xf8,
                                   0x48,0x8b,0x45,0xf8,
                                   0x48,0x89,0xc7,0xe8,
                                   0xe8,0xcf,0xff,0xff,
                                   0x90,0xc9,0xc3,0x90};

void justAFun(char *justAJust){
    puts(justAJust);
}


int main(int args,char *argv[]){
    int i = 0;
    char *justACharPtr       = NULL;
    void (*justAPtr)(char *) = NULL;
    justACharPtr             = (char *) justAFun;
    

    memset(&justAString[strlen(justAString)],NOP,M_SIZE-strlen(justAString));

    

    mprotect(justAString,24,PROT_EXEC | PROT_READ);  //尝试去赋予justString可执行属性,但不管用
    puts("justAFun函数16进制表示: ");
    do 
    {

        printf("%#x",justACharPtr[i] & 0xFF);        //显示justAFun的'内容'

    } while((justACharPtr[i++] & 0xFF) != 0xc3 && putchar(','));
    putchar('\n'); 

    justAFun(justAJust);                             //非常普通的调用justAFun

    justAPtr = justAFun;                             //使用函数指针调用justAFun
    justAPtr(justAJust); 

    justAPtr = (void (*)(char *))justAString;        //尝试调用一个字符串
    justAPtr(justAJust);

    return 0;

}

`

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

-小熊_ 2022-09-18 23:45:08

你的思路没问题,但有些细节没注意

  1. 没有检查 mprotect 函数的返回值,参考 man mprotect
  2. 实际上 mprotect 修改的内存地址必须以页大小对齐,且需要属性

举个例子

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

/*
int shell_code() { return 2019; }
*/
#if __x86_64__
static unsigned char SHELL_CODE[] = "\x48\xC7\xC0\xE3\x07\x00\x00\xC3"; // mov rax, 2019; ret;
#else
static unsigned char SHELL_CODE[] = "\xB8\xE3\x07\x00\x00\xC3"; // mov eax, 2019; ret;
#endif

int main()
{
    size_t pageSize = sysconf(_SC_PAGESIZE);
    uintptr_t start = (uintptr_t)SHELL_CODE;
    uintptr_t pageStart = start & -pageSize;
    if (-1 == mprotect((void*)pageStart, sizeof(SHELL_CODE) + start - pageStart, PROT_EXEC | PROT_READ | PROT_WRITE))
        exit(1);

    printf("result=%d\n", ((int (*)(void))SHELL_CODE)());
}
一笑百媚生 2022-09-18 23:45:08

可行,题主开始关心指令执行是个非常好的开始 :-D 执行不了只是因为你尝试去把data segment的内存修改为可执行,是不可以的,但是我们可以mmap一段可执行内存来存放函数指令,而运行时生成指令你还需要关心,调用约定、平台指令等问题,我举了个例子:
运行时生成 void method(char *str){puts(str);}
main.c


#ifdef __x86_64__
//x64
char code[]={
       0x55, //push %rbp
       0x48,0x89,0xe5, //mov %rsp,%rbp
       0x48,0x83,0xec,0x10, //sub  $0x10,%rsp
       0x48,0xb8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, //movabs $puts,%rax
       0xff,0xd0, //callq  *%rax
       0xc9,0xc3 //leaveq retq
};
int code_size = sizeof(code);
int puts_off = 10;

#elif __i386__
//x86
char code[]={
    0x55,   //push %ebp
    0x89,0xe5, //mov %esp,%ebp
    0x83,0xec,0x18, //sub $0x18,%esp
    0x8b,0x45,0x08, //mov 0x8(%ebp),%eax
    0x89,0x04,0x24, //mov %eax,(%esp)
    0xb8,0x0,0x0,0x0,0x0, //mov $puts,%eax
    0xff,0xd0, //call *%eax
    0xc9,0xc3 //leave ret
};

int code_size = sizeof(code);
int puts_off = 13;
#endif

void (*method)(char *) = NULL;

int main(int args,char *argv[]){

    *(void**)(code+puts_off) = (void*)puts;

    method = mmap(NULL,code_size,PROT_READ | PROT_WRITE | PROT_EXEC,MAP_PRIVATE | MAP_ANONYMOUS,0,0);

    if(method == NULL || method == MAP_FAILED){
        perror("mmap code mem fail");
        return -1;
    }

    memcpy(method,code,code_size);

    method("hello world");

    return 0;

}

gcc main.c && ./a.out

死开点丶别碍眼 2022-09-18 23:45:08

操作系统不会允许你这么做的,病毒喜欢这种方式,现代cpu都有内存权限控制,内存块有各种权限设置,比如指令执行权限,只有代码执行权限的内存cpu才能执行其中代码,否则会出错,而权限由操作系统分配,操作系统在加载你的程序的时候,会把指令部分加载到专门的内存区域,并且设置可以执行指令的权限,而常字符串、各种全局数据、堆栈还有heap,操作系统在分配这些内存的时候都不会给指令执行权限,所以你想执行保存在字符串中的指令是不可能的。

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文