使用个性系统调用使堆栈可执行

nad*_*vin 2 c linux assembly system-calls linux-kernel

我试图了解如何使用个性系统调用使进程堆栈可执行,因此我编写了这段代码,该代码创建一个新进程并在堆栈上运行 bash,并且由于我没有堆栈上的执行权限而出现段错误。我究竟做错了什么?

#include <stdio.h>
#include <sys/personality.h>

int main() 
{
    setvbuf(stdout, 0, 2, 0);
    unsigned char shellcode[] = "\x48\x31\xf6\x56\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54\x5f\x6a\x3b\x58\x99\x0f\x05"; // open bash
    
    if(personality(READ_IMPLIES_EXEC | ADDR_NO_RANDOMIZE) == -1) // return 0
    {
        printf("personality failed");
        exit(0);
    }

    int (*ret)() = (int(*)())shellcode;
    if(fork() == 0) // child proces
        ret();  
    
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

编译为gcc file.c -o file.o

$ uname -r
4.4.179-0404179-generic

$ readelf -l
...
GNU_STACK      0x000000 0x00000000 0x00000000 0x00000 0x00000 RW  0x10
Run Code Online (Sandbox Code Playgroud)

Mar*_*lli 8

有两个问题:

  1. 进程启动后,您无法更改其特性。Doing本身personality(READ_IMPLIES_EXEC)不会做任何事情,它只是设置当前进程的个性值(一个简单的 32 位整数),仅此而已。为了使更改生效,需要执行一个新程序(即通过execve)。当前进程(及其子进程)不会受到影响。

  2. READ_IMPLIES_EXEC如果 ELF 包含PT_GNU_STACK指定堆栈不应可执行的程序头,Linux 将忽略个性标志,这通常是编译器的默认选择。

默认情况下,GCC 将创建带有PT_GNU_STACK程序头的 ELF,其标志设置为 RW 而不是 RWX。为了拥有可执行堆栈,您必须-z execstack在编译时将选项传递给 GCC,该选项将设置PT_GNU_STACK为 RWX。您可以使用readelf -l your_elf(注意:readelf将显示E而不是X程序头标志)进行检查。

因此,就您的情况而言,应该做您想做的事情,并且您不需要真正gcc -zexecstack -o file file.c打电话。只需将您的 shellcode 放入堆栈并跳转到其中即可。理论上,您还可以在 ELF 文件中找到程序头并手动编辑标志(7 = RWX),例如使用十六进制编辑器,但这会比需要的工作更多。personality()fork()PT_GNU_STACK

所以,归根结底:

我试图了解如何使用个性系统调用使进程堆栈可执行

你不能。个性仅影响新的执行,而不影响现有的执行,并且除此之外,PT_GNU_STACK程序头等 ELF 属性会取代个性。但是,您可以按照上面的说明重新编译您的程序。

注意:尽管如此,您仍然可以将mprotect()堆栈内存页的权限更改为 RWX,因为您可以在运行时以某种方式推断堆栈基地址和大小(例如,获取函数中局部变量的地址并将最低 12 位清零) )。


对于像您的内核 (4.4) 这样的旧内核来说,这已经足够了,但从 Linux v5.8 开始,情况就更加微妙了。假设您使用的是 x86,您可以查看源代码中的此注释以获取解释:

/*
 * An executable for which elf_read_implies_exec() returns TRUE will
 * have the READ_IMPLIES_EXEC personality flag set automatically.
 *
 * The decision process for determining the results are:
 *
 *                 CPU: | lacks NX*  | has NX, ia32     | has NX, x86_64 |
 * ELF:                 |            |                  |                |
 * ---------------------|------------|------------------|----------------|
 * missing PT_GNU_STACK | exec-all   | exec-all         | exec-none      |
 * PT_GNU_STACK == RWX  | exec-stack | exec-stack       | exec-stack     |
 * PT_GNU_STACK == RW   | exec-none  | exec-none        | exec-none      |
 *
 *  exec-all  : all PROT_READ user mappings are executable, except when
 *              backed by files on a noexec-filesystem.
 *  exec-none : only PROT_EXEC user mappings are executable.
 *  exec-stack: only the stack and PROT_EXEC user mappings are executable.
 *
 *  *this column has no architectural effect: NX markings are ignored by
 *   hardware, but may have behavioral effects when "wants X" collides with
 *   "cannot be X" constraints in memory permission flags, as in
 *   https://lkml.kernel.org/r/20190418055759.GA3155@mellanox.com
 *
 */
#define elf_read_implies_exec(ex, executable_stack) \
    (mmap_is_ia32() && executable_stack == EXSTACK_DEFAULT)
Run Code Online (Sandbox Code Playgroud)

  • @nadavlevin Peter 是对的,你不能对 setuid 二进制文件这样做,如果可能的话,这将是一个巨大的安全缺陷。如果您有一个使用“PT_GNU_STACK=rw”*已编译*的程序,并且您的 CPU 支持 NX(对于任何现代 CPU 都是如此),那么您对此无能为力。 (2认同)