【问题标题】:What do I have to do to execute code in data areas, ( segment protection )我必须做什么才能在数据区域中执行代码,(段保护)
【发布时间】:2015-03-16 22:36:15
【问题描述】:

我在 linux 平台上工作,我在上面的程序中使用 g++,将函数从代码区复制到数据区。如何更改数据段的保护以允许我执行复制的功能?

代码如下:

#include <stdio.h>
#include <stdint.h>
#include <string.h>

#define Return asm volatile("pop %rbp; retq; retq; retq; retq; retq;")
int64_t funcEnd=0xc35dc3c3c3c3c35d;
constexpr int maxCode=0x800;
int8_t code[maxCode];

void testCode(void){
    int a=8,b=7;
    a+=b*a;
    Return;
}

typedef void (*action)(void);

int main(int argc, char **argv)
{
    action a=&testCode;
    testCode();

    int8_t *p0=(int8_t*)a,*p=p0,*p1=p0+maxCode;
    for(;p!=p1;p++)
        if ( (*(int64_t*)p)==funcEnd ) break;

    if(p!=p1){
            p+=sizeof(int64_t);
            printf("found\n");
        memcpy(&code,(void*)a,p-(int8_t*)a);
        ((action)&code)();
    }

  printf("returning 0\n");
    return 0;

}

【问题讨论】:

  • 你需要mprotect
  • 我需要将其他语言C++编译的函数作为数据导入。
  • 我在哪里可以找到 mprotect
  • man mprotect(它出现了)
  • C 还是 C++?选择一个

标签: c++ c assembly linux-kernel


【解决方案1】:

这取决于您是尝试静态(在构建时)还是动态(在运行时)。

构建时间

您需要告诉 GCC 将您的 blob 放在可执行的部分中。我们在创建时使用__attribute__((section))this trick来指定section的属性。

运行时

TL;DR:跳到我的答案末尾,我使用mmap

虽然其他人可能会质疑您为什么要在运行时允许这样的事情,但请记住,这正是具有 JIT 编译器(例如 Java VM、. NET CLR 等)在发出本机代码时执行。

您需要更改尝试执行的内存的内存保护。我们通过mprotect(addr, PROT_EXEC) 做到这一点。请注意,addr 必须与您平台的页面大小对齐。在 x86 上,页面大小为 4K。我们使用aligned_alloc 来保证这种对齐。

示例(两者):

#define _ISOC11_SOURCE
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>   /* mprotect() */

__attribute__((section(".my_executable_blob,\"awx\",@progbits#")))
static uint8_t code[] = {
    0xB8,0x2A,0x00,0x00,0x00,   /* mov  eax,0x2a    */
    0xC3,                       /* ret              */
};

int main(void)
{
    int (*func)(void);

    /* Execute a static blob of data */
    func = (void*)code;
    printf("(static) code returned %d\n", func());

    /* Execute a dynamically-allocated blob of data */
    void *p = aligned_alloc(0x1000, sizeof(code));
    if (!p) {
        fprintf(stderr, "aligned_alloc() failed\n");
        return 2;
    }
    memcpy(p, code, sizeof(code));
    if (mprotect(p, sizeof(code), PROT_EXEC) < 0) {
        perror("mprotect");
        return 2;
    }
    func = p;
    printf("(dynamic) code returned %d\n", func());

    return 0;
}

输出:

$ ./a.out 
(static) code returned 42
(dynamic) code returned 42

SELinux 影响

请注意,这会将您的可执行代码放在堆上,这可能有点危险。我的 CentOS 7 机器上的 SELinux 实际上拒绝了 mprotect 调用:

SELinux is preventing /home/jreinhart/so/a.out from using the execheap access on a process.

*****  Plugin allow_execheap (53.1 confidence) suggests   ********************

If you do not think /home/jreinhart/so/a.out should need to map heap memory that is both writable and executable.
Then you need to report a bug. This is a potentially dangerous access.

所以我不得不暂时sudo setenforce 0 才能让它工作。

但我不知道为什么,因为在查看/proc/[pid]/maps 时,页面被清楚地标记为可执行,而不是 SELinux 指示的“可写和可执行”。如果我将memcpy mprotect 之后移动,我的进程会出现段错误,因为我正在尝试写入不可写的内存。所以看来 SELinux 在这里有点过于热心了。

改用mmap

使用mmap 代替mprotect 堆的一个区域(使用aligned_alloc 分配)更直接。这也避免了 SELinux 的任何问题,因为我们没有尝试在堆上执行。

#include <stdio.h>
#include <stdint.h>
#include <unistd.h>
#include <string.h>
#include <sys/mman.h>   /* mmap() */

static uint8_t code[] = { 
    0xB8,0x2A,0x00,0x00,0x00,   /* mov  eax,0x2a    */
    0xC3,                       /* ret              */
};

int main(void)
{
    void *p = mmap(NULL, sizeof(code), PROT_READ|PROT_WRITE|PROT_EXEC,
            MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); 
    if (p==MAP_FAILED) {
        fprintf(stderr, "mmap() failed\n");
        return 2;
    }   
    memcpy(p, code, sizeof(code));

    int (*func)(void) = p;
    printf("(dynamic) code returned %d\n", func());

    pause();
    return 0;
}

最终解决方案

mmap 解决方案很好,但它不提供任何安全性;我们的mmaped 代码区域是可读、可写和可执行的。最好在我们将代码放置到位时仅允许内存可写,然后使其仅可执行。下面的代码就是这样做的:

#include <stdio.h>
#include <stdint.h>
#include <unistd.h>
#include <string.h>
#include <sys/mman.h>   /* mmap(), mprotect() */

static uint8_t code[] = {
    0xB8,0x2A,0x00,0x00,0x00,   /* mov  eax,0x2a    */
    0xC3,                       /* ret              */
};

int main(void)
{
    const size_t len = sizeof(code);

    /* mmap a region for our code */
    void *p = mmap(NULL, len, PROT_READ|PROT_WRITE,  /* No PROT_EXEC */
            MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); 
    if (p==MAP_FAILED) {
        fprintf(stderr, "mmap() failed\n");
        return 2;
    }   

    /* Copy it in (still not executable) */
    memcpy(p, code, len);

    /* Now make it execute-only */
    if (mprotect(p, len, PROT_EXEC) < 0) {
        fprintf(stderr, "mprotect failed to mark exec-only\n");
        return 2;
    } 

    /* Go! */
    int (*func)(void) = p;
    printf("(dynamic) code returned %d\n", func());

    pause();
    return 0;
}

【讨论】:

  • 我会选择mmap,因为在我看来它是上述解决方案中最不“hacky”和可移植的。
  • @tangrs 我完全同意。使用mmapmprotect 正确管理您的页面权限是明智之举。
  • @JonathonReinhart 我刚刚阅读了非常详细的 cmets。谢谢 !。与此同时,我已经到达 mmap 并进行了一些检查,但是当我执行时能够分配内存和复制时,我得到了段错误。我会检查你的代码并在必要时回来。
  • @JonathonReinhart 它工作但没有在代码中使用“printf”。如果我使用对“printf”的调用,则被调用的地址会被破坏,因此必须做一些不同的事情。
  • @user3723779 请注意call 指令分支到一个相对地址。也就是说,call 指令中的立即数告诉 CPU(从 next 指令开始)要跳转多少字节。 EIP &lt;- EIP + imm + 5,其中EIP是指令指针(当前指向callimm是调用位移(immediate值),5是指令的大小call 指令。您要么需要编写与位置无关的代码,要么修复所有调用库函数的 call 指令。
猜你喜欢
  • 1970-01-01
  • 2019-09-27
  • 2011-04-21
  • 1970-01-01
  • 2011-11-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多